The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 *  CIL code generator for TCC
 * 
 *  Copyright (c) 2002 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
 */

/* 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,
};

const 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:
        tcc_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) {
        tcc_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 | lvalue_type(sym->type.t), 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 */
/*************************************************************/