ARM64: Fold const-known EE ALU ops into the GPR cache as immediate Movs

This commit is contained in:
David Isztl
2026-06-11 21:14:14 +02:00
parent 129531e418
commit 2dafed0651
+194
View File
@@ -1255,6 +1255,194 @@ static bool recTryTranslateCachedStoreQuad(u32 rt, u32 rs, s32 imm, RecGprCacheS
return true;
}
// Constant folding into the register cache: when every source operand of an ALU op
// is const-known, compute the result at compile time and emit a single immediate Mov
// into the destination's cache register (dirty — flushed on demand like any cached
// write). The folding formulas below are kept textually identical to the tracking
// formulas in recConstApplyCachedEffects so the emitted value and the const state can
// never diverge. Runs before recTryTranslateCachedOp in recTranslateOpOptimized;
// returns false to fall through when any needed source is unknown.
static bool recTryTranslateCachedConstOp(u32 op, RecGprConstState& const_state, RecGprCacheState& cache)
{
const u32 opcode = op >> 26;
const u32 rs = (op >> 21) & 0x1f;
const u32 rt = (op >> 16) & 0x1f;
const u32 rd = (op >> 11) & 0x1f;
const u32 sa = (op >> 6) & 0x1f;
const u32 funct = op & 0x3f;
const s32 imm = static_cast<s16>(op);
const u32 imm_u = static_cast<u16>(op);
auto known = [&](u32 reg) -> bool {
return const_state.known[reg];
};
auto value = [&](u32 reg) -> u64 {
return const_state.value[reg];
};
auto emit_known = [&](u32 reg, u64 val) -> bool {
if (reg != 0)
{
const a64::Register& dst = recCacheDest(cache, reg);
armAsm->Mov(dst, val);
}
recConstSetKnown(const_state, reg, val);
return true;
};
switch (opcode)
{
case 0x08: // ADDI
case 0x09: // ADDIU
if (!known(rs))
return false;
return emit_known(rt, recSignExtend32(static_cast<u32>(value(rs)) + static_cast<u32>(imm)));
case 0x18: // DADDI
case 0x19: // DADDIU
if (!known(rs))
return false;
return emit_known(rt, value(rs) + static_cast<u64>(static_cast<s64>(imm)));
case 0x0A: // SLTI
if (!known(rs))
return false;
return emit_known(rt, (static_cast<s64>(value(rs)) < static_cast<s64>(imm)) ? 1 : 0);
case 0x0B: // SLTIU
if (!known(rs))
return false;
return emit_known(rt, (value(rs) < static_cast<u64>(static_cast<s64>(imm))) ? 1 : 0);
case 0x0C: // ANDI
if (!known(rs))
return false;
return emit_known(rt, value(rs) & imm_u);
case 0x0D: // ORI
if (!known(rs))
return false;
return emit_known(rt, value(rs) | imm_u);
case 0x0E: // XORI
if (!known(rs))
return false;
return emit_known(rt, value(rs) ^ imm_u);
case 0x0F: // LUI
return emit_known(rt, recSignExtend32(static_cast<u32>(imm_u) << 16));
case 0x00:
break;
default:
return false;
}
switch (funct)
{
case 0x00: // SLL
if (!known(rt)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(value(rt)) << sa));
case 0x02: // SRL
if (!known(rt)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(value(rt)) >> sa));
case 0x03: // SRA
if (!known(rt)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(static_cast<s32>(static_cast<u32>(value(rt))) >> sa)));
case 0x04: // SLLV
if (!known(rt) || !known(rs)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(value(rt)) << (value(rs) & 0x1f)));
case 0x06: // SRLV
if (!known(rt) || !known(rs)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(value(rt)) >> (value(rs) & 0x1f)));
case 0x07: // SRAV
if (!known(rt) || !known(rs)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(static_cast<s32>(static_cast<u32>(value(rt))) >> (value(rs) & 0x1f))));
case 0x14: // DSLLV
if (!known(rt) || !known(rs)) return false;
return emit_known(rd, value(rt) << (value(rs) & 0x3f));
case 0x16: // DSRLV
if (!known(rt) || !known(rs)) return false;
return emit_known(rd, value(rt) >> (value(rs) & 0x3f));
case 0x17: // DSRAV
if (!known(rt) || !known(rs)) return false;
return emit_known(rd, static_cast<u64>(static_cast<s64>(value(rt)) >> (value(rs) & 0x3f)));
case 0x38: // DSLL
if (!known(rt)) return false;
return emit_known(rd, value(rt) << sa);
case 0x3A: // DSRL
if (!known(rt)) return false;
return emit_known(rd, value(rt) >> sa);
case 0x3B: // DSRA
if (!known(rt)) return false;
return emit_known(rd, static_cast<u64>(static_cast<s64>(value(rt)) >> sa));
case 0x3C: // DSLL32
if (!known(rt)) return false;
return emit_known(rd, value(rt) << (sa + 32));
case 0x3E: // DSRL32
if (!known(rt)) return false;
return emit_known(rd, value(rt) >> (sa + 32));
case 0x3F: // DSRA32
if (!known(rt)) return false;
return emit_known(rd, static_cast<u64>(static_cast<s64>(value(rt)) >> (sa + 32)));
case 0x20: // ADD
case 0x21: // ADDU
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(value(rs)) + static_cast<u32>(value(rt))));
case 0x22: // SUB
case 0x23: // SUBU
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, recSignExtend32(static_cast<u32>(value(rs)) - static_cast<u32>(value(rt))));
case 0x2C: // DADD
case 0x2D: // DADDU
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, value(rs) + value(rt));
case 0x2E: // DSUB
case 0x2F: // DSUBU
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, value(rs) - value(rt));
case 0x24: // AND
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, value(rs) & value(rt));
case 0x25: // OR
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, value(rs) | value(rt));
case 0x26: // XOR
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, value(rs) ^ value(rt));
case 0x27: // NOR
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, ~(value(rs) | value(rt)));
case 0x2A: // SLT
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, (static_cast<s64>(value(rs)) < static_cast<s64>(value(rt))) ? 1 : 0);
case 0x2B: // SLTU
if (!known(rs) || !known(rt)) return false;
return emit_known(rd, (value(rs) < value(rt)) ? 1 : 0);
case 0x0A: // MOVZ
if (!known(rt))
return false;
if (value(rt) != 0) // condition false at compile time -> architectural no-op
return true;
if (!known(rs))
return false;
return emit_known(rd, value(rs));
case 0x0B: // MOVN
if (!known(rt))
return false;
if (value(rt) == 0) // condition false at compile time -> architectural no-op
return true;
if (!known(rs))
return false;
return emit_known(rd, value(rs));
default:
return false;
}
}
static bool recTryTranslateCachedOp(u32 op, RecGprCacheState& cache)
{
const u32 opcode = op >> 26;
@@ -1576,6 +1764,12 @@ static bool recTryTranslateCachedOp(u32 op, RecGprCacheState& cache)
static bool recTranslateOpOptimized(u32 op, RecGprConstState& const_state, RecGprCacheState& cache)
{
// Fold ops with fully const-known sources first: emits one immediate Mov into the
// destination's cache register and updates the const state itself, so neither the
// generic cached emitter nor the apply-effects pass runs for them.
if (recTryTranslateCachedConstOp(op, const_state, cache))
return true;
if (recTryTranslateCachedOp(op, cache))
{
if (!recConstApplyCachedEffects(op, const_state))