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);
}
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) {
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()) {
@@ -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,
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,
@@ -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 {
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 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:
@@ -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<RegisterBinding> registers;
std::vector<InputBinding> inputs;
std::vector<OutputBinding> outputs;
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> signed_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 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);
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);
@@ -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});
@@ -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();
@@ -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)});
@@ -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<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++) {
const auto reg = static_cast<IR::ScalarReg>(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<uint32_t>(cs->thread_ids_num, 3u) : 0u;
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));
}
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<IR::ScalarReg>(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<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));
}
} 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<IR::VectorReg>(reg++),
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
builtin(IR::StageInputKind::FragCoord, 0));
}
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));
}
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));
}
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));
}
if (ps->ps_front_face) {
prologue.SetVectorReg(static_cast<IR::VectorReg>(reg),
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg),
builtin(IR::StageInputKind::FrontFacing));
}
} 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));
prologue.SetVectorReg(static_cast<IR::VectorReg>(8),
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
builtin(IR::StageInputKind::InstanceIndex));
}
}
+1 -4
View File
@@ -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) {
@@ -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 &&
@@ -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:
@@ -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)
+144 -108
View File
@@ -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<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() {
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();