Skip to content

Commit 0e675ac

Browse files
committed
IR: Add SPIR-V disassembly for embedded downstream IR dumps
When dumping IR that contains embedded downstream SPIR-V code (via EmbeddedDownstreamIR instructions), display the disassembled SPIR-V instead of just showing "<binary blob>". This makes the IR dumps more readable and useful for debugging SPIR-V code generation. This CL also does: - Add SPIR-V tools integration to disassemble SPIR-V binary blobs - Add new precompiled-spirv-generics-test test to check for the disassembled SPIR-V
1 parent 6f56b47 commit 0e675ac

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

source/slang/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ set(slang_link_args
230230
slang-reflect-headers
231231
slang-lookup-tables
232232
SPIRV-Headers
233+
SPIRV-Tools
233234
)
234235
set(slang_interface_args INCLUDE_DIRECTORIES_PUBLIC ${slang_SOURCE_DIR}/include)
235236
set(slang_public_lib_args

source/slang/slang-compiler-tu.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@
1010

1111
namespace Slang
1212
{
13+
static void dumpIRIfEnabled(
14+
CodeGenContext* codeGenContext,
15+
IRModule* irModule,
16+
char const* label = nullptr)
17+
{
18+
if (codeGenContext->shouldDumpIR())
19+
{
20+
DiagnosticSinkWriter writer(codeGenContext->getSink());
21+
dumpIR(
22+
irModule,
23+
codeGenContext->getIRDumpOptions(),
24+
label,
25+
codeGenContext->getSourceManager(),
26+
&writer);
27+
}
28+
}
29+
1330
// Only attempt to precompile functions:
1431
// 1) With function bodies (not just empty decls)
1532
// 2) Not marked with unsafeForceInlineDecoration
@@ -103,6 +120,10 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
103120
applySettingsToDiagnosticSink(&sink, &sink, linkage->m_optionSet);
104121
applySettingsToDiagnosticSink(&sink, &sink, m_optionSet);
105122

123+
// Configure diagnostic writer to write directly to stderr
124+
static FileWriter stdError(stderr, WriterFlag::IsStatic | WriterFlag::IsUnowned);
125+
sink.writer = &stdError;
126+
106127
RefPtr<TargetRequest> targetReq = new TargetRequest(linkage, targetEnum);
107128

108129
List<RefPtr<ComponentType>> allComponentTypes;
@@ -143,6 +164,9 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
143164
CodeGenContext::Shared sharedCodeGenContext(&tp, entryPointIndices, &sink, nullptr);
144165
CodeGenContext codeGenContext(&sharedCodeGenContext);
145166

167+
// Dump initial IR before precompilation
168+
dumpIRIfEnabled(&codeGenContext, module, "PRECOMPILE_FOR_TARGET");
169+
146170
// Mark all public functions as exported, ensure there's at least one. Store a mapping
147171
// of function name to IRInst* for later reference. After linking is done, we'll scan
148172
// the linked result to see which functions survived the pruning and are included in the
@@ -181,6 +205,9 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
181205
return SLANG_E_NOT_AVAILABLE;
182206
}
183207

208+
// Dump IR after precompilation but before applying decorations
209+
dumpIRIfEnabled(&codeGenContext, module, "PRECOMPILE_FOR_TARGET_BEFORE_DECORATIONS");
210+
184211
for (const auto& mangledName : metadata->getExportedFunctionMangledNames())
185212
{
186213
auto moduleInst = nameToFunction[mangledName];
@@ -212,6 +239,9 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
212239
builder.setInsertInto(module);
213240

214241
builder.emitEmbeddedDownstreamIR(targetReq->getTarget(), blob);
242+
243+
// Dump final IR after all transformations
244+
dumpIRIfEnabled(&codeGenContext, module, "PRECOMPILE_FOR_TARGET_COMPLETE_ALL");
215245
return SLANG_OK;
216246
}
217247

source/slang/slang-ir.cpp

+75
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "slang-ir-util.h"
99
#include "slang-mangle.h"
1010

11+
#include "spirv-tools/libspirv.h"
12+
1113
namespace Slang
1214
{
1315
struct IRSpecContext;
@@ -7132,6 +7134,79 @@ static void dumpInstExpr(IRDumpContext* context, IRInst* inst)
71327134
}
71337135
}
71347136

7137+
// Special case EmbeddedDownstreamIR to show SPIR-V disassembly
7138+
if (op == kIROp_EmbeddedDownstreamIR)
7139+
{
7140+
auto targetInst = inst->getOperand(0);
7141+
auto blobInst = inst->getOperand(1);
7142+
7143+
// Get the target value
7144+
auto targetLit = as<IRIntLit>(targetInst);
7145+
if (!targetLit)
7146+
{
7147+
dump(context, "EmbeddedDownstreamIR(invalid target)");
7148+
return;
7149+
}
7150+
7151+
// Get the blob
7152+
auto blobLitInst = as<IRBlobLit>(blobInst);
7153+
if (!blobLitInst)
7154+
{
7155+
dump(context, "EmbeddedDownstreamIR(invalid blob)");
7156+
return;
7157+
}
7158+
7159+
dump(context, "EmbeddedDownstreamIR(");
7160+
dump(context, targetLit->getValue());
7161+
dump(context, " : Int, ");
7162+
7163+
// If target is SPIR-V (6), disassemble the blob
7164+
if (targetLit->getValue() == (IRIntegerValue)CodeGenTarget::SPIRV)
7165+
{
7166+
auto blob = blobLitInst->getStringSlice();
7167+
const uint32_t* spirvCode = (const uint32_t*)blob.begin();
7168+
const size_t spirvWordCount = blob.getLength() / sizeof(uint32_t);
7169+
7170+
// Create SPIR-V tools context
7171+
spv_context spvContext = spvContextCreate(SPV_ENV_UNIVERSAL_1_0);
7172+
spv_text text = nullptr;
7173+
spv_diagnostic diagnostic = nullptr;
7174+
7175+
// Disassemble with friendly names and comments
7176+
spv_result_t result = spvBinaryToText(
7177+
spvContext,
7178+
spirvCode,
7179+
spirvWordCount,
7180+
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_INDENT,
7181+
&text,
7182+
&diagnostic);
7183+
7184+
if (result == SPV_SUCCESS && text)
7185+
{
7186+
dump(context, "\n");
7187+
dumpIndent(context);
7188+
dump(context, text->str);
7189+
}
7190+
else
7191+
{
7192+
dump(context, "<invalid SPIR-V>");
7193+
}
7194+
7195+
// Cleanup
7196+
if (diagnostic)
7197+
spvDiagnosticDestroy(diagnostic);
7198+
if (text)
7199+
spvTextDestroy(text);
7200+
spvContextDestroy(spvContext);
7201+
}
7202+
else
7203+
{
7204+
dump(context, "<binary blob>");
7205+
}
7206+
dump(context, ")");
7207+
return;
7208+
}
7209+
71357210
// Special case the SPIR-V asm operands as the distinction here is
71367211
// clear anyway to the user
71377212
switch (op)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// This test checks the SPIR-V output when compiling a library containing generics with embedded downstream IR.
2+
3+
//TEST:SIMPLE(filecheck=CHECK): -o tests/library/export-library-generics-test.slang-module -target spirv -embed-downstream-ir -profile lib_6_6 -incomplete-library -dump-ir -verbose-paths
4+
5+
module "export-library-generics";
6+
7+
public cbuffer Constants {
8+
public float x;
9+
public float y;
10+
}
11+
12+
interface MyInterface
13+
{
14+
int myMethod(int a);
15+
}
16+
17+
struct MyType : MyInterface
18+
{
19+
int myMethod(int a)
20+
{
21+
return a * 3;
22+
}
23+
}
24+
25+
int genericFunc<T: MyInterface>(T arg)
26+
{
27+
return arg.myMethod(3);
28+
}
29+
30+
public int normalFuncUsesGeneric(int a)
31+
{
32+
MyType obj;
33+
return genericFunc(obj);
34+
}
35+
36+
public int normalFunc(int a, float b)
37+
{
38+
return a - floor(b);
39+
}
40+
41+
// CHECK:$
42+
// CHECK:let %29 : CapabilitySet = capabilityConjunction(1 : Int, 3 : Int, 64 : Int)
43+
// CHECK:let %28 : CapabilitySet = capabilityConjunction(1 : Int, 3 : Int, 63 : Int)
44+
// CHECK:let %27 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 76 : Int)
45+
// CHECK:let %26 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 75 : Int)
46+
// CHECK:let %25 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 74 : Int)
47+
// CHECK:let %24 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 73 : Int)
48+
// CHECK:let %23 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 72 : Int)
49+
// CHECK:let %22 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 71 : Int)
50+
// CHECK:let %21 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 70 : Int)
51+
// CHECK:let %20 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 69 : Int)
52+
// CHECK:let %19 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 68 : Int)
53+
// CHECK:let %18 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 67 : Int)
54+
// CHECK:let %17 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 66 : Int)
55+
// CHECK:let %16 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 65 : Int)
56+
// CHECK:let %15 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 64 : Int)
57+
// CHECK:let %14 : CapabilitySet = capabilityConjunction(1 : Int, 2 : Int, 63 : Int)
58+
// CHECK:[public]
59+
// CHECK:[export("_STR29export_2Dxlibrary_2Dxgenerics30SLANG_ParameterGroup_Constants")]
60+
// CHECK:[nameHint("SLANG_ParameterGroup_Constants")]
61+
// CHECK:struct %SLANGx5FParameterGroupx5FConstants : Type
62+
// CHECK:{
63+
// CHECK: field(%x, Float)
64+
// CHECK: field(%y, Float)
65+
// CHECK:}
66+
// CHECK:[public]
67+
// CHECK:[export("_SVR29export_2Dxlibrary_2Dxgenerics30SLANG_ParameterGroup_Constants1x")]
68+
// CHECK:[nameHint("x")]
69+
// CHECK:let %x : _ = key
70+
// CHECK:[public]
71+
// CHECK:[export("_SVR29export_2Dxlibrary_2Dxgenerics30SLANG_ParameterGroup_Constants1y")]
72+
// CHECK:[nameHint("y")]
73+
// CHECK:let %y : _ = key
74+
// CHECK:let %6 : witness_table_t(%x5Fx5FBuiltinFloatingPointType) = thisTypeWitness
75+
// CHECK:let %126 : _ = interface_req_entry(%127, witness_table_t(%x5Fx5FBuiltinRealType))
76+
// CHECK:let %128 : _ = interface_req_entry(%7, witness_table_t(%IFloat))
77+
// CHECK:let %129 : _ = interface_req_entry(%130, Func(this_type(%x5Fx5FBuiltinFloatingPointType)))
78+
// CHECK:[availableInDownstreamIR(6 : Int)]
79+
// CHECK:[treatAsDifferentiableDecoration]
80+
// CHECK:[constructor(true)]
81+
// CHECK:[method]
82+
// CHECK:[public]
83+
// CHECK:[export("_SR29export_2Dxlibrary_2Dxgenerics30SLANG_ParameterGroup_ConstantsR8_24xinitp2pi_fi_f")]
84+
// CHECK:[nameHint("SLANG_ParameterGroup_Constants.$init")]
85+
// CHECK:func %SLANGx5FParameterGroupx5FConstantsx5Fx24init : Func(%SLANGx5FParameterGroupx5FConstants, Float, Float)
86+
// CHECK:EmbeddedDownstreamIR(6 : Int,
87+
// CHECK:; SPIR-V
88+
// CHECK:; Version: 1.5
89+
// CHECK:; Generator: Khronos; 40
90+
// CHECK:; Bound: 61
91+
// CHECK:; Schema: 0
92+
// CHECK: OpCapability Linkage
93+
// CHECK: OpCapability Shader
94+
// CHECK: %44 = OpExtInstImport "GLSL.std.450"
95+
// CHECK: OpMemoryModel Logical GLSL450
96+
// CHECK: OpSource Slang 1
97+
// CHECK: OpName %SLANG_ParameterGroup_Constants "SLANG_ParameterGroup_Constants"
98+
// CHECK: OpMemberName %SLANG_ParameterGroup_Constants 0 "x"
99+
// CHECK: OpMemberName %SLANG_ParameterGroup_Constants 1 "y"
100+
// CHECK: OpName %x "x"
101+
// CHECK: OpName %y "y"
102+
// CHECK: OpName %SLANG_ParameterGroup_Constants__init "SLANG_ParameterGroup_Constants.$init"

0 commit comments

Comments
 (0)