Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IR: Add SPIR-V disassembly for embedded downstream IR dumps #6529

Merged
merged 7 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions source/compiler-core/slang-downstream-compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ class IDownstreamCompiler : public ICastable
/// Disassemble and print to stdout
virtual SLANG_NO_THROW SlangResult SLANG_MCALL
disassemble(const uint32_t* contents, int contentsSize) = 0;
/// Disassemble and return the result as a string
virtual SLANG_NO_THROW SlangResult SLANG_MCALL
disassembleWithResult(const uint32_t* contents, int contentsSize, String& outString) = 0;

/// True if underlying compiler uses file system to communicate source
virtual SLANG_NO_THROW bool SLANG_MCALL isFileBased() = 0;
Expand Down Expand Up @@ -398,6 +401,17 @@ class DownstreamCompilerBase : public ComBaseObject, public IDownstreamCompiler
return SLANG_FAIL;
}

virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassembleWithResult(
const uint32_t* contents,
int contentsSize,
String& outString) SLANG_OVERRIDE
{
SLANG_UNUSED(contents);
SLANG_UNUSED(contentsSize);
SLANG_UNUSED(outString);
return SLANG_FAIL;
}

DownstreamCompilerBase(const Desc& desc)
: m_desc(desc)
{
Expand Down
29 changes: 29 additions & 0 deletions source/compiler-core/slang-glslang-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class GlslangDownstreamCompiler : public DownstreamCompilerBase
validate(const uint32_t* contents, int contentsSize) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL
disassemble(const uint32_t* contents, int contentsSize) SLANG_OVERRIDE;
virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassembleWithResult(
const uint32_t* contents,
int contentsSize,
String& outString) SLANG_OVERRIDE;
int link(
const uint32_t** modules,
const uint32_t* moduleSizes,
Expand All @@ -71,6 +75,7 @@ class GlslangDownstreamCompiler : public DownstreamCompilerBase
glslang_CompileFunc_1_2 m_compile_1_2 = nullptr;
glslang_ValidateSPIRVFunc m_validate = nullptr;
glslang_DisassembleSPIRVFunc m_disassemble = nullptr;
glslang_DisassembleSPIRVWithResultFunc m_disassembleWithResult = nullptr;
glslang_LinkSPIRVFunc m_link = nullptr;

ComPtr<ISlangSharedLibrary> m_sharedLibrary;
Expand All @@ -86,6 +91,8 @@ SlangResult GlslangDownstreamCompiler::init(ISlangSharedLibrary* library)
m_validate = (glslang_ValidateSPIRVFunc)library->findFuncByName("glslang_validateSPIRV");
m_disassemble =
(glslang_DisassembleSPIRVFunc)library->findFuncByName("glslang_disassembleSPIRV");
m_disassembleWithResult = (glslang_DisassembleSPIRVWithResultFunc)library->findFuncByName(
"glslang_disassembleSPIRVWithResult");
m_link = (glslang_LinkSPIRVFunc)library->findFuncByName("glslang_linkSPIRV");

if (m_compile_1_0 == nullptr && m_compile_1_1 == nullptr && m_compile_1_2 == nullptr)
Expand Down Expand Up @@ -316,6 +323,28 @@ SlangResult GlslangDownstreamCompiler::validate(const uint32_t* contents, int co
return SLANG_FAIL;
}

SlangResult GlslangDownstreamCompiler::disassembleWithResult(
const uint32_t* contents,
int contentsSize,
String& outString)
{
if (m_disassembleWithResult == nullptr)
{
return SLANG_FAIL;
}

char* resultString = nullptr;
if (m_disassembleWithResult(contents, contentsSize, &resultString))
{
if (resultString)
{
outString = String(resultString);
return SLANG_OK;
}
}
return SLANG_FAIL;
}

SlangResult GlslangDownstreamCompiler::disassemble(const uint32_t* contents, int contentsSize)
{
if (m_disassemble == nullptr)
Expand Down
42 changes: 35 additions & 7 deletions source/slang-glslang/slang-glslang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,36 +184,64 @@ extern "C"
return tools.Validate(contents, contentsSize, options);
}

// Disassemble the given SPIRV-ASM instructions.
// Disassemble the given SPIRV-ASM instructions and return the result as a string.
extern "C"
#ifdef _MSC_VER
_declspec(dllexport)
#else
__attribute__((__visibility__("default")))
__attribute__((__visibility__("default")))
#endif
bool glslang_disassembleSPIRV(const uint32_t* contents, int contentsSize)
bool glslang_disassembleSPIRVWithResult(
const uint32_t* contents,
int contentsSize,
char** outString)
{
static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
spv_text text;

uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NONE;
options |= SPV_BINARY_TO_TEXT_OPTION_COMMENT;
options |= SPV_BINARY_TO_TEXT_OPTION_PRINT;
options |= SPV_BINARY_TO_TEXT_OPTION_COLOR;
options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;

spv_diagnostic diagnostic = nullptr;
spv_context context = spvContextCreate(kDefaultEnvironment);
spv_result_t error =
spvBinaryToText(context, contents, contentsSize, options, nullptr, &diagnostic);
spvBinaryToText(context, contents, contentsSize, options, &text, &diagnostic);
spvContextDestroy(context);
if (error)
{
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
return false;
}
else
{
if (outString)
{
// Allocate memory for the output string and copy the result
size_t len = text->length + 1; // +1 for null terminator
*outString = new char[len];
memcpy(*outString, text->str, text->length);
(*outString)[text->length] = '\0'; // Ensure null termination
}

spvTextDestroy(text);
return true;
}
}


return true;
// Disassemble the given SPIRV-ASM instructions.
extern "C"
#ifdef _MSC_VER
_declspec(dllexport)
#else
__attribute__((__visibility__("default")))
#endif
bool glslang_disassembleSPIRV(const uint32_t* contents, int contentsSize)
{
return glslang_disassembleSPIRVWithResult(contents, contentsSize, nullptr);
}

// Apply the SPIRV-Tools optimizer to generated SPIR-V based on the desired optimization level
Expand Down
4 changes: 4 additions & 0 deletions source/slang-glslang/slang-glslang.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,9 @@ typedef int (*glslang_CompileFunc_1_1)(glslang_CompileRequest_1_1* request);
typedef int (*glslang_CompileFunc_1_2)(glslang_CompileRequest_1_2* request);
typedef bool (*glslang_ValidateSPIRVFunc)(const uint32_t* contents, int contentsSize);
typedef bool (*glslang_DisassembleSPIRVFunc)(const uint32_t* contents, int contentsSize);
typedef bool (*glslang_DisassembleSPIRVWithResultFunc)(
const uint32_t* contents,
int contentsSize,
char** outString);
typedef bool (*glslang_LinkSPIRVFunc)(glslang_LinkRequest* request);
#endif
11 changes: 10 additions & 1 deletion source/slang-llvm/slang-llvm.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#include "clang/Basic/Stack.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/Version.h"
Expand Down Expand Up @@ -147,6 +146,16 @@ class LLVMDownstreamCompiler : public ComBaseObject, public IDownstreamCompiler
SLANG_UNUSED(contentsSize);
return SLANG_FAIL;
}
virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassembleWithResult(
const uint32_t* contents,
int contentsSize,
String& outString) SLANG_OVERRIDE
{
SLANG_UNUSED(contents);
SLANG_UNUSED(contentsSize);
SLANG_UNUSED(outString);
return SLANG_FAIL;
}

LLVMDownstreamCompiler()
: m_desc(
Expand Down
77 changes: 77 additions & 0 deletions source/slang/slang-ir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7117,6 +7117,76 @@ void dumpIRGeneric(IRDumpContext* context, IRGeneric* witnessTable)
dump(context, "}\n");
}

static void dumpEmbeddedDownstream(IRDumpContext* context, IRInst* inst)
{
auto targetInst = inst->getOperand(0);
auto blobInst = inst->getOperand(1);

// Get the target value
auto targetLit = as<IRIntLit>(targetInst);
if (!targetLit)
{
dump(context, "EmbeddedDownstreamIR(invalid target)");
return;
}

// Get the blob
auto blobLitInst = as<IRBlobLit>(blobInst);
if (!blobLitInst)
{
dump(context, "EmbeddedDownstreamIR(invalid blob)");
return;
}

dump(context, "EmbeddedDownstreamIR(");
dump(context, targetLit->getValue());
dump(context, " : Int, ");

// If target is SPIR-V (6), disassemble the blob
if (targetLit->getValue() == (IRIntegerValue)CodeGenTarget::SPIRV)
{
auto blob = blobLitInst->getStringSlice();
const uint32_t* spirvCode = (const uint32_t*)blob.begin();
const size_t spirvWordCount = blob.getLength() / sizeof(uint32_t);

// Get the compiler from the session through the module
auto module = inst->getModule();
auto session = module->getSession();
IDownstreamCompiler* compiler =
session->getOrLoadDownstreamCompiler(PassThroughMode::SpirvDis, nullptr);

if (compiler)
{
// Use glslang interface to disassemble with string output
String disassemblyOutput;
if (SLANG_SUCCEEDED(compiler->disassembleWithResult(
spirvCode,
int(spirvWordCount),
disassemblyOutput)))
{
// Dump the captured disassembly
dump(context, "\n");
dumpIndent(context);
dump(context, disassemblyOutput);
}
else
{
dump(context, "<disassembly failed>");
}
}
else
{
dump(context, "<unavailable disassembler>");
}
}
else
{
// TODO: Add DXIL disassembly call here.
dump(context, "<binary blob>");
}
dump(context, ")");
}

static void dumpInstExpr(IRDumpContext* context, IRInst* inst)
{
if (!inst)
Expand Down Expand Up @@ -7166,6 +7236,13 @@ static void dumpInstExpr(IRDumpContext* context, IRInst* inst)
}
}

// Special case EmbeddedDownstreamIR to show SPIR-V disassembly
if (op == kIROp_EmbeddedDownstreamIR)
{
dumpEmbeddedDownstream(context, inst);
return;
}

// Special case the SPIR-V asm operands as the distinction here is
// clear anyway to the user
switch (op)
Expand Down
18 changes: 18 additions & 0 deletions source/slang/slang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3747,6 +3747,24 @@ SlangResult EndToEndCompileRequest::executeActionsInner()
{
SLANG_RETURN_ON_FAIL(
translationUnit->getModule()->precompileForTarget(targetEnum, nullptr));

if (frontEndReq->optionSet.shouldDumpIR())
{
DiagnosticSinkWriter writer(frontEndReq->getSink());

dumpIR(
translationUnit->getModule()->getIRModule(),
frontEndReq->m_irDumpOptions,
"PRECOMPILE_FOR_TARGET_COMPLETE_ALL",
frontEndReq->getSourceManager(),
&writer);

dumpIR(
translationUnit->getModule()->getIRModule()->getModuleInst(),
frontEndReq->m_irDumpOptions,
frontEndReq->getSourceManager(),
&writer);
}
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion tests/library/export-library-generics.slang
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//TEST_IGNORE_FILE:
// This test checks the SPIR-V output when compiling a library containing generics with embedded downstream IR.

//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

// export-library-generics.slang

Expand Down Expand Up @@ -37,3 +39,10 @@ public int normalFunc(int a, float b)
{
return a - floor(b);
}

// CHECK:[availableInDownstreamIR(6 : Int)]
// CHECK:EmbeddedDownstreamIR(6 : Int,
// CHECK: OpCapability Linkage
// CHECK:OpDecorate %SLANG_ParameterGroup_Constants__init LinkageAttributes "_SR29export_2Dxlibrary_2Dxgenerics30SLANG_ParameterGroup_ConstantsR8_24xinitp2pi_fi_f" Export
// CHECK: OpDecorate %MyType_myMethod LinkageAttributes "_SR29export_2Dxlibrary_2Dxgenerics6MyType8myMethodp1pi_ii" Export
// CHECK: OpDecorate %normalFuncUsesGeneric LinkageAttributes "_SR29export_2Dxlibrary_2Dxgenerics21normalFuncUsesGenericp1pi_ii" Export
45 changes: 45 additions & 0 deletions tests/modules/multi-target-module.slang
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// multi-target-module.slang
// Test that a slang-module can store both SPIR-V and DXIL blobs separately

//TEST:SIMPLE(filecheck=CHECK): -o tests/modules/multi-target-module.slang-module -target dxil -embed-downstream-ir -target spirv -embed-downstream-ir -profile lib_6_6 -incomplete-library -dump-ir -verbose-paths

module multi_target_module;

// Simple function that will work on both SPIR-V and DXIL targets
public float4 addVectors(float4 a, float4 b)
{
return a + b;
}

// Another function that should be compatible with both targets
public float3 normalizeVector(float3 v)
{
return normalize(v);
}

[shader("compute")]
[numthreads(8, 8, 1)]
void main(uint3 dispatchThreadID : SV_DispatchThreadID)
{
float4 a = float4(1.0, 2.0, 3.0, 4.0);
float4 b = float4(5.0, 6.0, 7.0, 8.0);

float4 result = addVectors(a, b);

float3 v = float3(1.0, 1.0, 1.0);
float3 n = normalizeVector(v);
}

// Check for the first occurrence of availableInDownstreamIR for addVectors in this section
// Check that there are two entries, one for dxil and one for spirv.
// CHECK: [availableInDownstreamIR(6 : Int)]
// CHECK: [availableInDownstreamIR(10 : Int)]
// CHECK: [public]
// CHECK: [export("_S19multi_target_module10addVectorsp2pi_v4fi_v4fv4f")]

// Check for the second occurrence of availableInDownstreamIR for normalizeVector in this section
// Check that there are two entries, one for dxil and one for spirv.
// CHECK: [availableInDownstreamIR(6 : Int)]
// CHECK: [availableInDownstreamIR(10 : Int)]
// CHECK: [public]
// CHECK: [export("_S19multi_target_module15normalizeVectorp1pi_v3fv3f")]
Loading