ack/mach/proto/mcg/pass_lowerpushes.c
David Given bbb708717a Add the OPTIONS clause to the mcgg grammar; add an optional pass which converts
sequences of PUSHes to a single STACKADJUST followed by STOREs. This should
dramatically improve code on stack-unfriendly architectures like MIPS.
2018-09-22 11:19:00 +02:00

141 lines
3.1 KiB
C

#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(&current_bb->irs, ir, runstart);
cursor++;
}
void pass_lower_pushes(void)
{
int i;
for (i=0; i<current_proc->blocks.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