From d581703ab4d17a326dafb4a15535050e10429b49 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 22 Aug 2026 03:31:59 +0200 Subject: [PATCH] shader: preserve folded control-flow values --- .../shader/recompiler/ShaderRecompiler.cpp | 6 +-- .../shader/recompiler/ir/ValueProgram.cpp | 7 ++++ .../shader/recompiler/ir/ValueProgram.h | 1 + tests/ScalarProvenanceTests.cpp | 42 +++++++++++++++++++ tests/shaderCfgTests.cpp | 6 +-- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index fa781713a..27b2fbbef 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -729,10 +729,7 @@ bool TryRecompile(std::span code, const CompileOptions& options, ir.blocks.clear(); IR::RewriteToSsa(ir.values->blocks); IR::ConstantPropagationPass(ir.values->blocks); - for (auto& info: ir.values->block_info) { - info.condition = info.condition.Resolve(); - info.indirect_target = info.indirect_target.Resolve(); - } + IR::ResolveControlFlowIdentities(*ir.values); IR::RemoveIdentities(ir.values->blocks); IR::EliminateDeadCode(ir.values->blocks); const auto read_lane_stats = IR::EliminateReadLane(*ir.values, ir.wave_size); @@ -740,6 +737,7 @@ bool TryRecompile(std::span code, const CompileOptions& options, LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options), read_lane_stats.rewritten_reads); IR::ConstantPropagationPass(ir.values->blocks); + IR::ResolveControlFlowIdentities(*ir.values); IR::RemoveIdentities(ir.values->blocks); IR::EliminateDeadCode(ir.values->blocks); } diff --git a/src/graphics/shader/recompiler/ir/ValueProgram.cpp b/src/graphics/shader/recompiler/ir/ValueProgram.cpp index 0c0ea2612..7586172fc 100644 --- a/src/graphics/shader/recompiler/ir/ValueProgram.cpp +++ b/src/graphics/shader/recompiler/ir/ValueProgram.cpp @@ -591,6 +591,13 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st return true; } +void ResolveControlFlowIdentities(ValueProgram& program) { + for (auto& info: program.block_info) { + info.condition = info.condition.Resolve(); + info.indirect_target = info.indirect_target.Resolve(); + } +} + std::string ValueProgramToString(const ValueProgram& program) { std::map ids; size_t next_id = 1; diff --git a/src/graphics/shader/recompiler/ir/ValueProgram.h b/src/graphics/shader/recompiler/ir/ValueProgram.h index 0cf580596..6d1f7f866 100644 --- a/src/graphics/shader/recompiler/ir/ValueProgram.h +++ b/src/graphics/shader/recompiler/ir/ValueProgram.h @@ -68,6 +68,7 @@ struct ValueProgram { }; bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::string* error); +void ResolveControlFlowIdentities(ValueProgram& program); bool EquivalentValue(const ValueProgram& program, Value left, Value right); Value ResolveInvariantPhi(const ValueProgram& program, Value value); std::string ValueProgramToString(const ValueProgram& program); diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index 141210f6c..a90678d3d 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -402,6 +402,47 @@ void TestOptimizationPipeline() { "elimination regressed"); } +void TestControlFlowValueSurvivesReadLaneFolding() { + Fixture fixture(3); + auto *entry = fixture.program.values->blocks[0]; + auto *taken = fixture.program.values->blocks[1]; + auto *other = fixture.program.values->blocks[2]; + entry->AddBranch(taken); + entry->AddBranch(other); + + auto &entry_info = fixture.program.values->block_info[0]; + entry_info.terminator.kind = + Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch; + entry_info.terminator.true_block = 1; + entry_info.terminator.false_block = 2; + fixture.program.values->block_info[1].terminator.kind = + Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return; + fixture.program.values->block_info[2].terminator.kind = + Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return; + + const auto undef = fixture.Emit(ValueOpcode::UndefU32); + const auto write = fixture.Emit( + ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)}); + const auto read = + fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)}); + entry_info.condition = + fixture.Emit(ValueOpcode::IEqual32, {read, Value(42u)}); + fixture.Emit(ValueOpcode::Reference, {entry_info.condition}); + + Check(EliminateReadLane(*fixture.program.values, 64).rewritten_reads == 1, + "control-flow fixture did not eliminate its fixed-lane read"); + ConstantPropagationPass(fixture.program.values->blocks); + ResolveControlFlowIdentities(*fixture.program.values); + RemoveIdentities(fixture.program.values->blocks); + EliminateDeadCode(fixture.program.values->blocks); + + std::string error; + Check(entry_info.condition == Value(true) && + ValidateValueProgram(*fixture.program.values, true, &error), + error.empty() ? "folded branch condition did not survive identity removal" + : error.c_str()); +} + void TestUndefinedRuntimeValueFails() { Fixture fixture; const auto undef = fixture.Emit(ValueOpcode::UndefU32); @@ -443,6 +484,7 @@ int main() { TestConstantBufferBounds(); TestReadLaneElimination(); TestOptimizationPipeline(); + TestControlFlowValueSurvivesReadLaneFolding(); TestUndefinedRuntimeValueFails(); std::cout << "TypedValuePlanningTests: all cases passed\n"; return 0; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index af048ab6c..4f2bc174b 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -10498,16 +10498,14 @@ bool LowerTypedPlanning(const uint32_t *code, uint32_t words, ir.blocks.clear(); ShaderRecompiler::IR::RewriteToSsa(ir.values->blocks); ShaderRecompiler::IR::ConstantPropagationPass(ir.values->blocks); - for (auto &info : ir.values->block_info) { - info.condition = info.condition.Resolve(); - info.indirect_target = info.indirect_target.Resolve(); - } + ShaderRecompiler::IR::ResolveControlFlowIdentities(*ir.values); ShaderRecompiler::IR::RemoveIdentities(ir.values->blocks); ShaderRecompiler::IR::EliminateDeadCode(ir.values->blocks); const auto read_lane = ShaderRecompiler::IR::EliminateReadLane(*ir.values, ir.wave_size); if (read_lane.rewritten_reads != 0) { ShaderRecompiler::IR::ConstantPropagationPass(ir.values->blocks); + ShaderRecompiler::IR::ResolveControlFlowIdentities(*ir.values); ShaderRecompiler::IR::RemoveIdentities(ir.values->blocks); ShaderRecompiler::IR::EliminateDeadCode(ir.values->blocks); }