mirror of
https://github.com/frida/tinycc
synced 2024-11-27 10:09:42 +03:00
263232e8cf
- i386: Align first argument on a 16 byte boundary. - arm64: Support Apple's version of the AArch64 ABI, including zero- extension of variadic arguments smaller than 32 bits. - arm64e: Make all indirect branches authenticated, and sign function pointer values both from the perspective of the C code and when looking up symbols. - arm64e: Support signing and stripping pointers. - Query page size to support text segments larger than 4096 bytes on i/macOS systems with 16K pages. - Avoid RWX page allocations on most platforms as they're off limits on modern Apple platforms. - Split relocation and marking pages executable to avoid crashing when marking fails. Co-authored-by: Francesco Tamagni <mrmacete@protonmail.ch>
1141 lines
30 KiB
C
1141 lines
30 KiB
C
/*
|
|
* X86 code generator for TCC
|
|
*
|
|
* Copyright (c) 2001-2004 Fabrice Bellard
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef TARGET_DEFS_ONLY
|
|
|
|
/* number of available registers */
|
|
#define NB_REGS 5
|
|
#define NB_ASM_REGS 8
|
|
#define CONFIG_TCC_ASM
|
|
|
|
/* a register can belong to several classes. The classes must be
|
|
sorted from more general to more precise (see gv2() code which does
|
|
assumptions on it). */
|
|
#define RC_INT 0x0001 /* generic integer register */
|
|
#define RC_FLOAT 0x0002 /* generic float register */
|
|
#define RC_EAX 0x0004
|
|
#define RC_ST0 0x0008
|
|
#define RC_ECX 0x0010
|
|
#define RC_EDX 0x0020
|
|
#define RC_EBX 0x0040
|
|
|
|
#define RC_IRET RC_EAX /* function return: integer register */
|
|
#define RC_IRE2 RC_EDX /* function return: second integer register */
|
|
#define RC_FRET RC_ST0 /* function return: float register */
|
|
|
|
/* pretty names for the registers */
|
|
enum {
|
|
TREG_EAX = 0,
|
|
TREG_ECX,
|
|
TREG_EDX,
|
|
TREG_EBX,
|
|
TREG_ST0,
|
|
TREG_ESP = 4
|
|
};
|
|
|
|
/* return registers for function */
|
|
#define REG_IRET TREG_EAX /* single word int return register */
|
|
#define REG_IRE2 TREG_EDX /* second word return register (for long long) */
|
|
#define REG_FRET TREG_ST0 /* float return register */
|
|
|
|
/* defined if function parameters must be evaluated in reverse order */
|
|
#define INVERT_FUNC_PARAMS
|
|
|
|
/* defined if structures are passed as pointers. Otherwise structures
|
|
are directly pushed on stack. */
|
|
/* #define FUNC_STRUCT_PARAM_AS_PTR */
|
|
|
|
/* pointer size, in bytes */
|
|
#define PTR_SIZE 4
|
|
|
|
/* long double size and alignment, in bytes */
|
|
#define LDOUBLE_SIZE 12
|
|
#define LDOUBLE_ALIGN 4
|
|
/* maximum alignment (for aligned attribute support) */
|
|
#define MAX_ALIGN 8
|
|
|
|
/* define if return values need to be extended explicitely
|
|
at caller side (for interfacing with non-TCC compilers) */
|
|
#define PROMOTE_RET
|
|
|
|
/******************************************************/
|
|
#else /* ! TARGET_DEFS_ONLY */
|
|
/******************************************************/
|
|
#define USING_GLOBALS
|
|
#include "tcc.h"
|
|
|
|
/* define to 1/0 to [not] have EBX as 4th register */
|
|
#define USE_EBX 0
|
|
|
|
ST_DATA const int reg_classes[NB_REGS] = {
|
|
/* eax */ RC_INT | RC_EAX,
|
|
/* ecx */ RC_INT | RC_ECX,
|
|
/* edx */ RC_INT | RC_EDX,
|
|
/* ebx */ (RC_INT | RC_EBX) * USE_EBX,
|
|
/* st0 */ RC_FLOAT | RC_ST0,
|
|
};
|
|
|
|
static unsigned long func_sub_sp_offset;
|
|
static int func_ret_sub;
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
static addr_t func_bound_offset;
|
|
static unsigned long func_bound_ind;
|
|
ST_DATA int func_bound_add_epilog;
|
|
static void gen_bounds_prolog(void);
|
|
static void gen_bounds_epilog(void);
|
|
#endif
|
|
|
|
/* XXX: make it faster ? */
|
|
ST_FUNC void g(int c)
|
|
{
|
|
int ind1;
|
|
if (nocode_wanted)
|
|
return;
|
|
ind1 = ind + 1;
|
|
if (ind1 > cur_text_section->data_allocated)
|
|
section_realloc(cur_text_section, ind1);
|
|
cur_text_section->data[ind] = c;
|
|
ind = ind1;
|
|
}
|
|
|
|
ST_FUNC void o(unsigned int c)
|
|
{
|
|
while (c) {
|
|
g(c);
|
|
c = c >> 8;
|
|
}
|
|
}
|
|
|
|
ST_FUNC void gen_le16(int v)
|
|
{
|
|
g(v);
|
|
g(v >> 8);
|
|
}
|
|
|
|
ST_FUNC void gen_le32(int c)
|
|
{
|
|
g(c);
|
|
g(c >> 8);
|
|
g(c >> 16);
|
|
g(c >> 24);
|
|
}
|
|
|
|
/* output a symbol and patch all calls to it */
|
|
ST_FUNC void gsym_addr(int t, int a)
|
|
{
|
|
while (t) {
|
|
unsigned char *ptr = cur_text_section->data + t;
|
|
uint32_t n = read32le(ptr); /* next value */
|
|
write32le(ptr, a - t - 4);
|
|
t = n;
|
|
}
|
|
}
|
|
|
|
/* instruction + 4 bytes data. Return the address of the data */
|
|
static int oad(int c, int s)
|
|
{
|
|
int t;
|
|
if (nocode_wanted)
|
|
return s;
|
|
o(c);
|
|
t = ind;
|
|
gen_le32(s);
|
|
return t;
|
|
}
|
|
|
|
ST_FUNC void gen_fill_nops(int bytes)
|
|
{
|
|
while (bytes--)
|
|
g(0x90);
|
|
}
|
|
|
|
/* generate jmp to a label */
|
|
#define gjmp2(instr,lbl) oad(instr,lbl)
|
|
|
|
/* output constant with relocation if 'r & VT_SYM' is true */
|
|
ST_FUNC void gen_addr32(int r, Sym *sym, int c)
|
|
{
|
|
if (r & VT_SYM)
|
|
greloc(cur_text_section, sym, ind, R_386_32);
|
|
gen_le32(c);
|
|
}
|
|
|
|
ST_FUNC void gen_addrpc32(int r, Sym *sym, int c)
|
|
{
|
|
if (r & VT_SYM)
|
|
greloc(cur_text_section, sym, ind, R_386_PC32);
|
|
gen_le32(c - 4);
|
|
}
|
|
|
|
/* generate a modrm reference. 'op_reg' contains the additional 3
|
|
opcode bits */
|
|
static void gen_modrm(int op_reg, int r, Sym *sym, int c)
|
|
{
|
|
op_reg = op_reg << 3;
|
|
if ((r & VT_VALMASK) == VT_CONST) {
|
|
/* constant memory reference */
|
|
o(0x05 | op_reg);
|
|
gen_addr32(r, sym, c);
|
|
} else if ((r & VT_VALMASK) == VT_LOCAL) {
|
|
/* currently, we use only ebp as base */
|
|
if (c == (char)c) {
|
|
/* short reference */
|
|
o(0x45 | op_reg);
|
|
g(c);
|
|
} else {
|
|
oad(0x85 | op_reg, c);
|
|
}
|
|
} else {
|
|
g(0x00 | op_reg | (r & VT_VALMASK));
|
|
}
|
|
}
|
|
|
|
/* load 'r' from value 'sv' */
|
|
ST_FUNC void load(int r, SValue *sv)
|
|
{
|
|
int v, t, ft, fc, fr;
|
|
SValue v1;
|
|
|
|
#ifdef TCC_TARGET_PE
|
|
SValue v2;
|
|
sv = pe_getimport(sv, &v2);
|
|
#endif
|
|
|
|
fr = sv->r;
|
|
ft = sv->type.t & ~VT_DEFSIGN;
|
|
fc = sv->c.i;
|
|
|
|
ft &= ~(VT_VOLATILE | VT_CONSTANT);
|
|
|
|
v = fr & VT_VALMASK;
|
|
if (fr & VT_LVAL) {
|
|
if (v == VT_LLOCAL) {
|
|
v1.type.t = VT_INT;
|
|
v1.r = VT_LOCAL | VT_LVAL;
|
|
v1.c.i = fc;
|
|
v1.sym = NULL;
|
|
fr = r;
|
|
if (!(reg_classes[fr] & RC_INT))
|
|
fr = get_reg(RC_INT);
|
|
load(fr, &v1);
|
|
}
|
|
if ((ft & VT_BTYPE) == VT_FLOAT) {
|
|
o(0xd9); /* flds */
|
|
r = 0;
|
|
} else if ((ft & VT_BTYPE) == VT_DOUBLE) {
|
|
o(0xdd); /* fldl */
|
|
r = 0;
|
|
} else if ((ft & VT_BTYPE) == VT_LDOUBLE) {
|
|
o(0xdb); /* fldt */
|
|
r = 5;
|
|
} else if ((ft & VT_TYPE) == VT_BYTE || (ft & VT_TYPE) == VT_BOOL) {
|
|
o(0xbe0f); /* movsbl */
|
|
} else if ((ft & VT_TYPE) == (VT_BYTE | VT_UNSIGNED)) {
|
|
o(0xb60f); /* movzbl */
|
|
} else if ((ft & VT_TYPE) == VT_SHORT) {
|
|
o(0xbf0f); /* movswl */
|
|
} else if ((ft & VT_TYPE) == (VT_SHORT | VT_UNSIGNED)) {
|
|
o(0xb70f); /* movzwl */
|
|
} else {
|
|
o(0x8b); /* movl */
|
|
}
|
|
gen_modrm(r, fr, sv->sym, fc);
|
|
} else {
|
|
if (v == VT_CONST) {
|
|
o(0xb8 + r); /* mov $xx, r */
|
|
gen_addr32(fr, sv->sym, fc);
|
|
} else if (v == VT_LOCAL) {
|
|
if (fc) {
|
|
o(0x8d); /* lea xxx(%ebp), r */
|
|
gen_modrm(r, VT_LOCAL, sv->sym, fc);
|
|
} else {
|
|
o(0x89);
|
|
o(0xe8 + r); /* mov %ebp, r */
|
|
}
|
|
} else if (v == VT_CMP) {
|
|
o(0x0f); /* setxx %br */
|
|
o(fc);
|
|
o(0xc0 + r);
|
|
o(0xc0b60f + r * 0x90000); /* movzbl %al, %eax */
|
|
} else if (v == VT_JMP || v == VT_JMPI) {
|
|
t = v & 1;
|
|
oad(0xb8 + r, t); /* mov $1, r */
|
|
o(0x05eb); /* jmp after */
|
|
gsym(fc);
|
|
oad(0xb8 + r, t ^ 1); /* mov $0, r */
|
|
} else if (v != r) {
|
|
o(0x89);
|
|
o(0xc0 + r + v * 8); /* mov v, r */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* store register 'r' in lvalue 'v' */
|
|
ST_FUNC void store(int r, SValue *v)
|
|
{
|
|
int fr, bt, ft, fc;
|
|
|
|
#ifdef TCC_TARGET_PE
|
|
SValue v2;
|
|
v = pe_getimport(v, &v2);
|
|
#endif
|
|
|
|
ft = v->type.t;
|
|
fc = v->c.i;
|
|
fr = v->r & VT_VALMASK;
|
|
ft &= ~(VT_VOLATILE | VT_CONSTANT);
|
|
bt = ft & VT_BTYPE;
|
|
/* XXX: incorrect if float reg to reg */
|
|
if (bt == VT_FLOAT) {
|
|
o(0xd9); /* fsts */
|
|
r = 2;
|
|
} else if (bt == VT_DOUBLE) {
|
|
o(0xdd); /* fstpl */
|
|
r = 2;
|
|
} else if (bt == VT_LDOUBLE) {
|
|
o(0xc0d9); /* fld %st(0) */
|
|
o(0xdb); /* fstpt */
|
|
r = 7;
|
|
} else {
|
|
if (bt == VT_SHORT)
|
|
o(0x66);
|
|
if (bt == VT_BYTE || bt == VT_BOOL)
|
|
o(0x88);
|
|
else
|
|
o(0x89);
|
|
}
|
|
if (fr == VT_CONST ||
|
|
fr == VT_LOCAL ||
|
|
(v->r & VT_LVAL)) {
|
|
gen_modrm(r, v->r, v->sym, fc);
|
|
} else if (fr != r) {
|
|
o(0xc0 + fr + r * 8); /* mov r, fr */
|
|
}
|
|
}
|
|
|
|
static void gadd_sp(int val)
|
|
{
|
|
if (val == (char)val) {
|
|
o(0xc483);
|
|
g(val);
|
|
} else {
|
|
oad(0xc481, val); /* add $xxx, %esp */
|
|
}
|
|
}
|
|
|
|
#if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_PE
|
|
static void gen_static_call(int v)
|
|
{
|
|
Sym *sym;
|
|
|
|
sym = external_global_sym(v, &func_old_type);
|
|
oad(0xe8, -4);
|
|
greloc(cur_text_section, sym, ind-4, R_386_PC32);
|
|
}
|
|
#endif
|
|
|
|
/* 'is_jmp' is '1' if it is a jump */
|
|
static void gcall_or_jmp(int is_jmp)
|
|
{
|
|
int r;
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST && (vtop->r & VT_SYM)) {
|
|
/* constant and relocation case */
|
|
greloc(cur_text_section, vtop->sym, ind + 1, R_386_PC32);
|
|
oad(0xe8 + is_jmp, vtop->c.i - 4); /* call/jmp im */
|
|
} else {
|
|
/* otherwise, indirect call */
|
|
r = gv(RC_INT);
|
|
o(0xff); /* call/jmp *r */
|
|
o(0xd0 + r + (is_jmp << 4));
|
|
}
|
|
}
|
|
|
|
static uint8_t fastcall_regs[3] = { TREG_EAX, TREG_EDX, TREG_ECX };
|
|
static uint8_t fastcallw_regs[2] = { TREG_ECX, TREG_EDX };
|
|
|
|
/* Return the number of registers needed to return the struct, or 0 if
|
|
returning via struct pointer. */
|
|
ST_FUNC int gfunc_sret(CType *vt, int variadic, CType *ret, int *ret_align, int *regsize)
|
|
{
|
|
#ifdef TCC_TARGET_PE
|
|
int size, align;
|
|
*ret_align = 1; // Never have to re-align return values for x86
|
|
*regsize = 4;
|
|
size = type_size(vt, &align);
|
|
if (size > 8 || (size & (size - 1)))
|
|
return 0;
|
|
if (size == 8)
|
|
ret->t = VT_LLONG;
|
|
else if (size == 4)
|
|
ret->t = VT_INT;
|
|
else if (size == 2)
|
|
ret->t = VT_SHORT;
|
|
else
|
|
ret->t = VT_BYTE;
|
|
ret->ref = NULL;
|
|
return 1;
|
|
#else
|
|
*ret_align = 1; // Never have to re-align return values for x86
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* Generate function call. The function address is pushed first, then
|
|
all the parameters in call order. This functions pops all the
|
|
parameters and the function address. */
|
|
ST_FUNC void gfunc_call(int nb_args)
|
|
{
|
|
int align_placeholder_offset, stack_align_delta, saved_ind;
|
|
int size, align, r, args_size, i, func_call;
|
|
Sym *func_sym;
|
|
|
|
align_placeholder_offset = oad(0xec81, 0); /* sub $xxx, %esp */
|
|
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (tcc_state->do_bounds_check)
|
|
gbound_args(nb_args);
|
|
#endif
|
|
|
|
args_size = 0;
|
|
for(i = 0;i < nb_args; i++) {
|
|
if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) {
|
|
size = type_size(&vtop->type, &align);
|
|
/* align to stack align size */
|
|
size = (size + 3) & ~3;
|
|
/* allocate the necessary size on stack */
|
|
#ifdef TCC_TARGET_PE
|
|
if (size >= 0x4096) {
|
|
/* cannot call alloca with bound checking. Do stack probing. */
|
|
o(0x50); // push %eax
|
|
oad(0xb8, size - 4); // mov size-4,%eax
|
|
oad(0x3d, 4096); // p1: cmp $4096,%eax
|
|
o(0x1476); // jbe <p2>
|
|
oad(0x248485,-4096); // test %eax,-4096(%esp)
|
|
oad(0xec81, 4096); // sub $4096,%esp
|
|
oad(0x2d, 4096); // sub $4096,%eax
|
|
o(0xe5eb); // jmp <p1>
|
|
o(0xc429); // p2: sub %eax,%esp
|
|
oad(0xc481, size - 4); // add size-4,%esp
|
|
o(0x58); // pop %eax
|
|
}
|
|
#endif
|
|
oad(0xec81, size); /* sub $xxx, %esp */
|
|
/* generate structure store */
|
|
r = get_reg(RC_INT);
|
|
o(0x89); /* mov %esp, r */
|
|
o(0xe0 + r);
|
|
vset(&vtop->type, r | VT_LVAL, 0);
|
|
vswap();
|
|
vstore();
|
|
args_size += size;
|
|
} else if (is_float(vtop->type.t)) {
|
|
gv(RC_FLOAT); /* only one float register */
|
|
if ((vtop->type.t & VT_BTYPE) == VT_FLOAT)
|
|
size = 4;
|
|
else if ((vtop->type.t & VT_BTYPE) == VT_DOUBLE)
|
|
size = 8;
|
|
else
|
|
size = 12;
|
|
oad(0xec81, size); /* sub $xxx, %esp */
|
|
if (size == 12)
|
|
o(0x7cdb);
|
|
else
|
|
o(0x5cd9 + size - 4); /* fstp[s|l] 0(%esp) */
|
|
g(0x24);
|
|
g(0x00);
|
|
args_size += size;
|
|
} else {
|
|
/* simple type (currently always same size) */
|
|
/* XXX: implicit cast ? */
|
|
r = gv(RC_INT);
|
|
if ((vtop->type.t & VT_BTYPE) == VT_LLONG) {
|
|
size = 8;
|
|
o(0x50 + vtop->r2); /* push r */
|
|
} else {
|
|
size = 4;
|
|
}
|
|
o(0x50 + r); /* push r */
|
|
args_size += size;
|
|
}
|
|
vtop--;
|
|
}
|
|
save_regs(0); /* save used temporary registers */
|
|
func_sym = vtop->type.ref;
|
|
func_call = func_sym->f.func_call;
|
|
/* fast call case */
|
|
if ((func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) ||
|
|
func_call == FUNC_FASTCALLW) {
|
|
int fastcall_nb_regs;
|
|
uint8_t *fastcall_regs_ptr;
|
|
if (func_call == FUNC_FASTCALLW) {
|
|
fastcall_regs_ptr = fastcallw_regs;
|
|
fastcall_nb_regs = 2;
|
|
} else {
|
|
fastcall_regs_ptr = fastcall_regs;
|
|
fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1;
|
|
}
|
|
for(i = 0;i < fastcall_nb_regs; i++) {
|
|
if (args_size <= 0)
|
|
break;
|
|
o(0x58 + fastcall_regs_ptr[i]); /* pop r */
|
|
/* XXX: incorrect for struct/floats */
|
|
args_size -= 4;
|
|
}
|
|
}
|
|
#ifndef TCC_TARGET_PE
|
|
else if ((vtop->type.ref->type.t & VT_BTYPE) == VT_STRUCT)
|
|
args_size -= 4;
|
|
#endif
|
|
|
|
stack_align_delta = ((args_size + 15) & -16) - args_size;
|
|
args_size += stack_align_delta;
|
|
|
|
saved_ind = ind;
|
|
ind = align_placeholder_offset;
|
|
o(stack_align_delta);
|
|
ind = saved_ind;
|
|
|
|
gcall_or_jmp(0);
|
|
|
|
if (args_size && func_call != FUNC_STDCALL && func_call != FUNC_FASTCALLW)
|
|
gadd_sp(args_size);
|
|
vtop--;
|
|
}
|
|
|
|
#ifdef TCC_TARGET_PE
|
|
#define FUNC_PROLOG_SIZE (10 + USE_EBX)
|
|
#else
|
|
#define FUNC_PROLOG_SIZE (9 + USE_EBX)
|
|
#endif
|
|
|
|
/* generate function prolog of type 't' */
|
|
ST_FUNC void gfunc_prolog(Sym *func_sym)
|
|
{
|
|
CType *func_type = &func_sym->type;
|
|
int addr, align, size, func_call, fastcall_nb_regs;
|
|
int param_index, param_addr;
|
|
uint8_t *fastcall_regs_ptr;
|
|
Sym *sym;
|
|
CType *type;
|
|
|
|
sym = func_type->ref;
|
|
func_call = sym->f.func_call;
|
|
addr = 8;
|
|
loc = 0;
|
|
func_vc = 0;
|
|
|
|
if (func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) {
|
|
fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1;
|
|
fastcall_regs_ptr = fastcall_regs;
|
|
} else if (func_call == FUNC_FASTCALLW) {
|
|
fastcall_nb_regs = 2;
|
|
fastcall_regs_ptr = fastcallw_regs;
|
|
} else {
|
|
fastcall_nb_regs = 0;
|
|
fastcall_regs_ptr = NULL;
|
|
}
|
|
param_index = 0;
|
|
|
|
ind += FUNC_PROLOG_SIZE;
|
|
func_sub_sp_offset = ind;
|
|
/* if the function returns a structure, then add an
|
|
implicit pointer parameter */
|
|
#ifdef TCC_TARGET_PE
|
|
size = type_size(&func_vt,&align);
|
|
if (((func_vt.t & VT_BTYPE) == VT_STRUCT)
|
|
&& (size > 8 || (size & (size - 1)))) {
|
|
#else
|
|
if ((func_vt.t & VT_BTYPE) == VT_STRUCT) {
|
|
#endif
|
|
/* XXX: fastcall case ? */
|
|
func_vc = addr;
|
|
addr += 4;
|
|
param_index++;
|
|
}
|
|
/* define parameters */
|
|
while ((sym = sym->next) != NULL) {
|
|
type = &sym->type;
|
|
size = type_size(type, &align);
|
|
size = (size + 3) & ~3;
|
|
#ifdef FUNC_STRUCT_PARAM_AS_PTR
|
|
/* structs are passed as pointer */
|
|
if ((type->t & VT_BTYPE) == VT_STRUCT) {
|
|
size = 4;
|
|
}
|
|
#endif
|
|
if (param_index < fastcall_nb_regs) {
|
|
/* save FASTCALL register */
|
|
loc -= 4;
|
|
o(0x89); /* movl */
|
|
gen_modrm(fastcall_regs_ptr[param_index], VT_LOCAL, NULL, loc);
|
|
param_addr = loc;
|
|
} else {
|
|
param_addr = addr;
|
|
addr += size;
|
|
}
|
|
sym_push(sym->v & ~SYM_FIELD, type,
|
|
VT_LOCAL | VT_LVAL, param_addr);
|
|
param_index++;
|
|
}
|
|
func_ret_sub = 0;
|
|
/* pascal type call or fastcall ? */
|
|
if (func_call == FUNC_STDCALL || func_call == FUNC_FASTCALLW)
|
|
func_ret_sub = addr - 8;
|
|
#ifndef TCC_TARGET_PE
|
|
else if (func_vc)
|
|
func_ret_sub = 4;
|
|
#endif
|
|
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (tcc_state->do_bounds_check)
|
|
gen_bounds_prolog();
|
|
#endif
|
|
}
|
|
|
|
/* generate function epilog */
|
|
ST_FUNC void gfunc_epilog(void)
|
|
{
|
|
addr_t v, saved_ind;
|
|
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (tcc_state->do_bounds_check)
|
|
gen_bounds_epilog();
|
|
#endif
|
|
|
|
/* align local size & save local variables */
|
|
v = -loc;
|
|
if (v <= 8) {
|
|
v = 8;
|
|
} else {
|
|
addr_t align_remainder = (v - 8) % 16;
|
|
if (align_remainder != 0)
|
|
v += 16 - align_remainder;
|
|
}
|
|
|
|
#if USE_EBX
|
|
o(0x8b);
|
|
gen_modrm(TREG_EBX, VT_LOCAL, NULL, -(v+4));
|
|
#endif
|
|
|
|
o(0xc9); /* leave */
|
|
if (func_ret_sub == 0) {
|
|
o(0xc3); /* ret */
|
|
} else {
|
|
o(0xc2); /* ret n */
|
|
g(func_ret_sub);
|
|
g(func_ret_sub >> 8);
|
|
}
|
|
saved_ind = ind;
|
|
ind = func_sub_sp_offset - FUNC_PROLOG_SIZE;
|
|
#ifdef TCC_TARGET_PE
|
|
if (v >= 4096) {
|
|
oad(0xb8, v); /* mov stacksize, %eax */
|
|
gen_static_call(TOK___chkstk); /* call __chkstk, (does the stackframe too) */
|
|
} else
|
|
#endif
|
|
{
|
|
o(0xe58955); /* push %ebp, mov %esp, %ebp */
|
|
o(0xec81); /* sub esp, stacksize */
|
|
gen_le32(v);
|
|
#ifdef TCC_TARGET_PE
|
|
o(0x90); /* adjust to FUNC_PROLOG_SIZE */
|
|
#endif
|
|
}
|
|
o(0x53 * USE_EBX); /* push ebx */
|
|
ind = saved_ind;
|
|
}
|
|
|
|
/* generate a jump to a label */
|
|
ST_FUNC int gjmp(int t)
|
|
{
|
|
return gjmp2(0xe9, t);
|
|
}
|
|
|
|
/* generate a jump to a fixed address */
|
|
ST_FUNC void gjmp_addr(int a)
|
|
{
|
|
int r;
|
|
r = a - ind - 2;
|
|
if (r == (char)r) {
|
|
g(0xeb);
|
|
g(r);
|
|
} else {
|
|
oad(0xe9, a - ind - 5);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* generate a jump to a fixed address */
|
|
ST_FUNC void gjmp_cond_addr(int a, int op)
|
|
{
|
|
int r = a - ind - 2;
|
|
if (r == (char)r)
|
|
g(op - 32), g(r);
|
|
else
|
|
g(0x0f), gjmp2(op - 16, r - 4);
|
|
}
|
|
#endif
|
|
|
|
ST_FUNC int gjmp_append(int n, int t)
|
|
{
|
|
void *p;
|
|
/* insert vtop->c jump list in t */
|
|
if (n) {
|
|
uint32_t n1 = n, n2;
|
|
while ((n2 = read32le(p = cur_text_section->data + n1)))
|
|
n1 = n2;
|
|
write32le(p, t);
|
|
t = n;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
ST_FUNC int gjmp_cond(int op, int t)
|
|
{
|
|
g(0x0f);
|
|
t = gjmp2(op - 16, t);
|
|
return t;
|
|
}
|
|
|
|
ST_FUNC void gen_opi(int op)
|
|
{
|
|
int r, fr, opc, c;
|
|
|
|
switch(op) {
|
|
case '+':
|
|
case TOK_ADDC1: /* add with carry generation */
|
|
opc = 0;
|
|
gen_op8:
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) {
|
|
/* constant case */
|
|
vswap();
|
|
r = gv(RC_INT);
|
|
vswap();
|
|
c = vtop->c.i;
|
|
if (c == (char)c) {
|
|
/* generate inc and dec for smaller code */
|
|
if ((c == 1 || c == -1) && (op == '+' || op == '-')) {
|
|
opc = (c == 1) ^ (op == '+');
|
|
o (0x40 | (opc << 3) | r); // inc,dec
|
|
} else {
|
|
o(0x83);
|
|
o(0xc0 | (opc << 3) | r);
|
|
g(c);
|
|
}
|
|
} else {
|
|
o(0x81);
|
|
oad(0xc0 | (opc << 3) | r, c);
|
|
}
|
|
} else {
|
|
gv2(RC_INT, RC_INT);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
o((opc << 3) | 0x01);
|
|
o(0xc0 + r + fr * 8);
|
|
}
|
|
vtop--;
|
|
if (op >= TOK_ULT && op <= TOK_GT)
|
|
vset_VT_CMP(op);
|
|
break;
|
|
case '-':
|
|
case TOK_SUBC1: /* sub with carry generation */
|
|
opc = 5;
|
|
goto gen_op8;
|
|
case TOK_ADDC2: /* add with carry use */
|
|
opc = 2;
|
|
goto gen_op8;
|
|
case TOK_SUBC2: /* sub with carry use */
|
|
opc = 3;
|
|
goto gen_op8;
|
|
case '&':
|
|
opc = 4;
|
|
goto gen_op8;
|
|
case '^':
|
|
opc = 6;
|
|
goto gen_op8;
|
|
case '|':
|
|
opc = 1;
|
|
goto gen_op8;
|
|
case '*':
|
|
gv2(RC_INT, RC_INT);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
vtop--;
|
|
o(0xaf0f); /* imul fr, r */
|
|
o(0xc0 + fr + r * 8);
|
|
break;
|
|
case TOK_SHL:
|
|
opc = 4;
|
|
goto gen_shift;
|
|
case TOK_SHR:
|
|
opc = 5;
|
|
goto gen_shift;
|
|
case TOK_SAR:
|
|
opc = 7;
|
|
gen_shift:
|
|
opc = 0xc0 | (opc << 3);
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) {
|
|
/* constant case */
|
|
vswap();
|
|
r = gv(RC_INT);
|
|
vswap();
|
|
c = vtop->c.i & 0x1f;
|
|
o(0xc1); /* shl/shr/sar $xxx, r */
|
|
o(opc | r);
|
|
g(c);
|
|
} else {
|
|
/* we generate the shift in ecx */
|
|
gv2(RC_INT, RC_ECX);
|
|
r = vtop[-1].r;
|
|
o(0xd3); /* shl/shr/sar %cl, r */
|
|
o(opc | r);
|
|
}
|
|
vtop--;
|
|
break;
|
|
case '/':
|
|
case TOK_UDIV:
|
|
case TOK_PDIV:
|
|
case '%':
|
|
case TOK_UMOD:
|
|
case TOK_UMULL:
|
|
/* first operand must be in eax */
|
|
/* XXX: need better constraint for second operand */
|
|
gv2(RC_EAX, RC_ECX);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
vtop--;
|
|
save_reg(TREG_EDX);
|
|
/* save EAX too if used otherwise */
|
|
save_reg_upstack(TREG_EAX, 1);
|
|
if (op == TOK_UMULL) {
|
|
o(0xf7); /* mul fr */
|
|
o(0xe0 + fr);
|
|
vtop->r2 = TREG_EDX;
|
|
r = TREG_EAX;
|
|
} else {
|
|
if (op == TOK_UDIV || op == TOK_UMOD) {
|
|
o(0xf7d231); /* xor %edx, %edx, div fr, %eax */
|
|
o(0xf0 + fr);
|
|
} else {
|
|
o(0xf799); /* cltd, idiv fr, %eax */
|
|
o(0xf8 + fr);
|
|
}
|
|
if (op == '%' || op == TOK_UMOD)
|
|
r = TREG_EDX;
|
|
else
|
|
r = TREG_EAX;
|
|
}
|
|
vtop->r = r;
|
|
break;
|
|
default:
|
|
opc = 7;
|
|
goto gen_op8;
|
|
}
|
|
}
|
|
|
|
/* generate a floating point operation 'v = t1 op t2' instruction. The
|
|
two operands are guaranteed to have the same floating point type */
|
|
/* XXX: need to use ST1 too */
|
|
ST_FUNC void gen_opf(int op)
|
|
{
|
|
int a, ft, fc, swapped, r;
|
|
|
|
/* convert constants to memory references */
|
|
if ((vtop[-1].r & (VT_VALMASK | VT_LVAL)) == VT_CONST) {
|
|
vswap();
|
|
gv(RC_FLOAT);
|
|
vswap();
|
|
}
|
|
if ((vtop[0].r & (VT_VALMASK | VT_LVAL)) == VT_CONST)
|
|
gv(RC_FLOAT);
|
|
|
|
/* must put at least one value in the floating point register */
|
|
if ((vtop[-1].r & VT_LVAL) &&
|
|
(vtop[0].r & VT_LVAL)) {
|
|
vswap();
|
|
gv(RC_FLOAT);
|
|
vswap();
|
|
}
|
|
swapped = 0;
|
|
/* swap the stack if needed so that t1 is the register and t2 is
|
|
the memory reference */
|
|
if (vtop[-1].r & VT_LVAL) {
|
|
vswap();
|
|
swapped = 1;
|
|
}
|
|
if (op >= TOK_ULT && op <= TOK_GT) {
|
|
/* load on stack second operand */
|
|
load(TREG_ST0, vtop);
|
|
save_reg(TREG_EAX); /* eax is used by FP comparison code */
|
|
if (op == TOK_GE || op == TOK_GT)
|
|
swapped = !swapped;
|
|
else if (op == TOK_EQ || op == TOK_NE)
|
|
swapped = 0;
|
|
if (swapped)
|
|
o(0xc9d9); /* fxch %st(1) */
|
|
if (op == TOK_EQ || op == TOK_NE)
|
|
o(0xe9da); /* fucompp */
|
|
else
|
|
o(0xd9de); /* fcompp */
|
|
o(0xe0df); /* fnstsw %ax */
|
|
if (op == TOK_EQ) {
|
|
o(0x45e480); /* and $0x45, %ah */
|
|
o(0x40fC80); /* cmp $0x40, %ah */
|
|
} else if (op == TOK_NE) {
|
|
o(0x45e480); /* and $0x45, %ah */
|
|
o(0x40f480); /* xor $0x40, %ah */
|
|
op = TOK_NE;
|
|
} else if (op == TOK_GE || op == TOK_LE) {
|
|
o(0x05c4f6); /* test $0x05, %ah */
|
|
op = TOK_EQ;
|
|
} else {
|
|
o(0x45c4f6); /* test $0x45, %ah */
|
|
op = TOK_EQ;
|
|
}
|
|
vtop--;
|
|
vset_VT_CMP(op);
|
|
} else {
|
|
/* no memory reference possible for long double operations */
|
|
if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) {
|
|
load(TREG_ST0, vtop);
|
|
swapped = !swapped;
|
|
}
|
|
|
|
switch(op) {
|
|
default:
|
|
case '+':
|
|
a = 0;
|
|
break;
|
|
case '-':
|
|
a = 4;
|
|
if (swapped)
|
|
a++;
|
|
break;
|
|
case '*':
|
|
a = 1;
|
|
break;
|
|
case '/':
|
|
a = 6;
|
|
if (swapped)
|
|
a++;
|
|
break;
|
|
}
|
|
ft = vtop->type.t;
|
|
fc = vtop->c.i;
|
|
if ((ft & VT_BTYPE) == VT_LDOUBLE) {
|
|
o(0xde); /* fxxxp %st, %st(1) */
|
|
o(0xc1 + (a << 3));
|
|
} else {
|
|
/* if saved lvalue, then we must reload it */
|
|
r = vtop->r;
|
|
if ((r & VT_VALMASK) == VT_LLOCAL) {
|
|
SValue v1;
|
|
r = get_reg(RC_INT);
|
|
v1.type.t = VT_INT;
|
|
v1.r = VT_LOCAL | VT_LVAL;
|
|
v1.c.i = fc;
|
|
v1.sym = NULL;
|
|
load(r, &v1);
|
|
fc = 0;
|
|
}
|
|
|
|
if ((ft & VT_BTYPE) == VT_DOUBLE)
|
|
o(0xdc);
|
|
else
|
|
o(0xd8);
|
|
gen_modrm(a, r, vtop->sym, fc);
|
|
}
|
|
vtop--;
|
|
}
|
|
}
|
|
|
|
/* convert integers to fp 't' type. Must handle 'int', 'unsigned int'
|
|
and 'long long' cases. */
|
|
ST_FUNC void gen_cvt_itof(int t)
|
|
{
|
|
save_reg(TREG_ST0);
|
|
gv(RC_INT);
|
|
if ((vtop->type.t & VT_BTYPE) == VT_LLONG) {
|
|
/* signed long long to float/double/long double (unsigned case
|
|
is handled generically) */
|
|
o(0x50 + vtop->r2); /* push r2 */
|
|
o(0x50 + (vtop->r & VT_VALMASK)); /* push r */
|
|
o(0x242cdf); /* fildll (%esp) */
|
|
o(0x08c483); /* add $8, %esp */
|
|
vtop->r2 = VT_CONST;
|
|
} else if ((vtop->type.t & (VT_BTYPE | VT_UNSIGNED)) ==
|
|
(VT_INT | VT_UNSIGNED)) {
|
|
/* unsigned int to float/double/long double */
|
|
o(0x6a); /* push $0 */
|
|
g(0x00);
|
|
o(0x50 + (vtop->r & VT_VALMASK)); /* push r */
|
|
o(0x242cdf); /* fildll (%esp) */
|
|
o(0x08c483); /* add $8, %esp */
|
|
} else {
|
|
/* int to float/double/long double */
|
|
o(0x50 + (vtop->r & VT_VALMASK)); /* push r */
|
|
o(0x2404db); /* fildl (%esp) */
|
|
o(0x04c483); /* add $4, %esp */
|
|
}
|
|
vtop->r2 = VT_CONST;
|
|
vtop->r = TREG_ST0;
|
|
}
|
|
|
|
/* convert fp to int 't' type */
|
|
ST_FUNC void gen_cvt_ftoi(int t)
|
|
{
|
|
int bt = vtop->type.t & VT_BTYPE;
|
|
if (bt == VT_FLOAT)
|
|
vpush_global_sym(&func_old_type, TOK___fixsfdi);
|
|
else if (bt == VT_LDOUBLE)
|
|
vpush_global_sym(&func_old_type, TOK___fixxfdi);
|
|
else
|
|
vpush_global_sym(&func_old_type, TOK___fixdfdi);
|
|
vswap();
|
|
gfunc_call(1);
|
|
vpushi(0);
|
|
vtop->r = REG_IRET;
|
|
if ((t & VT_BTYPE) == VT_LLONG)
|
|
vtop->r2 = REG_IRE2;
|
|
}
|
|
|
|
/* convert from one floating point type to another */
|
|
ST_FUNC void gen_cvt_ftof(int t)
|
|
{
|
|
/* all we have to do on i386 is to put the float in a register */
|
|
gv(RC_FLOAT);
|
|
}
|
|
|
|
/* char/short to int conversion */
|
|
ST_FUNC void gen_cvt_csti(int t)
|
|
{
|
|
int r, sz, xl;
|
|
r = gv(RC_INT);
|
|
sz = !(t & VT_UNSIGNED);
|
|
xl = (t & VT_BTYPE) == VT_SHORT;
|
|
o(0xc0b60f /* mov[sz] %a[xl], %eax */
|
|
| (sz << 3 | xl) << 8
|
|
| (r << 3 | r) << 16
|
|
);
|
|
}
|
|
|
|
/* computed goto support */
|
|
ST_FUNC void ggoto(void)
|
|
{
|
|
gcall_or_jmp(1);
|
|
vtop--;
|
|
}
|
|
|
|
/* bound check support functions */
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
|
|
static void gen_bounds_prolog(void)
|
|
{
|
|
/* leave some room for bound checking code */
|
|
func_bound_offset = lbounds_section->data_offset;
|
|
func_bound_ind = ind;
|
|
func_bound_add_epilog = 0;
|
|
oad(0xb8, 0); /* lbound section pointer */
|
|
oad(0xb8, 0); /* call to function */
|
|
}
|
|
|
|
static void gen_bounds_epilog(void)
|
|
{
|
|
addr_t saved_ind;
|
|
addr_t *bounds_ptr;
|
|
Sym *sym_data;
|
|
int offset_modified = func_bound_offset != lbounds_section->data_offset;
|
|
|
|
if (!offset_modified && !func_bound_add_epilog)
|
|
return;
|
|
|
|
/* add end of table info */
|
|
bounds_ptr = section_ptr_add(lbounds_section, sizeof(addr_t));
|
|
*bounds_ptr = 0;
|
|
|
|
sym_data = get_sym_ref(&char_pointer_type, lbounds_section,
|
|
func_bound_offset, lbounds_section->data_offset);
|
|
|
|
/* generate bound local allocation */
|
|
if (offset_modified) {
|
|
saved_ind = ind;
|
|
ind = func_bound_ind;
|
|
greloc(cur_text_section, sym_data, ind + 1, R_386_32);
|
|
ind = ind + 5;
|
|
gen_static_call(TOK___bound_local_new);
|
|
ind = saved_ind;
|
|
}
|
|
|
|
/* generate bound check local freeing */
|
|
o(0x5250); /* save returned value, if any */
|
|
greloc(cur_text_section, sym_data, ind + 1, R_386_32);
|
|
oad(0xb8, 0); /* mov %eax, xxx */
|
|
gen_static_call(TOK___bound_local_delete);
|
|
o(0x585a); /* restore returned value, if any */
|
|
}
|
|
#endif
|
|
|
|
/* Save the stack pointer onto the stack */
|
|
ST_FUNC void gen_vla_sp_save(int addr) {
|
|
/* mov %esp,addr(%ebp)*/
|
|
o(0x89);
|
|
gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr);
|
|
}
|
|
|
|
/* Restore the SP from a location on the stack */
|
|
ST_FUNC void gen_vla_sp_restore(int addr) {
|
|
o(0x8b);
|
|
gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr);
|
|
}
|
|
|
|
/* Subtract from the stack pointer, and push the resulting value onto the stack */
|
|
ST_FUNC void gen_vla_alloc(CType *type, int align) {
|
|
int use_call = 0;
|
|
|
|
#if defined(CONFIG_TCC_BCHECK)
|
|
use_call = tcc_state->do_bounds_check;
|
|
#endif
|
|
#ifdef TCC_TARGET_PE /* alloca does more than just adjust %rsp on Windows */
|
|
use_call = 1;
|
|
#endif
|
|
if (use_call)
|
|
{
|
|
vpush_global_sym(&func_old_type, TOK_alloca);
|
|
vswap(); /* Move alloca ref past allocation size */
|
|
gfunc_call(1);
|
|
}
|
|
else {
|
|
int r;
|
|
r = gv(RC_INT); /* allocation size */
|
|
/* sub r,%rsp */
|
|
o(0x2b);
|
|
o(0xe0 | r);
|
|
/* We align to 16 bytes rather than align */
|
|
/* and ~15, %esp */
|
|
o(0xf0e483);
|
|
vpop();
|
|
}
|
|
}
|
|
|
|
/* end of X86 code generator */
|
|
/*************************************************************/
|
|
#endif
|
|
/*************************************************************/
|