From 7524e8f79ed2eaf7da0f370225626c99efb98613 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 25 Oct 2026 04:31:34 +0100 Subject: [PATCH] shader: count one SGPR for lane reads and wave32 lane masks in call targets Co-authored-by: Ali Almohaya Co-authored-by: Senaxx Co-authored-by: Mac --- .../shader/recompiler/ShaderRecompiler.cpp | 11 +++++++---- .../shader/recompiler/ShaderRecompiler.h | 1 + .../shader/recompiler/frontend/cfg/ShaderCFG.cpp | 16 ++++++++++------ .../shader/recompiler/frontend/cfg/ShaderCFG.h | 5 +++-- tests/shaderCfgTests.cpp | 12 ++++++++++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index dc9a973fd..855bc3805 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -467,6 +467,7 @@ Decoder::Program DecodeFusedProgram(std::span front, std::span Value { std::vector crossed_calls; - const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code, crossed_calls); + const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code, + source.wave_size, crossed_calls); for (const auto crossed: crossed_calls) { std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code); } @@ -702,10 +704,11 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR EXIT_IF(function_pc > UINT32_MAX - function_size); const auto function_offset = static_cast(linked.code.size()); for (auto inst: function) { - EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) || - CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u)); + EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg, source.wave_size) || + CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u, source.wave_size)); for (uint32_t code = 0; code < site.preserved_registers.size(); ++code) { - if (site.preserved_registers[code] && CFG::MayWriteScalarRegister(inst, code)) + if (site.preserved_registers[code] && + CFG::MayWriteScalarRegister(inst, code, source.wave_size)) EXIT("shader call at pc 0x%08x clobbers scalar target dependency s%u", call.pc, code); } linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count); diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.h b/src/graphics/shader/recompiler/ShaderRecompiler.h index 1dd5a409a..dc4becfcd 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.h +++ b/src/graphics/shader/recompiler/ShaderRecompiler.h @@ -62,6 +62,7 @@ struct ShaderSource { IR::ResourcePlan call_targets; std::vector calls; uint32_t unsupported_call_pc = UINT32_MAX; + uint32_t wave_size = 64; std::optional linked; uint64_t revision = 0; std::vector> reads; diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp index 3e609c1fa..a8f1a701a 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp @@ -1710,21 +1710,25 @@ Graph Structurize(const Graph& graph) { return GotoStructurizer(graph).Run(); } -bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) { - // Vector comparisons and carry results can write a scalar pair in wave64. +bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size) { + // A vector lane mask (comparison or carry) spans wave_size / 32 SGPRs; a lane read writes one. if (inst.family == Decoder::Family::VOP1 || inst.family == Decoder::Family::VOP2 || inst.family == Decoder::Family::VOPC || inst.family == Decoder::Family::VOP3 || inst.family == Decoder::Family::VOP3P) { + const bool lane_read = + inst.opcode == Opcode::V_READLANE_B32 || inst.opcode == Opcode::V_READFIRSTLANE_B32; + const uint32_t size = lane_read ? 1u : wave_size / 32u; uint32_t destination = 0; for (const auto* operand: {&inst.dst, &inst.dst2}) - if (ScalarOperandCode(*operand, destination) && code >= destination && code <= destination + 1u) + if (ScalarOperandCode(*operand, destination) && code >= destination && code < destination + size) return true; } return InstructionWritesScalarCode(inst, code); } uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph, - uint32_t before, uint32_t code, std::vector& crossed_calls) { + uint32_t before, uint32_t code, uint32_t wave_size, + std::vector& crossed_calls) { EXIT_IF(before >= program.instructions.size()); const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) { return block.inst_begin <= before && before < block.inst_end; @@ -1743,11 +1747,11 @@ uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& grap for (uint32_t i = point.second; i > block->inst_begin;) { const auto& inst = program.instructions[--i]; if (inst.opcode == Opcode::S_SWAPPC_B64) { - EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code)); + EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code, wave_size)); crossed_calls.push_back(i); continue; } - if (MayWriteScalarRegister(inst, code)) { index = i; break; } + if (MayWriteScalarRegister(inst, code, wave_size)) { index = i; break; } } if (index != UINT32_MAX || block->id == graph.entry_block) { EXIT_IF(definition && *definition != index); diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h index 3a63ef7e2..2e636c8d5 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h @@ -113,9 +113,10 @@ struct Graph { }; Graph BuildGraph(const Decoder::Program& program); -bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code); +bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size); uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph, - uint32_t before, uint32_t code, std::vector& crossed_calls); + uint32_t before, uint32_t code, uint32_t wave_size, + std::vector& crossed_calls); // Returns structured control flow or failure diagnostics without changing the native graph. // On failure, failure_block is an original block ID or UINT32_MAX. Graph Structurize(const Graph& graph); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 75844609f..3e143d6f4 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -11024,6 +11024,18 @@ void TestMemoryFedScalarLeafCall() { ExpectFatal([&] { (void)PrepareShaderSource(multiple, options); }, "a native return pair was accepted as a later call target"); #endif + // A lane read and a wave32 lane mask write one SGPR, so neither s13 writer defines s14. + for (const auto& [wave_size, writer] : {std::pair{64u, 0x360u}, std::pair{32u, 0xc2u}}) { + const uint32_t neighbor[] = {EncodeVop3Word0(writer, 13), EncodeVop3Word1(256, 128, 0), + EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)}; + auto neighbor_options = options; + neighbor_options.wave_size = wave_size; + const auto neighbor_source = PrepareShaderSource(neighbor, neighbor_options); + const auto* target = neighbor_source.calls.front().target[0].TryInstruction(); + Check(target != nullptr && target->GetOpcode() == IR::ValueOpcode::GetUserData && + IR::RegIndex(target->Arg(0).ScalarRegister()) == 14u, + "a single-SGPR vector write was taken as the next SGPR's call-target definition"); + } } void TestNewShaderRecompilerSetpcBranch() {