#include "mcg.h" #if defined MCGG_OPTION_LOWER_PUSHES_TO_LOADS_AND_STORES /* On architectures which can't push and pop cheaply, a push typically * * sub sp, sp, -4 * sw r5, 0(sp) * * This is hugely wasteful when you want to push or multiple things * at once, which happens a lot because that's how the procedure calling * convention works. This code detects these runs and turns them into a * single stack adjustment and then offsetted accesses via the stack * pointer. In order to be efficient, the table needs to know how to * handle this efficiently: * * STACKADJUST(CONST(-4)) * STORE.I(ADD.I(GETSP(), CONST(0))) * * ...otherwise the code will be *even worse*. * * We have to be careful, though, because after we do the adjustment, * the physical stack pointer won't match em's idea of the stack pointer * until the last 'push' happens. So we need to check that this is never * used. * * With this option set, PUSH will never be seen by the instruction * selector. */ static struct basicblock* current_bb; static int cursor; static bool accesses_stack_pointer_cb(struct ir* ir, void* user) { switch (ir->opcode) { case IR_SETSP: case IR_GETSP: case IR_CALL: case IR_STACKADJUST: return true; default: return false; } } static bool accesses_stack_pointer(struct ir* ir) { return !!ir_walk(ir, accesses_stack_pointer_cb, NULL); } static void consider_push(struct ir* ir) { int runstart; int delta; int i; if (ir->opcode != IR_PUSH) { cursor++; return; } /* This is the first push of a run; we'll want to insert the STACKADJUST * before this one. */ tracef('P', "found push in %s at IR index %d\n", current_bb->name, cursor); runstart = cursor; /* Now start walking forward until we find an IR which isn't a safe push. * The current IR is always safe. */ for (;;) { struct ir* ir; cursor++; if (cursor == current_bb->irs.count) break; ir = current_bb->irs.item[cursor]; if (ir->opcode != IR_PUSH) break; if (accesses_stack_pointer(ir)) break; delta += ir->size; } tracef('P', "found end of run at IR index %d\n", cursor); /* Now work backwards, converting each push into a stack write. */ delta = 0; i = cursor - 1; while (i >= runstart) { struct ir* ir = current_bb->irs.item[i]; struct ir* value_ir = ir->left; assert(ir->opcode == IR_PUSH); ir->opcode = IR_STORE; ir->left = new_ir2( IR_ADD, EM_pointersize, new_ir0(IR_GETSP, EM_pointersize), new_wordir(delta) ); ir->left->root = ir->left->left->root = ir->left->right->root = ir->root; ir->right = value_ir; delta += ir->size; i--; } /* And finally, before the place where the first push was, adjust the * stack. */ ir = new_ir1(IR_STACKADJUST, EM_pointersize, new_wordir(-delta)); ir->left->root = ir->root = ir; array_insert(¤t_bb->irs, ir, runstart); cursor++; } void pass_lower_pushes(void) { int i; for (i=0; iblocks.count; i++) { current_bb = current_proc->blocks.item[i]; cursor = 0; while (cursor < current_bb->irs.count) consider_push(current_bb->irs.item[cursor]); } } #endif