#include "mcg.h" static struct basicblock* current_bb; static int stackptr; static struct ir* stack[64]; struct jumptable { struct basicblock* defaulttarget; IMAPOF(struct basicblock) targets; }; static struct ir* convert(struct ir* src, int srcsize, int destsize, int opcode); static struct ir* appendir(struct ir* ir); static void insn_ivalue(int opcode, arith value); static void parse_csa(struct basicblock* data_bb, struct jumptable* table); static void parse_csb(struct basicblock* data_bb, struct jumptable* table); static void emit_jumptable(struct ir* targetvalue, struct jumptable* table); static void reset_stack(void) { stackptr = 0; } static void push(struct ir* ir) { if (stackptr == sizeof(stack)/sizeof(*stack)) fatal("stack overflow"); #if 0 /* If we try to push something which is too small, convert it to a word * first. */ if (ir->size < EM_wordsize) ir = convertu(ir, EM_wordsize); #endif stack[stackptr++] = ir; } /* Returns the size of the top item on the stack. */ static int peek(int delta) { if (stackptr <= delta) return EM_wordsize; else { struct ir* ir = stack[stackptr-1-delta]; return ir->size; } } static struct ir* pop(int size) { if (size < EM_wordsize) size = EM_wordsize; if (stackptr == 0) { /* Nothing in our fake stack, so we have to read from the real stack. */ if (size < EM_wordsize) size = EM_wordsize; return appendir( new_ir0( IR_POP, size ) ); } else { struct ir* ir = stack[--stackptr]; #if 0 /* If we try to pop something which is smaller than a word, convert it first. */ if (size < EM_wordsize) ir = convertu(ir, size); #endif if (ir->size != size) { if ((size == (EM_wordsize*2)) && (ir->size == EM_wordsize)) { /* Tried to read a long, but we got an int. Assemble the long * out of two ints. Note that EM doesn't specify an order. */ return new_ir2( IR_FROMIPAIR, size, ir, pop(EM_wordsize) ); } else if ((size == EM_wordsize) && (ir->size == (EM_wordsize*2))) { appendir(ir); /* Tried to read an int, but we got a long. */ push( new_ir1( IR_FROML1, EM_wordsize, ir ) ); return new_ir1( IR_FROML0, EM_wordsize, ir ); } else fatal("expected an item on stack of size %d, but got %d\n", size, ir->size); } return ir; } } static void print_stack(void) { int i; tracef('E', "E: stack:"); for (i=0; iid, ir->size); } tracef('E', " (top)\n"); } static struct ir* appendir(struct ir* ir) { int i; assert(current_bb != NULL); array_appendu(¤t_bb->irs, ir); ir_print('0', ir); return ir; } static void sequence_point(void) { int i; /* Ensures that any partially-evaluated expressions on the stack are executed right * now. This typically needs to happen before store operations, to prevents loads of * the same address being delayed until after the store (at which point they'll * return incorrect values). */ assert(current_bb != NULL); for (i=0; iirs, ir); } } static void materialise_stack(void) { int i; for (i=0; isize, ir ) ); } reset_stack(); } void tb_filestart(void) { } void tb_fileend(void) { } void tb_regvar(struct procedure* procedure, arith offset, int size, int type, int priority) { struct local* local = calloc(1, sizeof(*local)); local->size = size; local->offset = offset; local->is_register = true; imap_put(&procedure->locals, offset, local); } static struct ir* address_of_external(const char* label, arith offset) { if (offset != 0) return new_ir2( IR_ADD, EM_pointersize, new_labelir(label), new_wordir(offset) ); else return new_labelir(label); } static struct ir* convert(struct ir* src, int srcsize, int destsize, int opcode) { if (srcsize == 1) { if ((opcode == IR_FROMSI) || (opcode == IR_FROMSL)) { src = new_ir1( IR_EXTENDB, EM_wordsize, src ); } srcsize = EM_wordsize; } if ((srcsize == 2) && (srcsize != EM_wordsize)) { if ((opcode == IR_FROMSI) || (opcode == IR_FROMSL)) { src = new_ir1( IR_EXTENDH, EM_wordsize, src ); } srcsize = EM_wordsize; } if (src->size == EM_wordsize) {} else if (src->size == (2*EM_wordsize)) opcode++; else fatal("can't convert from %d to %d", src->size, destsize); return new_ir1( opcode, destsize, src ); } static struct ir* compare(struct ir* left, struct ir* right, int size, int opcode) { if (size == EM_wordsize) {} else if (size == (2*EM_wordsize)) opcode++; else fatal("can't compare things of size %d", size); return new_ir2( opcode, EM_wordsize, left, right ); } static struct ir* store(int size, struct ir* address, int offset, struct ir* value) { int opcode; sequence_point(); if (size == 1) { opcode = IR_STOREB; size = EM_wordsize; } else if ((size < EM_wordsize) && (size == 2)) { opcode = IR_STOREH; size = EM_wordsize; } else opcode = IR_STORE; if (offset != 0) address = new_ir2( IR_ADD, EM_pointersize, address, new_wordir(offset) ); return new_ir2( opcode, size, address, value ); } static struct ir* load(int size, struct ir* address, int offset) { int opcode; if (size == 1) { opcode = IR_LOADB; size = EM_wordsize; } else if ((size < EM_wordsize) && (size == 2)) { opcode = IR_LOADH; size = EM_wordsize; } else opcode = IR_LOAD; if (offset != 0) address = new_ir2( IR_ADD, EM_pointersize, address, new_wordir(offset) ); return new_ir1( opcode, size, address ); } static struct ir* tristate_compare(int size, int opcode) { struct ir* right = pop(size); struct ir* left = pop(size); return compare(left, right, size, opcode); } static struct ir* tristate_compare0(int size, int opcode) { struct ir* right = new_wordir(0); struct ir* left = pop(size); return compare(left, right, size, opcode); } static void simple_convert(int opcode) { struct ir* destsize = pop(EM_wordsize); struct ir* srcsize = pop(EM_wordsize); struct ir* value; assert(srcsize->opcode == IR_CONST); assert(destsize->opcode == IR_CONST); value = pop(srcsize->u.ivalue); push( convert(value, srcsize->u.ivalue, destsize->u.ivalue, opcode) ); } static void simple_branch2(int opcode, int size, struct basicblock* truebb, struct basicblock* falsebb, int irop) { struct ir* right = pop(size); struct ir* left = pop(size); materialise_stack(); appendir( new_ir2( irop, 0, compare(left, right, size, IR_COMPARESI), new_ir2( IR_PAIR, 0, new_bbir(truebb), new_bbir(falsebb) ) ) ); } static void compare0_branch2(int opcode, struct basicblock* truebb, struct basicblock* falsebb, int irop) { push( new_wordir(0) ); simple_branch2(opcode, EM_wordsize, truebb, falsebb, irop); } static void simple_test(int size, int irop) { push( new_ir1( irop, EM_wordsize, tristate_compare0(size, IR_COMPARESI) ) ); } static void simple_test_neg(int size, int irop) { simple_test(size, irop); push( new_ir1( IR_NOT, EM_wordsize, pop(EM_wordsize) ) ); } static void helper_function(const char* name) { /* Delegates to a helper function; these leave their result on the stack * rather than returning values through lfr. */ materialise_stack(); appendir( new_ir1( IR_CALL, 0, new_labelir(name) ) ); } static void helper_function_with_arg(const char* name, struct ir* arg) { /* Abuses IR_SETRET to set a register to pass one argument to a * helper function. * * FIXME: As of January 2018, mach/powerpc/libem takes an * argument in register r3 only for ".los4", ".sts4", ".trp". * This is an accident. Should the argument be on the stack, or * should other helpers use a register? */ materialise_stack(); appendir( new_ir1( IR_SETRET, arg->size, arg ) ); appendir( new_ir1( IR_CALL, 0, new_labelir(name) ) ); } static void insn_simple(int opcode) { switch (opcode) { case op_bra: { struct ir* dest = pop(EM_pointersize); materialise_stack(); appendir( new_ir1( IR_JUMP, 0, dest ) ); break; } case op_cii: simple_convert(IR_FROMSI); break; case op_ciu: simple_convert(IR_FROMSI); break; case op_cui: simple_convert(IR_FROMUI); break; case op_cuu: simple_convert(IR_FROMUI); break; case op_cfu: simple_convert(IR_FROMUF); break; case op_cfi: simple_convert(IR_FROMSF); break; case op_cif: simple_convert(IR_FROMSI); break; case op_cuf: simple_convert(IR_FROMUI); break; case op_cff: simple_convert(IR_FROMSF); break; case op_cmp: push( tristate_compare(EM_pointersize, IR_COMPAREUI) ); break; case op_teq: simple_test( EM_wordsize, IR_IFEQ); break; case op_tne: simple_test_neg(EM_wordsize, IR_IFEQ); break; case op_tlt: simple_test( EM_wordsize, IR_IFLT); break; case op_tge: simple_test_neg(EM_wordsize, IR_IFLT); break; case op_tle: simple_test( EM_wordsize, IR_IFLE); break; case op_tgt: simple_test_neg(EM_wordsize, IR_IFLE); break; case op_cai: { struct ir* dest = pop(EM_pointersize); materialise_stack(); appendir( new_ir1( IR_CALL, 0, dest ) ); break; } case op_inc: { push( new_ir2( IR_ADD, EM_wordsize, pop(EM_wordsize), new_wordir(1) ) ); break; } case op_dec: { push( new_ir2( IR_SUB, EM_wordsize, pop(EM_wordsize), new_wordir(1) ) ); break; } case op_lim: { /* Traps use only 16 bits of .ignmask, but we keep an * entire word, even if a word has more than 2 bytes. */ push( load( EM_wordsize, new_labelir(".ignmask"), 0 ) ); break; } case op_sim: { appendir( store( EM_wordsize, new_labelir(".ignmask"), 0, pop(EM_wordsize) ) ); break; } case op_trp: helper_function_with_arg(".trp", pop(EM_wordsize)); break; case op_sig: { struct ir* label = new_labelir(".trppc"); struct ir* value = pop(EM_pointersize); appendir(label); /* because we need to use label twice */ push( load( EM_pointersize, label, 0 ) ); appendir( store( EM_pointersize, label, 0, value ) ); break; } case op_rtt: { insn_ivalue(op_ret, 0); break; } case op_and: helper_function(".and"); break; case op_ior: helper_function(".ior"); break; case op_xor: helper_function(".xor"); break; case op_com: helper_function(".com"); break; case op_cms: helper_function(".cms"); break; case op_set: helper_function(".set"); break; case op_inn: helper_function(".inn"); break; case op_dch: push( new_ir1( IR_CHAINFP, EM_pointersize, pop(EM_pointersize) ) ); break; case op_lpb: push( new_ir1( IR_FPTOAB, EM_pointersize, pop(EM_pointersize) ) ); break; case op_lni: { /* Increment line number --- ignore. */ break; } default: fatal("treebuilder: unknown simple instruction '%s'", em_mnem[opcode - sp_fmnem]); } } static void insn_bvalue(int opcode, struct basicblock* leftbb, struct basicblock* rightbb) { switch (opcode) { case op_zeq: compare0_branch2(opcode, leftbb, rightbb, IR_CJUMPEQ); break; case op_zlt: compare0_branch2(opcode, leftbb, rightbb, IR_CJUMPLT); break; case op_zle: compare0_branch2(opcode, leftbb, rightbb, IR_CJUMPLE); break; case op_zne: compare0_branch2(opcode, rightbb, leftbb, IR_CJUMPEQ); break; case op_zge: compare0_branch2(opcode, rightbb, leftbb, IR_CJUMPLT); break; case op_zgt: compare0_branch2(opcode, rightbb, leftbb, IR_CJUMPLE); break; case op_beq: simple_branch2(opcode, EM_wordsize, leftbb, rightbb, IR_CJUMPEQ); break; case op_blt: simple_branch2(opcode, EM_wordsize, leftbb, rightbb, IR_CJUMPLT); break; case op_ble: simple_branch2(opcode, EM_wordsize, leftbb, rightbb, IR_CJUMPLE); break; case op_bne: simple_branch2(opcode, EM_wordsize, rightbb, leftbb, IR_CJUMPEQ); break; case op_bge: simple_branch2(opcode, EM_wordsize, rightbb, leftbb, IR_CJUMPLT); break; case op_bgt: simple_branch2(opcode, EM_wordsize, rightbb, leftbb, IR_CJUMPLE); break; case op_bra: { materialise_stack(); appendir( new_ir1( IR_JUMP, 0, new_bbir(leftbb) ) ); break; } case op_lae: push( new_bbir(leftbb) ); break; default: fatal("treebuilder: unknown bvalue instruction '%s'", em_mnem[opcode - sp_fmnem]); } } static void simple_alu1(int opcode, int size, int irop, const char* fallback) { if (size > (2*EM_wordsize)) { if (!fallback) fatal("treebuilder: can't do opcode %s with size %d", em_mnem[opcode - sp_fmnem], size); push( new_wordir(size) ); helper_function(fallback); } else { struct ir* val = pop(size); push( new_ir1( irop, size, val ) ); } } static void simple_alu2(int opcode, int size, int irop, const char* fallback) { if (size > (2*EM_wordsize)) { if (!fallback) fatal("treebuilder: can't do opcode %s with size %d", em_mnem[opcode - sp_fmnem], size); push( new_wordir(size) ); helper_function(fallback); } else { struct ir* right = pop(size); struct ir* left = pop(size); push( new_ir2( irop, size, left, right ) ); } } static void rotate(int opcode, int size, int irop, int irop_reverse) { if (size > (2*EM_wordsize)) fatal("treebuilder: can't do opcode %s with size %d", em_mnem[opcode - sp_fmnem], size); else { struct ir* right = pop(size); struct ir* left = pop(size); struct ir* bits = new_wordir(8 * size); /* Fix left and right so we can refer to them multiple times. */ appendir(right); appendir(left); /* a rol b -> (a << b) | (a >> (32 - b)) */ push( new_ir2( IR_OR, size, new_ir2(irop, size, left, right), new_ir2( irop_reverse, size, left, new_ir2(IR_SUB, size, bits, right) ) ) ); } } static void change_by(struct ir* address, int amount) { appendir( store( EM_wordsize, address, 0, new_ir2( IR_ADD, EM_wordsize, load( EM_wordsize, address, 0 ), new_wordir(amount) ) ) ); } static struct ir* ptradd(struct ir* address, int offset) { if (offset == 0) return address; return new_ir2( IR_ADD, EM_pointersize, address, new_wordir(offset) ); } static struct ir* walk_static_chain(int level) { struct ir* ir; /* The static chain, when it exists, is the first argument of each * procedure. The chain begins with the current frame at level 0, * and continues until we reach the outermost procedure. */ ir = new_ir0( IR_GETFP, EM_pointersize ); while (level--) { /* Walk to the next frame pointer. */ ir = load( EM_pointersize, new_ir1( IR_FPTOAB, EM_pointersize, ir ), 0 ); } return ir; } static void insn_ivalue(int opcode, arith value) { switch (opcode) { case op_adi: simple_alu2(opcode, value, IR_ADD, NULL); break; case op_sbi: simple_alu2(opcode, value, IR_SUB, NULL); break; case op_mli: simple_alu2(opcode, value, IR_MUL, NULL); break; case op_dvi: simple_alu2(opcode, value, IR_DIV, NULL); break; case op_rmi: simple_alu2(opcode, value, IR_MOD, NULL); break; case op_sli: simple_alu2(opcode, value, IR_ASL, NULL); break; case op_sri: simple_alu2(opcode, value, IR_ASR, NULL); break; case op_ngi: simple_alu1(opcode, value, IR_NEG, NULL); break; case op_adu: simple_alu2(opcode, value, IR_ADD, NULL); break; case op_sbu: simple_alu2(opcode, value, IR_SUB, NULL); break; case op_mlu: simple_alu2(opcode, value, IR_MUL, NULL); break; case op_slu: simple_alu2(opcode, value, IR_LSL, NULL); break; case op_sru: simple_alu2(opcode, value, IR_LSR, NULL); break; case op_rmu: simple_alu2(opcode, value, IR_MODU, NULL); break; case op_dvu: simple_alu2(opcode, value, IR_DIVU, NULL); break; case op_and: simple_alu2(opcode, value, IR_AND, ".and"); break; case op_ior: simple_alu2(opcode, value, IR_OR, ".ior"); break; case op_xor: simple_alu2(opcode, value, IR_EOR, ".xor"); break; case op_com: simple_alu1(opcode, value, IR_NOT, ".com"); break; case op_rol: rotate(opcode, value, IR_LSL, IR_LSR); break; case op_ror: rotate(opcode, value, IR_LSR, IR_LSL); break; case op_adf: simple_alu2(opcode, value, IR_ADDF, NULL); break; case op_sbf: simple_alu2(opcode, value, IR_SUBF, NULL); break; case op_mlf: simple_alu2(opcode, value, IR_MULF, NULL); break; case op_dvf: simple_alu2(opcode, value, IR_DIVF, NULL); break; case op_ngf: simple_alu1(opcode, value, IR_NEGF, NULL); break; case op_cms: if (value > (2*EM_wordsize)) { push(new_wordir(value)); helper_function(".cms"); break; } /* fall through */ case op_cmu: push(tristate_compare(value, IR_COMPAREUI)); break; case op_cmi: push(tristate_compare(value, IR_COMPARESI)); break; case op_cmf: push(tristate_compare(value, IR_COMPAREF)); break; case op_rck: if (value != EM_wordsize) fatal("'rck %d' not supported", value); helper_function(".rck"); break; case op_set: push(new_wordir(value)); helper_function(".set"); break; case op_inn: push(new_wordir(value)); helper_function(".inn"); break; case op_lol: push( load( EM_wordsize, new_localir(value), 0 ) ); break; case op_ldl: push( load( EM_wordsize*2, new_localir(value), 0 ) ); break; case op_stl: appendir( store( EM_wordsize, new_localir(value), 0, pop(EM_wordsize) ) ); break; case op_sdl: appendir( store( EM_wordsize*2, new_localir(value), 0, pop(EM_wordsize*2) ) ); break; case op_lal: push( new_localir(value) ); break; case op_lil: push( load( EM_wordsize, load( EM_pointersize, new_localir(value), 0 ), 0 ) ); break; case op_sil: appendir( store( EM_wordsize, load( EM_pointersize, new_localir(value), 0 ), 0, pop(EM_wordsize) ) ); break; case op_inl: change_by(new_localir(value), 1); break; case op_del: change_by(new_localir(value), -1); break; case op_zrl: appendir( store( EM_wordsize, new_localir(value), 0, new_wordir(0) ) ); break; case op_zrf: { struct ir* ir = new_constir(value, 0); ir->opcode = IR_CONST; push(ir); break; } case op_loe: push( load( EM_wordsize, new_labelir(".hol0"), value ) ); break; case op_lae: push( new_ir2( IR_ADD, EM_pointersize, new_labelir(".hol0"), new_wordir(value) ) ); break; case op_ste: appendir( store( EM_wordsize, new_labelir(".hol0"), value, pop(EM_wordsize) ) ); break; case op_zre: appendir( store( EM_wordsize, new_labelir(".hol0"), value, new_wordir(0) ) ); break; case op_loc: push( new_wordir(value) ); break; case op_loi: { struct ir* ptr = pop(EM_pointersize); int offset = 0; if (value > (EM_wordsize*2)) { /* We're going to need to do multiple loads; fix the address * so it'll go into a register and we can do maths on it. */ appendir(ptr); } /* Stack grows down. Load backwards. */ while (value > 0) { int s = EM_wordsize*2; if (value < s) s = value; value -= s; push( load( s, ptr, value ) ); } assert(value == 0); break; } case op_lof: { struct ir* ptr = pop(EM_pointersize); push( load( EM_wordsize, ptr, value ) ); break; } case op_ldf: { struct ir* ptr = pop(EM_pointersize); push( load( EM_wordsize*2, ptr, value ) ); break; } case op_sti: { struct ir* ptr = pop(EM_pointersize); int offset = 0; if (value > peek(0)) { /* We're going to need to do multiple stores; fix the address * so it'll go into a register and we can do maths on it. */ appendir(ptr); } while (value > 0) { struct ir* v = pop(peek(0)); int s = v->size; if (value < s) s = value; appendir( store( s, ptr, offset, v ) ); value -= s; offset += s; } assert(value == 0); break; } case op_stf: { struct ir* ptr = pop(EM_pointersize); struct ir* val = pop(EM_wordsize); appendir( store( EM_wordsize, ptr, value, val ) ); break; } case op_sdf: { struct ir* ptr = pop(EM_pointersize); struct ir* val = pop(EM_wordsize*2); appendir( store( EM_wordsize*2, ptr, value, val ) ); break; } case op_ads: { struct ir* off = pop(value); struct ir* ptr = pop(EM_pointersize); if (value != EM_pointersize) off = convert(off, value, EM_pointersize, IR_FROMUI); push( new_ir2( IR_ADD, EM_pointersize, ptr, off ) ); break; } case op_adp: { struct ir* ptr = pop(EM_pointersize); push( new_ir2( IR_ADD, EM_pointersize, ptr, new_wordir(value) ) ); break; } case op_sbs: { struct ir* right = pop(EM_pointersize); struct ir* left = pop(EM_pointersize); struct ir* delta = new_ir2( IR_SUB, EM_pointersize, left, right ); if (value != EM_pointersize) delta = convert(delta, EM_pointersize, value, IR_FROMUI); push(delta); break; } case op_dup: { sequence_point(); if (value > (2*EM_wordsize)) { push(new_wordir(value)); helper_function(".dus4"); } else if ((value == (EM_wordsize*2)) && (peek(0) == EM_wordsize) && (peek(1) == EM_wordsize)) { struct ir* v1 = pop(EM_wordsize); struct ir* v2 = pop(EM_wordsize); push(v2); push(v1); push(v2); push(v1); } else { struct ir* v = pop(value); push(v); push(v); } break; } case op_dus: { if (value != EM_wordsize) fatal("'dus %d' not supported", value); helper_function(".dus4"); break; } case op_exg: { if (value > (2*EM_wordsize)) { push( new_wordir(value) ); helper_function(".exg"); } else { struct ir* v1 = pop(value); struct ir* v2 = pop(value); push(v1); push(v2); } break; } case op_zer: { if (value <= EM_wordsize) push(new_constir(value, 0)); else { push(new_wordir(value)); helper_function(".zer"); } break; } case op_asp: { switch (value) { case 0: break; case -1: case -2: case -4: case -8: push(new_anyir(-value)); break; default: while ((value > 0) && (stackptr > 0)) { int s = peek(0); if (s > value) s = value; pop(s); value -= s; } appendir( new_ir1( IR_STACKADJUST, EM_pointersize, new_wordir(value) ) ); break; } break; } case op_ass: appendir( new_ir1( IR_STACKADJUST, EM_pointersize, pop(value) ) ); break; case op_ret: { if (value > 0) { struct ir* retval = pop(value); materialise_stack(); appendir( new_ir1( IR_SETRET, value, retval ) ); } if (!current_proc->exit) { current_proc->exit = bb_get(NULL); array_append(¤t_proc->blocks, current_proc->exit); /* This is actually ignored --- the entire block gets special * treatment. But a lot of the rest of the code assumes that * all basic blocks have one instruction, so we insert one. */ array_append(¤t_proc->exit->irs, new_ir0( IR_RET, 0 ) ); } appendir( new_ir1( IR_JUMP, 0, new_bbir(current_proc->exit) ) ); break; } case op_lfr: { push( appendir( new_ir0( IR_GETRET, value ) ) ); break; } case op_csa: { struct ir* descriptor = pop(EM_pointersize); struct ir* targetvalue = appendir(pop(EM_pointersize)); struct jumptable jumptable = {}; int i; if (descriptor->opcode != IR_LABEL) fatal("csa is only supported if it refers " "directly to a descriptor block"); parse_csa(bb_get(descriptor->u.lvalue), &jumptable); emit_jumptable(targetvalue, &jumptable); break; } case op_csb: { struct ir* descriptor = pop(EM_pointersize); struct ir* targetvalue = appendir(pop(EM_pointersize)); struct jumptable jumptable = {}; int i; if (descriptor->opcode != IR_LABEL) fatal("csb is only supported if it refers " "directly to a descriptor block"); parse_csb(bb_get(descriptor->u.lvalue), &jumptable); emit_jumptable(targetvalue, &jumptable); break; } case op_sar: case op_lar: case op_aar: { const char* helper; if (value != EM_wordsize) fatal("sar/lar/aar are only supported when using " "word-size descriptors"); switch (opcode) { case op_sar: helper = ".sar4"; break; case op_lar: helper = ".lar4"; break; case op_aar: helper = ".aar4"; break; } materialise_stack(); /* No push here, because the helper function leaves the result on * the physical stack (which is very dubious). */ appendir( new_ir1( IR_CALL, 0, new_labelir(helper) ) ); break; } case op_lxl: push( walk_static_chain(value) ); break; case op_lxa: push( new_ir1( IR_FPTOAB, EM_pointersize, walk_static_chain(value) ) ); break; case op_fef: { struct ir* f = pop(value); /* fef is implemented by calling a helper function which then mutates * the stack. We read the return values off the stack when retracting * the stack pointer. */ push(f); push(new_wordir(0)); materialise_stack(); appendir( new_ir1( IR_CALL, 0, new_labelir((value == 4) ? ".fef4" : ".fef8") ) ); /* exit, leaving an int and then a float (or double) on the stack. */ break; } case op_fif: { /* fif is implemented by calling a helper function which then mutates * the stack. We read the return values off the stack when retracting * the stack pointer. */ /* We start with two floats on the stack. */ materialise_stack(); appendir( new_ir1( IR_CALL, 0, new_labelir((value == 4) ? ".fif4" : ".fif8") ) ); /* exit, leaving two floats (or doubles) on the stack. */ break; } case op_lor: { switch (value) { case 0: push( appendir( new_ir1( IR_FPTOLB, EM_pointersize, new_ir0( IR_GETFP, EM_pointersize ) ) ) ); break; case 1: materialise_stack(); push( appendir( new_ir0( IR_GETSP, EM_pointersize ) ) ); break; default: fatal("'lor %d' not supported", value); } break; } case op_str: { switch (value) { case 0: appendir( new_ir1( IR_SETFP, EM_pointersize, pop(EM_pointersize) ) ); break; case 1: appendir( new_ir1( IR_SETSP, EM_pointersize, pop(EM_pointersize) ) ); break; default: fatal("'str %d' not supported", value); } break; } case op_blm: push(new_wordir(value)); helper_function(".bls4"); break; case op_bls: if (value != EM_wordsize) fatal("'bls %d' not supported", value); helper_function(".bls4"); break; case op_los: if (value != EM_wordsize) fatal("'los %d' not supported", value); helper_function_with_arg(".los4", pop(EM_wordsize)); break; case op_sts: if (value != EM_wordsize) fatal("'sts %d' not supported", value); helper_function_with_arg(".sts4", pop(EM_wordsize)); break; case op_lin: { /* Set line number --- ignore. */ break; } default: fatal("treebuilder: unknown ivalue instruction '%s'", em_mnem[opcode - sp_fmnem]); } } static void insn_lvalue(int opcode, const char* label, arith offset) { switch (opcode) { case op_lpi: case op_lae: push( address_of_external(label, offset) ); break; case op_loe: push( new_ir1( IR_LOAD, EM_wordsize, address_of_external(label, offset) ) ); break; case op_lde: push( new_ir1( IR_LOAD, EM_wordsize*2, address_of_external(label, offset) ) ); break; case op_ste: sequence_point(); appendir( new_ir2( IR_STORE, EM_wordsize, address_of_external(label, offset), pop(EM_wordsize) ) ); break; case op_sde: sequence_point(); appendir( new_ir2( IR_STORE, EM_wordsize*2, address_of_external(label, offset), pop(EM_wordsize*2) ) ); break; case op_zre: sequence_point(); appendir( new_ir2( IR_STORE, EM_wordsize, address_of_external(label, offset), new_wordir(0) ) ); break; case op_ine: sequence_point(); appendir( new_ir2( IR_STORE, EM_wordsize, address_of_external(label, offset), new_ir2( IR_ADD, EM_wordsize, new_ir1( IR_LOAD, EM_wordsize, address_of_external(label, offset) ), new_wordir(1) ) ) ); break; case op_dee: sequence_point(); appendir( new_ir2( IR_STORE, EM_wordsize, address_of_external(label, offset), new_ir2( IR_ADD, EM_wordsize, new_ir1( IR_LOAD, EM_wordsize, address_of_external(label, offset) ), new_wordir(-1) ) ) ); break; case op_cal: assert(offset == 0); materialise_stack(); appendir( new_ir1( IR_CALL, 0, new_labelir(label) ) ); break; case op_bra: assert(offset == 0); materialise_stack(); appendir( new_ir1( IR_JUMP, 0, new_labelir(label) ) ); break; case op_gto: { struct ir* descriptor = address_of_external(label, offset); appendir( new_ir1( IR_SETFP, EM_pointersize, load(EM_pointersize, descriptor, EM_pointersize*2) ) ); appendir( new_ir1( IR_SETSP, EM_pointersize, load(EM_pointersize, descriptor, EM_pointersize*1) ) ); appendir( new_ir1( IR_JUMP, 0, load(EM_pointersize, descriptor, EM_pointersize*0) ) ); break; } case op_fil: { /* Set filename --- ignore. */ break; } default: fatal("treebuilder: unknown lvalue instruction '%s'", em_mnem[opcode - sp_fmnem]); } } static void generate_tree(struct basicblock* bb) { int i; tracef('0', "0: block %s\n", bb->name); current_bb = bb; reset_stack(); for (i=0; iems.count; i++) { struct em* em = bb->ems.item[i]; tracef('E', "E: read %s ", em_mnem[em->opcode - sp_fmnem]); switch (em->paramtype) { case PARAM_NONE: tracef('E', "\n"); insn_simple(em->opcode); break; case PARAM_IVALUE: tracef('E', "value=%d\n", em->u.ivalue); insn_ivalue(em->opcode, em->u.ivalue); break; case PARAM_LVALUE: tracef('E', "label=%s offset=%d\n", em->u.lvalue.label, em->u.lvalue.offset); insn_lvalue(em->opcode, em->u.lvalue.label, em->u.lvalue.offset); break; case PARAM_BVALUE: tracef('E', "true=%s", em->u.bvalue.left->name); if (em->u.bvalue.right) tracef('E', " false=%s", em->u.bvalue.right->name); tracef('E', "\n"); insn_bvalue(em->opcode, em->u.bvalue.left, em->u.bvalue.right); break; default: assert(0); } if (tracing('E')) print_stack(); } /* Yes, we are allowed to leave stuff on the stack at the end of the procedure. * It's discarded as part of the function return. */ } void tb_procedure(void) { int i; for (i=0; iblocks.count; i++) generate_tree(current_proc->blocks.item[i]); } static void parse_csa(struct basicblock* data_bb, struct jumptable* table) { struct em* em; int lowerbound; int count; int i; assert(data_bb->ems.count >= 3); /* Default target */ em = data_bb->ems.item[0]; assert(em->opcode == op_bra); assert(em->paramtype == PARAM_BVALUE); table->defaulttarget = em->u.bvalue.left; /* Lower bound */ em = data_bb->ems.item[1]; assert(em->opcode == op_loc); assert(em->paramtype == PARAM_IVALUE); lowerbound = em->u.ivalue; /* Count */ em = data_bb->ems.item[2]; assert(em->opcode == op_loc); assert(em->paramtype == PARAM_IVALUE); count = em->u.ivalue + 1; /* value in descriptor is inclusive */ assert(data_bb->ems.count >= (count + 3)); /* Now, each target in turn. */ for (i=0; iems.item[3 + i]; assert(em->opcode == op_bra); assert(em->paramtype == PARAM_BVALUE); target = em->u.bvalue.left; imap_put(&table->targets, lowerbound+i, target); } } static void parse_csb(struct basicblock* data_bb, struct jumptable* table) { struct em* em; int count; int i; assert(data_bb->ems.count >= 2); /* Default target */ em = data_bb->ems.item[0]; assert(em->opcode == op_bra); assert(em->paramtype == PARAM_BVALUE); table->defaulttarget = em->u.bvalue.left; /* Number of targets */ em = data_bb->ems.item[1]; assert(em->opcode == op_loc); assert(em->paramtype == PARAM_IVALUE); count = em->u.ivalue; assert(data_bb->ems.count >= (count*2 + 2)); /* Now, each target in turn. */ for (i=0; iems.item[2 + i*2]; assert(em->opcode == op_loc); assert(em->paramtype == PARAM_IVALUE); value = em->u.ivalue; em = data_bb->ems.item[3 + i*2]; assert(em->opcode == op_bra); assert(em->paramtype == PARAM_BVALUE); target = em->u.bvalue.left; imap_put(&table->targets, value, target); } } static void emit_jumptable(struct ir* targetvalue, struct jumptable* jumptable) { int i; materialise_stack(); for (i=0; itargets.count; i++) { int value = jumptable->targets.item[i].left; struct basicblock* target = jumptable->targets.item[i].right; struct basicblock* nextblock = bb_get(NULL); array_append(¤t_proc->blocks, nextblock); appendir( new_ir2( IR_CJUMPEQ, 0, new_ir2( IR_COMPARESI, EM_wordsize, targetvalue, new_wordir(value) ), new_ir2( IR_PAIR, 0, new_bbir(target), new_bbir(nextblock) ) ) ); current_bb = nextblock; } appendir( new_ir1( IR_JUMP, 0, new_bbir(jumptable->defaulttarget) ) ); } /* vim: set sw=4 ts=4 expandtab : */