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:
Richard Henderson 2019-01-05 07:21:57 -05:00 committed by Lioncash
parent c2be1cee79
commit 64843e8c09
No known key found for this signature in database
GPG key ID: 4E3C3CC1031BA9C7

View file

@ -1871,6 +1871,21 @@ static void reachable_code_pass(TCGContext *s)
#define IS_DEAD_ARG(n) (arg_life & (DEAD_ARG << (n))) #define IS_DEAD_ARG(n) (arg_life & (DEAD_ARG << (n)))
#define NEED_SYNC_ARG(n) (arg_life & (SYNC_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 /* liveness analysis: end of function: all temps are dead, and globals
should be in memory. */ should be in memory. */
static void la_func_end(TCGContext *s, int ng, int nt) 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) { for (i = 0; i < ng; ++i) {
s->temps[i].state = TS_DEAD | TS_MEM; s->temps[i].state = TS_DEAD | TS_MEM;
la_reset_pref(s, &s->temps[i]);
} }
for (i = ng; i < nt; ++i) { for (i = ng; i < nt; ++i) {
s->temps[i].state = TS_DEAD; 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) { for (i = 0; i < ng; ++i) {
s->temps[i].state = TS_DEAD | TS_MEM; s->temps[i].state = TS_DEAD | TS_MEM;
la_reset_pref(s, &s->temps[i]);
} }
for (i = ng; i < nt; ++i) { for (i = ng; i < nt; ++i) {
s->temps[i].state = (s->temps[i].temp_local s->temps[i].state = (s->temps[i].temp_local
? TS_DEAD | TS_MEM ? TS_DEAD | TS_MEM
: TS_DEAD); : TS_DEAD);
la_reset_pref(s, &s->temps[i]);
} }
} }
@ -1924,7 +1943,12 @@ static void la_global_sync(TCGContext *s, int ng)
int i; int i;
for (i = 0; i < ng; ++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++) { for (i = 0; i < ng; i++) {
s->temps[i].state = TS_DEAD | TS_MEM; 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_globals = s->nb_globals;
int nb_temps = s->nb_temps; int nb_temps = s->nb_temps;
TCGOp *op, *op_prev; 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); la_func_end(s, nb_globals, nb_temps);
QTAILQ_FOREACH_REVERSE_SAFE(op, &s->ops, TCGOpHead, link, op_prev) { 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; TCGOpcode opc_new, opc_new2;
bool have_opc_new2; bool have_opc_new2;
TCGLifeData arg_life = 0; TCGLifeData arg_life = 0;
TCGTemp *arg_ts; TCGTemp *ts;
TCGOpcode opc = op->opc; TCGOpcode opc = op->opc;
const TCGOpDef *def = &s->tcg_op_defs[opc]; const TCGOpDef *def = &s->tcg_op_defs[opc];
@ -1962,6 +2016,7 @@ static void liveness_pass_1(TCGContext *s)
case INDEX_op_call: case INDEX_op_call:
{ {
int call_flags; int call_flags;
int nb_call_regs;
nb_oargs = TCGOP_CALLO(op); nb_oargs = TCGOP_CALLO(op);
nb_iargs = TCGOP_CALLI(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 */ /* pure functions can be removed if their result is unused */
if (call_flags & TCG_CALL_NO_SIDE_EFFECTS) { if (call_flags & TCG_CALL_NO_SIDE_EFFECTS) {
for (i = 0; i < nb_oargs; i++) { for (i = 0; i < nb_oargs; i++) {
arg_ts = arg_temp(op->args[i]); ts = arg_temp(op->args[i]);
if (arg_ts->state != TS_DEAD) { if (ts->state != TS_DEAD) {
goto do_not_remove_call; goto do_not_remove_call;
} }
} }
goto do_remove; goto do_remove;
} else { }
do_not_remove_call: do_not_remove_call:
/* output args are dead */ /* Output args are dead. */
for (i = 0; i < nb_oargs; i++) { for (i = 0; i < nb_oargs; i++) {
arg_ts = arg_temp(op->args[i]); ts = arg_temp(op->args[i]);
if (arg_ts->state & TS_DEAD) { if (ts->state & TS_DEAD) {
arg_life |= DEAD_ARG << i; arg_life |= DEAD_ARG << i;
}
if (arg_ts->state & TS_MEM) {
arg_life |= SYNC_ARG << i;
}
arg_ts->state = TS_DEAD;
} }
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 | /* Not used -- it will be tcg_target_call_oarg_regs[i]. */
TCG_CALL_NO_READ_GLOBALS))) { op->output_pref[i] = 0;
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 */ if (!(call_flags & (TCG_CALL_NO_WRITE_GLOBALS |
for (i = nb_oargs; i < nb_iargs + nb_oargs; i++) { TCG_CALL_NO_READ_GLOBALS))) {
arg_ts = arg_temp(op->args[i]); la_global_kill(s, nb_globals);
if (arg_ts && arg_ts->state & TS_DEAD) { } else if (!(call_flags & TCG_CALL_NO_READ_GLOBALS)) {
arg_life |= DEAD_ARG << i; 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]); /* For all live registers, remove call-clobbered prefs. */
if (arg_ts) { la_cross_call(s, nb_temps);
arg_ts->state &= ~TS_DEAD;
} 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; break;
case INDEX_op_discard: case INDEX_op_discard:
/* mark the temporary as dead */ /* 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; break;
case INDEX_op_add2_i32: case INDEX_op_add2_i32:
@ -2120,17 +2204,23 @@ static void liveness_pass_1(TCGContext *s)
do_not_remove: do_not_remove:
/* output args are dead */ /* output args are dead */
for (i = 0; i < nb_oargs; i++) { for (i = 0; i < nb_oargs; i++) {
arg_ts = arg_temp(op->args[i]); ts = arg_temp(op->args[i]);
if (arg_ts->state & TS_DEAD) {
/* 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; arg_life |= DEAD_ARG << i;
} }
if (arg_ts->state & TS_MEM) { if (ts->state & TS_MEM) {
arg_life |= SYNC_ARG << i; 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) { if (def->flags & TCG_OPF_BB_END) {
// Unicorn: do not optimize dead temps on brcond, // Unicorn: do not optimize dead temps on brcond,
// this causes problem because check_exit_request() inserts // 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) { } else if (def->flags & TCG_OPF_SIDE_EFFECTS) {
la_global_sync(s, nb_globals); 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++) { for (i = nb_oargs; i < nb_oargs + nb_iargs; i++) {
arg_ts = arg_temp(op->args[i]); ts = arg_temp(op->args[i]);
if (arg_ts->state & TS_DEAD) { if (ts->state & TS_DEAD) {
arg_life |= DEAD_ARG << i; 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++) { 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; break;
} }
op->life = arg_life; op->life = arg_life;
op->output_pref[0] = 0;
op->output_pref[1] = 0;
} }
} }