diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 959123540..e0102174e 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -651,9 +651,6 @@ TranslateResult TranslateProgram(std::span code, const CompileOp IR::ResolveControlFlowIdentities(ir); IR::RemoveIdentities(ir.blocks); IR::EliminateDeadCode(ir.blocks); - IR::EliminateMaskedValues(ir); - IR::RemoveIdentities(ir.blocks); - IR::EliminateDeadCode(ir.blocks); const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size); if (read_lane_stats.rewritten_reads != 0) { LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options), diff --git a/src/graphics/shader/recompiler/frontend/translate/Control.cpp b/src/graphics/shader/recompiler/frontend/translate/Control.cpp index abd189d7a..3399ebe80 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Control.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Control.cpp @@ -268,20 +268,13 @@ void Translator::S_MOV_B64(const Decoder::Instruction& inst) { } void Translator::S_WQM_B64(const Decoder::Instruction& inst) { - const auto source_mask = ReadMask(inst.src0); const auto mask_valid = ReadMaskValid(inst.src0); const auto result = IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {ReadOperand(inst.src0, IR::Type::U64)})); - // WQM activates whole quads and cannot clear an already active lane. - const auto widened_mask = ir.LogicalOr(source_mask, ThreadBit(ExtractU64(result))); WriteOperand(DestinationOperand(inst), result); - if (inst.dst.kind == Decoder::OperandKind::ExecLo) { - ir.SetExec(widened_mask); - } else if (inst.dst.kind == Decoder::OperandKind::VccLo) { - ir.SetVcc(widened_mask); - } else if (inst.dst.kind == Decoder::OperandKind::Sgpr) { + if (inst.dst.kind == Decoder::OperandKind::Sgpr) { const auto dst = static_cast(inst.dst.reg); - ir.SetThreadBitScalarReg(dst, widened_mask); + ir.SetThreadBitScalarReg(dst, ThreadBit(ExtractU64(result))); ir.SetScalarMaskTag(dst, mask_valid); } ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})}))); diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp index 5647a9fa8..6abbb8095 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace Libs::Graphics::ShaderRecompiler::IR { namespace { @@ -118,95 +118,6 @@ bool FoldSelect(Inst& inst) { return false; } -bool IsSelect(ValueOpcode opcode) { - return opcode == ValueOpcode::SelectU1 || opcode == ValueOpcode::SelectU32 || - opcode == ValueOpcode::SelectF32; -} - -bool Implies(Value condition, Value required, uint32_t depth = 0) { - condition = condition.Resolve(); - required = required.Resolve(); - if (condition == required) return true; - if (depth >= 64u) return false; - const auto* target = required.TryInstruction(); - if (target != nullptr && target->GetOpcode() == ValueOpcode::LogicalOr && - (Implies(condition, target->Arg(0), depth + 1u) || - Implies(condition, target->Arg(1), depth + 1u))) { - return true; - } - const auto* inst = condition.TryInstruction(); - return inst != nullptr && inst->GetOpcode() == ValueOpcode::LogicalAnd && - (Implies(inst->Arg(0), required, depth + 1u) || - Implies(inst->Arg(1), required, depth + 1u)); -} - -bool IsAcyclic(const BlockList& blocks) { - std::unordered_map completed; - const auto visit = [&](auto&& self, const Block* block) -> bool { - const auto [entry, added] = completed.emplace(block, false); - if (!added) return entry->second; - for (const auto* successor: block->ImmSuccessors()) { - if (!self(self, successor)) return false; - } - completed[block] = true; - return true; - }; - for (const auto* block: blocks) { - if (!visit(visit, block)) return false; - } - return true; -} - -class MaskedUses { -public: - MaskedUses(const Program& program, Value condition) - : program(program), condition(condition.Resolve()) {} - - bool OnlyWhenActive(const Inst& inst) { - const auto [entry, added] = completed.emplace(&inst, false); - if (!added) return entry->second; - for (const auto& use: inst.Uses()) { - const auto& user = *use.user; - const auto op = user.GetOpcode(); - if (IsSelect(op)) { - if (use.operand == 0) return false; - if (use.operand == 1 && Implies(user.Arg(0), condition)) continue; - } else if (op == ValueOpcode::SetAttribute) { - if (use.operand == 0 && Implies(user.Arg(1), condition)) continue; - return false; - } else if (!IsLaneLocal(user, use.operand)) { - return false; - } - if (!OnlyWhenActive(user)) return false; - } - completed[&inst] = true; - return true; - } - -private: - bool IsLaneLocal(const Inst& inst, size_t operand) const { - const auto op = inst.GetOpcode(); - // These opcode-table groups contain only construction, conversion and arithmetic. - if ((op >= ValueOpcode::BitCastU16F16 && op <= ValueOpcode::BitFieldSExtract) || - (op >= ValueOpcode::WqmU64 && op <= ValueOpcode::FPFract32) || - op == ValueOpcode::Identity || op == ValueOpcode::Phi || - op == ValueOpcode::MakeImageAddress) { - return true; - } - if (op != ValueOpcode::ImageSampleRaw || operand != 2) return false; - // Explicit LOD or gradients keep coordinates local; implicit derivatives read neighbors. - const auto index = inst.Flags().index; - return index < program.memory_info.size() && - (program.memory_info[index].image_sample_flags & - (Decoder::ImageSampleFlagLod | Decoder::ImageSampleFlagLevelZero | - Decoder::ImageSampleFlagDerivative)) != 0; - } - - const Program& program; - Value condition; - std::unordered_map completed; -}; - bool FoldPhi(Inst& inst) { Value same; for (size_t index = 0; index < inst.NumArgs(); index++) { @@ -283,7 +194,8 @@ bool FoldCompositeExtract(Inst& inst, ValueOpcode construct, size_t components) return false; } -void FoldInstruction(Block& block, Block::iterator instruction) { +void FoldInstruction(Block& block, Block::iterator instruction, + std::unordered_set& lowered_ancillary) { auto& inst = *instruction; switch (inst.GetOpcode()) { case ValueOpcode::Phi: FoldPhi(inst); return; @@ -315,7 +227,7 @@ void FoldInstruction(Block& block, Block::iterator instruction) { const auto value = Arg(inst, 0); const auto offset = Arg(inst, 1); const auto count = Arg(inst, 2); - const auto* source = value.TryInstruction(); + auto* source = value.TryInstruction(); if (source != nullptr && source->GetOpcode() == ValueOpcode::GetBuiltin && source->Arg(0) == Value(static_cast(StageInputKind::PackedAncillary)) && IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && count.U32() != 0u) { @@ -333,6 +245,7 @@ void FoldInstruction(Block& block, Block::iterator instruction) { {Value(static_cast(field.kind)), Value(0u)}); inst.SetArg(0, Value(&*input)); inst.SetArg(1, Value(offset.U32() - field.start)); + lowered_ancillary.insert(source); return; } } @@ -697,23 +610,25 @@ void FoldInstruction(Block& block, Block::iterator instruction) { } // namespace void ConstantPropagationPass(const BlockList& blocks) { + std::unordered_set lowered_ancillary; for (auto* block: blocks) { for (auto inst = block->begin(); inst != block->end(); ++inst) { - FoldInstruction(*block, inst); + FoldInstruction(*block, inst, lowered_ancillary); } } -} - -void EliminateMaskedValues(Program& program) { - // A loop-carried value may have been produced under a previous iteration's predicate. - if (!IsAcyclic(program.blocks)) return; - for (auto block = program.blocks.rbegin(); block != program.blocks.rend(); ++block) { - auto& instructions = (*block)->Instructions(); - for (auto inst = instructions.rbegin(); inst != instructions.rend(); ++inst) { - if (IsSelect(inst->GetOpcode()) && - MaskedUses(program, inst->Arg(0)).OnlyWhenActive(*inst)) { - Replace(*inst, inst->Arg(1)); + // Normalize retained PHI/select values only after every supported field read has + // been lowered; direct raw consumers remain unsupported. + for (auto* source: lowered_ancillary) { + const bool retained_only = std::ranges::all_of(source->Uses(), [](const Use& use) { + const auto& user = *use.user; + if (!user.HasUses() && !user.MayHaveSideEffects()) { + return true; } + return user.GetOpcode() == ValueOpcode::Phi || + (user.GetOpcode() == ValueOpcode::SelectU32 && use.operand == 2u); + }); + if (retained_only) { + Replace(*source, Value(0u)); } } } diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h index 2491b106f..e158c1ea1 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h @@ -4,9 +4,6 @@ namespace Libs::Graphics::ShaderRecompiler::IR { -struct Program; - void ConstantPropagationPass(const BlockList& blocks); -void EliminateMaskedValues(Program& program); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 9bf0a926b..f4901b0fb 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6007,6 +6007,30 @@ void TestPixelAncillaryLayerInput() { CheckSpirvBinaryValidates(wqm_result.spirv); } + const uint32_t loop[] = { + EncodeSop1(0x04, 8, 126), // Save the original EXEC mask. + EncodeSop1(0x0a, 126, 8), // Widen for helper lanes. + EncodeSop1(0x24, 12, 0), // Narrow EXEC while saving the widened mask. + EncodeSop1(0x04, 10, 126), // Save the final export mask. + EncodeSopp(0x08, 6), // Skip the body when EXEC is zero. + EncodeVop3Word0(0x148, 5), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11), + EncodeSMovB32(2, 128), + EncodeSop2(0x00, 2, 2, 129), + EncodeSopc(0x0a, 2, 131), + EncodeSopp(0x05, 0xfffdu), // Repeat the scalar loop three times. + EncodeSop1(0x04, 126, 12), // Pack under the widened mask. + EncodeVop2(0x2f, 6, 5 + 256, 5), + EncodeSop1(0x04, 126, 10), // Export only lanes that entered the body. + EncodeExp0(0x00, 0x1, true, false, true), EncodeExp1(6, 0, 0, 0), EncodeSopp(0x01), + }; + auto loop_result = RecompileForTest(loop, options); + Check(ProgramHasInput(loop_result.program, StageInputKind::Layer) && + !ProgramHasInput(loop_result.program, StageInputKind::PackedAncillary), + "loop and skipped body retained the symbolic ancillary input after field lowering"); + Check(SpirvContainsOpcode(loop_result.spirv, 246u), + "ancillary regression did not retain its scalar loop"); + CheckSpirvBinaryValidates(loop_result.spirv); + const uint32_t unused[] = {EncodeSopp(0x01)}; auto unused_result = RecompileForTest(unused, options); Check(!ProgramHasInput(unused_result.program, StageInputKind::Layer) && @@ -6031,122 +6055,14 @@ void TestPixelAncillaryLayerInput() { shader[1] = EncodeVop3Word1(5 + 256, 128 + 12, 128 + 4); ExpectFatal([&] { (void)RecompileForTest(shader, options); }, "unsupported live ancillary field was silently replaced"); + shader[1] = EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11); + shader[4] = EncodeExp0(0x00, 0x7); + shader[5] = EncodeExp1(6, 7, 5, 0); + ExpectFatal([&] { (void)RecompileForTest(shader, options); }, + "direct raw ancillary export was silently replaced after field lowering"); #endif } -void TestMaskedValueDemand() { - using namespace ShaderRecompiler::IR; - enum class Use { Guarded, Conjunction, Wqm, WqmConjunction, WqmOtherGuard, - TrueArm, Unguarded, OtherGuard, Predicate, - ReadLane, ExplicitLod, ExplicitGradient, LevelZero, ImplicitLod, - Mixed, Phi, Loop }; - for (const auto use : {Use::Guarded, Use::Conjunction, Use::Wqm, - Use::WqmConjunction, Use::WqmOtherGuard, - Use::TrueArm, Use::Unguarded, - Use::OtherGuard, Use::Predicate, Use::ReadLane, - Use::ExplicitLod, Use::ExplicitGradient, Use::LevelZero, - Use::ImplicitLod, Use::Mixed, Use::Phi, Use::Loop}) { - Program program; - for (uint32_t i = 0; i < 4; ++i) { - program.block_storage.push_back(std::make_unique()); - program.blocks.push_back(program.block_storage.back().get()); - } - auto &entry = *program.blocks[0]; - auto &join = *program.blocks[3]; - entry.AddBranch(program.blocks[1]); - entry.AddBranch(program.blocks[2]); - program.blocks[1]->AddBranch(&join); - program.blocks[2]->AddBranch(&join); - if (use == Use::Loop) join.AddBranch(&entry); - const auto input = Value(&entry.AppendNewInst(ValueOpcode::GetUserData, - {Value(static_cast(0))})); - const auto predicate = Value(&entry.AppendNewInst(ValueOpcode::INotEqual32, - {input, Value(0u)})); - const auto other = Value(&entry.AppendNewInst(ValueOpcode::IEqual32, - {input, Value(2u)})); - auto mask = predicate; - if (use == Use::Wqm || use == Use::WqmConjunction || use == Use::WqmOtherGuard) { - // WQM includes the original active lanes and additional helper lanes. - const auto helpers = Value(&entry.AppendNewInst(ValueOpcode::GetUserData, - {Value(static_cast(1))})); - const auto helper = Value(&entry.AppendNewInst(ValueOpcode::INotEqual32, - {helpers, Value(0u)})); - mask = Value(&entry.AppendNewInst(ValueOpcode::LogicalOr, {predicate, helper})); - } - auto &masked = entry.AppendNewInst(ValueOpcode::SelectU32, - {mask, Value(10u), Value(20u)}); - Value value(&masked); - if (use == Use::Phi || use == Use::Loop) { - auto &phi = join.AppendNewInst(ValueOpcode::Phi, {}, static_cast(Type::U32)); - phi.AddPhiOperand(program.blocks[1], value); - phi.AddPhiOperand(program.blocks[2], Value(0u)); - value = Value(&phi); - } - value = Value(&join.AppendNewInst(ValueOpcode::BitCastF32U32, {value})); - value = Value(&join.AppendNewInst(ValueOpcode::FPMul32, {value, Value::F32(2.f)})); - value = Value(&join.AppendNewInst(ValueOpcode::BitCastU32F32, {value})); - if (use == Use::ReadLane) { - value = Value(&join.AppendNewInst(ValueOpcode::ReadLane, {value, Value(0u)})); - } - if (use == Use::ExplicitLod || use == Use::ExplicitGradient || - use == Use::LevelZero || use == Use::ImplicitLod) { - const auto zero = Value(0u); - auto &address = join.AppendNewInst(ValueOpcode::MakeImageAddress, - {value, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero}); - auto &image = entry.AppendNewInst(ValueOpcode::GetImageResource, - {zero, zero, zero, zero, zero, zero, zero, zero}); - auto &sampler = entry.AppendNewInst(ValueOpcode::GetSamplerResource, - {zero, zero, zero, zero}); - auto &memory = program.memory_info.emplace_back(); - switch (use) { - case Use::ExplicitLod: - memory.image_sample_flags = - ShaderRecompiler::Decoder::ImageSampleFlagLod; - break; - case Use::ExplicitGradient: - memory.image_sample_flags = - ShaderRecompiler::Decoder::ImageSampleFlagDerivative; - break; - case Use::LevelZero: - memory.image_sample_flags = - ShaderRecompiler::Decoder::ImageSampleFlagLevelZero; - break; - default: - break; - } - value = Value(&join.AppendNewInst(ValueOpcode::ImageSampleRaw, - {Value(&image), Value(&sampler), Value(&address)})); - value = Value(&join.AppendNewInst(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)})); - } - auto guard = predicate; - if (use == Use::Conjunction || use == Use::WqmConjunction) { - guard = Value(&join.AppendNewInst(ValueOpcode::LogicalAnd, {predicate, other})); - } else if (use == Use::WqmOtherGuard) { - guard = Value(&join.AppendNewInst(ValueOpcode::IEqual32, {input, Value(0u)})); - } else if (use == Use::OtherGuard) { - guard = other; - } else if (use == Use::Unguarded) { - guard = Value(true); - } else if (use == Use::Predicate) { - guard = Value(&join.AppendNewInst(ValueOpcode::INotEqual32, {value, Value(0u)})); - } else if (use == Use::TrueArm) { - value = Value(&join.AppendNewInst(ValueOpcode::SelectU32, {predicate, value, Value(0u)})); - guard = Value(true); - } - value = Value(&join.AppendNewInst(ValueOpcode::CompositeConstructU32x4, - {value, Value(0u), Value(0u), Value(0u)})); - join.AppendNewInst(ValueOpcode::SetAttribute, {value, guard}); - if (use == Use::Mixed) join.AppendNewInst(ValueOpcode::SetAttribute, {value, Value(true)}); - EliminateMaskedValues(program); - const bool removed = Value(&masked).Resolve() == Value(10u); - const bool safe = use == Use::Guarded || use == Use::Conjunction || - use == Use::Wqm || use == Use::WqmConjunction || use == Use::TrueArm || - use == Use::ExplicitLod || use == Use::ExplicitGradient || - use == Use::LevelZero || use == Use::Phi; - Check(removed == safe, "masked value demand crossed an unguarded or nonlocal use"); - } -} - void TestGraphicsCreateInterpolantMapping() { ShaderRegister regs[32] = {}; @@ -12787,7 +12703,6 @@ int main() { TestNewShaderRecompilerRejectsDppOn64BitCompares(); TestPsInputCountRegisterDecode(); TestPixelAncillaryLayerInput(); - TestMaskedValueDemand(); TestNewShaderRecompilerUnbasedFlatUsesBda(); TestNewShaderRecompilerFlatUserPointerUsesDma(); TestNewShaderRecompilerFlatAddressDomainsUseDma();