shader: map debug SPIR-V blocks to native RDNA2 PCs

This commit is contained in:
nmzik
2026-10-14 22:58:17 +02:00
parent c1f6838e40
commit 50844badbf
10 changed files with 76 additions and 5 deletions
@@ -394,6 +394,7 @@ struct PipelineCache::ProgramCache {
options.back_code = params.back_code;
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
options.early_dump = options.dump_ir;
options.emit_debug_info = Config::GraphicsDebugDumpEnabled();
options.dump_label = label;
options.input_info = stage_input;
@@ -887,7 +887,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword,
options.maintenance8_enabled);
options.maintenance8_enabled, options.emit_debug_info);
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
" elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
@@ -21,6 +21,7 @@ struct CompileOptions {
uint64_t shader_hash = 0;
bool dump_ir = true;
bool early_dump = false;
bool emit_debug_info = false;
bool maintenance8_enabled = false;
const char* dump_label = nullptr;
std::span<const uint32_t> user_data;
@@ -160,6 +160,14 @@ void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_
AppendInstruction(m_entry_points, spv::OpEntryPoint, operands);
}
uint32_t Builder::AddString(const char* text) {
const auto id = AllocateId();
std::vector<uint32_t> operands = {id};
AppendString(operands, text);
AppendInstruction(m_debug, spv::OpString, operands);
return id;
}
void Builder::AddName(uint32_t target, const char* name) {
std::vector<uint32_t> operands = {target};
AppendString(operands, name);
@@ -66,6 +66,7 @@ public:
AppendInstruction(m_execution_modes, spv::OpExecutionMode, entry_point, mode, operands...);
}
uint32_t AddString(const char* text);
void AddName(uint32_t target, const char* name);
template <typename... Args>
void AddAnnotation(spv::Op opcode, const Args&... operands) {
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <cstdio>
namespace Libs::Graphics::ShaderRecompiler::Spirv {
@@ -203,7 +204,8 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
} // namespace
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
uint32_t push_data_start_dword, bool maintenance8_enabled) {
uint32_t push_data_start_dword, bool maintenance8_enabled,
bool emit_debug_info) {
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
@@ -219,6 +221,12 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
IR::ValidateProgram(program, true);
program.bindings = {.push_data_start_dword = push_data_start_dword};
EmitterState state(program, input_info, maintenance8_enabled);
if (emit_debug_info) {
char name[64];
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
program.shader_hash);
state.debug_source = state.builder.AddString(name);
}
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
state.lane_count =
workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u
@@ -12,7 +12,8 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
// Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently.
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
uint32_t push_data_start_dword = 0,
bool maintenance8_enabled = false);
bool maintenance8_enabled = false,
bool emit_debug_info = false);
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -89,6 +89,7 @@ struct EmitterState {
IR::Program& program;
ShaderStageInputInfo input_info;
bool maintenance8_enabled;
uint32_t debug_source = 0;
uint32_t void_type = 0;
uint32_t bool_type = 0;
uint32_t u32_type = 0;
@@ -182,6 +183,11 @@ uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_cl
inline void EmitLabel(EmitterState& state, uint32_t label) {
state.current_label = label;
state.builder.AddFunction(spv::OpLabel, label);
if (state.debug_source != 0 && state.current_block != nullptr) {
// Source lines encode the containing native block's byte offset plus one.
state.builder.AddFunction(spv::OpLine, state.debug_source,
state.current_block->start_pc + 1u, 0u);
}
}
uint32_t TypeId(EmitterState& state, IR::Type type);
@@ -348,6 +348,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
const auto initial_parent = state.current_label;
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
state.current_block = nullptr;
EmitLabel(state, dispatcher.header_label);
const auto pc = state.builder.AllocateId();
const auto next_pc = state.builder.AllocateId();
@@ -383,6 +384,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
next_pc_words.push_back(state.current_label);
state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label);
}
state.current_block = nullptr;
EmitLabel(state, dispatcher.after_switch_label);
state.builder.AddFunction(next_pc_words);
state.builder.AddFunction(spv::OpBranch, dispatcher.continue_label);
@@ -747,6 +749,7 @@ void EmitProgram(EmitterState& state) {
EmitStructuredFunction(ctx);
}
state.builder.AddFunction(spv::OpFunctionEnd);
state.current_block = nullptr;
if (state.program.stage == ShaderType::Mesh) {
EmitMeshEntryPoint(state);
}
+44 -2
View File
@@ -15711,6 +15711,46 @@ void TestRepeatedExportsHaveOneInterface() {
"repeated exports duplicated Position or its clipping-error plane");
}
void CheckSpirvBlockSourceMapping(TestCompileResult &result) {
using namespace ShaderRecompiler;
const auto options = MakeCompileOptions(ShaderType::Compute);
Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpString) == 0 &&
SpirvInstructionOpcodeCount(result.spirv, spv::OpLine) == 0,
"default emission unexpectedly contains source mapping");
const auto mapped = Spirv::EmitProgram(result.program, options.input_info, 0,
false, true);
CheckSpirvBinaryValidates(mapped);
uint32_t source = 0;
uint32_t label = 0;
size_t lines = 0;
uint32_t previous = spv::OpNop;
for (size_t at = 5; at < mapped.size();) {
const auto op = mapped[at] & spv::OpCodeMask;
if (op == spv::OpLabel) label = mapped[at + 1];
if (op == spv::OpString) {
source = mapped[at + 1];
Check(std::string(reinterpret_cast<const char *>(&mapped[at + 2]))
.find("-block-byte-pc-plus-one") != std::string::npos,
"debug source filename does not identify block offset encoding");
} else if (op == spv::OpLine) {
Check(previous == spv::OpLabel && mapped[at + 1] == source &&
mapped[at + 3] == 0 &&
std::ranges::any_of(result.program.blocks, [&](const auto *block) {
return block->Definition() == label &&
mapped[at + 2] == block->start_pc + 1u;
}),
"debug source line does not identify a native block after its label");
++lines;
}
previous = op;
at += mapped[at] >> spv::WordCountShift;
}
Check(source != 0 && lines >= result.program.blocks.size(),
"debug emission omitted native block source mapping");
Check(Spirv::EmitProgram(result.program, options.input_info) == result.spirv,
"debug re-emission changed the default binary");
}
void TestNewShaderRecompilerSpirvSizeBaselines() {
const auto compile = [](const char *name, std::span<const uint32_t> shader,
const SpirvMetrics &budget,
@@ -15755,7 +15795,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
EncodeSopp(0x02, 0xfffcu), // continue/backedge
EncodeSopp(0x01),
};
const auto structured_result = compile("structured-phi", structured_phi,
auto structured_result = compile("structured-phi", structured_phi,
{.words = 174,
.instructions = 49,
.phis = 1,
@@ -15772,6 +15812,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
structured_metrics.loads == 0u && structured_metrics.stores == 0u,
"structured Phi retained a spill variable or edge memory operation");
CheckSpirvPhiParents(structured_result.spirv);
CheckSpirvBlockSourceMapping(structured_result);
const auto structured_repeat =
compile("structured-phi-repeat", structured_phi,
{.words = 174,
@@ -15942,7 +15983,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
EncodeSopp(0x02, 0xfffeu), // B -> C
EncodeSopp(0x01),
};
const auto dispatcher_result = compile("dispatcher", dispatcher,
auto dispatcher_result = compile("dispatcher", dispatcher,
{.words = 255,
.instructions = 70,
.variables = 3,
@@ -15963,6 +16004,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 251u) == 1u,
"dispatcher size fixture lost its two control Phis or switch");
CheckSpirvPhiParents(dispatcher_result.spirv);
CheckSpirvBlockSourceMapping(dispatcher_result);
}
#include "ShaderRayTracingTests.inc"