mirror of
https://github.com/yuzu-emu/unicorn.git
synced 2025-01-11 04:05:37 +00:00
tcg: Record register preferences during liveness
With these preferences, we can arrange for function call arguments to be computed into the proper registers instead of requiring extra moves. Backports commit 25f49c5f1508ddf081ce89fa6bbfd87a51eea37b from qemu
This commit is contained in:
parent
c2be1cee79
commit
64843e8c09
229
qemu/tcg/tcg.c
229
qemu/tcg/tcg.c
|
@ -1871,6 +1871,21 @@ static void reachable_code_pass(TCGContext *s)
|
|||
#define IS_DEAD_ARG(n) (arg_life & (DEAD_ARG << (n)))
|
||||
#define NEED_SYNC_ARG(n) (arg_life & (SYNC_ARG << (n)))
|
||||
|
||||
/* For liveness_pass_1, the register preferences for a given temp. */
|
||||
static inline TCGRegSet *la_temp_pref(TCGTemp *ts)
|
||||
{
|
||||
return ts->state_ptr;
|
||||
}
|
||||
|
||||
/* For liveness_pass_1, reset the preferences for a given temp to the
|
||||
* maximal regset for its type.
|
||||
*/
|
||||
static inline void la_reset_pref(TCGContext *s, TCGTemp *ts)
|
||||
{
|
||||
*la_temp_pref(ts)
|
||||
= (ts->state == TS_DEAD ? 0 : s->tcg_target_available_regs[ts->type]);
|
||||
}
|
||||
|
||||
/* liveness analysis: end of function: all temps are dead, and globals
|
||||
should be in memory. */
|
||||
static void la_func_end(TCGContext *s, int ng, int nt)
|
||||
|
@ -1879,9 +1894,11 @@ static void la_func_end(TCGContext *s, int ng, int nt)
|
|||
|
||||
for (i = 0; i < ng; ++i) {
|
||||
s->temps[i].state = TS_DEAD | TS_MEM;
|
||||
la_reset_pref(s, &s->temps[i]);
|
||||
}
|
||||
for (i = ng; i < nt; ++i) {
|
||||
s->temps[i].state = TS_DEAD;
|
||||
la_reset_pref(s, &s->temps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1893,11 +1910,13 @@ static void la_bb_end(TCGContext *s, int ng, int nt)
|
|||
|
||||
for (i = 0; i < ng; ++i) {
|
||||
s->temps[i].state = TS_DEAD | TS_MEM;
|
||||
la_reset_pref(s, &s->temps[i]);
|
||||
}
|
||||
for (i = ng; i < nt; ++i) {
|
||||
s->temps[i].state = (s->temps[i].temp_local
|
||||
? TS_DEAD | TS_MEM
|
||||
: TS_DEAD);
|
||||
la_reset_pref(s, &s->temps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1924,7 +1943,12 @@ static void la_global_sync(TCGContext *s, int ng)
|
|||
int i;
|
||||
|
||||
for (i = 0; i < ng; ++i) {
|
||||
s->temps[i].state |= TS_MEM;
|
||||
int state = s->temps[i].state;
|
||||
s->temps[i].state = state | TS_MEM;
|
||||
if (state == TS_DEAD) {
|
||||
/* If the global was previously dead, reset prefs. */
|
||||
la_reset_pref(s, &s->temps[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1935,6 +1959,29 @@ static void la_global_kill(TCGContext *s, int ng)
|
|||
|
||||
for (i = 0; i < ng; i++) {
|
||||
s->temps[i].state = TS_DEAD | TS_MEM;
|
||||
la_reset_pref(s, &s->temps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* liveness analysis: note live globals crossing calls. */
|
||||
static void la_cross_call(TCGContext *s, int nt)
|
||||
{
|
||||
TCGRegSet mask = ~s->tcg_target_call_clobber_regs;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nt; i++) {
|
||||
TCGTemp *ts = &s->temps[i];
|
||||
if (!(ts->state & TS_DEAD)) {
|
||||
TCGRegSet *pset = la_temp_pref(ts);
|
||||
TCGRegSet set = *pset;
|
||||
|
||||
set &= mask;
|
||||
/* If the combination is not possible, restart. */
|
||||
if (set == 0) {
|
||||
set = s->tcg_target_available_regs[ts->type] & mask;
|
||||
}
|
||||
*pset = set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1946,15 +1993,22 @@ static void liveness_pass_1(TCGContext *s)
|
|||
int nb_globals = s->nb_globals;
|
||||
int nb_temps = s->nb_temps;
|
||||
TCGOp *op, *op_prev;
|
||||
TCGRegSet *prefs;
|
||||
int i;
|
||||
|
||||
prefs = tcg_malloc(s, sizeof(TCGRegSet) * nb_temps);
|
||||
for (i = 0; i < nb_temps; ++i) {
|
||||
s->temps[i].state_ptr = prefs + i;
|
||||
}
|
||||
|
||||
la_func_end(s, nb_globals, nb_temps);
|
||||
|
||||
QTAILQ_FOREACH_REVERSE_SAFE(op, &s->ops, TCGOpHead, link, op_prev) {
|
||||
int i, nb_iargs, nb_oargs;
|
||||
int nb_iargs, nb_oargs;
|
||||
TCGOpcode opc_new, opc_new2;
|
||||
bool have_opc_new2;
|
||||
TCGLifeData arg_life = 0;
|
||||
TCGTemp *arg_ts;
|
||||
TCGTemp *ts;
|
||||
TCGOpcode opc = op->opc;
|
||||
const TCGOpDef *def = &s->tcg_op_defs[opc];
|
||||
|
||||
|
@ -1962,6 +2016,7 @@ static void liveness_pass_1(TCGContext *s)
|
|||
case INDEX_op_call:
|
||||
{
|
||||
int call_flags;
|
||||
int nb_call_regs;
|
||||
|
||||
nb_oargs = TCGOP_CALLO(op);
|
||||
nb_iargs = TCGOP_CALLI(op);
|
||||
|
@ -1970,47 +2025,74 @@ static void liveness_pass_1(TCGContext *s)
|
|||
/* pure functions can be removed if their result is unused */
|
||||
if (call_flags & TCG_CALL_NO_SIDE_EFFECTS) {
|
||||
for (i = 0; i < nb_oargs; i++) {
|
||||
arg_ts = arg_temp(op->args[i]);
|
||||
if (arg_ts->state != TS_DEAD) {
|
||||
ts = arg_temp(op->args[i]);
|
||||
if (ts->state != TS_DEAD) {
|
||||
goto do_not_remove_call;
|
||||
}
|
||||
}
|
||||
goto do_remove;
|
||||
} else {
|
||||
do_not_remove_call:
|
||||
}
|
||||
do_not_remove_call:
|
||||
|
||||
/* output args are dead */
|
||||
for (i = 0; i < nb_oargs; i++) {
|
||||
arg_ts = arg_temp(op->args[i]);
|
||||
if (arg_ts->state & TS_DEAD) {
|
||||
arg_life |= DEAD_ARG << i;
|
||||
}
|
||||
if (arg_ts->state & TS_MEM) {
|
||||
arg_life |= SYNC_ARG << i;
|
||||
}
|
||||
arg_ts->state = TS_DEAD;
|
||||
/* Output args are dead. */
|
||||
for (i = 0; i < nb_oargs; i++) {
|
||||
ts = arg_temp(op->args[i]);
|
||||
if (ts->state & TS_DEAD) {
|
||||
arg_life |= DEAD_ARG << i;
|
||||
}
|
||||
if (ts->state & TS_MEM) {
|
||||
arg_life |= SYNC_ARG << i;
|
||||
}
|
||||
ts->state = TS_DEAD;
|
||||
la_reset_pref(s, ts);
|
||||
|
||||
if (!(call_flags & (TCG_CALL_NO_WRITE_GLOBALS |
|
||||
TCG_CALL_NO_READ_GLOBALS))) {
|
||||
la_global_kill(s, nb_globals);
|
||||
} else if (!(call_flags & TCG_CALL_NO_READ_GLOBALS)) {
|
||||
la_global_sync(s, nb_globals);
|
||||
}
|
||||
/* Not used -- it will be tcg_target_call_oarg_regs[i]. */
|
||||
op->output_pref[i] = 0;
|
||||
}
|
||||
|
||||
/* record arguments that die in this helper */
|
||||
for (i = nb_oargs; i < nb_iargs + nb_oargs; i++) {
|
||||
arg_ts = arg_temp(op->args[i]);
|
||||
if (arg_ts && arg_ts->state & TS_DEAD) {
|
||||
arg_life |= DEAD_ARG << i;
|
||||
}
|
||||
if (!(call_flags & (TCG_CALL_NO_WRITE_GLOBALS |
|
||||
TCG_CALL_NO_READ_GLOBALS))) {
|
||||
la_global_kill(s, nb_globals);
|
||||
} else if (!(call_flags & TCG_CALL_NO_READ_GLOBALS)) {
|
||||
la_global_sync(s, nb_globals);
|
||||
}
|
||||
|
||||
/* Record arguments that die in this helper. */
|
||||
for (i = nb_oargs; i < nb_iargs + nb_oargs; i++) {
|
||||
ts = arg_temp(op->args[i]);
|
||||
if (ts && ts->state & TS_DEAD) {
|
||||
arg_life |= DEAD_ARG << i;
|
||||
}
|
||||
/* input arguments are live for preceding opcodes */
|
||||
for (i = nb_oargs; i < nb_iargs + nb_oargs; i++) {
|
||||
arg_ts = arg_temp(op->args[i]);
|
||||
if (arg_ts) {
|
||||
arg_ts->state &= ~TS_DEAD;
|
||||
}
|
||||
}
|
||||
|
||||
/* For all live registers, remove call-clobbered prefs. */
|
||||
la_cross_call(s, nb_temps);
|
||||
|
||||
nb_call_regs = ARRAY_SIZE(tcg_target_call_iarg_regs);
|
||||
|
||||
/* Input arguments are live for preceding opcodes. */
|
||||
for (i = 0; i < nb_iargs; i++) {
|
||||
ts = arg_temp(op->args[i + nb_oargs]);
|
||||
if (ts && ts->state & TS_DEAD) {
|
||||
/* For those arguments that die, and will be allocated
|
||||
* in registers, clear the register set for that arg,
|
||||
* to be filled in below. For args that will be on
|
||||
* the stack, reset to any available reg.
|
||||
*/
|
||||
*la_temp_pref(ts)
|
||||
= (i < nb_call_regs ? 0 :
|
||||
s->tcg_target_available_regs[ts->type]);
|
||||
ts->state &= ~TS_DEAD;
|
||||
}
|
||||
}
|
||||
|
||||
/* For each input argument, add its input register to prefs.
|
||||
If a temp is used once, this produces a single set bit. */
|
||||
for (i = 0; i < MIN(nb_call_regs, nb_iargs); i++) {
|
||||
ts = arg_temp(op->args[i + nb_oargs]);
|
||||
if (ts) {
|
||||
tcg_regset_set_reg(*la_temp_pref(ts),
|
||||
tcg_target_call_iarg_regs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2019,7 +2101,9 @@ static void liveness_pass_1(TCGContext *s)
|
|||
break;
|
||||
case INDEX_op_discard:
|
||||
/* mark the temporary as dead */
|
||||
arg_temp(op->args[0])->state = TS_DEAD;
|
||||
ts = arg_temp(op->args[0]);
|
||||
ts->state = TS_DEAD;
|
||||
la_reset_pref(s, ts);
|
||||
break;
|
||||
|
||||
case INDEX_op_add2_i32:
|
||||
|
@ -2120,17 +2204,23 @@ static void liveness_pass_1(TCGContext *s)
|
|||
do_not_remove:
|
||||
/* output args are dead */
|
||||
for (i = 0; i < nb_oargs; i++) {
|
||||
arg_ts = arg_temp(op->args[i]);
|
||||
if (arg_ts->state & TS_DEAD) {
|
||||
ts = arg_temp(op->args[i]);
|
||||
|
||||
/* Remember the preference of the uses that followed. */
|
||||
op->output_pref[i] = *la_temp_pref(ts);
|
||||
|
||||
/* Output args are dead. */
|
||||
if (ts->state & TS_DEAD) {
|
||||
arg_life |= DEAD_ARG << i;
|
||||
}
|
||||
if (arg_ts->state & TS_MEM) {
|
||||
if (ts->state & TS_MEM) {
|
||||
arg_life |= SYNC_ARG << i;
|
||||
}
|
||||
arg_ts->state = TS_DEAD;
|
||||
ts->state = TS_DEAD;
|
||||
la_reset_pref(s, ts);
|
||||
}
|
||||
|
||||
/* if end of basic block, update */
|
||||
/* If end of basic block, update. */
|
||||
if (def->flags & TCG_OPF_BB_END) {
|
||||
// Unicorn: do not optimize dead temps on brcond,
|
||||
// this causes problem because check_exit_request() inserts
|
||||
|
@ -2148,25 +2238,68 @@ static void liveness_pass_1(TCGContext *s)
|
|||
}
|
||||
} else if (def->flags & TCG_OPF_SIDE_EFFECTS) {
|
||||
la_global_sync(s, nb_globals);
|
||||
if (def->flags & TCG_OPF_CALL_CLOBBER) {
|
||||
la_cross_call(s, nb_temps);
|
||||
}
|
||||
}
|
||||
|
||||
/* record arguments that die in this opcode */
|
||||
/* Record arguments that die in this opcode. */
|
||||
for (i = nb_oargs; i < nb_oargs + nb_iargs; i++) {
|
||||
arg_ts = arg_temp(op->args[i]);
|
||||
if (arg_ts->state & TS_DEAD) {
|
||||
ts = arg_temp(op->args[i]);
|
||||
if (ts->state & TS_DEAD) {
|
||||
arg_life |= DEAD_ARG << i;
|
||||
}
|
||||
}
|
||||
/* input arguments are live for preceding opcodes */
|
||||
/* Input arguments are live for preceding opcodes. */
|
||||
for (i = nb_oargs; i < nb_oargs + nb_iargs; i++) {
|
||||
arg_temp(op->args[i])->state &= ~TS_DEAD;
|
||||
ts = arg_temp(op->args[i]);
|
||||
if (ts->state & TS_DEAD) {
|
||||
/* For operands that were dead, initially allow
|
||||
all regs for the type. */
|
||||
*la_temp_pref(ts) = s->tcg_target_available_regs[ts->type];
|
||||
ts->state &= ~TS_DEAD;
|
||||
}
|
||||
}
|
||||
|
||||
/* Incorporate constraints for this operand. */
|
||||
switch (opc) {
|
||||
case INDEX_op_mov_i32:
|
||||
case INDEX_op_mov_i64:
|
||||
/* Note that these are TCG_OPF_NOT_PRESENT and do not
|
||||
have proper constraints. That said, special case
|
||||
moves to propagate preferences backward. */
|
||||
if (IS_DEAD_ARG(1)) {
|
||||
*la_temp_pref(arg_temp(op->args[0]))
|
||||
= *la_temp_pref(arg_temp(op->args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
for (i = nb_oargs; i < nb_oargs + nb_iargs; i++) {
|
||||
const TCGArgConstraint *ct = &def->args_ct[i];
|
||||
TCGRegSet set, *pset;
|
||||
|
||||
ts = arg_temp(op->args[i]);
|
||||
pset = la_temp_pref(ts);
|
||||
set = *pset;
|
||||
|
||||
set &= ct->u.regs;
|
||||
if (ct->ct & TCG_CT_IALIAS) {
|
||||
set &= op->output_pref[ct->alias_index];
|
||||
}
|
||||
/* If the combination is not possible, restart. */
|
||||
if (set == 0) {
|
||||
set = ct->u.regs;
|
||||
}
|
||||
*pset = set;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
op->life = arg_life;
|
||||
op->output_pref[0] = 0;
|
||||
op->output_pref[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue