ack/mach/proto/mcg/treebuilder.c
David Given ac856f3b09 The approach I was taking to csa and csb turns out not to work --- critical
edge splitting can cause new basic blocks to be added to the graph, but while
the graph itself gets properly rewritten the descriptor tables can't be updated
to take these into account, so they end up pointing at the wrong blocks. This
causes really hard-to-debug problems.

The new approach is to parse the descriptor blocks and then generate a
comparison chain. Brute force, but much easier for the compiler to reason
about.
2018-09-20 00:12:03 +02:00

1893 lines
46 KiB
C

#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; i<stackptr; i++)
{
struct ir* ir = stack[i];
tracef('E', " $%d.%d", ir->id, ir->size);
}
tracef('E', " (top)\n");
}
static struct ir* appendir(struct ir* ir)
{
int i;
assert(current_bb != NULL);
array_appendu(&current_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; i<stackptr; i++)
{
struct ir* ir = stack[i];
array_appendu(&current_bb->irs, ir);
}
}
static void materialise_stack(void)
{
int i;
for (i=0; i<stackptr; i++)
{
struct ir* ir = stack[i];
appendir(
new_ir1(
IR_PUSH, ir->size,
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(&current_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(&current_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; i<bb->ems.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; i<current_proc->blocks.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; i<count; i++)
{
struct basicblock* target;
em = data_bb->ems.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; i<count; i++)
{
int value;
struct basicblock* target;
em = data_bb->ems.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; i<jumptable->targets.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(&current_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 : */