shader: preserve folded native constants for descriptor provenance

This commit is contained in:
nmzik
2026-10-15 23:17:01 +02:00
parent e4019af522
commit c8caa27936
11 changed files with 111 additions and 43 deletions
@@ -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);
@@ -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"
@@ -319,7 +319,7 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
case Decoder::OperandKind::Sgpr: {
const auto reg = static_cast<IR::ScalarReg>(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::ScalarReg>(IR::RegIndex(reg) - 1u),
@@ -685,13 +685,13 @@ std::array<IR::U32, 2> 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::ScalarReg>(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)));
}
@@ -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<const Inst*, size_t> ids;
size_t next_id = 1;
+1 -2
View File
@@ -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<ScalarWrite> 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,
@@ -1,11 +1,21 @@
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <algorithm>
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) {
@@ -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
@@ -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;
}
+2 -2
View File
@@ -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<uint32_t, 1> user_data{0x4000u};
+3 -4
View File
@@ -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),
+68 -14
View File
@@ -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<uint32_t> 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<uint32_t, 4> sampler{0u, 0u, 0x09500000u, 0u};
const auto sampler = inline_constants ? std::array<uint32_t, 4>{0x92u, 0x00fff000u, 0x05000000u, 0u}
: std::array<uint32_t, 4>{0u, 0u, 0x09500000u, 0u};
const std::array<uint32_t, 4> clobbered{5u, 6u, 7u, 8u};
const auto clobber_address = reinterpret_cast<uint64_t>(clobbered.data());
user_data[20] = static_cast<uint32_t>(clobber_address);
user_data[21] = static_cast<uint32_t>(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<uint32_t, 4>{};
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();