diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index 6508feb26..6da900e31 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -1063,19 +1063,35 @@ private: return edge && edge->positive ? edge->condition : Value {}; } + bool GuardedOnEntry(const Block* block, const auto& stops, const auto& accepts) const { + std::vector pending {block}; + for (size_t i = 0; i < pending.size(); ++i) { + const auto* current = pending[i]; + if (stops(current) || current->ImmPredecessors().empty()) return false; + for (const auto* previous: current->ImmPredecessors()) { + const auto edge = ConditionalEdge(previous, current); + if (edge && accepts(*edge)) continue; + if (std::ranges::find(pending, previous) == pending.end()) + pending.push_back(previous); + } + } + return true; + } + bool HasActiveLane(Value mask, const Block* use) const { mask = mask.Resolve(); const auto incoming_is_nonempty = [&](const Block* from, const Block* to, Value incoming, const Block* header) { - for (size_t depth = 0; depth < m_program.blocks.size(); ++depth) { - const auto edge = ConditionalEdge(from, to); - if (edge && edge->positive && Implies(edge->condition, incoming)) return true; - if (from == header || from->ImmSuccessors().size() != 1u || - from->ImmPredecessors().size() != 1u) return false; - to = from; - from = from->ImmPredecessors()[0]; - } - return false; + const auto accepts = [&](const EdgePredicate& edge) { + return edge.positive && Implies(edge.condition, incoming); + }; + const auto edge = ConditionalEdge(from, to); + if (edge && accepts(*edge)) return true; + const auto* definition = incoming.Resolve().TryInstruction(); + return GuardedOnEntry(from, [&](const Block* block) { + // A witness before this definition may belong to an earlier loop iteration. + return block == header || (definition != nullptr && block == definition->Parent()); + }, accepts); }; const auto* phi = mask.TryInstruction(); if (phi != nullptr && phi->GetOpcode() == ValueOpcode::Phi && @@ -1401,32 +1417,19 @@ private: } if (increment_block == nullptr) return {}; - const auto guarded_on_entry = [&](const Block* block, const auto& accepts) { - std::vector pending {block}; - for (size_t i = 0; i < pending.size(); ++i) { - const auto* current = pending[i]; - if (current == phi->Parent() || current->ImmPredecessors().empty()) return false; - for (const auto* previous: current->ImmPredecessors()) { - const auto edge = ConditionalEdge(previous, current); - if (edge && accepts(*edge)) continue; - if (std::ranges::find(pending, previous) == pending.end()) - pending.push_back(previous); - } - } - return true; - }; + const auto stops = [&](const Block* block) { return block == phi->Parent(); }; for (const auto& use_of_key: phi->Uses()) { const auto* compare = use_of_key.user; if ((compare->GetOpcode() != ValueOpcode::SLessThan32 && compare->GetOpcode() != ValueOpcode::ULessThan32) || use_of_key.operand != 0u || !ValidateRuntimeValue(m_program, compare->Arg(1), RuntimeValueType::Integer)) continue; - if (!guarded_on_entry(use, [&](const EdgePredicate& edge) { + if (!GuardedOnEntry(use, stops, [&](const EdgePredicate& edge) { return edge.positive && Implies(edge.condition, Value(use_of_key.user)); })) continue; LoopBoundProof proof(m_program, *phi, *compare); // The image bound and the increment guard are separate obligations: a // skipped image alone does not prevent signed induction wraparound. - if (guarded_on_entry(increment_block, [&](const EdgePredicate& edge) { + if (GuardedOnEntry(increment_block, stops, [&](const EdgePredicate& edge) { return proof.Excludes(edge.condition, edge.positive); })) return compare; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index fe4e04dcf..36f0eb3f5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -35614,13 +35614,28 @@ void CheckImageSamplerSpecialization() { user_data[48] = 0x3000u; std::vector code{EncodeVopc(0xc5, InlineU32(0), 0), EncodeSop1(0x24, 24, 106), EncodeSopp(0x08, 0), - EncodeMubuf0(0x0c, 0, true, false), EncodeMubuf1(15, 1, 0), - EncodeVop2(0x16, 15, InlineU32(4), 15), - EncodeVop1(0x02, 20, Vgpr(15)), EncodeVopc(0xd2, 20, 15), - EncodeSop2(0x1e, 20, 20, InlineU32(5)), - EncodeSmem0(0x0b, 12), EncodeSmem1(0, 20), - EncodeMimg0(0x24, 1), EncodeMimg1(4, 0, 3, 2)}; + EncodeSop1(0x04, 26, 126), EncodeVop1(0x01, 16, InlineU32(1))}; + // An earlier descriptor waterfall separates the EXECZ witness from this material load. + const auto previous_loop = code.size(); + code.insert(code.end(), {EncodeSop1(0x04, 28, 126), + EncodeVop1(0x02, 22, Vgpr(0)), EncodeVopc(0xd2, 22, 0), + EncodeVop1(0x01, 16, InlineU32(0)), + EncodeSop1(0x04, 126, 28), EncodeVopc(0xd5, InlineU32(0), 16)}); + code.push_back(EncodeSopp(0x09, static_cast(previous_loop - code.size() - 1u))); + code.insert(code.end(), {EncodeSop1(0x04, 126, 26), + EncodeMubuf0(0x0c, 0, true, false), EncodeMubuf1(15, 1, 0), + EncodeVop2(0x16, 15, InlineU32(4), 15), + EncodeVop1(0x01, 16, InlineU32(1))}); + const auto image_loop = code.size(); + code.insert(code.end(), {EncodeSop1(0x04, 28, 126), + EncodeVop1(0x02, 20, Vgpr(15)), EncodeVopc(0xd2, 20, 15), + EncodeSop2(0x1e, 20, 20, InlineU32(5)), + EncodeSmem0(0x0b, 12), EncodeSmem1(0, 20), + EncodeMimg0(0x24, 1), EncodeMimg1(4, 0, 3, 2)}); AppendBufferStoreDword(&code, 4, 0); + code.insert(code.end(), {EncodeVop1(0x01, 16, InlineU32(0)), + EncodeSop1(0x04, 126, 28), EncodeVopc(0xd5, InlineU32(0), 16)}); + code.push_back(EncodeSopp(0x09, static_cast(image_loop - code.size() - 1u))); code[2] = EncodeSopp(0x08, static_cast(code.size() - 3u)); AppendEnd(&code); ShaderComputeInputInfo compute{}; @@ -35675,6 +35690,12 @@ void CheckImageSamplerSpecialization() { Require(name, "dirty material rejection", !MaterializeResources(plan, dirty, snapshot, specialization), "GPU-selected texture materialization bypassed strict read provenance"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + ExpectFatal("MaterialImageWithoutEntryWitness", [&] { + code[2] = EncodeSopp(0x00, 0); // S_NOP removes the original nonempty-EXEC witness. + (void)ShaderRecompiler::TranslateProgram(code, options); + }); +#endif } std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId");