0,0 → 1,667 |
/* |
* CIL code generator for TCC |
* |
* Copyright (c) 2002 Fabrice Bellard |
* |
* This program is free software; you can redistribute it and/or modify |
* it under the terms of the GNU General Public License as published by |
* the Free Software Foundation; either version 2 of the License, or |
* (at your option) any later version. |
* |
* This program 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 General Public License for more details. |
* |
* You should have received a copy of the GNU General Public License |
* along with this program; if not, write to the Free Software |
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
|
/* number of available registers */ |
#define NB_REGS 3 |
|
/* 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_ST 0x0001 /* any stack entry */ |
#define RC_ST0 0x0002 /* top of stack */ |
#define RC_ST1 0x0004 /* top - 1 */ |
|
#define RC_INT RC_ST |
#define RC_FLOAT RC_ST |
#define RC_IRET RC_ST0 /* function return: integer register */ |
#define RC_LRET RC_ST0 /* function return: second integer register */ |
#define RC_FRET RC_ST0 /* function return: float register */ |
|
/* pretty names for the registers */ |
enum { |
REG_ST0 = 0, |
REG_ST1, |
REG_ST2, |
}; |
|
int reg_classes[NB_REGS] = { |
/* ST0 */ RC_ST | RC_ST0, |
/* ST1 */ RC_ST | RC_ST1, |
/* ST2 */ RC_ST, |
}; |
|
/* return registers for function */ |
#define REG_IRET REG_ST0 /* single word int return register */ |
#define REG_LRET REG_ST0 /* second word return register (for long long) */ |
#define REG_FRET REG_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 8 |
#define LDOUBLE_ALIGN 8 |
|
/* function call context */ |
typedef struct GFuncContext { |
int func_call; /* func call type (FUNC_STDCALL or FUNC_CDECL) */ |
} GFuncContext; |
|
/******************************************************/ |
/* opcode definitions */ |
|
#define IL_OP_PREFIX 0xFE |
|
enum ILOPCodes { |
#define OP(name, str, n) IL_OP_ ## name = n, |
#include "il-opcodes.h" |
#undef OP |
}; |
|
char *il_opcodes_str[] = { |
#define OP(name, str, n) [n] = str, |
#include "il-opcodes.h" |
#undef OP |
}; |
|
/******************************************************/ |
|
/* arguments variable numbers start from there */ |
#define ARG_BASE 0x70000000 |
|
static FILE *il_outfile; |
|
static void out_byte(int c) |
{ |
*(char *)ind++ = c; |
} |
|
static void out_le32(int c) |
{ |
out_byte(c); |
out_byte(c >> 8); |
out_byte(c >> 16); |
out_byte(c >> 24); |
} |
|
static void init_outfile(void) |
{ |
if (!il_outfile) { |
il_outfile = stdout; |
fprintf(il_outfile, |
".assembly extern mscorlib\n" |
"{\n" |
".ver 1:0:2411:0\n" |
"}\n\n"); |
} |
} |
|
static void out_op1(int op) |
{ |
if (op & 0x100) |
out_byte(IL_OP_PREFIX); |
out_byte(op & 0xff); |
} |
|
/* output an opcode with prefix */ |
static void out_op(int op) |
{ |
out_op1(op); |
fprintf(il_outfile, " %s\n", il_opcodes_str[op]); |
} |
|
static void out_opb(int op, int c) |
{ |
out_op1(op); |
out_byte(c); |
fprintf(il_outfile, " %s %d\n", il_opcodes_str[op], c); |
} |
|
static void out_opi(int op, int c) |
{ |
out_op1(op); |
out_le32(c); |
fprintf(il_outfile, " %s 0x%x\n", il_opcodes_str[op], c); |
} |
|
/* XXX: not complete */ |
static void il_type_to_str(char *buf, int buf_size, |
int t, const char *varstr) |
{ |
int bt; |
Sym *s, *sa; |
char buf1[256]; |
const char *tstr; |
|
t = t & VT_TYPE; |
bt = t & VT_BTYPE; |
buf[0] = '\0'; |
if (t & VT_UNSIGNED) |
pstrcat(buf, buf_size, "unsigned "); |
switch(bt) { |
case VT_VOID: |
tstr = "void"; |
goto add_tstr; |
case VT_BOOL: |
tstr = "bool"; |
goto add_tstr; |
case VT_BYTE: |
tstr = "int8"; |
goto add_tstr; |
case VT_SHORT: |
tstr = "int16"; |
goto add_tstr; |
case VT_ENUM: |
case VT_INT: |
case VT_LONG: |
tstr = "int32"; |
goto add_tstr; |
case VT_LLONG: |
tstr = "int64"; |
goto add_tstr; |
case VT_FLOAT: |
tstr = "float32"; |
goto add_tstr; |
case VT_DOUBLE: |
case VT_LDOUBLE: |
tstr = "float64"; |
add_tstr: |
pstrcat(buf, buf_size, tstr); |
break; |
case VT_STRUCT: |
error("structures not handled yet"); |
break; |
case VT_FUNC: |
s = sym_find((unsigned)t >> VT_STRUCT_SHIFT); |
il_type_to_str(buf, buf_size, s->t, varstr); |
pstrcat(buf, buf_size, "("); |
sa = s->next; |
while (sa != NULL) { |
il_type_to_str(buf1, sizeof(buf1), sa->t, NULL); |
pstrcat(buf, buf_size, buf1); |
sa = sa->next; |
if (sa) |
pstrcat(buf, buf_size, ", "); |
} |
pstrcat(buf, buf_size, ")"); |
goto no_var; |
case VT_PTR: |
s = sym_find((unsigned)t >> VT_STRUCT_SHIFT); |
pstrcpy(buf1, sizeof(buf1), "*"); |
if (varstr) |
pstrcat(buf1, sizeof(buf1), varstr); |
il_type_to_str(buf, buf_size, s->t, buf1); |
goto no_var; |
} |
if (varstr) { |
pstrcat(buf, buf_size, " "); |
pstrcat(buf, buf_size, varstr); |
} |
no_var: ; |
} |
|
|
/* patch relocation entry with value 'val' */ |
void greloc_patch1(Reloc *p, int val) |
{ |
} |
|
/* output a symbol and patch all calls to it */ |
void gsym_addr(t, a) |
{ |
} |
|
/* output jump and return symbol */ |
static int out_opj(int op, int c) |
{ |
out_op1(op); |
out_le32(0); |
if (c == 0) { |
c = ind - (int)cur_text_section->data; |
} |
fprintf(il_outfile, " %s L%d\n", il_opcodes_str[op], c); |
return c; |
} |
|
void gsym(int t) |
{ |
fprintf(il_outfile, "L%d:\n", t); |
} |
|
/* load 'r' from value 'sv' */ |
void load(int r, SValue *sv) |
{ |
int v, fc, ft; |
|
v = sv->r & VT_VALMASK; |
fc = sv->c.i; |
ft = sv->t; |
|
if (sv->r & VT_LVAL) { |
if (v == VT_LOCAL) { |
if (fc >= ARG_BASE) { |
fc -= ARG_BASE; |
if (fc >= 0 && fc <= 4) { |
out_op(IL_OP_LDARG_0 + fc); |
} else if (fc <= 0xff) { |
out_opb(IL_OP_LDARG_S, fc); |
} else { |
out_opi(IL_OP_LDARG, fc); |
} |
} else { |
if (fc >= 0 && fc <= 4) { |
out_op(IL_OP_LDLOC_0 + fc); |
} else if (fc <= 0xff) { |
out_opb(IL_OP_LDLOC_S, fc); |
} else { |
out_opi(IL_OP_LDLOC, fc); |
} |
} |
} else if (v == VT_CONST) { |
/* XXX: handle globals */ |
out_opi(IL_OP_LDSFLD, 0); |
} else { |
if ((ft & VT_BTYPE) == VT_FLOAT) { |
out_op(IL_OP_LDIND_R4); |
} else if ((ft & VT_BTYPE) == VT_DOUBLE) { |
out_op(IL_OP_LDIND_R8); |
} else if ((ft & VT_BTYPE) == VT_LDOUBLE) { |
out_op(IL_OP_LDIND_R8); |
} else if ((ft & VT_TYPE) == VT_BYTE) |
out_op(IL_OP_LDIND_I1); |
else if ((ft & VT_TYPE) == (VT_BYTE | VT_UNSIGNED)) |
out_op(IL_OP_LDIND_U1); |
else if ((ft & VT_TYPE) == VT_SHORT) |
out_op(IL_OP_LDIND_I2); |
else if ((ft & VT_TYPE) == (VT_SHORT | VT_UNSIGNED)) |
out_op(IL_OP_LDIND_U2); |
else |
out_op(IL_OP_LDIND_I4); |
} |
} else { |
if (v == VT_CONST) { |
/* XXX: handle globals */ |
if (fc >= -1 && fc <= 8) { |
out_op(IL_OP_LDC_I4_M1 + fc + 1); |
} else { |
out_opi(IL_OP_LDC_I4, fc); |
} |
} else if (v == VT_LOCAL) { |
if (fc >= ARG_BASE) { |
fc -= ARG_BASE; |
if (fc <= 0xff) { |
out_opb(IL_OP_LDARGA_S, fc); |
} else { |
out_opi(IL_OP_LDARGA, fc); |
} |
} else { |
if (fc <= 0xff) { |
out_opb(IL_OP_LDLOCA_S, fc); |
} else { |
out_opi(IL_OP_LDLOCA, fc); |
} |
} |
} else { |
/* XXX: do it */ |
} |
} |
} |
|
/* store register 'r' in lvalue 'v' */ |
void store(int r, SValue *sv) |
{ |
int v, fc, ft; |
|
v = sv->r & VT_VALMASK; |
fc = sv->c.i; |
ft = sv->t; |
if (v == VT_LOCAL) { |
if (fc >= ARG_BASE) { |
fc -= ARG_BASE; |
/* XXX: check IL arg store semantics */ |
if (fc <= 0xff) { |
out_opb(IL_OP_STARG_S, fc); |
} else { |
out_opi(IL_OP_STARG, fc); |
} |
} else { |
if (fc >= 0 && fc <= 4) { |
out_op(IL_OP_STLOC_0 + fc); |
} else if (fc <= 0xff) { |
out_opb(IL_OP_STLOC_S, fc); |
} else { |
out_opi(IL_OP_STLOC, fc); |
} |
} |
} else if (v == VT_CONST) { |
/* XXX: handle globals */ |
out_opi(IL_OP_STSFLD, 0); |
} else { |
if ((ft & VT_BTYPE) == VT_FLOAT) |
out_op(IL_OP_STIND_R4); |
else if ((ft & VT_BTYPE) == VT_DOUBLE) |
out_op(IL_OP_STIND_R8); |
else if ((ft & VT_BTYPE) == VT_LDOUBLE) |
out_op(IL_OP_STIND_R8); |
else if ((ft & VT_BTYPE) == VT_BYTE) |
out_op(IL_OP_STIND_I1); |
else if ((ft & VT_BTYPE) == VT_SHORT) |
out_op(IL_OP_STIND_I2); |
else |
out_op(IL_OP_STIND_I4); |
} |
} |
|
/* start function call and return function call context */ |
void gfunc_start(GFuncContext *c, int func_call) |
{ |
c->func_call = func_call; |
} |
|
/* push function parameter which is in (vtop->t, vtop->c). Stack entry |
is then popped. */ |
void gfunc_param(GFuncContext *c) |
{ |
if ((vtop->t & VT_BTYPE) == VT_STRUCT) { |
error("structures passed as value not handled yet"); |
} else { |
/* simply push on stack */ |
gv(RC_ST0); |
} |
vtop--; |
} |
|
/* generate function call with address in (vtop->t, vtop->c) and free function |
context. Stack entry is popped */ |
void gfunc_call(GFuncContext *c) |
{ |
char buf[1024]; |
|
if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { |
/* XXX: more info needed from tcc */ |
il_type_to_str(buf, sizeof(buf), vtop->t, "xxx"); |
fprintf(il_outfile, " call %s\n", buf); |
} else { |
/* indirect call */ |
gv(RC_INT); |
il_type_to_str(buf, sizeof(buf), vtop->t, NULL); |
fprintf(il_outfile, " calli %s\n", buf); |
} |
vtop--; |
} |
|
/* generate function prolog of type 't' */ |
void gfunc_prolog(int t) |
{ |
int addr, u, func_call; |
Sym *sym; |
char buf[1024]; |
|
init_outfile(); |
|
/* XXX: pass function name to gfunc_prolog */ |
il_type_to_str(buf, sizeof(buf), t, funcname); |
fprintf(il_outfile, ".method static %s il managed\n", buf); |
fprintf(il_outfile, "{\n"); |
/* XXX: cannot do better now */ |
fprintf(il_outfile, " .maxstack %d\n", NB_REGS); |
fprintf(il_outfile, " .locals (int32, int32, int32, int32, int32, int32, int32, int32)\n"); |
|
if (!strcmp(funcname, "main")) |
fprintf(il_outfile, " .entrypoint\n"); |
|
sym = sym_find((unsigned)t >> VT_STRUCT_SHIFT); |
func_call = sym->r; |
|
addr = ARG_BASE; |
/* if the function returns a structure, then add an |
implicit pointer parameter */ |
func_vt = sym->t; |
if ((func_vt & VT_BTYPE) == VT_STRUCT) { |
func_vc = addr; |
addr++; |
} |
/* define parameters */ |
while ((sym = sym->next) != NULL) { |
u = sym->t; |
sym_push(sym->v & ~SYM_FIELD, u, |
VT_LOCAL | VT_LVAL, addr); |
addr++; |
} |
} |
|
/* generate function epilog */ |
void gfunc_epilog(void) |
{ |
out_op(IL_OP_RET); |
fprintf(il_outfile, "}\n\n"); |
} |
|
/* generate a jump to a label */ |
int gjmp(int t) |
{ |
return out_opj(IL_OP_BR, t); |
} |
|
/* generate a jump to a fixed address */ |
void gjmp_addr(int a) |
{ |
/* XXX: handle syms */ |
out_opi(IL_OP_BR, a); |
} |
|
/* generate a test. set 'inv' to invert test. Stack entry is popped */ |
int gtst(int inv, int t) |
{ |
int v, *p, c; |
|
v = vtop->r & VT_VALMASK; |
if (v == VT_CMP) { |
c = vtop->c.i ^ inv; |
switch(c) { |
case TOK_EQ: |
c = IL_OP_BEQ; |
break; |
case TOK_NE: |
c = IL_OP_BNE_UN; |
break; |
case TOK_LT: |
c = IL_OP_BLT; |
break; |
case TOK_LE: |
c = IL_OP_BLE; |
break; |
case TOK_GT: |
c = IL_OP_BGT; |
break; |
case TOK_GE: |
c = IL_OP_BGE; |
break; |
case TOK_ULT: |
c = IL_OP_BLT_UN; |
break; |
case TOK_ULE: |
c = IL_OP_BLE_UN; |
break; |
case TOK_UGT: |
c = IL_OP_BGT_UN; |
break; |
case TOK_UGE: |
c = IL_OP_BGE_UN; |
break; |
} |
t = out_opj(c, t); |
} else if (v == VT_JMP || v == VT_JMPI) { |
/* && or || optimization */ |
if ((v & 1) == inv) { |
/* insert vtop->c jump list in t */ |
p = &vtop->c.i; |
while (*p != 0) |
p = (int *)*p; |
*p = t; |
t = vtop->c.i; |
} else { |
t = gjmp(t); |
gsym(vtop->c.i); |
} |
} else { |
if (is_float(vtop->t)) { |
vpushi(0); |
gen_op(TOK_NE); |
} |
if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_FORWARD)) == VT_CONST) { |
/* constant jmp optimization */ |
if ((vtop->c.i != 0) != inv) |
t = gjmp(t); |
} else { |
v = gv(RC_INT); |
t = out_opj(IL_OP_BRTRUE - inv, t); |
} |
} |
vtop--; |
return t; |
} |
|
/* generate an integer binary operation */ |
void gen_opi(int op) |
{ |
gv2(RC_ST1, RC_ST0); |
switch(op) { |
case '+': |
out_op(IL_OP_ADD); |
goto std_op; |
case '-': |
out_op(IL_OP_SUB); |
goto std_op; |
case '&': |
out_op(IL_OP_AND); |
goto std_op; |
case '^': |
out_op(IL_OP_XOR); |
goto std_op; |
case '|': |
out_op(IL_OP_OR); |
goto std_op; |
case '*': |
out_op(IL_OP_MUL); |
goto std_op; |
case TOK_SHL: |
out_op(IL_OP_SHL); |
goto std_op; |
case TOK_SHR: |
out_op(IL_OP_SHR_UN); |
goto std_op; |
case TOK_SAR: |
out_op(IL_OP_SHR); |
goto std_op; |
case '/': |
case TOK_PDIV: |
out_op(IL_OP_DIV); |
goto std_op; |
case TOK_UDIV: |
out_op(IL_OP_DIV_UN); |
goto std_op; |
case '%': |
out_op(IL_OP_REM); |
goto std_op; |
case TOK_UMOD: |
out_op(IL_OP_REM_UN); |
std_op: |
vtop--; |
vtop[0].r = REG_ST0; |
break; |
case TOK_EQ: |
case TOK_NE: |
case TOK_LT: |
case TOK_LE: |
case TOK_GT: |
case TOK_GE: |
case TOK_ULT: |
case TOK_ULE: |
case TOK_UGT: |
case TOK_UGE: |
vtop--; |
vtop[0].r = VT_CMP; |
vtop[0].c.i = op; |
break; |
} |
} |
|
/* generate a floating point operation 'v = t1 op t2' instruction. The |
two operands are guaranted to have the same floating point type */ |
void gen_opf(int op) |
{ |
/* same as integer */ |
gen_opi(op); |
} |
|
/* convert integers to fp 't' type. Must handle 'int', 'unsigned int' |
and 'long long' cases. */ |
void gen_cvt_itof(int t) |
{ |
gv(RC_ST0); |
if (t == VT_FLOAT) |
out_op(IL_OP_CONV_R4); |
else |
out_op(IL_OP_CONV_R8); |
} |
|
/* convert fp to int 't' type */ |
/* XXX: handle long long case */ |
void gen_cvt_ftoi(int t) |
{ |
gv(RC_ST0); |
switch(t) { |
case VT_INT | VT_UNSIGNED: |
out_op(IL_OP_CONV_U4); |
break; |
case VT_LLONG: |
out_op(IL_OP_CONV_I8); |
break; |
case VT_LLONG | VT_UNSIGNED: |
out_op(IL_OP_CONV_U8); |
break; |
default: |
out_op(IL_OP_CONV_I4); |
break; |
} |
} |
|
/* convert from one floating point type to another */ |
void gen_cvt_ftof(int t) |
{ |
gv(RC_ST0); |
if (t == VT_FLOAT) { |
out_op(IL_OP_CONV_R4); |
} else { |
out_op(IL_OP_CONV_R8); |
} |
} |
|
/* end of CIL code generator */ |
/*************************************************************/ |
|