aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 09:25:40 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 09:25:40 -0400
commit9a5a1fbdf68859db978b0082f2bd257e6faa60c4 (patch)
tree6157b1a63e65045edae016b79fc230f3552cbdfe /src
parentc7dffce68ad2678c9998045191991e1a6c8b228a (diff)
dynarmic: skip untouched locations when an IR instruction ends
Compiling a block is most of what makes Mario Kart World stutter in its first minute: the game's thread translates ~20,000 blocks in a few hundred milliseconds when a scene loads, and the frame waits. A CPU profile of those bursts had ~40% of the compile time in two loops over all 96 host locations (16 GPRs, 16 XMMs, 64 spill slots) per IR instruction: - EndOfAllocScope called ReleaseAll on every location, though an instruction touches a few. It now skips the ones ReleaseAll would not change (no references or locks, not scratch, no last use pending, and values either still live or fully cleared). - DefineValueImpl checked, with an assert that stays on in release builds, that the defined value was in no location yet, and afterwards that it was where it was put. Those scans are DEBUG_ASSERTs now. Mario Kart World, first 45 s (boot, world map, title, attract demo): 18.5 -> 12.8 us per compiled block; frames over 60 ms 57 -> 31, over 100 ms in the first 600 presents 23 -> 12; frames render as before.
Diffstat (limited to 'src')
-rw-r--r--src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp14
-rw-r--r--src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h9
2 files changed, 16 insertions, 7 deletions
diff --git a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp
index 5a828839c1..4f3d253441 100644
--- a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp
+++ b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp
@@ -465,22 +465,24 @@ std::optional<HostLoc> RegAlloc::ValueLocation(const IR::Inst* value) const noex
return std::nullopt;
}
+// The checks that scan every location for def_inst are debug-only: per IR instruction they cost as
+// much as the rest of register allocation.
void RegAlloc::DefineValueImpl(BlockOfCode& code, IR::Inst* def_inst, HostLoc host_loc) noexcept {
- ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined");
+ DEBUG_ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined");
LocInfo(host_loc).AddValue(host_loc, def_inst);
- ASSERT(*ValueLocation(def_inst) == host_loc);
+ DEBUG_ASSERT(*ValueLocation(def_inst) == host_loc);
}
void RegAlloc::DefineValueImpl(BlockOfCode& code, IR::Inst* def_inst, const IR::Value& use_inst) noexcept {
- ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined");
+ DEBUG_ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined");
if (use_inst.IsImmediate()) {
const HostLoc location = ScratchImpl(code, gpr_order);
DefineValueImpl(code, def_inst, location);
LoadImmediate(code, use_inst, location);
} else {
- ASSERT(ValueLocation(use_inst.GetInst()) && "use_inst must already be defined");
- const HostLoc location = *ValueLocation(use_inst.GetInst());
- DefineValueImpl(code, def_inst, location);
+ const auto location = ValueLocation(use_inst.GetInst());
+ ASSERT(location && "use_inst must already be defined");
+ DefineValueImpl(code, def_inst, *location);
}
}
diff --git a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h
index 455c6a970a..0cad07796c 100644
--- a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h
+++ b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h
@@ -65,6 +65,11 @@ public:
}
void ReleaseOne() noexcept;
void ReleaseAll() noexcept;
+ /// True when ReleaseAll would change nothing, as for most of the spill slots most of the time.
+ inline bool IsReleased() const noexcept {
+ return current_references == 0 && is_being_used_count == 0 && !is_scratch && !is_set_last_use &&
+ (total_uses != accumulated_uses || (values.empty() && total_uses == 0 && max_bit_width == 0));
+ }
constexpr size_t GetMaxBitWidth() const noexcept { return 1 << max_bit_width; }
void AddValue(HostLoc loc, IR::Inst* inst) noexcept;
/// Checks if the given instruction is in our values set
@@ -201,8 +206,10 @@ public:
void ReleaseStackSpace(BlockOfCode& code, const size_t stack_space) noexcept;
inline void EndOfAllocScope() noexcept {
+ // After every IR instruction: skip the locations it left alone.
for (auto& iter : hostloc_info)
- iter.ReleaseAll();
+ if (!iter.IsReleased())
+ iter.ReleaseAll();
}
inline void AssertNoMoreUses() noexcept {
ASSERT(std::all_of(hostloc_info.begin(), hostloc_info.end(), [](const auto& i) noexcept { return i.IsEmpty(); }));