From 3bc5d9c1d56ad317f6b4120d147cf0638bbb167d Mon Sep 17 00:00:00 2001 From: acheney Date: Tue, 18 Mar 2025 11:25:20 -0400 Subject: [PATCH 1/4] Add -dump-module command to slangc The new -dump-module command to slangc will load and disassemble a slang module, similar to what would be seen by the -dump-ir command, except that -dump-ir tells slangc to print IR as it performs some compilation command. That is, -dump-ir requires some larger compilation task. -dump-module on the otherhand requires no additional goal and will simply load a module and print its IR to stdout independently from other compilation steps. Its intended purpose is to inspect .slang-module files on disk. It can also be used on .slang files which will be parsed and lowered if slang does not find an associated ".slang-module" version of the module on disk. The compilation API is extended with a new IModule::disassemble() method which retrieves the string representation of the dumped IR. Closes #6599 --- include/slang.h | 6 ++ .../record/slang-module.cpp | 8 ++ .../slang-record-replay/record/slang-module.h | 1 + source/slang/slang-compiler.h | 13 ++++ source/slang/slang-ir.cpp | 23 ++++-- source/slang/slang-ir.h | 6 +- source/slang/slang-options.cpp | 78 ++++++++++++++++++- tests/ir/dump-module.slang | 53 +++++++++++++ 8 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 tests/ir/dump-module.slang diff --git a/include/slang.h b/include/slang.h index aab6fc47cb..d0917b14fd 100644 --- a/include/slang.h +++ b/include/slang.h @@ -1015,6 +1015,7 @@ typedef uint32_t SlangSizeT; SaveGLSLModuleBinSource, SkipDownstreamLinking, // bool, experimental + DumpModule, CountOf, }; @@ -4417,6 +4418,11 @@ struct IModule : public IComponentType virtual SLANG_NO_THROW char const* SLANG_MCALL getDependencyFilePath(SlangInt32 index) = 0; virtual SLANG_NO_THROW DeclReflection* SLANG_MCALL getModuleReflection() = 0; + + /** Disassemble a module. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassemble( + slang::IBlob** outDisassembledBlob) = 0; }; #define SLANG_UUID_IModule IModule::getTypeGuid() diff --git a/source/slang-record-replay/record/slang-module.cpp b/source/slang-record-replay/record/slang-module.cpp index 2071a84917..f17a62360f 100644 --- a/source/slang-record-replay/record/slang-module.cpp +++ b/source/slang-record-replay/record/slang-module.cpp @@ -244,4 +244,12 @@ IEntryPointRecorder* ModuleRecorder::getEntryPointRecorder(slang::IEntryPoint* e return result.detach(); } } + +SlangResult ModuleRecorder::disassemble(ISlangBlob** outBlob) +{ + // No need to record this call as it is just a query. + slangRecordLog(LogLevel::Verbose, "%s\n", __PRETTY_FUNCTION__); + auto res = m_actualModule->disassemble(outBlob); + return res; +} } // namespace SlangRecord diff --git a/source/slang-record-replay/record/slang-module.h b/source/slang-record-replay/record/slang-module.h index 7608ac52d6..aceb8941b2 100644 --- a/source/slang-record-replay/record/slang-module.h +++ b/source/slang-record-replay/record/slang-module.h @@ -56,6 +56,7 @@ class ModuleRecorder : public IModuleRecorder, public IComponentTypeRecorder ISlangBlob** outDiagnostics) override; virtual SLANG_NO_THROW SlangInt32 SLANG_MCALL getDependencyFileCount() override; virtual SLANG_NO_THROW char const* SLANG_MCALL getDependencyFilePath(SlangInt32 index) override; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassemble(slang::IBlob** outDisassembly) override; // Interfaces for `IComponentType` virtual SLANG_NO_THROW slang::ISession* SLANG_MCALL getSession() override diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index cfcbe816f3..316b210095 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1832,6 +1832,19 @@ class Module : public ComponentType, public slang::IModule // Source files that have been pulled into the module with `__include`. Dictionary m_mapSourceFileToFileDecl; + + public: + SLANG_NO_THROW SlangResult SLANG_MCALL disassemble( + slang::IBlob** outDisassembledBlob) override + { + if (!outDisassembledBlob) + return SLANG_E_INVALID_ARG; + String disassembly; + this->getIRModule()->getModuleInst()->dump(disassembly); + auto blob = StringUtil::createStringBlob(disassembly); + *outDisassembledBlob = blob.detach(); + return SLANG_OK; + } }; typedef Module LoadedModule; diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index 92e51fe3b9..d82340b947 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -8950,24 +8950,33 @@ void IRInst::addBlock(IRBlock* block) block->insertAtEnd(this); } -void IRInst::dump() -{ +void IRInst::dump(String &outStr) +{ + StringBuilder sb; + if (auto intLit = as(this)) { - std::cout << intLit->getValue() << std::endl; + sb << intLit->getValue(); } else if (auto stringLit = as(this)) { - std::cout << stringLit->getStringSlice().begin() << std::endl; + sb << stringLit->getStringSlice(); } else - { - StringBuilder sb; + { IRDumpOptions options; StringWriter writer(&sb, Slang::WriterFlag::AutoFlush); dumpIR(this, options, nullptr, &writer); - std::cout << sb.toString().begin() << std::endl; } + + outStr = sb.toString(); +} + +void IRInst::dump() +{ + String s; + dump(s); + std::cout << s.begin() << std::endl; } } // namespace Slang diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index aa74c07044..32dca99a34 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -813,10 +813,14 @@ struct IRInst /// void _insertAt(IRInst* inPrev, IRInst* inNext, IRInst* inParent); - /// Print the IR to stdout for debugging purposes + /// Print the IR to stdout for debugging purposes. /// void dump(); + /// Print the IR to a string for debugging purposes. + /// + void dump(String &outStr); + /// Insert a basic block at the end of this func/code containing inst. void addBlock(IRBlock* block); diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 977cf322cc..ef7a249d35 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -802,7 +802,8 @@ void initCommandOptions(CommandOptions& options) {OptionKind::VerifyDebugSerialIr, "-verify-debug-serial-ir", nullptr, - "Verify IR in the front-end."}}; + "Verify IR in the front-end."}, + {OptionKind::DumpModule, "-dump-module", nullptr, "Disassemble and print the module IR."}}; _addOptions(makeConstArrayView(debuggingOpts), options); /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Experimental !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ @@ -2947,6 +2948,81 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) Int index = 0; SLANG_RETURN_ON_FAIL(_expectInt(arg, index)); linkage->m_optionSet.add(OptionKind::BindlessSpaceIndex, (int)index); + break; + } + case OptionKind::DumpModule: + { + CommandLineArg fileName; + SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName)); + auto desc = slang::SessionDesc(); + ComPtr session; + m_session->createSession(desc, session.writeRef()); + ComPtr diagnostics; + + // Coerce Slang to load from the given file, without letting it automatically + // choose .slang-module files over .slang files. + // First try to load as source string, and fall back to loading as an IR Blob. + // Avoid guessing based on filename or inspect the file contents. + FILE* file; + fopen_s(&file, fileName.value.getBuffer(), "rb"); + if (!file) + { + m_sink->diagnose(arg.loc, Diagnostics::cannotOpenFile, fileName.value); + return SLANG_FAIL; + } + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, 0, SEEK_SET); + std::vector buffer(size+1); + size_t result = fread(buffer.data(), 1, size, file); + if (result != size) + { + m_sink->diagnoseRaw(Severity::Error, "Failed to read file"); + return SLANG_FAIL; + } + buffer[size] = 0; + fclose(file); + + ComPtr module; + module = session->loadModuleFromSourceString("module", "path", buffer.data(), diagnostics.writeRef()); + if (!module) + { + // Load buffer as an IR blob + ComPtr blob; + blob = RawBlob::create(buffer.data(), size); + + module = session->loadModuleFromIRBlob( + "module", + "path", + blob, + diagnostics.writeRef()); + } + + if (module) + { + ComPtr disassemblyBlob; + if (SLANG_FAILED(module->disassemble(disassemblyBlob.writeRef()))) + { + m_sink->diagnose(arg.loc, Diagnostics::cannotDisassemble, fileName.value); + return SLANG_FAIL; + } + else + { + m_sink->diagnoseRaw(Severity::Note, (const char*)disassemblyBlob->getBufferPointer()); + } + } + else + { + if (diagnostics) + { + m_sink->diagnoseRaw( + Severity::Error, + (const char*)diagnostics->getBufferPointer()); + } + return SLANG_FAIL; + } + + break; } default: diff --git a/tests/ir/dump-module.slang b/tests/ir/dump-module.slang new file mode 100644 index 0000000000..e22c3fca5d --- /dev/null +++ b/tests/ir/dump-module.slang @@ -0,0 +1,53 @@ +// This tests slangc's -dump-module command. +// Dumping ".slang" should mean just that, and it should not automatically load up +// a ".slang-module" when available, because the intent of the -dump-module command +// is to see the file you requested. If there's a bug in slang-module output, it's +// important that -dump-module looks at the specific file you requested. + +//TEST:COMPILE: tests/ir/dump-module.slang -o tests/ir/dump-module.slang-module -target spirv -embed-downstream-ir + +//TEST:SIMPLE(filecheck=CHECK1): -dump-module tests/ir/dump-module.slang-module +//TEST:SIMPLE(filecheck=CHECK2): -dump-module tests/ir/dump-module.slang + +module "export-library-generics"; + +public cbuffer Constants { + public float x; + public float y; +} + +interface MyInterface +{ + int myMethod(int a); +} + +struct MyType : MyInterface +{ + int myMethod(int a) + { + return a * 3; + } +} + +int genericFunc(T arg) +{ + return arg.myMethod(3); +} + +public int normalFuncUsesGeneric(int a) +{ + MyType obj; + return genericFunc(obj); +} + +public int normalFunc(int a, float b) +{ + return a - floor(b); +} + +// CHECK1:EmbeddedDownstreamIR(6 : Int, +// CHECK1: OpCapability Linkage + +// CHECK2-NOT:EmbeddedDownstreamIR(6 : Int, +// CHECK2-NOT: OpCapability Linkage + From 3d3af5afeafcbc0a768f4bc36af8d7ac2c34c0f8 Mon Sep 17 00:00:00 2001 From: slangbot <186143334+slangbot@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:09:47 +0000 Subject: [PATCH 2/4] format code --- include/slang.h | 4 +-- .../record/slang-module.cpp | 2 +- .../slang-record-replay/record/slang-module.h | 3 +- source/slang/slang-compiler.h | 23 ++++++++------- source/slang/slang-ir.cpp | 10 +++---- source/slang/slang-ir.h | 2 +- source/slang/slang-options.cpp | 28 +++++++++++-------- 7 files changed, 39 insertions(+), 33 deletions(-) diff --git a/include/slang.h b/include/slang.h index d0917b14fd..10f4e74742 100644 --- a/include/slang.h +++ b/include/slang.h @@ -4421,8 +4421,8 @@ struct IModule : public IComponentType /** Disassemble a module. */ - virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassemble( - slang::IBlob** outDisassembledBlob) = 0; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL + disassemble(slang::IBlob** outDisassembledBlob) = 0; }; #define SLANG_UUID_IModule IModule::getTypeGuid() diff --git a/source/slang-record-replay/record/slang-module.cpp b/source/slang-record-replay/record/slang-module.cpp index f17a62360f..628a29c90e 100644 --- a/source/slang-record-replay/record/slang-module.cpp +++ b/source/slang-record-replay/record/slang-module.cpp @@ -249,7 +249,7 @@ SlangResult ModuleRecorder::disassemble(ISlangBlob** outBlob) { // No need to record this call as it is just a query. slangRecordLog(LogLevel::Verbose, "%s\n", __PRETTY_FUNCTION__); - auto res = m_actualModule->disassemble(outBlob); + auto res = m_actualModule->disassemble(outBlob); return res; } } // namespace SlangRecord diff --git a/source/slang-record-replay/record/slang-module.h b/source/slang-record-replay/record/slang-module.h index aceb8941b2..d9c83576db 100644 --- a/source/slang-record-replay/record/slang-module.h +++ b/source/slang-record-replay/record/slang-module.h @@ -56,7 +56,8 @@ class ModuleRecorder : public IModuleRecorder, public IComponentTypeRecorder ISlangBlob** outDiagnostics) override; virtual SLANG_NO_THROW SlangInt32 SLANG_MCALL getDependencyFileCount() override; virtual SLANG_NO_THROW char const* SLANG_MCALL getDependencyFilePath(SlangInt32 index) override; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL disassemble(slang::IBlob** outDisassembly) override; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL + disassemble(slang::IBlob** outDisassembly) override; // Interfaces for `IComponentType` virtual SLANG_NO_THROW slang::ISession* SLANG_MCALL getSession() override diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 316b210095..d4e05bc48e 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1833,18 +1833,17 @@ class Module : public ComponentType, public slang::IModule // Source files that have been pulled into the module with `__include`. Dictionary m_mapSourceFileToFileDecl; - public: - SLANG_NO_THROW SlangResult SLANG_MCALL disassemble( - slang::IBlob** outDisassembledBlob) override - { - if (!outDisassembledBlob) - return SLANG_E_INVALID_ARG; - String disassembly; - this->getIRModule()->getModuleInst()->dump(disassembly); - auto blob = StringUtil::createStringBlob(disassembly); - *outDisassembledBlob = blob.detach(); - return SLANG_OK; - } +public: + SLANG_NO_THROW SlangResult SLANG_MCALL disassemble(slang::IBlob** outDisassembledBlob) override + { + if (!outDisassembledBlob) + return SLANG_E_INVALID_ARG; + String disassembly; + this->getIRModule()->getModuleInst()->dump(disassembly); + auto blob = StringUtil::createStringBlob(disassembly); + *outDisassembledBlob = blob.detach(); + return SLANG_OK; + } }; typedef Module LoadedModule; diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index d82340b947..6d1d76afce 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -8950,9 +8950,9 @@ void IRInst::addBlock(IRBlock* block) block->insertAtEnd(this); } -void IRInst::dump(String &outStr) -{ - StringBuilder sb; +void IRInst::dump(String& outStr) +{ + StringBuilder sb; if (auto intLit = as(this)) { @@ -8960,10 +8960,10 @@ void IRInst::dump(String &outStr) } else if (auto stringLit = as(this)) { - sb << stringLit->getStringSlice(); + sb << stringLit->getStringSlice(); } else - { + { IRDumpOptions options; StringWriter writer(&sb, Slang::WriterFlag::AutoFlush); dumpIR(this, options, nullptr, &writer); diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index 32dca99a34..64125be9a2 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -819,7 +819,7 @@ struct IRInst /// Print the IR to a string for debugging purposes. /// - void dump(String &outStr); + void dump(String& outStr); /// Insert a basic block at the end of this func/code containing inst. void addBlock(IRBlock* block); diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index ef7a249d35..f6ef817d2b 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -2953,14 +2953,14 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) case OptionKind::DumpModule: { CommandLineArg fileName; - SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName)); + SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName)); auto desc = slang::SessionDesc(); ComPtr session; m_session->createSession(desc, session.writeRef()); ComPtr diagnostics; // Coerce Slang to load from the given file, without letting it automatically - // choose .slang-module files over .slang files. + // choose .slang-module files over .slang files. // First try to load as source string, and fall back to loading as an IR Blob. // Avoid guessing based on filename or inspect the file contents. FILE* file; @@ -2973,7 +2973,7 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) fseek(file, 0, SEEK_END); size_t size = ftell(file); fseek(file, 0, SEEK_SET); - std::vector buffer(size+1); + std::vector buffer(size + 1); size_t result = fread(buffer.data(), 1, size, file); if (result != size) { @@ -2982,22 +2982,26 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) } buffer[size] = 0; fclose(file); - + ComPtr module; - module = session->loadModuleFromSourceString("module", "path", buffer.data(), diagnostics.writeRef()); + module = session->loadModuleFromSourceString( + "module", + "path", + buffer.data(), + diagnostics.writeRef()); if (!module) { // Load buffer as an IR blob ComPtr blob; blob = RawBlob::create(buffer.data(), size); - + module = session->loadModuleFromIRBlob( "module", "path", blob, diagnostics.writeRef()); } - + if (module) { ComPtr disassemblyBlob; @@ -3008,7 +3012,9 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) } else { - m_sink->diagnoseRaw(Severity::Note, (const char*)disassemblyBlob->getBufferPointer()); + m_sink->diagnoseRaw( + Severity::Note, + (const char*)disassemblyBlob->getBufferPointer()); } } else @@ -3018,11 +3024,11 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) m_sink->diagnoseRaw( Severity::Error, (const char*)diagnostics->getBufferPointer()); - } + } return SLANG_FAIL; } - - + + break; } default: From 2983ed2eaf08ee4efb8e045040cf637709f2e737 Mon Sep 17 00:00:00 2001 From: acheney Date: Wed, 19 Mar 2025 12:51:25 -0400 Subject: [PATCH 3/4] Use FileStream not FILE --- source/slang/slang-options.cpp | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index f6ef817d2b..3a3a03ec1a 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -2962,38 +2962,34 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) // Coerce Slang to load from the given file, without letting it automatically // choose .slang-module files over .slang files. // First try to load as source string, and fall back to loading as an IR Blob. - // Avoid guessing based on filename or inspect the file contents. - FILE* file; - fopen_s(&file, fileName.value.getBuffer(), "rb"); - if (!file) + // Avoid guessing based on filename or inspecting the file contents. + FileStream file; + if (SLANG_FAILED(file.init(fileName.value, FileMode::Open, FileAccess::Read, FileShare::None))) { m_sink->diagnose(arg.loc, Diagnostics::cannotOpenFile, fileName.value); return SLANG_FAIL; } - fseek(file, 0, SEEK_END); - size_t size = ftell(file); - fseek(file, 0, SEEK_SET); - std::vector buffer(size + 1); - size_t result = fread(buffer.data(), 1, size, file); - if (result != size) - { - m_sink->diagnoseRaw(Severity::Error, "Failed to read file"); - return SLANG_FAIL; - } - buffer[size] = 0; - fclose(file); + List buffer; + file.seek(SeekOrigin::End, 0); + const Int64 size = file.getPosition(); + buffer.setCount(size + 1); + file.seek(SeekOrigin::Start, 0); + SLANG_RETURN_ON_FAIL(file.readExactly(buffer.getBuffer(), (size_t)size)); + buffer[size] = 0; + file.close(); + ComPtr module; module = session->loadModuleFromSourceString( "module", "path", - buffer.data(), + (const char*)buffer.getBuffer(), diagnostics.writeRef()); if (!module) { // Load buffer as an IR blob ComPtr blob; - blob = RawBlob::create(buffer.data(), size); + blob = RawBlob::create(buffer.getBuffer(), size); module = session->loadModuleFromIRBlob( "module", @@ -3012,6 +3008,7 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) } else { + // success, print out the disassembly in a way that slang-test can read m_sink->diagnoseRaw( Severity::Note, (const char*)disassemblyBlob->getBufferPointer()); From 4fe0b7753b9607b79bb9e656baf759ffcd398ed4 Mon Sep 17 00:00:00 2001 From: slangbot <186143334+slangbot@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:54:18 +0000 Subject: [PATCH 4/4] format code --- source/slang/slang-options.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 3a3a03ec1a..ed7775e847 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -2964,7 +2964,11 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) // First try to load as source string, and fall back to loading as an IR Blob. // Avoid guessing based on filename or inspecting the file contents. FileStream file; - if (SLANG_FAILED(file.init(fileName.value, FileMode::Open, FileAccess::Read, FileShare::None))) + if (SLANG_FAILED(file.init( + fileName.value, + FileMode::Open, + FileAccess::Read, + FileShare::None))) { m_sink->diagnose(arg.loc, Diagnostics::cannotOpenFile, fileName.value); return SLANG_FAIL; @@ -2978,7 +2982,7 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) SLANG_RETURN_ON_FAIL(file.readExactly(buffer.getBuffer(), (size_t)size)); buffer[size] = 0; file.close(); - + ComPtr module; module = session->loadModuleFromSourceString( "module",