diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 1fc2a6d37..757ba9fcc 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -566,7 +566,7 @@ void PrepareCallTarget(ShaderSource& source, const CompileOptions& options) { } RewriteToSsa(query.blocks); ConstantPropagationPass(query.blocks, query.wave_size); - RemoveIdentities(query.blocks); + RemoveIdentities(query); EliminateDeadCode(query.blocks); for (auto& value: source.call->target) value = value.Instruction()->Arg(0).Resolve(); query.value_storage.clear(); @@ -791,8 +791,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO IR::RewriteToSsa(ir.blocks); const auto simplify = [&ir] { IR::ConstantPropagationPass(ir.blocks, ir.wave_size); - IR::ResolveControlFlowIdentities(ir); - IR::RemoveIdentities(ir.blocks); + IR::RemoveIdentities(ir); IR::EliminateDeadCode(ir.blocks); }; simplify(); @@ -872,7 +871,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o } } ir.value_storage.clear(); - IR::RemoveIdentities(ir.blocks); + IR::RemoveIdentities(ir); IR::EliminateDeadCode(ir.blocks); IR::CollectShaderInfo(ir, options.input_info); diff --git a/src/graphics/shader/recompiler/Tessellation.cpp b/src/graphics/shader/recompiler/Tessellation.cpp index 8f42333fb..bcfc7626c 100644 --- a/src/graphics/shader/recompiler/Tessellation.cpp +++ b/src/graphics/shader/recompiler/Tessellation.cpp @@ -318,7 +318,7 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options } } ConstantPropagationPass(program.blocks, program.wave_size); - RemoveIdentities(program.blocks); + RemoveIdentities(program); EliminateDeadCode(program.blocks); LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n", options.stage == ShaderType::Local ? "LS" diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index d18be59de..ff142684c 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -319,7 +319,7 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) { case Decoder::OperandKind::Sgpr: { const auto reg = static_cast(operand.reg); ir.SetScalarReg(reg, value); - program.scalar_writes.push_back({current_pc, reg}); + program.scalar_writes.push_back({current_pc, reg, value}); ir.SetScalarMaskTag(reg, IR::U1(IR::Value(false))); if (IR::RegIndex(reg) > 0u) { ir.SetScalarMaskTag(static_cast(IR::RegIndex(reg) - 1u), @@ -685,13 +685,13 @@ std::array Translator::WriteMask(const Decoder::Operand& operand, IR IR::U1(IR::Value(false))); } ir.SetScalarReg(reg, mask[0]); - program.scalar_writes.push_back({current_pc, reg}); + program.scalar_writes.push_back({current_pc, reg, mask[0]}); // A wave32 VALU mask destination must not overwrite the neighboring SGPR. if ((write_64 || program.wave_size == 64u) && IR::RegIndex(reg) + 1u < IR::NumScalarRegs) { const auto high = static_cast(IR::RegIndex(reg) + 1u); ir.SetScalarReg(high, mask[1]); - program.scalar_writes.push_back({current_pc, high}); + program.scalar_writes.push_back({current_pc, high, mask[1]}); ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false))); ir.SetScalarMaskTag(high, IR::U1(IR::Value(false))); } diff --git a/src/graphics/shader/recompiler/ir/Program.cpp b/src/graphics/shader/recompiler/ir/Program.cpp index aab0e0e5a..4bc82fc6d 100644 --- a/src/graphics/shader/recompiler/ir/Program.cpp +++ b/src/graphics/shader/recompiler/ir/Program.cpp @@ -679,13 +679,6 @@ void ValidateProgram(const Program& program, bool require_ssa) { } } -void ResolveControlFlowIdentities(Program& program) { - for (auto* block: program.blocks) { - block->condition = block->condition.Resolve(); - block->indirect_target = block->indirect_target.Resolve(); - } -} - std::string ProgramToString(const Program& program) { std::map ids; size_t next_id = 1; diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 9b52a565b..e8283c841 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -701,7 +701,7 @@ struct Program: ResourcePlan { bool dispatcher_fallback = false; CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None; std::string fallback_reason; - struct ScalarWrite { uint32_t pc; ScalarReg reg; }; + struct ScalarWrite { uint32_t pc; ScalarReg reg; Value value; }; std::vector scalar_writes; // Typed memory and export instructions reference shader-local metadata by dense index. // Decoder-only details (such as NSA register numbers) have already become IR operands. @@ -718,7 +718,6 @@ std::string ProgramToString(const Program& program); bool HasShaderMemoryWrites(const Program& program); void ValidateProgram(const Program& program, bool require_ssa); -void ResolveControlFlowIdentities(Program& program); bool EquivalentValue(const ResourcePlan& program, Value left, Value right); bool EquivalentSelectorDomain(const ResourcePlan& program, const DescriptorSource::IndirectDescriptor& left, diff --git a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp index 6bae6cb9e..c6b775a06 100644 --- a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp +++ b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp @@ -1,11 +1,21 @@ #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" +#include "graphics/shader/recompiler/ir/ShaderIR.h" #include namespace Libs::Graphics::ShaderRecompiler::IR { -void RemoveIdentities(const BlockList& blocks) { - for (auto* block: blocks) { +void RemoveIdentities(Program& program) { + // Resolve metadata before deleting its identity nodes; retain only folded native constants. + for (auto& write: program.scalar_writes) { + const auto value = write.value.Resolve(); + write.value = value.IsImmediate() && value.GetType() == Type::U32 ? value : Value {}; + } + for (auto* block: program.blocks) { + block->condition = block->condition.Resolve(); + block->indirect_target = block->indirect_target.Resolve(); + } + for (auto* block: program.blocks) { auto& instructions = block->Instructions(); for (auto inst = instructions.begin(); inst != instructions.end();) { if (inst->GetOpcode() != ValueOpcode::Identity) { diff --git a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.h b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.h index 6d2edafb5..8cad3cf75 100644 --- a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.h +++ b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.h @@ -4,7 +4,9 @@ namespace Libs::Graphics::ShaderRecompiler::IR { -void RemoveIdentities(const BlockList& blocks); +struct Program; + +void RemoveIdentities(Program& program); void EliminateDeadCode(const BlockList& blocks); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index ee9fa8059..c1c8286df 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -604,9 +604,20 @@ private: candidates.push_back(inst); } } - if (candidates.empty()) return value; const auto source_at = [&](uint32_t pc) { Value source; + bool dynamic = false; + for (auto write = std::ranges::lower_bound(m_scalar_writes, pc, {}, &Program::ScalarWrite::pc); + write != m_scalar_writes.end() && write->pc == pc; ++write) { + if (RegIndex(write->reg) != reg) continue; + if (!write->value.IsImmediate() || write->value.GetType() != Type::U32) { + dynamic = true; + continue; + } + if (!source.IsEmpty() && source != write->value) return Value {}; + source = write->value; + } + if (!source.IsEmpty()) return dynamic ? Value {} : source; const auto native = std::ranges::lower_bound(m_decoded.instructions, pc, {}, &Decoder::Instruction::pc); for (const auto* candidate: candidates) { @@ -644,8 +655,9 @@ private: const auto source = source_at(pc); if (source.IsEmpty()) return false; if (!selected.IsEmpty()) { - const auto op = source.TryInstruction()->GetOpcode(); - if ((op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) && + const auto* inst = source.TryInstruction(); + if (inst != nullptr && + (inst->GetOpcode() == ValueOpcode::LoadAddressU32 || inst->GetOpcode() == ValueOpcode::ReadConstBuffer) && selected_pc != pc) return false; if (!EquivalentValue(m_program, selected, source)) return false; } diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 57d110c95..503a406b5 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -5079,7 +5079,7 @@ void TestFmaskLoadSpecialization() { specialization), "FMASK resources did not materialize"); ApplyResourceSpecialization(fixture.program, specialization); - RemoveIdentities(fixture.program.blocks); + RemoveIdentities(fixture.program); EliminateDeadCode(fixture.program.blocks); Check(fixture.program.info.images.size() == 1 && snapshot.images.size() == 1 && snapshot.images[0].dwords[0] == 0x2000u && @@ -7392,7 +7392,7 @@ void TestLoopCycleEnteredThroughRuntimeValue() { {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, 16), loop); ConstantPropagationPass(fixture.program.blocks); - RemoveIdentities(fixture.program.blocks); + RemoveIdentities(fixture.program); fixture.PlanAndTrack(); auto plan = ExtractResourcePlan(fixture.program); const std::array user_data{0x4000u}; diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index 21e9265bb..d1144eeb1 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -718,7 +718,7 @@ void TestCompleteUMinReduction() { Check(reduction->GetOpcode() == ValueOpcode::SubgroupUMinU32 && reduction->Arg(0).Resolve() == seed.Resolve(), "UMin reduction changed the neutral-masked input"); - RemoveIdentities(fixture.program.blocks); + RemoveIdentities(fixture.program); EliminateDeadCode(fixture.program.blocks); uint32_t updates = 0; for (const auto &inst : fixture.BlockAt()) { @@ -745,7 +745,7 @@ void TestOptimizationPipeline() { fixture.Emit(ValueOpcode::IMul32, {Value(6u), Value(7u)}); ConstantPropagationPass(fixture.program.blocks); - RemoveIdentities(fixture.program.blocks); + RemoveIdentities(fixture.program); EliminateDeadCode(fixture.program.blocks); const auto &instructions = fixture.BlockAt().Instructions(); @@ -785,8 +785,7 @@ void TestControlFlowValueSurvivesReadLaneFolding() { Check(EliminateReadLane(fixture.program, 64).rewritten_reads == 1, "control-flow fixture did not eliminate its fixed-lane read"); ConstantPropagationPass(fixture.program.blocks); - ResolveControlFlowIdentities(fixture.program); - RemoveIdentities(fixture.program.blocks); + RemoveIdentities(fixture.program); EliminateDeadCode(fixture.program.blocks); Check(entry_info.condition == Value(true), diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 79d69e921..b7089f6c3 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -9304,7 +9304,7 @@ void TestScalarSelectedVccBufferAddress() { } } -void TestNativeGuardedSamplerSource() { +void TestNativeGuardedSamplerSource(bool inline_constants) { constexpr uint32_t nested = 12, sample = 18, join = 20, end = 22; const uint32_t shader[] = { EncodeSopc(0x07, 21, 128), @@ -9325,8 +9325,29 @@ void TestNativeGuardedSamplerSource() { EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01), }; + std::vector code; + if (inline_constants) { + // Native paths reaching the sample bypass both clobbers, although + // structurization merges their definitions into the sampler's SSA Phis. + code = {EncodeSopk(0x00, 16, 146), EncodeSop2(0x24, 17, 140, 140), + EncodeSMovB32(18, 255), 0x05000000u, EncodeSMovB32(19, 244)}; + } + const auto prefix = code.size(); + code.insert(code.end(), std::begin(shader), std::end(shader)); + if (inline_constants) { + for (const uint32_t clobber : {4u, 12u}) { + code[prefix + clobber] = EncodeSmem0(0x02, 16, 14); + code[prefix + clobber + 1u] = 125u << 25u; + code[prefix + clobber + 2u] = code[prefix + clobber + 3u] = EncodeSopp(0x00); + } + } auto user_data = ImageTestUserData(); - const std::array sampler{0u, 0u, 0x09500000u, 0u}; + const auto sampler = inline_constants ? std::array{0x92u, 0x00fff000u, 0x05000000u, 0u} + : std::array{0u, 0u, 0x09500000u, 0u}; + const std::array clobbered{5u, 6u, 7u, 8u}; + const auto clobber_address = reinterpret_cast(clobbered.data()); + user_data[20] = static_cast(clobber_address); + user_data[21] = static_cast(clobber_address >> 32u); std::ranges::copy(sampler, user_data.begin() + 8); user_data[13] = 1u; for (uint32_t mode = 0; mode != 3; ++mode) { @@ -9334,7 +9355,7 @@ void TestNativeGuardedSamplerSource() { auto options = MakeCompileOptions(ShaderType::Pixel); options.user_data_base = 8; options.user_data = user_data; - const auto result = RecompileForTest(shader, options); + const auto result = RecompileForTest(code, options, ReadHostTestMemory); const auto expected = mode == 2u ? sampler : std::array{}; Check(!result.program.dispatcher_fallback && result.resources.samplers.size() == 1u && result.resources.samplers[0].dword_count == expected.size() && @@ -9344,6 +9365,41 @@ void TestNativeGuardedSamplerSource() { } } +void TestNativeConstantSamplerSelection() { + const uint32_t shader[] = { + EncodeSopk(0x00, 16, 146), EncodeSop2(0x24, 17, 140, 140), + EncodeSMovB32(18, 255), 0x05000000u, EncodeSMovB32(19, 128), + EncodeVop1(0x01, 1, 191), EncodeVopc(0xc4, 256, 1), + EncodeSopp(0x06, 1), EncodeSMovB32(16, 128), + EncodeMimg0(0x27, 0x1), EncodeMimg1(0, 2, 4, 2), + EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(0, 0, 0), + EncodeSopp(0x01), + }; + auto user_data = ImageTestUserData(); + user_data[0] = 0x1000u; + user_data[1] = 0u; + user_data[2] = 4u; + user_data[3] = 0x30005204u; + auto options = MakeCompileOptions(ShaderType::Compute); + auto input = *options.input_info.compute; + input.thread_ids_num = 1u; + input.threads_num[0] = 128u; + options.wave_size = input.wave_size = 64u; + options.input_info.compute = &input; + options.user_data = user_data; + const auto result = RecompileForTest(shader, options); + const auto selected = std::ranges::find_if(result.program.info.samplers, + [](const auto& sampler) { return !sampler.indirect_resources.empty(); }); + Check(selected != result.program.info.samplers.end() && selected->indirect_resources.size() == 2u, + "different native sampler definitions were folded into a single state"); + const auto& choices = selected->indirect_resources; + const auto first = result.resources.samplers[choices[0]].dwords[0]; + const auto second = result.resources.samplers[choices[1]].dwords[0]; + Check((first == 0x92u && second == 0u) || (first == 0u && second == 0x92u), + "native sampler selection lost either reachable constant state"); + CheckSpirvBinaryValidates(result.spirv); +} + void TestBoundedMaterialBufferStores(bool scalar_key) { // Captured batch search and descriptor waterfall, with deformation arithmetic removed. const uint32_t shader[] = { @@ -11367,7 +11423,7 @@ void TestEmbeddedVertexFormatSwizzle() { } } RewriteToSsa(program.blocks); - RemoveIdentities(program.blocks); + RemoveIdentities(program); ConstantPropagationPass(program.blocks); uint32_t exports = 0; for (const auto *block : program.blocks) { @@ -12270,7 +12326,7 @@ void TestTypedEntryStateIsMinimal() { "typed entry did not materialize the active invocation mask once"); IR::RewriteToSsa(values.blocks); - IR::RemoveIdentities(values.blocks); + IR::RemoveIdentities(values); IR::EliminateDeadCode(values.blocks); IR::ValidateProgram(values, true); }; @@ -12734,8 +12790,7 @@ void TestLaneMaskProjection() { Check(raw_consumer.Resolve().TryInstruction()->Arg(0).Resolve() == initial_mask.Resolve() && gated_raw_consumer.Resolve().TryInstruction()->Arg(0).Resolve() == gated_mask.Resolve(), "mask projection changed a raw scalar mask consumer"); - ResolveControlFlowIdentities(program); - RemoveIdentities(program.blocks); + RemoveIdentities(program); ValidateProgram(program, true); } } @@ -12804,9 +12859,8 @@ void TestLateConstantPropagation() { selected.Resolve() == Value(42u) && projected.Resolve() == Value(true) && absorbed.Resolve() == active.Resolve(), "late constants did not reach Phi, nested mask patterns or new logical nodes"); - ResolveControlFlowIdentities(program); + RemoveIdentities(program); Check(exit->condition == Value(true), "late constant lost its control-flow use"); - RemoveIdentities(program.blocks); ValidateProgram(program, true); } } @@ -14091,15 +14145,13 @@ void BuildTypedPlan(const uint32_t *code, uint32_t words, ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options); ShaderRecompiler::IR::RewriteToSsa(ir.blocks); ShaderRecompiler::IR::ConstantPropagationPass(ir.blocks); - ShaderRecompiler::IR::ResolveControlFlowIdentities(ir); - ShaderRecompiler::IR::RemoveIdentities(ir.blocks); + ShaderRecompiler::IR::RemoveIdentities(ir); ShaderRecompiler::IR::EliminateDeadCode(ir.blocks); const auto read_lane = ShaderRecompiler::IR::EliminateReadLane(ir, ir.wave_size); if (read_lane.rewritten_reads != 0) { ShaderRecompiler::IR::ConstantPropagationPass(ir.blocks); - ShaderRecompiler::IR::ResolveControlFlowIdentities(ir); - ShaderRecompiler::IR::RemoveIdentities(ir.blocks); + ShaderRecompiler::IR::RemoveIdentities(ir); ShaderRecompiler::IR::EliminateDeadCode(ir.blocks); } ShaderRecompiler::IR::TrackResources(ir, decoded, cfg); @@ -16264,7 +16316,9 @@ int main() { TestNestedSelectionPreservesDescriptorSources(); TestNativeScalarReadDescriptorPlanning(); TestScalarSelectedVccBufferAddress(); - TestNativeGuardedSamplerSource(); + TestNativeGuardedSamplerSource(false); + TestNativeGuardedSamplerSource(true); + TestNativeConstantSamplerSelection(); for (const bool scalar_key : {false, true}) TestBoundedMaterialBufferStores(scalar_key); TestNativeDescriptorProvenanceKeepsGpuSelection(); TestCfgSiblingSharedExit();