tcc-stupidos/libtcc/cc/i386/gen.c

1005 lines
26 KiB
C
Raw Normal View History

2001-12-13 22:28:53 +00:00
/*
* X86 code generator for TCC
*
2004-10-27 21:38:03 +00:00
* Copyright (c) 2001-2004 Fabrice Bellard
2001-12-13 22:28:53 +00:00
*
2003-05-24 14:11:17 +00:00
* 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.
2001-12-13 22:28:53 +00:00
*
2003-05-24 14:11:17 +00:00
* 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.
2001-12-13 22:28:53 +00:00
*
2003-05-24 14:11:17 +00:00
* 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
2001-12-13 22:28:53 +00:00
*/
2001-12-12 21:16:34 +00:00
2025-02-21 14:10:26 +00:00
#include "tcc/object/coff.h"
#define USING_GLOBALS
#include <tcc.h>
2025-02-14 16:47:45 +00:00
#include "cc/token.h"
#define USING_GLOBALS
#include "cc/cc.h"
const char * const target_machine_defs =
"__i386__\0"
"__i386\0"
;
/* define to 1/0 to [not] have EBX as 4th register */
#define USE_EBX 0
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,
};
2001-12-12 21:16:34 +00:00
static unsigned long func_sub_sp_offset;
2002-01-26 17:23:44 +00:00
static int func_ret_sub;
/* XXX: make it faster ? */
2025-02-21 14:10:26 +00:00
void
g(int c)
2001-12-12 21:16:34 +00:00
{
2025-02-21 14:10:26 +00:00
int ind1;
if (nocode_wanted)
{
return;
}
ind1 = ind + 1;
if (ind1 > tcc_state->cur_text_section->data_allocated)
{
coff_section_realloc(tcc_state->cur_text_section, ind1);
}
tcc_state->cur_text_section->data[ind] = c;
ind = ind1;
2001-12-12 21:16:34 +00:00
}
void o(unsigned int c)
2001-12-12 21:16:34 +00:00
{
while (c) {
g(c);
2004-10-04 22:06:22 +00:00
c = c >> 8;
2001-12-12 21:16:34 +00:00
}
}
void gen_le16(int v)
{
g(v);
g(v >> 8);
}
void gen_le32(int c)
2001-12-12 21:16:34 +00:00
{
g(c);
g(c >> 8);
g(c >> 16);
g(c >> 24);
2001-12-12 21:16:34 +00:00
}
/* output a symbol and patch all calls to it */
void gsym_addr(int t, int a)
2001-12-12 21:16:34 +00:00
{
while (t) {
2025-02-11 19:16:44 +00:00
unsigned char *ptr = tcc_state->cur_text_section->data + t;
uint32_t n = read32le(ptr); /* next value */
write32le(ptr, a - t - 4);
2001-12-12 21:16:34 +00:00
t = n;
}
}
/* instruction + 4 bytes data. Return the address of the data */
static int oad(int c, int s)
2001-12-12 21:16:34 +00:00
{
int t;
if (nocode_wanted)
return s;
o(c);
t = ind;
gen_le32(s);
return t;
2001-12-12 21:16:34 +00:00
}
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 */
2025-02-21 14:10:26 +00:00
void
gen_addr32(int r, Sym *sym, int c)
2001-12-12 21:16:34 +00:00
{
2025-02-21 14:10:26 +00:00
if (r & VT_SYM)
{
greloc(tcc_state->cur_text_section, sym, ind, COFF_R_DIR32);
}
gen_le32(c);
2001-12-12 21:16:34 +00:00
}
void gen_addrpc32(int r, Sym *sym, int c)
{
2025-02-21 14:10:26 +00:00
if (r & VT_SYM)
{
greloc(tcc_state->cur_text_section, sym, ind, COFF_R_PCRLONG);
}
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));
}
}
2001-12-20 01:05:21 +00:00
/* load 'r' from value 'sv' */
void load(int r, SValue *sv)
2001-12-12 21:16:34 +00:00
{
2001-12-20 01:05:21 +00:00
int v, t, ft, fc, fr;
SValue v1;
2001-12-12 21:16:34 +00:00
2001-12-20 01:05:21 +00:00
fr = sv->r;
ft = sv->type.t & ~VT_DEFSIGN;
fc = sv->c.i;
2001-12-20 01:05:21 +00:00
ft &= ~(VT_VOLATILE | VT_CONSTANT);
2001-12-20 01:05:21 +00:00
v = fr & VT_VALMASK;
if (fr & VT_LVAL) {
2001-12-12 21:16:34 +00:00
if (v == VT_LLOCAL) {
v1.type.t = VT_INT;
2001-12-20 01:05:21 +00:00
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);
2001-12-12 21:16:34 +00:00
}
if ((ft & VT_BTYPE) == VT_FLOAT) {
o(0xd9); /* flds */
2001-12-12 21:16:34 +00:00
r = 0;
} else if ((ft & VT_BTYPE) == VT_DOUBLE) {
o(0xdd); /* fldl */
2001-12-12 21:16:34 +00:00
r = 0;
2001-12-13 22:28:53 +00:00
} else if ((ft & VT_BTYPE) == VT_LDOUBLE) {
o(0xdb); /* fldt */
2001-12-13 22:28:53 +00:00
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);
2001-12-12 21:16:34 +00:00
} else {
if (v == VT_CONST) {
o(0xb8 + r); /* mov $xx, r */
gen_addr32(fr, sv->sym, fc);
2001-12-12 21:16:34 +00:00
} 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 */
}
2001-12-12 21:16:34 +00:00
} else if (v == VT_CMP) {
o(0x0f); /* setxx %br */
o(fc);
o(0xc0 + r);
o(0xc0b60f + r * 0x90000); /* movzbl %al, %eax */
2001-12-12 21:16:34 +00:00
} 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 */
2001-12-12 21:16:34 +00:00
} else if (v != r) {
o(0x89);
o(0xc0 + r + v * 8); /* mov v, r */
2001-12-12 21:16:34 +00:00
}
}
}
2001-12-20 01:05:21 +00:00
/* store register 'r' in lvalue 'v' */
void store(int r, SValue *v)
2001-12-12 21:16:34 +00:00
{
2001-12-20 01:05:21 +00:00
int fr, bt, ft, fc;
2001-12-12 21:16:34 +00:00
ft = v->type.t;
fc = v->c.i;
2001-12-20 01:05:21 +00:00
fr = v->r & VT_VALMASK;
ft &= ~(VT_VOLATILE | VT_CONSTANT);
2001-12-12 21:16:34 +00:00
bt = ft & VT_BTYPE;
/* XXX: incorrect if float reg to reg */
2001-12-12 21:16:34 +00:00
if (bt == VT_FLOAT) {
o(0xd9); /* fsts */
2001-12-17 21:56:48 +00:00
r = 2;
2001-12-12 21:16:34 +00:00
} else if (bt == VT_DOUBLE) {
o(0xdd); /* fstpl */
2001-12-17 21:56:48 +00:00
r = 2;
2001-12-13 22:28:53 +00:00
} else if (bt == VT_LDOUBLE) {
o(0xc0d9); /* fld %st(0) */
o(0xdb); /* fstpt */
2001-12-13 22:28:53 +00:00
r = 7;
2001-12-12 21:16:34 +00:00
} else {
if (bt == VT_SHORT)
o(0x66);
2005-04-17 13:15:31 +00:00
if (bt == VT_BYTE || bt == VT_BOOL)
o(0x88);
2001-12-12 21:16:34 +00:00
else
o(0x89);
2001-12-12 21:16:34 +00:00
}
if (fr == VT_CONST ||
fr == VT_LOCAL ||
(v->r & VT_LVAL)) {
gen_modrm(r, v->r, v->sym, fc);
2001-12-12 21:16:34 +00:00
} else if (fr != r) {
o(0xc0 + fr + r * 8); /* mov r, fr */
2001-12-12 21:16:34 +00:00
}
}
static void gadd_sp(int val)
{
if (val == (char)val) {
o(0xc483);
g(val);
} else {
oad(0xc481, val); /* add $xxx, %esp */
}
}
2002-11-03 00:43:01 +00:00
/* 'is_jmp' is '1' if it is a jump */
2025-02-21 14:10:26 +00:00
static void
gcall_or_jmp(int is_jmp)
2001-12-12 21:16:34 +00:00
{
2025-02-21 14:10:26 +00:00
int r;
if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST && (vtop->r & VT_SYM))
{
/* constant and relocation case */
greloc(tcc_state->cur_text_section, vtop->sym, ind + 1, COFF_R_PCRLONG);
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));
}
2002-11-03 00:43:01 +00:00
}
static const uint8_t fastcall_regs[3] = { TREG_EAX, TREG_EDX, TREG_ECX };
static const uint8_t fastcallw_regs[2] = { TREG_ECX, TREG_EDX };
2004-10-18 00:14:40 +00:00
/* Return the number of registers needed to return the struct, or 0 if
returning via struct pointer. */
int gfunc_sret(CType *vt, int variadic, CType *ret, int *ret_align, int *regsize)
{
2021-01-11 18:26:10 +00:00
#if defined(TCC_TARGET_PE) || TARGETOS_FreeBSD || TARGETOS_OpenBSD
int size, align, nregs;
*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;
nregs = 1;
if (size == 8)
ret->t = VT_INT, nregs = 2;
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 nregs;
#else
(void)vt;
(void)variadic;
(void)ret;
(void)regsize;
*ret_align = 1; // Never have to re-align return values for x86
return 0;
#endif
}
2003-04-16 21:16:20 +00:00
/* 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. */
void gfunc_call(int nb_args)
2002-11-03 00:43:01 +00:00
{
2004-10-18 00:14:40 +00:00
int size, align, r, args_size, i, func_call;
2003-04-16 21:16:20 +00:00
Sym *func_sym;
2003-04-16 21:16:20 +00:00
args_size = 0;
for(i = 0;i < nb_args; i++) {
if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) {
/* fetch cpu flag before generating any code */
if ((vtop->r & VT_VALMASK) == VT_CMP)
gv(RC_INT);
size = type_size(&vtop->type, &align);
2003-04-16 21:16:20 +00:00
/* align to stack align size */
size = (size + 3) & ~3;
/* allocate the necessary size on stack */
#ifdef TCC_TARGET_PE
if (size >= 4096) {
r = get_reg(RC_EAX);
oad(0x68, size); // push size
/* cannot call normal 'alloca' with bound checking */
gen_static_call(tok_alloc_const("__alloca"));
gadd_sp(4);
} else
#endif
{
oad(0xec81, size); /* sub $xxx, %esp */
/* generate structure store */
r = get_reg(RC_INT);
o(0xe089 + (r << 8)); /* mov %esp, r */
}
vset(&vtop->type, r | VT_LVAL, 0);
vswap();
vstore();
2003-04-16 21:16:20 +00:00
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)
2003-04-16 21:16:20 +00:00
size = 4;
else if ((vtop->type.t & VT_BTYPE) == VT_DOUBLE)
2003-04-16 21:16:20 +00:00
size = 8;
else
size = 12;
oad(0xec81, size); /* sub $xxx, %esp */
2003-04-16 21:16:20 +00:00
if (size == 12)
o(0x7cdb);
2003-04-16 21:16:20 +00:00
else
o(0x5cd9 + size - 4); /* fstp[s|l] 0(%esp) */
g(0x24);
g(0x00);
2003-04-16 21:16:20 +00:00
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) {
2003-04-16 21:16:20 +00:00
size = 8;
o(0x50 + vtop->r2); /* push r */
2003-04-16 21:16:20 +00:00
} else {
size = 4;
}
o(0x50 + r); /* push r */
2003-04-16 21:16:20 +00:00
args_size += size;
}
vtop--;
2003-04-16 21:16:20 +00:00
}
save_regs(0); /* save used temporary registers */
func_sym = vtop->type.ref;
func_call = func_sym->f.func_call;
2004-10-18 00:14:40 +00:00
/* fast call case */
2005-09-03 22:21:22 +00:00
if ((func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) ||
func_call == FUNC_FASTCALLW || func_call == FUNC_THISCALL) {
2004-10-18 00:14:40 +00:00
int fastcall_nb_regs;
const uint8_t *fastcall_regs_ptr;
2005-09-03 22:21:22 +00:00
if (func_call == FUNC_FASTCALLW) {
fastcall_regs_ptr = fastcallw_regs;
fastcall_nb_regs = 2;
} else if (func_call == FUNC_THISCALL) {
fastcall_regs_ptr = fastcallw_regs;
fastcall_nb_regs = 1;
2005-09-03 22:21:22 +00:00
} else {
fastcall_regs_ptr = fastcall_regs;
fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1;
}
2004-10-18 00:14:40 +00:00
for(i = 0;i < fastcall_nb_regs; i++) {
if (args_size <= 0)
break;
o(0x58 + fastcall_regs_ptr[i]); /* pop r */
2004-10-18 00:14:40 +00:00
/* XXX: incorrect for struct/floats */
args_size -= 4;
}
}
2021-01-11 18:26:10 +00:00
#if !defined(TCC_TARGET_PE) && !TARGETOS_FreeBSD || TARGETOS_OpenBSD
else if ((vtop->type.ref->type.t & VT_BTYPE) == VT_STRUCT)
args_size -= 4;
#endif
gcall_or_jmp(0);
if (args_size && func_call != FUNC_STDCALL && func_call != FUNC_THISCALL && func_call != FUNC_FASTCALLW)
gadd_sp(args_size);
vtop--;
2001-12-12 21:16:34 +00:00
}
2005-04-10 22:15:08 +00:00
#ifdef TCC_TARGET_PE
#define FUNC_PROLOG_SIZE (10 + USE_EBX)
2005-04-10 22:15:08 +00:00
#else
#define FUNC_PROLOG_SIZE (9 + USE_EBX)
2005-04-10 22:15:08 +00:00
#endif
/* generate function prolog of type 't' */
void gfunc_prolog(Sym *func_sym)
{
2019-12-10 07:07:25 +00:00
CType *func_type = &func_sym->type;
2004-10-18 00:14:40 +00:00
int addr, align, size, func_call, fastcall_nb_regs;
int param_index, param_addr;
const 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;
2004-10-18 00:14:40 +00:00
if (func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) {
fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1;
2005-09-03 22:21:22 +00:00
fastcall_regs_ptr = fastcall_regs;
} else if (func_call == FUNC_FASTCALLW) {
fastcall_nb_regs = 2;
fastcall_regs_ptr = fastcallw_regs;
} else if (func_call == FUNC_THISCALL) {
fastcall_nb_regs = 1;
fastcall_regs_ptr = fastcallw_regs;
2004-10-18 00:14:40 +00:00
} else {
fastcall_nb_regs = 0;
2005-09-03 22:21:22 +00:00
fastcall_regs_ptr = NULL;
2004-10-18 00:14:40 +00:00
}
param_index = 0;
ind += FUNC_PROLOG_SIZE;
func_sub_sp_offset = ind;
/* if the function returns a structure, then add an
implicit pointer parameter */
2021-01-11 18:26:10 +00:00
#if defined(TCC_TARGET_PE) || TARGETOS_FreeBSD || TARGETOS_OpenBSD
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
2004-10-18 00:14:40 +00:00
/* XXX: fastcall case ? */
func_vc = addr;
addr += 4;
2004-10-18 00:14:40 +00:00
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
2004-10-18 00:14:40 +00:00
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;
2004-10-18 00:14:40 +00:00
} else {
param_addr = addr;
addr += size;
}
sym_push(sym->v & ~SYM_FIELD, type,
VT_LOCAL | VT_LVAL, param_addr);
2004-10-18 00:14:40 +00:00
param_index++;
}
2002-01-26 17:23:44 +00:00
func_ret_sub = 0;
/* pascal type call or fastcall ? */
if (func_call == FUNC_STDCALL || func_call == FUNC_FASTCALLW || func_call == FUNC_THISCALL)
2002-01-26 17:23:44 +00:00
func_ret_sub = addr - 8;
2021-01-11 18:26:10 +00:00
#if !defined(TCC_TARGET_PE) && !TARGETOS_FreeBSD || TARGETOS_OpenBSD
else if (func_vc)
func_ret_sub = 4;
#endif
2004-10-18 00:14:40 +00:00
}
/* generate function epilog */
void gfunc_epilog(void)
{
addr_t v, saved_ind;
2005-04-10 22:15:08 +00:00
/* align local size to word & save local variables */
v = (-loc + 3) & -4;
#if USE_EBX
o(0x8b);
gen_modrm(TREG_EBX, VT_LOCAL, NULL, -(v+4));
#endif
o(0xc9); /* leave */
2002-01-26 17:23:44 +00:00
if (func_ret_sub == 0) {
o(0xc3); /* ret */
2002-01-26 17:23:44 +00:00
} else {
o(0xc2); /* ret n */
g(func_ret_sub);
g(func_ret_sub >> 8);
2002-01-26 17:23:44 +00:00
}
saved_ind = ind;
ind = func_sub_sp_offset - FUNC_PROLOG_SIZE;
2005-04-10 22:15:08 +00:00
#ifdef TCC_TARGET_PE
if (v >= 4096) {
oad(0xb8, v); /* mov stacksize, %eax */
gen_static_call(TOK___chkstk); /* call __chkstk, (does the stackframe too) */
2005-04-10 22:15:08 +00:00
} 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 */
2005-04-10 22:15:08 +00:00
#endif
}
o(0x53 * USE_EBX); /* push ebx */
ind = saved_ind;
}
2002-02-10 16:13:14 +00:00
/* generate a jump to a label */
int gjmp(int t)
2001-12-12 21:16:34 +00:00
{
return gjmp2(0xe9, t);
2001-12-12 21:16:34 +00:00
}
2002-02-10 16:13:14 +00:00
/* generate a jump to a fixed address */
void gjmp_addr(int a)
2002-02-10 16:13:14 +00:00
{
2002-09-08 22:06:11 +00:00
int r;
r = a - ind - 2;
2002-09-08 22:06:11 +00:00
if (r == (char)r) {
g(0xeb);
g(r);
2002-09-08 22:06:11 +00:00
} else {
oad(0xe9, a - ind - 5);
2002-09-08 22:06:11 +00:00
}
2002-02-10 16:13:14 +00:00
}
#if 0
/* generate a jump to a fixed address */
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
int gjmp_append(int n, int t)
2001-12-12 21:16:34 +00:00
{
void *p;
/* insert vtop->c jump list in t */
if (n) {
uint32_t n1 = n, n2;
2025-02-11 19:16:44 +00:00
while ((n2 = read32le(p = tcc_state->cur_text_section->data + n1)))
n1 = n2;
write32le(p, t);
t = n;
2001-12-12 21:16:34 +00:00
}
return t;
}
int gjmp_cond(int op, int t)
{
g(0x0f);
t = gjmp2(op - 16, t);
return t;
}
void gen_opi(int op)
2001-12-12 21:16:34 +00:00
{
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) {
2010-04-03 18:20:34 +00:00
/* 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
2010-04-03 18:20:34 +00:00
} else {
o(0x83);
o(0xc0 | (opc << 3) | r);
g(c);
2010-04-03 18:20:34 +00:00
}
} 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);
2001-12-12 21:16:34 +00:00
}
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;
2002-12-08 14:34:48 +00:00
r = TREG_EAX;
2001-12-12 21:16:34 +00:00
} 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)
2002-12-08 14:34:48 +00:00
r = TREG_EDX;
else
2002-12-08 14:34:48 +00:00
r = TREG_EAX;
2001-12-12 21:16:34 +00:00
}
vtop->r = r;
break;
default:
opc = 7;
goto gen_op8;
2001-12-12 21:16:34 +00:00
}
}
/* generate a floating point operation 'v = t1 op t2' instruction. The
2017-05-08 04:38:09 +00:00
two operands are guaranteed to have the same floating point type */
/* XXX: need to use ST1 too */
void gen_opf(int op)
2001-12-12 21:16:34 +00:00
{
2002-01-26 17:23:44 +00:00
int a, ft, fc, swapped, r;
2001-12-12 21:16:34 +00:00
if (op == TOK_NEG) { /* unary minus */
gv(RC_FLOAT);
o(0xe0d9); /* fchs */
return;
}
2001-12-17 21:56:48 +00:00
/* convert constants to memory references */
if ((vtop[-1].r & (VT_VALMASK | VT_LVAL)) == VT_CONST) {
vswap();
gv(RC_FLOAT);
vswap();
2001-12-17 21:56:48 +00:00
}
if ((vtop[0].r & (VT_VALMASK | VT_LVAL)) == VT_CONST)
gv(RC_FLOAT);
2001-12-17 21:56:48 +00:00
2001-12-12 21:16:34 +00:00
/* 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();
2001-12-12 21:16:34 +00:00
}
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) {
2001-12-12 21:16:34 +00:00
/* load on stack second operand */
load(TREG_ST0, vtop);
save_reg(TREG_EAX); /* eax is used by FP comparison code */
2001-12-12 21:16:34 +00:00
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 */
2001-12-12 21:16:34 +00:00
if (op == TOK_EQ) {
o(0x45e480); /* and $0x45, %ah */
o(0x40fC80); /* cmp $0x40, %ah */
2001-12-12 21:16:34 +00:00
} else if (op == TOK_NE) {
o(0x45e480); /* and $0x45, %ah */
o(0x40f480); /* xor $0x40, %ah */
2001-12-12 21:16:34 +00:00
op = TOK_NE;
} else if (op == TOK_GE || op == TOK_LE) {
o(0x05c4f6); /* test $0x05, %ah */
2001-12-12 21:16:34 +00:00
op = TOK_EQ;
} else {
o(0x45c4f6); /* test $0x45, %ah */
2001-12-12 21:16:34 +00:00
op = TOK_EQ;
}
vtop--;
vset_VT_CMP(op);
2001-12-12 21:16:34 +00:00
} else {
/* no memory reference possible for long double operations */
if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) {
load(TREG_ST0, vtop);
swapped = !swapped;
}
2001-12-12 21:16:34 +00:00
switch(op) {
default:
case '+':
a = 0;
break;
case '-':
a = 4;
2001-12-12 21:16:34 +00:00
if (swapped)
a++;
2001-12-12 21:16:34 +00:00
break;
case '*':
a = 1;
2001-12-12 21:16:34 +00:00
break;
case '/':
a = 6;
2001-12-12 21:16:34 +00:00
if (swapped)
a++;
2001-12-12 21:16:34 +00:00
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));
2001-12-12 21:16:34 +00:00
} else {
2002-01-26 17:23:44 +00:00
/* if saved lvalue, then we must reload it */
r = vtop->r;
2002-01-26 17:23:44 +00:00
if ((r & VT_VALMASK) == VT_LLOCAL) {
SValue v1;
r = get_reg(RC_INT);
v1.type.t = VT_INT;
2002-01-26 17:23:44 +00:00
v1.r = VT_LOCAL | VT_LVAL;
v1.c.i = fc;
v1.sym = NULL;
load(r, &v1);
2002-01-26 17:23:44 +00:00
fc = 0;
}
if ((ft & VT_BTYPE) == VT_DOUBLE)
o(0xdc);
else
o(0xd8);
gen_modrm(a, r, vtop->sym, fc);
2001-12-12 21:16:34 +00:00
}
vtop--;
2001-12-12 21:16:34 +00:00
}
}
/* convert integers to fp 't' type. Must handle 'int', 'unsigned int'
and 'long long' cases. */
void gen_cvt_itof(void)
2001-12-12 21:16:34 +00:00
{
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)) {
2001-12-13 22:28:53 +00:00
/* 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 */
2001-12-12 21:16:34 +00:00
} else {
2001-12-13 22:28:53 +00:00
/* int to float/double/long double */
o(0x50 + (vtop->r & VT_VALMASK)); /* push r */
o(0x2404db); /* fildl (%esp) */
o(0x04c483); /* add $4, %esp */
2001-12-12 21:16:34 +00:00
}
vtop->r2 = VT_CONST;
vtop->r = TREG_ST0;
2001-12-17 21:56:48 +00:00
}
/* convert fp to int 't' type */
void gen_cvt_ftoi(int t)
2001-12-17 21:56:48 +00:00
{
int bt = vtop->type.t & VT_BTYPE;
if (bt == VT_FLOAT)
vpush_helper_func(TOK___fixsfdi);
else if (bt == VT_LDOUBLE)
vpush_helper_func(TOK___fixxfdi);
else
vpush_helper_func(TOK___fixdfdi);
vswap();
gfunc_call(1);
vpushi(0);
vtop->r = REG_IRET;
if ((t & VT_BTYPE) == VT_LLONG)
vtop->r2 = REG_IRE2;
2001-12-12 21:16:34 +00:00
}
2001-12-17 21:56:48 +00:00
/* convert from one floating point type to another */
void gen_cvt_ftof(void)
2001-12-17 21:56:48 +00:00
{
/* all we have to do on i386 is to put the float in a register */
gv(RC_FLOAT);
2001-12-17 21:56:48 +00:00
}
2019-12-16 17:51:28 +00:00
/* char/short to int conversion */
void gen_cvt_csti(int t)
2019-12-16 17:51:28 +00:00
{
int r, sz, xl;
r = gv(RC_INT);
2019-12-16 17:51:28 +00:00
sz = !(t & VT_UNSIGNED);
xl = (t & VT_BTYPE) == VT_SHORT;
o(0xc0b60f /* mov[sz] %a[xl], %eax */
2019-12-16 17:51:28 +00:00
| (sz << 3 | xl) << 8
| (r << 3 | r) << 16
);
}
2002-11-03 00:43:01 +00:00
/* computed goto support */
void ggoto(void)
2002-11-03 00:43:01 +00:00
{
gcall_or_jmp(1);
vtop--;
2002-11-03 00:43:01 +00:00
}
/* Save the stack pointer onto the stack */
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 */
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 */
void gen_vla_alloc(CType *type, int align) {
int use_call = 0;
#ifdef TCC_TARGET_PE /* alloca does more than just adjust %rsp on Windows */
use_call = 1;
#endif
if (use_call)
{
vpush_helper_func(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();
}
}
2001-12-12 21:16:34 +00:00
/* end of X86 code generator */