From e9bcc77e0ba8c91a48ff667a2aaa42b785f33979 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 18 Oct 2026 20:22:54 +0200 Subject: [PATCH] shader: retain resource guards for 64-bit scalar comparisons --- .../shader/recompiler/ir/passes/SrtWalker.cpp | 7 +++ tests/ResourceTrackingTests.cpp | 60 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index da24f04a5..05e7c3b10 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -159,9 +159,11 @@ bool IsRuntimeUniformOp(ValueOpcode op) { case ValueOpcode::ULessThan32: case ValueOpcode::ULessThanEqual32: case ValueOpcode::IEqual32: + case ValueOpcode::IEqual64: case ValueOpcode::UGreaterThan32: case ValueOpcode::SGreaterThanEqual32: case ValueOpcode::INotEqual32: + case ValueOpcode::INotEqual64: case ValueOpcode::LogicalOr: case ValueOpcode::LogicalAnd: case ValueOpcode::LogicalXor: @@ -1053,6 +1055,11 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) { return true; } return false; + case ValueOpcode::IEqual64: + case ValueOpcode::INotEqual64: + if (!binary()) return false; + result = inst.GetOpcode() == ValueOpcode::IEqual64 ? a == b : a != b; + return true; case ValueOpcode::ULessThan32: // No U32 is below zero, including an unknown loop-carried value. if (!Arg(inst, 1, b)) return false; diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 61ea2aab5..034022ddc 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -5408,6 +5408,65 @@ void TestSrtFlatteningAndRuntimeMemoization() { "flattened typed SRT reads were not collected"); } +void TestScalarDataReadControlFlow() { + namespace CFG = Libs::Graphics::ShaderRecompiler::CFG; + for (const auto comparison : {ValueOpcode::Void, ValueOpcode::IEqual64, + ValueOpcode::INotEqual64}) { + Fixture fixture; + const auto low = fixture.UserData(0); + const auto high = fixture.UserData(1); + const auto address = fixture.Address(low, high); + auto* use = fixture.block; + if (comparison != ValueOpcode::Void) { + const auto pointer = fixture.Emit(ValueOpcode::CompositeConstructU64, {low, high}); + auto enabled = fixture.Emit(comparison, {pointer, Value(uint64_t{0})}); + if (comparison == ValueOpcode::IEqual64) + enabled = fixture.Emit(ValueOpcode::LogicalNot, {enabled}); + const auto lane = fixture.Emit(ValueOpcode::GetBuiltin, + {Value(static_cast(StageInputKind::LocalInvocationId)), Value(0u)}); + const auto varying = fixture.Emit(ValueOpcode::INotEqual32, {lane, Value(0u)}); + fixture.block->condition = fixture.Emit(ValueOpcode::LogicalAnd, {varying, enabled}); + use = fixture.AddBlock(); + auto* done = fixture.AddBlock(); + fixture.block->AddBranch(use); + fixture.block->AddBranch(done); + use->AddBranch(done); + fixture.block->terminator = {.kind = CFG::TerminatorKind::ConditionalBranch, + .true_block = use, .false_block = done}; + use->terminator = {.kind = CFG::TerminatorKind::Branch, .true_block = done}; + done->terminator.kind = CFG::TerminatorKind::Return; + } + MemoryInfo scalar; + scalar.kind = ResourceKind::ScalarAddress; + fixture.Emit(ValueOpcode::LoadAddressU32, + {address, Value(0u), Value(0u), Value(true)}, fixture.AddMemory(scalar, 4), use); + fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); + Check(plan.srt_reads.size() == 1 && !fixture.program.info.uses_dma && + bool(plan.srt_code) == (comparison == ValueOpcode::Void), + "scalar data lost flattening or conditional reads entered the unconditional JIT"); + uint32_t reads = 0; + std::array user_data {}; + SrtRuntime runtime {.user_data = user_data, + .read_memory = +[](void* data, uint64_t address, std::span words) { + ++*static_cast(data); + if (address == 0) return false; + words[0] = 42; + return true; + }, .userdata = &reads}; + ResourceSnapshot snapshot; + ResourceSpecialization specialization; + for (const uint64_t pointer : {uint64_t{0}, uint64_t{0x1000}, uint64_t{1} << 32u}) { + if (pointer == 0 && comparison == ValueOpcode::Void) continue; + user_data = {static_cast(pointer), static_cast(pointer >> 32u)}; + reads = 0; + Check(MaterializeResources(plan, runtime, snapshot, specialization) && + reads == (pointer != 0) && snapshot.flattened_srt[0] == (pointer ? 42u : 0u), + "scalar data refresh ignored a mixed EXEC/64-bit null-pointer branch"); + } + } +} + void TestDynamicSrtReadRemainsExplicit() { Fixture fixture; const auto base = @@ -8311,6 +8370,7 @@ int main() { Run("shared append selector bounds", TestSharedAppendSelectorBounds); Run("descending image range", TestDescendingImageRange); Run("SRT runtime", TestSrtFlatteningAndRuntimeMemoization); + Run("scalar data control flow", TestScalarDataReadControlFlow); Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit); Run("writable descriptor phi", TestWritableDescriptorPhi); Run("bounded formatted buffer store", TestBoundedFormattedBufferStore);