refactor(shader): remove register-state compatibility paths

This commit is contained in:
nmzik
2026-08-19 03:52:12 +02:00
parent 33545ca6c4
commit e1bcc298a0
14 changed files with 222 additions and 389 deletions
@@ -232,51 +232,6 @@ static void ReportReserveExceeded(const IR::Program& program, const char* vector
InitialEmitterVectorReserve); InitialEmitterVectorReserve);
} }
void CollectValueRegisterMirrors(const IR::ValueProgram& program,
std::vector<Emitter::RegisterBinding>& 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) { IR::SpirvRequirements GetProgramRequirements(const IR::Program& program) {
if (program.spirv_requirements.has_value()) { if (program.spirv_requirements.has_value()) {
return *program.spirv_requirements; return *program.spirv_requirements;
@@ -418,14 +373,9 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
state.stage = program.stage; state.stage = program.stage;
state.wave_size = program.wave_size; state.wave_size = program.wave_size;
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation; 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.inputs.reserve(InitialEmitterVectorReserve);
state.outputs.reserve(InitialEmitterVectorReserve); state.outputs.reserve(InitialEmitterVectorReserve);
state.interface_variables.reserve(InitialEmitterVectorReserve); state.interface_variables.reserve(InitialEmitterVectorReserve);
state.reachable_blocks.reserve(InitialEmitterVectorReserve);
CollectValueRegisterMirrors(value_program, state.registers);
CopyProgramInputsAndOutputs(state, program); CopyProgramInputsAndOutputs(state, program);
state.needs_subgroup_ballot = requirements.subgroup_ballot; state.needs_subgroup_ballot = requirements.subgroup_ballot;
state.needs_subgroup_shuffle = requirements.subgroup_shuffle; state.needs_subgroup_shuffle = requirements.subgroup_shuffle;
@@ -438,16 +388,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
AllocateOutputVariables(state); AllocateOutputVariables(state);
AllocateDescriptorVariables(state); AllocateDescriptorVariables(state);
EmitHeaderAndTypes(state); EmitHeaderAndTypes(state);
AllocateRegisterVariables(state);
if (!EmitValueProgram(state, value_program, error)) { if (!EmitValueProgram(state, value_program, error)) {
return false; return false;
} }
ReportReserveExceeded(program, "registers", state.registers.size());
ReportReserveExceeded(program, "inputs", state.inputs.size()); ReportReserveExceeded(program, "inputs", state.inputs.size());
ReportReserveExceeded(program, "outputs", state.outputs.size()); ReportReserveExceeded(program, "outputs", state.outputs.size());
ReportReserveExceeded(program, "interface_variables", state.interface_variables.size()); ReportReserveExceeded(program, "interface_variables", state.interface_variables.size());
ReportReserveExceeded(program, "reachable_blocks", state.reachable_blocks.size());
auto binary = state.builder.Build(); auto binary = state.builder.Build();
if (binary.empty()) { if (binary.empty()) {
@@ -38,18 +38,6 @@ void SetError(std::string* error, const char* message) {
} }
} }
void CollectRegister(std::vector<RegisterBinding>& 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<OutputBinding>& outputs, IR::StageOutputKind kind, bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind,
uint32_t index) { uint32_t index) {
return std::any_of(outputs.begin(), outputs.end(), [kind, index](const OutputBinding& binding) { 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; 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); uint32_t ConstantU32(EmitterState& state, uint32_t value);
[[noreturn]] void ExitDescriptorBindingFailure(const EmitterState& state, [[noreturn]] void ExitDescriptorBindingFailure(const EmitterState& state,
IR::DescriptorBindingKind kind, uint32_t resource, IR::DescriptorBindingKind kind, uint32_t resource,
@@ -1,52 +0,0 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
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<uint32_t>(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
@@ -5,6 +5,19 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace { 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<uint32_t>(found - registers.begin());
return true;
}
uint32_t EmitWqmLaneU32(EmitterState& state, uint32_t src) { uint32_t EmitWqmLaneU32(EmitterState& state, uint32_t src) {
uint32_t ret = ConstantU32(state, 0); uint32_t ret = ConstantU32(state, 0);
for (uint32_t i = 0; i < 8u; i++) { for (uint32_t i = 0; i < 8u; i++) {
@@ -32,19 +45,6 @@ uint32_t BoolAsU32(ValueEmitContext& ctx, uint32_t value) {
return result; 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) { uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t component) {
auto& state = ctx.state; auto& state = ctx.state;
if (kind == IR::StageInputKind::LocalInvocationIndex) { if (kind == IR::StageInputKind::LocalInvocationIndex) {
@@ -338,61 +338,9 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
switch (inst.GetOpcode()) { switch (inst.GetOpcode()) {
case IR::ValueOpcode::Identity: case IR::ValueOpcode::Identity:
case IR::ValueOpcode::ConditionRef: ctx.Define(inst, ctx.Arg(inst, 0)); return true; 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::Void:
case IR::ValueOpcode::Reference: case IR::ValueOpcode::Reference:
case IR::ValueOpcode::ReferenceU32: case IR::ValueOpcode::ReferenceU32:
case IR::ValueOpcode::Prologue:
case IR::ValueOpcode::Epilogue:
case IR::ValueOpcode::ControlNop: case IR::ValueOpcode::ControlNop:
case IR::ValueOpcode::Waitcnt: case IR::ValueOpcode::Waitcnt:
case IR::ValueOpcode::Sendmsg: case IR::ValueOpcode::Sendmsg:
@@ -273,11 +273,6 @@ enum : uint32_t {
GlslFindUMsb = 75, GlslFindUMsb = 75,
}; };
struct RegisterBinding {
IR::Register reg;
uint32_t pointer_id = 0;
};
struct InputBinding { struct InputBinding {
IR::StageInputKind kind = IR::StageInputKind::VertexIndex; IR::StageInputKind kind = IR::StageInputKind::VertexIndex;
uint32_t location = 0; uint32_t location = 0;
@@ -327,7 +322,6 @@ struct EmitterState {
ShaderStageInputInfo input_info; ShaderStageInputInfo input_info;
ShaderType stage = ShaderType::Unknown; ShaderType stage = ShaderType::Unknown;
uint32_t wave_size = 64; uint32_t wave_size = 64;
bool exact_subgroup_operations = false;
bool per_invocation_masks = false; bool per_invocation_masks = false;
uint32_t void_type = 0; uint32_t void_type = 0;
uint32_t bool_type = 0; uint32_t bool_type = 0;
@@ -426,15 +420,9 @@ struct EmitterState {
bool needs_image_gather_extended = false; bool needs_image_gather_extended = false;
bool needs_function_lds = false; bool needs_function_lds = false;
bool needs_pixel_valid_mask = 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<RegisterBinding> registers;
std::vector<InputBinding> inputs; std::vector<InputBinding> inputs;
std::vector<OutputBinding> outputs; std::vector<OutputBinding> outputs;
std::vector<uint32_t> interface_variables; std::vector<uint32_t> interface_variables;
std::vector<bool> reachable_blocks;
std::map<uint32_t, uint32_t> block_labels;
std::map<uint32_t, uint32_t> constants; std::map<uint32_t, uint32_t> constants;
std::map<uint32_t, uint32_t> signed_constants; std::map<uint32_t, uint32_t> signed_constants;
std::map<uint32_t, uint32_t> float_constants; std::map<uint32_t, uint32_t> float_constants;
@@ -621,18 +609,12 @@ uint32_t VertexParameterInputPointerType(const EmitterState& state, VertexInputS
void SetError(std::string* error, const char* message); void SetError(std::string* error, const char* message);
void CollectRegister(std::vector<RegisterBinding>& registers, IR::Register reg);
bool IsInactiveWave32ExecHigh(const EmitterState& state, IR::Register reg);
bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind, uint32_t index); bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind, uint32_t index);
void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program); void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program);
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp); 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 ConstantU32(EmitterState& state, uint32_t value);
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state); uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
@@ -714,8 +696,6 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state);
void EmitHeaderAndTypes(EmitterState& state); void EmitHeaderAndTypes(EmitterState& state);
void AllocateRegisterVariables(EmitterState& state);
void AllocateDescriptorVariables(EmitterState& state); void AllocateDescriptorVariables(EmitterState& state);
uint32_t EmitTrueBool(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); 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 StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem,
uint32_t use_pc); 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 EmitF16BitsToF32(EmitterState& state, uint32_t bits);
uint32_t InitialRegisterValue(const EmitterState& state, IR::Register reg);
bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst); bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst);
bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst); bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -2,6 +2,28 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { 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) { uint32_t EmitAddU32(EmitterState& state, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId(); const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpIAdd, state.uint_type, ret, lhs, rhs}); state.builder.AddFunction({OpIAdd, state.uint_type, ret, lhs, rhs});
@@ -803,18 +803,6 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddType({OpTypeFunction, state.func_type, state.void_type}); 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) { void AllocateDescriptorVariables(EmitterState& state) {
if (DescriptorBinding(state, IR::DescriptorBindingKind::Buffers) != nullptr) { if (DescriptorBinding(state, IR::DescriptorBindingKind::Buffers) != nullptr) {
state.storage_buffer_variable = state.builder.AllocateId(); state.storage_buffer_variable = state.builder.AllocateId();
@@ -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) { bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std::string* error) {
ValueEmitContext ctx(state, program); 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) { for (const auto* block: program.blocks) {
ctx.labels.emplace(block, state.builder.AllocateId()); ctx.labels.emplace(block, state.builder.AllocateId());
for (const auto& inst: *block) { for (const auto& inst: *block) {
@@ -512,10 +516,6 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
state.builder.AddFunction( state.builder.AddFunction(
{OpVariable, state.ptr_func_uint, state.dispatch_pc_variable, StorageClassFunction}); {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* block: program.blocks) {
for (const auto& inst: *block) { for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::Phi) { if (inst.GetOpcode() != IR::ValueOpcode::Phi) {
@@ -540,10 +540,6 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
state.builder.AddFunction( state.builder.AddFunction(
{OpVariable, state.ptr_func_uint, ctx.scratch_u32_variable, StorageClassFunction}); {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) { if (state.pixel_valid_mask_variable != 0) {
state.builder.AddFunction( state.builder.AddFunction(
{OpStore, state.pixel_valid_mask_variable, ConstantU32(state, 1)}); {OpStore, state.pixel_valid_mask_variable, ConstantU32(state, 1)});
@@ -805,7 +805,7 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
return block.id; return block.id;
})->id; })->id;
if (max_id == UINT32_MAX) { 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; CFG::Terminator terminator;
terminator.kind = CFG::TerminatorKind::Branch; terminator.kind = CFG::TerminatorKind::Branch;
@@ -831,47 +831,35 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
} }
if (!result.blocks.empty()) { if (!result.blocks.empty()) {
result.blocks.front()->AddBranch(result.blocks[1]); 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) { const auto builtin = [&](IR::StageInputKind kind, uint32_t component = 0u) {
return IR::U32( return IR::U32(
prologue.Emit(IR::ValueOpcode::GetBuiltin, entry_ir.Emit(IR::ValueOpcode::GetBuiltin,
{IR::Value(static_cast<uint32_t>(kind)), IR::Value(component)})); {IR::Value(static_cast<uint32_t>(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<IR::ScalarReg>(index), zero);
}
for (uint32_t index = 0; index < vector_limit; index++) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(index), zero);
}
for (uint32_t index = 0; index < source.user_data_count; index++) { for (uint32_t index = 0; index < source.user_data_count; index++) {
const auto reg = static_cast<IR::ScalarReg>(source.user_data_base + index); const auto reg = static_cast<IR::ScalarReg>(source.user_data_base + index);
if (IR::RegIndex(reg) >= IR::NumScalarRegs) { if (IR::RegIndex(reg) >= IR::NumScalarRegs) {
break; break;
} }
prologue.SetScalarReg(reg, prologue.GetUserData(reg)); entry_ir.SetScalarReg(reg, entry_ir.GetUserData(reg));
} }
prologue.SetScc(IR::U1(IR::Value(false))); entry_ir.SetExec(IR::U1(IR::Value(true)));
prologue.SetExec(IR::U1(IR::Value(true))); entry_ir.SetExecLo(IR::U32(IR::Value(per_invocation_masks ? 1u : 0xffffffffu)));
prologue.SetExecLo(IR::U32(IR::Value(0xffffffffu))); entry_ir.SetExecHi(IR::U32(IR::Value(
prologue.SetExecHi(IR::U32(IR::Value(source.wave_size > 32u ? 0xffffffffu : 0u))); !per_invocation_masks && 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)));
if (source.stage == ShaderType::Compute) { if (source.stage == ShaderType::Compute) {
const auto* cs = compute_input_info; const auto* cs = compute_input_info;
const auto thread_ids = const auto thread_ids =
cs->thread_ids_num > 0 ? std::min<uint32_t>(cs->thread_ids_num, 3u) : 0u; cs->thread_ids_num > 0 ? std::min<uint32_t>(cs->thread_ids_num, 3u) : 0u;
for (uint32_t index = 0; index < thread_ids; index++) { for (uint32_t index = 0; index < thread_ids; index++) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(index), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(index),
builtin(IR::StageInputKind::LocalInvocationId, index)); builtin(IR::StageInputKind::LocalInvocationId, index));
} }
uint32_t reg_offset = 0; uint32_t reg_offset = 0;
for (uint32_t index = 0; index < 3u; index++) { for (uint32_t index = 0; index < 3u; index++) {
if (cs->group_id[index]) { if (cs->group_id[index]) {
prologue.SetScalarReg( entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(cs->workgroup_register + reg_offset++), static_cast<IR::ScalarReg>(cs->workgroup_register + reg_offset++),
builtin(IR::StageInputKind::WorkgroupId, index)); 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 waves = std::min((total_threads + wave_size - 1u) / wave_size, 0x3fu);
const auto local_index = builtin(IR::StageInputKind::LocalInvocationIndex); const auto local_index = builtin(IR::StageInputKind::LocalInvocationIndex);
const auto wave_id = IR::U32( const auto wave_id = IR::U32(
prologue.Emit(IR::ValueOpcode::UDiv32, {local_index, IR::Value(wave_size)})); entry_ir.Emit(IR::ValueOpcode::UDiv32, {local_index, IR::Value(wave_size)}));
const auto wave_bits = prologue.ShiftLeftLogical(wave_id, IR::U32(IR::Value(20u))); const auto wave_bits = entry_ir.ShiftLeftLogical(wave_id, IR::U32(IR::Value(20u)));
const auto first_bit = 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))); IR::U32(IR::Value(0x80000000u)), IR::U32(IR::Value(0u)));
prologue.SetScalarReg( entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(cs->workgroup_register + reg_offset), static_cast<IR::ScalarReg>(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)); first_bit));
} }
} else if (source.stage == ShaderType::Pixel) { } else if (source.stage == ShaderType::Pixel) {
const auto* ps = pixel_input_info; const auto* ps = pixel_input_info;
uint32_t reg = ps->ps_system_input_base; uint32_t reg = ps->ps_system_input_base;
if (ps->ps_pos_x) { if (ps->ps_pos_x) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
builtin(IR::StageInputKind::FragCoord, 0)); builtin(IR::StageInputKind::FragCoord, 0));
} }
if (ps->ps_pos_y) { if (ps->ps_pos_y) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
builtin(IR::StageInputKind::FragCoord, 1)); builtin(IR::StageInputKind::FragCoord, 1));
} }
if (ps->ps_pos_z) { if (ps->ps_pos_z) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
builtin(IR::StageInputKind::FragCoord, 2)); builtin(IR::StageInputKind::FragCoord, 2));
} }
if (ps->ps_pos_w) { if (ps->ps_pos_w) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg++), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
builtin(IR::StageInputKind::FragCoord, 3)); builtin(IR::StageInputKind::FragCoord, 3));
} }
if (ps->ps_front_face) { if (ps->ps_front_face) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg),
builtin(IR::StageInputKind::FrontFacing)); builtin(IR::StageInputKind::FrontFacing));
} }
} else if (source.stage == ShaderType::Vertex) { } else if (source.stage == ShaderType::Vertex) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(5), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::VertexIndex)); builtin(IR::StageInputKind::VertexIndex));
prologue.SetVectorReg(static_cast<IR::VectorReg>(8), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
builtin(IR::StageInputKind::InstanceIndex)); builtin(IR::StageInputKind::InstanceIndex));
} }
} }
+1 -4
View File
@@ -7,10 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
enum class ScalarReg : uint16_t {}; enum class ScalarReg : uint16_t {};
enum class VectorReg : uint16_t {}; enum class VectorReg : uint16_t {};
// Architectural SGPRs occupy the low range. The current shader-only migration also accepts the constexpr uint32_t NumScalarRegs = 106;
// 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 NumVectorRegs = 256; constexpr uint32_t NumVectorRegs = 256;
constexpr uint32_t RegIndex(ScalarReg reg) { constexpr uint32_t RegIndex(ScalarReg reg) {
@@ -12,20 +12,32 @@ using ShaderError::Fail;
namespace { namespace {
bool IsRegisterGet(ValueOpcode opcode) { bool IsRegisterStatePseudo(ValueOpcode opcode) {
switch (opcode) { switch (opcode) {
case ValueOpcode::GetThreadBitScalarRegister: case ValueOpcode::GetThreadBitScalarRegister:
case ValueOpcode::SetThreadBitScalarRegister:
case ValueOpcode::GetScalarRegister: case ValueOpcode::GetScalarRegister:
case ValueOpcode::SetScalarRegister:
case ValueOpcode::GetVectorRegister: case ValueOpcode::GetVectorRegister:
case ValueOpcode::SetVectorRegister:
case ValueOpcode::GetGotoVariable: case ValueOpcode::GetGotoVariable:
case ValueOpcode::SetGotoVariable:
case ValueOpcode::GetScc: case ValueOpcode::GetScc:
case ValueOpcode::SetScc:
case ValueOpcode::GetExec: case ValueOpcode::GetExec:
case ValueOpcode::SetExec:
case ValueOpcode::GetExecLo: case ValueOpcode::GetExecLo:
case ValueOpcode::SetExecLo:
case ValueOpcode::GetExecHi: case ValueOpcode::GetExecHi:
case ValueOpcode::SetExecHi:
case ValueOpcode::GetVcc: case ValueOpcode::GetVcc:
case ValueOpcode::SetVcc:
case ValueOpcode::GetVccLo: case ValueOpcode::GetVccLo:
case ValueOpcode::SetVccLo:
case ValueOpcode::GetVccHi: case ValueOpcode::GetVccHi:
case ValueOpcode::GetM0: return true; case ValueOpcode::SetVccHi:
case ValueOpcode::GetM0:
case ValueOpcode::SetM0: return true;
default: return false; default: return false;
} }
} }
@@ -137,8 +149,8 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
if (inst.Parent() != block) { if (inst.Parent() != block) {
return Fail(error, "value IR instruction has the wrong parent block"); return Fail(error, "value IR instruction has the wrong parent block");
} }
if (require_ssa && IsRegisterGet(inst.GetOpcode())) { if (require_ssa && IsRegisterStatePseudo(inst.GetOpcode())) {
return Fail(error, fmt::format("register getter {} survived SSA rewrite", return Fail(error, fmt::format("register-state pseudo {} survived SSA rewrite",
ValueOpcodeName(inst.GetOpcode()))); ValueOpcodeName(inst.GetOpcode())));
} }
if (inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity && if (inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity &&
@@ -74,8 +74,6 @@ bool HasSideEffects(ValueOpcode opcode) {
switch (opcode) { switch (opcode) {
case ValueOpcode::Reference: case ValueOpcode::Reference:
case ValueOpcode::ReferenceU32: case ValueOpcode::ReferenceU32:
case ValueOpcode::Prologue:
case ValueOpcode::Epilogue:
case ValueOpcode::StoreAddressU8: case ValueOpcode::StoreAddressU8:
case ValueOpcode::StoreAddressU16: case ValueOpcode::StoreAddressU16:
case ValueOpcode::StoreAddressU32: case ValueOpcode::StoreAddressU32:
@@ -7,8 +7,6 @@ VALUE_OPCODE(Reference, Void, U1)
VALUE_OPCODE(ReferenceU32, Void, U32) VALUE_OPCODE(ReferenceU32, Void, U32)
// Translation and register-state pseudo operations. // Translation and register-state pseudo operations.
VALUE_OPCODE(Prologue, Void)
VALUE_OPCODE(Epilogue, Void)
VALUE_OPCODE(GetUserData, U32, ScalarReg) VALUE_OPCODE(GetUserData, U32, ScalarReg)
VALUE_OPCODE(GetShaderBase, U64) VALUE_OPCODE(GetShaderBase, U64)
VALUE_OPCODE(GetBuiltin, U32, U32, U32) VALUE_OPCODE(GetBuiltin, U32, U32, U32)
+144 -108
View File
@@ -15,6 +15,7 @@
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h" #include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/frontend/translate/Translate.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/ShaderIR.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h" #include "graphics/shader/recompiler/ir/ValueProgram.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.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"); "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() { void TestNewShaderRecompilerBufferLoadsGuardedByExec() {
const uint32_t shader[] = { const uint32_t shader[] = {
EncodeMubuf0(0x0c), EncodeMubuf0(0x0c),
@@ -7885,9 +7799,119 @@ void TestNewShaderRecompilerExpVertexOutputs() {
CheckSpirvBinaryValidates(result.spirv); 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<Block>());
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() { void TestNewShaderRecompilerZeroInitialRegisterState() {
const uint32_t shader[] = { 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), EncodeExp0(0x0c, 0xf),
EncodeExp1(0, 1, 2, 3), // POS0 EncodeExp1(0, 1, 2, 3), // POS0
EncodeSopp(0x01), EncodeSopp(0x01),
@@ -7901,14 +7925,31 @@ void TestNewShaderRecompilerZeroInitialRegisterState() {
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str()); error.c_str());
Check(!Common::ContainsStr(result.ir_dump, "UndefU32"), 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); const auto source = DisassembleSpirvBinary(result.spirv);
Check(!Common::ContainsStr(source, "OpUndef"), Check(!Common::ContainsStr(source, "OpUndef"),
"zero-initialized guest registers became SPIR-V undef values"); "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); 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; using StageInputKind = ShaderRecompiler::IR::StageInputKind;
const uint32_t shader[] = { const uint32_t shader[] = {
@@ -7932,10 +7973,6 @@ void TestNewShaderRecompilerVertexSystemVgprs() {
"vertex shader missing VertexIndex input"); "vertex shader missing VertexIndex input");
Check(ProgramHasInput(result.program, StageInputKind::InstanceIndex), Check(ProgramHasInput(result.program, StageInputKind::InstanceIndex),
"vertex shader missing InstanceIndex input"); "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); CheckSpirvBinaryValidates(result.spirv);
const auto source = DisassembleSpirvBinary(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv);
@@ -7943,10 +7980,9 @@ void TestNewShaderRecompilerVertexSystemVgprs() {
"vertex SPIR-V does not load gl_VertexIndex"); "vertex SPIR-V does not load gl_VertexIndex");
Check(CountSourceOccurrences(source, "OpLoad %int %gl_InstanceIndex") == 1u, Check(CountSourceOccurrences(source, "OpLoad %int %gl_InstanceIndex") == 1u,
"vertex SPIR-V does not load gl_InstanceIndex"); "vertex SPIR-V does not load gl_InstanceIndex");
Check(CountSourceOccurrences(source, "OpStore %v5") >= 2u, Check(!Common::ContainsStr(source, "%v5") &&
"vertex SPIR-V does not seed guest v5 from gl_VertexIndex"); !Common::ContainsStr(source, "%v8"),
Check(CountSourceOccurrences(source, "OpStore %v8") >= 2u, "vertex system values were routed through guest VGPR mirrors");
"vertex SPIR-V does not seed guest v8 from gl_InstanceIndex");
} }
void TestNewShaderRecompilerVertexExportUsesLaneExecMask() { void TestNewShaderRecompilerVertexExportUsesLaneExecMask() {
@@ -7988,9 +8024,9 @@ void TestNewShaderRecompilerVertexExportUsesLaneExecMask() {
"per-invocation vertex export lost its EXEC guard"); "per-invocation vertex export lost its EXEC guard");
} }
void TestNewShaderRecompilerPerInvocationMasks() { void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
const uint32_t local_shader[] = { 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 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 EncodeSop1(0x24, 4, 126), // s_and_saveexec_b64 s[4:5], exec
EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0 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"); "per-invocation VCC producer still materialized a shared subgroup mask");
Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"), Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
"per-invocation BFM EXEC prefix still selected native subgroup lanes"); "per-invocation BFM EXEC prefix still selected native subgroup lanes");
Check(Common::ContainsStr(source, "OpLogicalAnd") && Check(!Common::ContainsStr(source, "%vcc_lo") &&
Common::ContainsStr(source, "OpLogicalNot"), !Common::ContainsStr(source, "%vcc_hi"),
"per-invocation wide mask ALU did not use Boolean operations"); "per-invocation comparison retained VCC register mirrors");
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");
const uint32_t wqm_shader[] = { const uint32_t wqm_shader[] = {
EncodeSop1(0x0a, 2, 126), // s_wqm_b64 s[2:3], exec EncodeSop1(0x0a, 2, 126), // s_wqm_b64 s[2:3], exec
EncodeVop1(0x01, 0, 2), // v_mov_b32 v0, s2
EncodeExp0(0x0c, 0xf), EncodeExp0(0x0c, 0xf),
EncodeExp1(0, 1, 2, 3), // POS0 EncodeExp1(0, 1, 2, 3), // POS0
EncodeSopp(0x01), EncodeSopp(0x01),
@@ -9760,8 +9792,12 @@ int main() {
TestNewShaderRecompilerSetpcBranch(); TestNewShaderRecompilerSetpcBranch();
TestNewShaderRecompilerSetpcJumpTable(); TestNewShaderRecompilerSetpcJumpTable();
TestNewShaderRecompilerSetpcDwordJumpTable(); TestNewShaderRecompilerSetpcDwordJumpTable();
TestTypedEntryStateIsMinimal();
TestFinalSsaRejectsRegisterStatePseudos();
TestNewShaderRecompilerZeroInitialRegisterState(); TestNewShaderRecompilerZeroInitialRegisterState();
TestNewShaderRecompilerVertexSystemInputsWithoutMirrors();
TestNewShaderRecompilerVertexExportUsesLaneExecMask(); TestNewShaderRecompilerVertexExportUsesLaneExecMask();
TestNewShaderRecompilerPerInvocationMasksWithoutMirrors();
TestNewShaderRecompilerPerInvocationU64Complement(); TestNewShaderRecompilerPerInvocationU64Complement();
TestNewShaderRecompilerExpPixelOutputs(); TestNewShaderRecompilerExpPixelOutputs();
TestRenderTargetReverseExportMapping(); TestRenderTargetReverseExportMapping();