From e1bcc298a0e4bc217e44ef9af8f9b96e1bdcdd2a Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 19 Aug 2026 03:52:12 +0200 Subject: [PATCH] refactor(shader): remove register-state compatibility paths --- .../recompiler/backend/spirv/SpirvEmitter.cpp | 53 ---- .../backend/spirv/spirvEmitterAnalysis.cpp | 21 -- .../backend/spirv/spirvEmitterControlFlow.cpp | 52 ---- .../backend/spirv/spirvEmitterFlow.cpp | 78 +----- .../backend/spirv/spirvEmitterInternal.h | 24 -- .../spirv/spirvEmitterMemoryHelpers.cpp | 22 ++ .../backend/spirv/spirvEmitterModule.cpp | 12 - .../backend/spirv/spirvEmitterProgram.cpp | 12 +- .../frontend/translate/Translate.cpp | 56 ++-- src/graphics/shader/recompiler/ir/Reg.h | 5 +- .../shader/recompiler/ir/ValueProgram.cpp | 20 +- .../recompiler/ir/opcodes/ValueOpcodes.cpp | 2 - .../recompiler/ir/opcodes/ValueOpcodes.inc | 2 - tests/shaderCfgTests.cpp | 252 ++++++++++-------- 14 files changed, 222 insertions(+), 389 deletions(-) delete mode 100644 src/graphics/shader/recompiler/backend/spirv/spirvEmitterControlFlow.cpp diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp index d3d9816c3..f56e584fd 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp @@ -232,51 +232,6 @@ static void ReportReserveExceeded(const IR::Program& program, const char* vector InitialEmitterVectorReserve); } -void CollectValueRegisterMirrors(const IR::ValueProgram& program, - std::vector& registers) { - for (const auto* block: program.blocks) { - for (const auto& inst: *block) { - switch (inst.GetOpcode()) { - case IR::ValueOpcode::SetScalarRegister: - case IR::ValueOpcode::SetThreadBitScalarRegister: - if (block != program.blocks.front()) { - Emitter::CollectRegister( - registers, - {IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())}); - } - break; - case IR::ValueOpcode::SetVectorRegister: - Emitter::CollectRegister( - registers, - {IR::RegisterFile::Vector, IR::RegIndex(inst.Arg(0).VectorRegister())}); - break; - case IR::ValueOpcode::SetM0: - if (block != program.blocks.front()) { - Emitter::CollectRegister(registers, {IR::RegisterFile::M0, 0}); - } - break; - case IR::ValueOpcode::SetExecLo: - case IR::ValueOpcode::SetExecHi: - if (block != program.blocks.front()) { - Emitter::CollectRegister( - registers, {IR::RegisterFile::Exec, - inst.GetOpcode() == IR::ValueOpcode::SetExecLo ? 0u : 1u}); - } - break; - case IR::ValueOpcode::SetVccLo: - case IR::ValueOpcode::SetVccHi: - if (block != program.blocks.front()) { - Emitter::CollectRegister( - registers, {IR::RegisterFile::Vcc, - inst.GetOpcode() == IR::ValueOpcode::SetVccLo ? 0u : 1u}); - } - break; - default: break; - } - } - } -} - IR::SpirvRequirements GetProgramRequirements(const IR::Program& program) { if (program.spirv_requirements.has_value()) { return *program.spirv_requirements; @@ -418,14 +373,9 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc state.stage = program.stage; state.wave_size = program.wave_size; state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation; - state.exact_subgroup_operations = - !state.per_invocation_masks && requirements.requires_exact_subgroup; - state.registers.reserve(InitialEmitterVectorReserve); state.inputs.reserve(InitialEmitterVectorReserve); state.outputs.reserve(InitialEmitterVectorReserve); state.interface_variables.reserve(InitialEmitterVectorReserve); - state.reachable_blocks.reserve(InitialEmitterVectorReserve); - CollectValueRegisterMirrors(value_program, state.registers); CopyProgramInputsAndOutputs(state, program); state.needs_subgroup_ballot = requirements.subgroup_ballot; state.needs_subgroup_shuffle = requirements.subgroup_shuffle; @@ -438,16 +388,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc AllocateOutputVariables(state); AllocateDescriptorVariables(state); EmitHeaderAndTypes(state); - AllocateRegisterVariables(state); if (!EmitValueProgram(state, value_program, error)) { return false; } - ReportReserveExceeded(program, "registers", state.registers.size()); ReportReserveExceeded(program, "inputs", state.inputs.size()); ReportReserveExceeded(program, "outputs", state.outputs.size()); ReportReserveExceeded(program, "interface_variables", state.interface_variables.size()); - ReportReserveExceeded(program, "reachable_blocks", state.reachable_blocks.size()); auto binary = state.builder.Build(); if (binary.empty()) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index c02d69249..cced2c455 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -38,18 +38,6 @@ void SetError(std::string* error, const char* message) { } } -void CollectRegister(std::vector& registers, IR::Register reg) { - if (std::any_of(registers.begin(), registers.end(), - [reg](const RegisterBinding& binding) { return binding.reg == reg; })) { - return; - } - registers.push_back({reg, 0}); -} - -bool IsInactiveWave32ExecHigh(const EmitterState& state, IR::Register reg) { - return state.wave_size == 32u && reg.file == IR::RegisterFile::Exec && reg.index == 1u; -} - bool HasOutput(const std::vector& outputs, IR::StageOutputKind kind, uint32_t index) { return std::any_of(outputs.begin(), outputs.end(), [kind, index](const OutputBinding& binding) { @@ -88,15 +76,6 @@ uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo return 0; } -uint32_t PointerForRegister(const EmitterState& state, IR::Register reg) { - for (const auto& binding: state.registers) { - if (binding.reg == reg) { - return binding.pointer_id; - } - } - return 0; -} - uint32_t ConstantU32(EmitterState& state, uint32_t value); [[noreturn]] void ExitDescriptorBindingFailure(const EmitterState& state, IR::DescriptorBindingKind kind, uint32_t resource, diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterControlFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterControlFlow.cpp deleted file mode 100644 index 6050fed09..000000000 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterControlFlow.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h" - -#include - -namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { - -bool UserDataDwordIndex(const EmitterState& state, IR::Register reg, uint32_t& dword_index) { - if (reg.file != IR::RegisterFile::Scalar) { - return false; - } - const auto& registers = state.program.bindings.user_data_registers; - const auto found = std::lower_bound(registers.begin(), registers.end(), reg.index); - if (found == registers.end() || *found != reg.index) { - return false; - } - dword_index = static_cast(found - registers.begin()); - return true; -} - -uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) { - if (state.push_constant_variable != 0) { - const auto pointer = state.builder.AllocateId(); - const auto value = state.builder.AllocateId(); - state.builder.AddFunction({OpAccessChain, state.ptr_push_constant_uint, pointer, - state.push_constant_variable, ConstantU32(state, 0), - ConstantU32(state, dword_index)}); - state.builder.AddFunction({OpLoad, state.uint_type, value, pointer}); - return value; - } - if (state.vsharp_storage_variable != 0) { - const auto pointer = state.builder.AllocateId(); - const auto value = state.builder.AllocateId(); - state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer, - state.vsharp_storage_variable, ConstantU32(state, 0), - ConstantU32(state, dword_index)}); - state.builder.AddFunction({OpLoad, state.uint_type, value, pointer}); - return value; - } - return ConstantU32(state, 0); -} - -uint32_t InitialRegisterValue(const EmitterState& state, IR::Register reg) { - if (reg.file != IR::RegisterFile::Exec) { - return 0; - } - if (state.per_invocation_masks) { - return reg.index == 0 ? 1u : 0u; - } - return reg.index == 0 || state.wave_size == 64u ? 0xffffffffu : 0u; -} - -} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 380be7616..60e6fdbcd 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -5,6 +5,19 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace { +bool UserDataDwordIndex(const EmitterState& state, IR::Register reg, uint32_t& dword_index) { + if (reg.file != IR::RegisterFile::Scalar) { + return false; + } + const auto& registers = state.program.bindings.user_data_registers; + const auto found = std::lower_bound(registers.begin(), registers.end(), reg.index); + if (found == registers.end() || *found != reg.index) { + return false; + } + dword_index = static_cast(found - registers.begin()); + return true; +} + uint32_t EmitWqmLaneU32(EmitterState& state, uint32_t src) { uint32_t ret = ConstantU32(state, 0); for (uint32_t i = 0; i < 8u; i++) { @@ -32,19 +45,6 @@ uint32_t BoolAsU32(ValueEmitContext& ctx, uint32_t value) { return result; } -void StoreRegisterMirror(ValueEmitContext& ctx, IR::Register reg, uint32_t value) { - if (const auto pointer = PointerForRegister(ctx.state, reg); pointer != 0) { - if (IsInactiveWave32ExecHigh(ctx.state, reg)) { - value = ConstantU32(ctx.state, 0); - } - ctx.state.builder.AddFunction({OpStore, pointer, value}); - } -} - -bool IsPrologue(const ValueEmitContext& ctx) { - return !ctx.program.blocks.empty() && ctx.current_block == ctx.program.blocks.front(); -} - uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t component) { auto& state = ctx.state; if (kind == IR::StageInputKind::LocalInvocationIndex) { @@ -338,61 +338,9 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::ValueOpcode::Identity: case IR::ValueOpcode::ConditionRef: ctx.Define(inst, ctx.Arg(inst, 0)); return true; - case IR::ValueOpcode::SetScalarRegister: - StoreRegisterMirror( - ctx, {IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())}, - ctx.Arg(inst, 1)); - return true; - case IR::ValueOpcode::SetThreadBitScalarRegister: - StoreRegisterMirror( - ctx, {IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())}, - BoolAsU32(ctx, ctx.Arg(inst, 1))); - return true; - case IR::ValueOpcode::SetVectorRegister: - StoreRegisterMirror( - ctx, {IR::RegisterFile::Vector, IR::RegIndex(inst.Arg(0).VectorRegister())}, - ctx.Arg(inst, 1)); - return true; - case IR::ValueOpcode::SetScc: ctx.Arg(inst, 0); return true; - case IR::ValueOpcode::SetExec: ctx.Arg(inst, 0); return true; - case IR::ValueOpcode::SetExecLo: - case IR::ValueOpcode::SetExecHi: - if (!IsPrologue(ctx)) { - StoreRegisterMirror(ctx, - {IR::RegisterFile::Exec, - inst.GetOpcode() == IR::ValueOpcode::SetExecLo ? 0u : 1u}, - ctx.Arg(inst, 0)); - } - return true; - case IR::ValueOpcode::SetVcc: ctx.Arg(inst, 0); return true; - case IR::ValueOpcode::SetVccLo: - case IR::ValueOpcode::SetVccHi: - if (!IsPrologue(ctx)) { - StoreRegisterMirror(ctx, - {IR::RegisterFile::Vcc, - inst.GetOpcode() == IR::ValueOpcode::SetVccLo ? 0u : 1u}, - ctx.Arg(inst, 0)); - } - return true; - case IR::ValueOpcode::SetM0: - StoreRegisterMirror(ctx, {IR::RegisterFile::M0, 0}, ctx.Arg(inst, 0)); - return true; - case IR::ValueOpcode::GetScalarRegister: - case IR::ValueOpcode::GetThreadBitScalarRegister: - case IR::ValueOpcode::GetVectorRegister: - case IR::ValueOpcode::GetScc: - case IR::ValueOpcode::GetExec: - case IR::ValueOpcode::GetExecLo: - case IR::ValueOpcode::GetExecHi: - case IR::ValueOpcode::GetVcc: - case IR::ValueOpcode::GetVccLo: - case IR::ValueOpcode::GetVccHi: - case IR::ValueOpcode::GetM0: return true; case IR::ValueOpcode::Void: case IR::ValueOpcode::Reference: case IR::ValueOpcode::ReferenceU32: - case IR::ValueOpcode::Prologue: - case IR::ValueOpcode::Epilogue: case IR::ValueOpcode::ControlNop: case IR::ValueOpcode::Waitcnt: case IR::ValueOpcode::Sendmsg: diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 7c8ccbaa1..7c199e788 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -273,11 +273,6 @@ enum : uint32_t { GlslFindUMsb = 75, }; -struct RegisterBinding { - IR::Register reg; - uint32_t pointer_id = 0; -}; - struct InputBinding { IR::StageInputKind kind = IR::StageInputKind::VertexIndex; uint32_t location = 0; @@ -327,7 +322,6 @@ struct EmitterState { ShaderStageInputInfo input_info; ShaderType stage = ShaderType::Unknown; uint32_t wave_size = 64; - bool exact_subgroup_operations = false; bool per_invocation_masks = false; uint32_t void_type = 0; uint32_t bool_type = 0; @@ -426,15 +420,9 @@ struct EmitterState { bool needs_image_gather_extended = false; bool needs_function_lds = false; bool needs_pixel_valid_mask = false; - bool unsupported_ir_instruction = false; - IR::Opcode unsupported_ir_opcode = IR::Opcode::Count; - uint32_t unsupported_ir_pc = 0; - std::vector registers; std::vector inputs; std::vector outputs; std::vector interface_variables; - std::vector reachable_blocks; - std::map block_labels; std::map constants; std::map signed_constants; std::map float_constants; @@ -621,18 +609,12 @@ uint32_t VertexParameterInputPointerType(const EmitterState& state, VertexInputS void SetError(std::string* error, const char* message); -void CollectRegister(std::vector& registers, IR::Register reg); - -bool IsInactiveWave32ExecHigh(const EmitterState& state, IR::Register reg); - bool HasOutput(const std::vector& outputs, IR::StageOutputKind kind, uint32_t index); void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program); uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp); -uint32_t PointerForRegister(const EmitterState& state, IR::Register reg); - uint32_t ConstantU32(EmitterState& state, uint32_t value); uint32_t EmitSubgroupLocalInvocationId(EmitterState& state); @@ -714,8 +696,6 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state); void EmitHeaderAndTypes(EmitterState& state); -void AllocateRegisterVariables(EmitterState& state); - void AllocateDescriptorVariables(EmitterState& state); uint32_t EmitTrueBool(EmitterState& state); @@ -754,8 +734,6 @@ uint32_t EmitBinaryU32(EmitterState& state, uint32_t opcode, uint32_t lhs, uint3 uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index); -bool UserDataDwordIndex(const EmitterState& state, IR::Register reg, uint32_t& dword_index); - uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc); @@ -878,8 +856,6 @@ uint32_t EmitFAbsValue(EmitterState& state, uint32_t value); uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits); -uint32_t InitialRegisterValue(const EmitterState& state, IR::Register reg); - bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst); bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp index 3d5325fa1..1856a4694 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemoryHelpers.cpp @@ -2,6 +2,28 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { +uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) { + if (state.push_constant_variable != 0) { + const auto pointer = state.builder.AllocateId(); + const auto value = state.builder.AllocateId(); + state.builder.AddFunction({OpAccessChain, state.ptr_push_constant_uint, pointer, + state.push_constant_variable, ConstantU32(state, 0), + ConstantU32(state, dword_index)}); + state.builder.AddFunction({OpLoad, state.uint_type, value, pointer}); + return value; + } + if (state.vsharp_storage_variable != 0) { + const auto pointer = state.builder.AllocateId(); + const auto value = state.builder.AllocateId(); + state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer, + state.vsharp_storage_variable, ConstantU32(state, 0), + ConstantU32(state, dword_index)}); + state.builder.AddFunction({OpLoad, state.uint_type, value, pointer}); + return value; + } + return ConstantU32(state, 0); +} + uint32_t EmitAddU32(EmitterState& state, uint32_t lhs, uint32_t rhs) { const auto ret = state.builder.AllocateId(); state.builder.AddFunction({OpIAdd, state.uint_type, ret, lhs, rhs}); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index de07e52be..428ec3fe3 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -803,18 +803,6 @@ void EmitHeaderAndTypes(EmitterState& state) { state.builder.AddType({OpTypeFunction, state.func_type, state.void_type}); } -void AllocateRegisterVariables(EmitterState& state) { - if (state.stage == ShaderType::Pixel && state.needs_pixel_valid_mask) { - state.pixel_valid_mask_variable = state.builder.AllocateId(); - state.builder.AddName(state.pixel_valid_mask_variable, "pixel_valid_mask_active"); - } - for (auto& binding: state.registers) { - binding.pointer_id = state.builder.AllocateId(); - const auto name = IR::RegisterToString(binding.reg); - state.builder.AddName(binding.pointer_id, name.c_str()); - } -} - void AllocateDescriptorVariables(EmitterState& state) { if (DescriptorBinding(state, IR::DescriptorBindingKind::Buffers) != nullptr) { state.storage_buffer_variable = state.builder.AllocateId(); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp index aac13a265..1e00e1571 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp @@ -430,6 +430,10 @@ void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) { bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std::string* error) { ValueEmitContext ctx(state, program); + if (state.stage == ShaderType::Pixel && state.needs_pixel_valid_mask) { + state.pixel_valid_mask_variable = state.builder.AllocateId(); + state.builder.AddName(state.pixel_valid_mask_variable, "pixel_valid_mask_active"); + } for (const auto* block: program.blocks) { ctx.labels.emplace(block, state.builder.AllocateId()); for (const auto& inst: *block) { @@ -512,10 +516,6 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std: state.builder.AddFunction( {OpVariable, state.ptr_func_uint, state.dispatch_pc_variable, StorageClassFunction}); } - for (const auto& binding: state.registers) { - state.builder.AddFunction( - {OpVariable, state.ptr_func_uint, binding.pointer_id, StorageClassFunction}); - } for (const auto* block: program.blocks) { for (const auto& inst: *block) { if (inst.GetOpcode() != IR::ValueOpcode::Phi) { @@ -540,10 +540,6 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std: state.builder.AddFunction( {OpVariable, state.ptr_func_uint, ctx.scratch_u32_variable, StorageClassFunction}); } - for (const auto& binding: state.registers) { - state.builder.AddFunction({OpStore, binding.pointer_id, - ConstantU32(state, InitialRegisterValue(state, binding.reg))}); - } if (state.pixel_valid_mask_variable != 0) { state.builder.AddFunction( {OpStore, state.pixel_valid_mask_variable, ConstantU32(state, 1)}); diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index cb17d547d..89afd4b97 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -805,7 +805,7 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result, return block.id; })->id; if (max_id == UINT32_MAX) { - return Fail(error, "cannot allocate a typed prologue block id"); + return Fail(error, "cannot allocate a typed entry block id"); } CFG::Terminator terminator; terminator.kind = CFG::TerminatorKind::Branch; @@ -831,47 +831,35 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result, } if (!result.blocks.empty()) { result.blocks.front()->AddBranch(result.blocks[1]); - IR::IREmitter prologue(result.blocks.front()); + IR::IREmitter entry_ir(result.blocks.front()); const auto builtin = [&](IR::StageInputKind kind, uint32_t component = 0u) { return IR::U32( - prologue.Emit(IR::ValueOpcode::GetBuiltin, + entry_ir.Emit(IR::ValueOpcode::GetBuiltin, {IR::Value(static_cast(kind)), IR::Value(component)})); }; - prologue.Emit(IR::ValueOpcode::Prologue); - const IR::U32 zero(IR::Value(0u)); - for (uint32_t index = 0; index < IR::NumScalarRegs; index++) { - prologue.SetScalarReg(static_cast(index), zero); - } - for (uint32_t index = 0; index < vector_limit; index++) { - prologue.SetVectorReg(static_cast(index), zero); - } for (uint32_t index = 0; index < source.user_data_count; index++) { const auto reg = static_cast(source.user_data_base + index); if (IR::RegIndex(reg) >= IR::NumScalarRegs) { break; } - prologue.SetScalarReg(reg, prologue.GetUserData(reg)); + entry_ir.SetScalarReg(reg, entry_ir.GetUserData(reg)); } - prologue.SetScc(IR::U1(IR::Value(false))); - prologue.SetExec(IR::U1(IR::Value(true))); - prologue.SetExecLo(IR::U32(IR::Value(0xffffffffu))); - prologue.SetExecHi(IR::U32(IR::Value(source.wave_size > 32u ? 0xffffffffu : 0u))); - prologue.SetVcc(IR::U1(IR::Value(false))); - prologue.SetVccLo(IR::U32(IR::Value(0u))); - prologue.SetVccHi(IR::U32(IR::Value(0u))); - prologue.SetM0(IR::U32(IR::Value(0u))); + entry_ir.SetExec(IR::U1(IR::Value(true))); + entry_ir.SetExecLo(IR::U32(IR::Value(per_invocation_masks ? 1u : 0xffffffffu))); + entry_ir.SetExecHi(IR::U32(IR::Value( + !per_invocation_masks && source.wave_size > 32u ? 0xffffffffu : 0u))); if (source.stage == ShaderType::Compute) { const auto* cs = compute_input_info; const auto thread_ids = cs->thread_ids_num > 0 ? std::min(cs->thread_ids_num, 3u) : 0u; for (uint32_t index = 0; index < thread_ids; index++) { - prologue.SetVectorReg(static_cast(index), + entry_ir.SetVectorReg(static_cast(index), builtin(IR::StageInputKind::LocalInvocationId, index)); } uint32_t reg_offset = 0; for (uint32_t index = 0; index < 3u; index++) { if (cs->group_id[index]) { - prologue.SetScalarReg( + entry_ir.SetScalarReg( static_cast(cs->workgroup_register + reg_offset++), builtin(IR::StageInputKind::WorkgroupId, index)); } @@ -884,43 +872,43 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result, const auto waves = std::min((total_threads + wave_size - 1u) / wave_size, 0x3fu); const auto local_index = builtin(IR::StageInputKind::LocalInvocationIndex); const auto wave_id = IR::U32( - prologue.Emit(IR::ValueOpcode::UDiv32, {local_index, IR::Value(wave_size)})); - const auto wave_bits = prologue.ShiftLeftLogical(wave_id, IR::U32(IR::Value(20u))); + entry_ir.Emit(IR::ValueOpcode::UDiv32, {local_index, IR::Value(wave_size)})); + const auto wave_bits = entry_ir.ShiftLeftLogical(wave_id, IR::U32(IR::Value(20u))); const auto first_bit = - prologue.Select(prologue.IEqual(wave_id, IR::U32(IR::Value(0u))), + entry_ir.Select(entry_ir.IEqual(wave_id, IR::U32(IR::Value(0u))), IR::U32(IR::Value(0x80000000u)), IR::U32(IR::Value(0u))); - prologue.SetScalarReg( + entry_ir.SetScalarReg( static_cast(cs->workgroup_register + reg_offset), - prologue.BitwiseOr(prologue.BitwiseOr(wave_bits, IR::U32(IR::Value(waves))), + entry_ir.BitwiseOr(entry_ir.BitwiseOr(wave_bits, IR::U32(IR::Value(waves))), first_bit)); } } else if (source.stage == ShaderType::Pixel) { const auto* ps = pixel_input_info; uint32_t reg = ps->ps_system_input_base; if (ps->ps_pos_x) { - prologue.SetVectorReg(static_cast(reg++), + entry_ir.SetVectorReg(static_cast(reg++), builtin(IR::StageInputKind::FragCoord, 0)); } if (ps->ps_pos_y) { - prologue.SetVectorReg(static_cast(reg++), + entry_ir.SetVectorReg(static_cast(reg++), builtin(IR::StageInputKind::FragCoord, 1)); } if (ps->ps_pos_z) { - prologue.SetVectorReg(static_cast(reg++), + entry_ir.SetVectorReg(static_cast(reg++), builtin(IR::StageInputKind::FragCoord, 2)); } if (ps->ps_pos_w) { - prologue.SetVectorReg(static_cast(reg++), + entry_ir.SetVectorReg(static_cast(reg++), builtin(IR::StageInputKind::FragCoord, 3)); } if (ps->ps_front_face) { - prologue.SetVectorReg(static_cast(reg), + entry_ir.SetVectorReg(static_cast(reg), builtin(IR::StageInputKind::FrontFacing)); } } else if (source.stage == ShaderType::Vertex) { - prologue.SetVectorReg(static_cast(5), + entry_ir.SetVectorReg(static_cast(5), builtin(IR::StageInputKind::VertexIndex)); - prologue.SetVectorReg(static_cast(8), + entry_ir.SetVectorReg(static_cast(8), builtin(IR::StageInputKind::InstanceIndex)); } } diff --git a/src/graphics/shader/recompiler/ir/Reg.h b/src/graphics/shader/recompiler/ir/Reg.h index ea81eda58..f8e8473ea 100644 --- a/src/graphics/shader/recompiler/ir/Reg.h +++ b/src/graphics/shader/recompiler/ir/Reg.h @@ -7,10 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { enum class ScalarReg : uint16_t {}; enum class VectorReg : uint16_t {}; -// Architectural SGPRs occupy the low range. The current shader-only migration also accepts the -// scalar shadow registers produced by Kyty's pre-existing read-lane pass; that pass disappears -// once read-lane elimination operates on the SSA graph. -constexpr uint32_t NumScalarRegs = 512; +constexpr uint32_t NumScalarRegs = 106; constexpr uint32_t NumVectorRegs = 256; constexpr uint32_t RegIndex(ScalarReg reg) { diff --git a/src/graphics/shader/recompiler/ir/ValueProgram.cpp b/src/graphics/shader/recompiler/ir/ValueProgram.cpp index 15f9fdc7d..05a68c578 100644 --- a/src/graphics/shader/recompiler/ir/ValueProgram.cpp +++ b/src/graphics/shader/recompiler/ir/ValueProgram.cpp @@ -12,20 +12,32 @@ using ShaderError::Fail; namespace { -bool IsRegisterGet(ValueOpcode opcode) { +bool IsRegisterStatePseudo(ValueOpcode opcode) { switch (opcode) { case ValueOpcode::GetThreadBitScalarRegister: + case ValueOpcode::SetThreadBitScalarRegister: case ValueOpcode::GetScalarRegister: + case ValueOpcode::SetScalarRegister: case ValueOpcode::GetVectorRegister: + case ValueOpcode::SetVectorRegister: case ValueOpcode::GetGotoVariable: + case ValueOpcode::SetGotoVariable: case ValueOpcode::GetScc: + case ValueOpcode::SetScc: case ValueOpcode::GetExec: + case ValueOpcode::SetExec: case ValueOpcode::GetExecLo: + case ValueOpcode::SetExecLo: case ValueOpcode::GetExecHi: + case ValueOpcode::SetExecHi: case ValueOpcode::GetVcc: + case ValueOpcode::SetVcc: case ValueOpcode::GetVccLo: + case ValueOpcode::SetVccLo: case ValueOpcode::GetVccHi: - case ValueOpcode::GetM0: return true; + case ValueOpcode::SetVccHi: + case ValueOpcode::GetM0: + case ValueOpcode::SetM0: return true; default: return false; } } @@ -137,8 +149,8 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st if (inst.Parent() != block) { return Fail(error, "value IR instruction has the wrong parent block"); } - if (require_ssa && IsRegisterGet(inst.GetOpcode())) { - return Fail(error, fmt::format("register getter {} survived SSA rewrite", + if (require_ssa && IsRegisterStatePseudo(inst.GetOpcode())) { + return Fail(error, fmt::format("register-state pseudo {} survived SSA rewrite", ValueOpcodeName(inst.GetOpcode()))); } if (inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity && diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp index c4c0d45f8..f4283bf5a 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp @@ -74,8 +74,6 @@ bool HasSideEffects(ValueOpcode opcode) { switch (opcode) { case ValueOpcode::Reference: case ValueOpcode::ReferenceU32: - case ValueOpcode::Prologue: - case ValueOpcode::Epilogue: case ValueOpcode::StoreAddressU8: case ValueOpcode::StoreAddressU16: case ValueOpcode::StoreAddressU32: diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index 327ff4396..73d851bef 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -7,8 +7,6 @@ VALUE_OPCODE(Reference, Void, U1) VALUE_OPCODE(ReferenceU32, Void, U32) // Translation and register-state pseudo operations. -VALUE_OPCODE(Prologue, Void) -VALUE_OPCODE(Epilogue, Void) VALUE_OPCODE(GetUserData, U32, ScalarReg) VALUE_OPCODE(GetShaderBase, U64) VALUE_OPCODE(GetBuiltin, U32, U32, U32) diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 6e1fb42ba..bd75d6d8a 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -15,6 +15,7 @@ #include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/recompiler/frontend/translate/Translate.h" +#include "graphics/shader/recompiler/ir/IREmitter.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/ValueProgram.h" #include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h" @@ -7506,93 +7507,6 @@ void TestComputeShaderInputWaveSize() { "cleared COMPUTE_PGM_RSRC1 W32_EN bit did not decode as wave64"); } -void TestNewShaderRecompilerWave32MasksExecHighStores() { - const uint32_t shader[] = { - EncodeSop1(0x04, 126, 193), // s_mov_b64 exec, -1 - EncodeSopp(0x01), - }; - - auto options = MakeCompileOptions(ShaderType::Compute); - options.dump_ir = true; - options.wave_size = 32; - - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); - Check(Common::ContainsStr(result.decoded_dump, "s_mov_b64 exec_lo, -1"), - "wave32 EXEC mask regression did not decode s_mov_b64 exec, -1"); - CheckSpirvBinaryValidates(result.spirv); - - const auto source = DisassembleSpirvBinary(result.spirv); - Check(CountSourceOccurrences(source, "OpStore %exec_hi %uint_0") >= 2u, - "wave32 EXEC high half was not clamped to zero in SPIR-V stores"); - Check(!Common::ContainsStr(source, "OpStore %exec_hi %uint_4294967295"), - "wave32 EXEC high half was stored as a full 64-lane mask"); -} - -void TestNewShaderRecompilerWave32VccHighScalarStores() { - const uint32_t shader[] = { - EncodeSmem0(0x09, 106, 4), - 0u, // s_buffer_load_dwordx2 vcc_lo - EncodeSopp(0x01), - }; - - auto options = MakeCompileOptions(ShaderType::Compute); - options.dump_ir = true; - options.wave_size = 32; - - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); - Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword vcc_hi"), - "wave32 VCC scalar regression did not lower the high VCC dword load"); - CheckSpirvBinaryValidates(result.spirv); - - const auto source = DisassembleSpirvBinary(result.spirv); - Check(CountSourceOccurrences(source, "OpStore %vcc_hi") >= 2u, - "wave32 VCC high scalar load did not store to vcc_hi"); - Check(CountSourceOccurrences(source, "OpStore %vcc_hi %uint_0") == 1u, - "wave32 VCC high scalar load was clamped to zero instead of preserving " - "data"); -} - -void TestNewShaderRecompilerCompareMaskIsFullWaveBallot() { - const uint32_t shader[] = { - EncodeVopc(0xc1, 0 + 256, 1), // v_cmp_lt_u32 vcc, v0, v1 - EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc - EncodeSop1(0x04, 126, 2), // s_mov_b64 exec, s[2:3] - EncodeSopp(0x01), - }; - - auto options = MakeCompileOptions(ShaderType::Compute); - options.dump_ir = true; - options.wave_size = 32; - - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); - Check(Common::ContainsStr(result.decoded_dump, "v_cmp_lt_u32"), - "compare-mask ballot regression did not decode V_CMP_LT_U32"); - Check(Common::ContainsStr(result.ir_dump, "CompareLtU32 vcc_lo"), - "compare-mask ballot regression did not lower compare to VCC"); - Check( - Common::ContainsStr(result.ir_dump, "BitwiseAndU64 s2, exec_lo, vcc_lo"), - "compare-mask ballot regression did not consume VCC through scalar mask " - "ALU"); - CheckSpirvBinaryValidates(result.spirv); - - const auto source = DisassembleSpirvBinary(result.spirv); - Check(Common::ContainsStr(source, "OpGroupNonUniformBallot"), - "compare-mask result was not materialized with a subgroup ballot"); - Check(Common::ContainsStr(source, "OpStore %vcc_lo"), - "compare-mask ballot regression did not store VCC low mask"); - Check(Common::ContainsStr(source, "OpStore %vcc_hi %uint_0"), - "wave32 compare-mask VCC high half was not cleared"); -} - void TestNewShaderRecompilerBufferLoadsGuardedByExec() { const uint32_t shader[] = { EncodeMubuf0(0x0c), @@ -7885,9 +7799,119 @@ void TestNewShaderRecompilerExpVertexOutputs() { CheckSpirvBinaryValidates(result.spirv); } +void TestTypedEntryStateIsMinimal() { + using namespace ShaderRecompiler; + + const auto translate = [](ShaderLaneMaskMode lane_mode, uint32_t wave_size) { + IR::Program source; + source.stage = ShaderType::Compute; + source.lane_mask_mode = lane_mode; + source.wave_size = wave_size; + source.user_data_count = 0; + source.blocks.emplace_back(); + source.blocks.front().id = 0; + source.blocks.front().terminator.kind = CFG::TerminatorKind::Return; + + IR::ValueProgram values; + ShaderComputeInputInfo compute{}; + std::string error; + Check(Frontend::TranslateProgram(source, values, nullptr, nullptr, &compute, + &error), + error.c_str()); + return values; + }; + + const auto check = [&](ShaderLaneMaskMode lane_mode, uint32_t wave_size) { + auto values = translate(lane_mode, wave_size); + uint32_t set_exec = 0; + uint32_t set_exec_lo = 0; + uint32_t set_exec_hi = 0; + IR::Value exec; + IR::Value exec_lo; + IR::Value exec_hi; + for (const auto *block : values.blocks) { + for (const auto &inst : *block) { + switch (inst.GetOpcode()) { + case IR::ValueOpcode::SetExec: + set_exec++; + exec = inst.Arg(0).Resolve(); + break; + case IR::ValueOpcode::SetExecLo: + set_exec_lo++; + exec_lo = inst.Arg(0).Resolve(); + break; + case IR::ValueOpcode::SetExecHi: + set_exec_hi++; + exec_hi = inst.Arg(0).Resolve(); + break; + case IR::ValueOpcode::SetScalarRegister: + case IR::ValueOpcode::SetThreadBitScalarRegister: + case IR::ValueOpcode::SetVectorRegister: + case IR::ValueOpcode::SetScc: + case IR::ValueOpcode::SetVcc: + case IR::ValueOpcode::SetVccLo: + case IR::ValueOpcode::SetVccHi: + case IR::ValueOpcode::SetM0: + Check(false, "minimal typed entry contains redundant state writes"); + break; + default: + break; + } + } + } + Check(set_exec == 1u && set_exec_lo == 1u && set_exec_hi == 1u && + exec.IsImmediate() && exec.U1(), + "typed entry does not define EXEC exactly once"); + if (lane_mode == ShaderLaneMaskMode::PerInvocation) { + Check(exec_lo.IsImmediate() && exec_lo.U32() == 1u && + exec_hi.IsImmediate() && exec_hi.U32() == 0u, + "per-invocation entry EXEC is not the local {1,0} mask"); + } else { + Check(exec_lo.IsImmediate() && exec_lo.U32() == 0xffffffffu && + exec_hi.IsImmediate() && + exec_hi.U32() == (wave_size == 32u ? 0u : 0xffffffffu), + "native entry EXEC does not match the guest wave width"); + } + + IR::RewriteToSsa(values.blocks); + IR::RemoveIdentities(values.blocks); + IR::EliminateDeadCode(values.blocks); + std::string error; + Check(IR::ValidateValueProgram(values, true, &error), error.c_str()); + }; + + check(ShaderLaneMaskMode::NativeWave, 32u); + check(ShaderLaneMaskMode::NativeWave, 64u); + check(ShaderLaneMaskMode::PerInvocation, 64u); +} + +void TestFinalSsaRejectsRegisterStatePseudos() { + using namespace ShaderRecompiler::IR; + + const auto check_rejected = [](bool setter) { + ValueProgram program; + program.block_storage.push_back(std::make_unique()); + program.blocks.push_back(program.block_storage.back().get()); + program.block_info.emplace_back(); + IREmitter ir(program.blocks.front()); + if (setter) { + ir.SetScalarReg(ScalarReg{0}, U32(Value(1u))); + } else { + ir.Emit(ValueOpcode::ReferenceU32, {ir.GetScalarReg(ScalarReg{0})}); + } + std::string error; + Check(!ValidateValueProgram(program, true, &error) && + Common::ContainsStr(error, "register-state pseudo"), + "final SSA accepted a register-state pseudo operation"); + }; + + check_rejected(false); + check_rejected(true); +} + void TestNewShaderRecompilerZeroInitialRegisterState() { const uint32_t shader[] = { - EncodeVop1(0x01, 0, 0), // v_mov_b32 v0, s0 + EncodeVop1(0x01, 0, 100), // v_mov_b32 v0, s100 EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0 EncodeSopp(0x01), @@ -7901,14 +7925,31 @@ void TestNewShaderRecompilerZeroInitialRegisterState() { Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str()); Check(!Common::ContainsStr(result.ir_dump, "UndefU32"), - "typed prologue left guest registers undefined"); + "SSA initial state left guest registers undefined"); const auto source = DisassembleSpirvBinary(result.spirv); Check(!Common::ContainsStr(source, "OpUndef"), "zero-initialized guest registers became SPIR-V undef values"); + Check(!Common::ContainsStr(source, "%s100") && + !Common::ContainsStr(source, "%v0"), + "final SPIR-V retained a guest register mirror"); CheckSpirvBinaryValidates(result.spirv); + + const uint32_t explicit_zero_shader[] = { + EncodeSMovB32(100, 128), // s_mov_b32 s100, 0 + EncodeVop1(0x01, 0, 100), // v_mov_b32 v0, s100 + EncodeExp0(0x0c, 0xf), + EncodeExp1(0, 1, 2, 3), // POS0 + EncodeSopp(0x01), + }; + ShaderRecompiler::CompileResult explicit_zero; + Check(ShaderRecompiler::TryRecompile(explicit_zero_shader, options, + explicit_zero, &error), + error.c_str()); + Check(result.spirv == explicit_zero.spirv, + "implicit and explicit zero register state emitted different SPIR-V"); } -void TestNewShaderRecompilerVertexSystemVgprs() { +void TestNewShaderRecompilerVertexSystemInputsWithoutMirrors() { using StageInputKind = ShaderRecompiler::IR::StageInputKind; const uint32_t shader[] = { @@ -7932,10 +7973,6 @@ void TestNewShaderRecompilerVertexSystemVgprs() { "vertex shader missing VertexIndex input"); Check(ProgramHasInput(result.program, StageInputKind::InstanceIndex), "vertex shader missing InstanceIndex input"); - Check(Common::ContainsStr(result.ir_dump, "MoveU32 v0, v5"), - "vertex shader did not keep v5 as a guest VGPR source"); - Check(Common::ContainsStr(result.ir_dump, "MoveU32 v1, v8"), - "vertex shader did not keep v8 as a guest VGPR source"); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); @@ -7943,10 +7980,9 @@ void TestNewShaderRecompilerVertexSystemVgprs() { "vertex SPIR-V does not load gl_VertexIndex"); Check(CountSourceOccurrences(source, "OpLoad %int %gl_InstanceIndex") == 1u, "vertex SPIR-V does not load gl_InstanceIndex"); - Check(CountSourceOccurrences(source, "OpStore %v5") >= 2u, - "vertex SPIR-V does not seed guest v5 from gl_VertexIndex"); - Check(CountSourceOccurrences(source, "OpStore %v8") >= 2u, - "vertex SPIR-V does not seed guest v8 from gl_InstanceIndex"); + Check(!Common::ContainsStr(source, "%v5") && + !Common::ContainsStr(source, "%v8"), + "vertex system values were routed through guest VGPR mirrors"); } void TestNewShaderRecompilerVertexExportUsesLaneExecMask() { @@ -7988,9 +8024,9 @@ void TestNewShaderRecompilerVertexExportUsesLaneExecMask() { "per-invocation vertex export lost its EXEC guard"); } -void TestNewShaderRecompilerPerInvocationMasks() { +void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() { const uint32_t local_shader[] = { - EncodeVopc(0xc1, 0 + 256, 1), // v_cmp_lt_u32 vcc, v0, v1 + EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8 EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc EncodeSop1(0x24, 4, 126), // s_and_saveexec_b64 s[4:5], exec EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0 @@ -8016,17 +8052,13 @@ void TestNewShaderRecompilerPerInvocationMasks() { "per-invocation VCC producer still materialized a shared subgroup mask"); Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"), "per-invocation BFM EXEC prefix still selected native subgroup lanes"); - Check(Common::ContainsStr(source, "OpLogicalAnd") && - Common::ContainsStr(source, "OpLogicalNot"), - "per-invocation wide mask ALU did not use Boolean operations"); - Check(Common::ContainsStr(source, "OpStore %vcc_lo") && - Common::ContainsStr(source, "OpStore %vcc_hi %uint_0"), - "per-invocation comparison did not store a local Boolean VCC pair"); - Check(Common::ContainsStr(result.ir_dump, "SaveexecB64 s4, exec_lo"), - "per-invocation SAVEEXEC regression did not reach IR"); + Check(!Common::ContainsStr(source, "%vcc_lo") && + !Common::ContainsStr(source, "%vcc_hi"), + "per-invocation comparison retained VCC register mirrors"); const uint32_t wqm_shader[] = { EncodeSop1(0x0a, 2, 126), // s_wqm_b64 s[2:3], exec + EncodeVop1(0x01, 0, 2), // v_mov_b32 v0, s2 EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0 EncodeSopp(0x01), @@ -9760,8 +9792,12 @@ int main() { TestNewShaderRecompilerSetpcBranch(); TestNewShaderRecompilerSetpcJumpTable(); TestNewShaderRecompilerSetpcDwordJumpTable(); + TestTypedEntryStateIsMinimal(); + TestFinalSsaRejectsRegisterStatePseudos(); TestNewShaderRecompilerZeroInitialRegisterState(); + TestNewShaderRecompilerVertexSystemInputsWithoutMirrors(); TestNewShaderRecompilerVertexExportUsesLaneExecMask(); + TestNewShaderRecompilerPerInvocationMasksWithoutMirrors(); TestNewShaderRecompilerPerInvocationU64Complement(); TestNewShaderRecompilerExpPixelOutputs(); TestRenderTargetReverseExportMapping();