summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcheneym2 <acheney@nvidia.com>2024-08-05 15:37:46 -0400
committerGitHub <noreply@github.com>2024-08-05 15:37:46 -0400
commitd72f9f6f72a7a74d7466a1e301e1853fea5daa25 (patch)
tree4ae77e2dd622779b64d063d1f50fc7af8c13a94a
parentd63f5e20f1edf7c51ca5c456baceb9eb9a84c95b (diff)
Initial support for precompiled DXIL in slang-modules (#4755)
* Add embedded precompiled binary IR ops Add IR operations to embed precompiled DXIL or SPIR-V blobs into IR. Adds a BlobLit literal that is mostly identical to StringLit except for its inability to be displayed, e.g. in dumped IR. In the future, the blob might be dumped as hexadecimal, but for now it is summarized as "<binary blob>". * EmbeddedDXIL and SPIR-V options The options, '-embed-dxil' and '-embed-spirv' in slangc, will cause a target dxil or spirv to be compiled and stored in the translation unit IR when written to a slang-module. Subsequent changes actually implement the options. * Per-translation unit DXIL precompilation When -embed-dxil is specified, perform a precompilation to DXIL of each TU, linked only with stdlib. Embed the resulting DXIL for the TU in a IR op. Being part of IR, the precompiled DXIL can be serialized to disk in a slang-module. Upon loading slang-modules, the new IR op will be searched for and the precompiled DXIL blob is saved with the loaded Module. During linking, if all the Modules have precompiled blobs they will be sent to the downstream compile commands as libraries instead of source, skipping the downstream compilation, using DXC only for linking. Fixes Issue #4580 * Remove placeholder embedded SPIRV support Code was added only to sketch out how other precompiled bins will be supported. * Remove the rest of the SPIRV placeholder support * Fix warnings, test error on non-windows * Remove lib_6_6 hack, add dxil_lib capability * Allocate blob value from irmodule memarena * Add null check after memarena allocation * Restore the request->e2erequest code path for generatewholeprogram * Update capability handling, move EmbedDXIL enum to end to preserve abi * Remove lib_6_6 hack * Move ICompileRequest functions to end
-rw-r--r--include/slang.h9
-rw-r--r--source/compiler-core/slang-dxc-compiler.cpp112
-rw-r--r--source/slang/slang-capabilities.capdef2
-rw-r--r--source/slang/slang-compiler-tu.cpp115
-rw-r--r--source/slang/slang-compiler.cpp59
-rwxr-xr-xsource/slang/slang-compiler.h14
-rw-r--r--source/slang/slang-diagnostic-defs.h3
-rw-r--r--source/slang/slang-ir-inst-defs.h4
-rw-r--r--source/slang/slang-ir-insts.h3
-rw-r--r--source/slang/slang-ir.cpp46
-rw-r--r--source/slang/slang-ir.h8
-rw-r--r--source/slang/slang-module-library.cpp9
-rw-r--r--source/slang/slang-options.cpp35
-rw-r--r--source/slang/slang-serialize-container.cpp7
-rw-r--r--source/slang/slang-serialize-ir.cpp9
-rw-r--r--source/slang/slang.cpp28
-rw-r--r--tests/library/precompiled-dxil.slang24
17 files changed, 430 insertions, 57 deletions
diff --git a/include/slang.h b/include/slang.h
index 25e364504..c68157759 100644
--- a/include/slang.h
+++ b/include/slang.h
@@ -970,6 +970,7 @@ extern "C"
UseUpToDateBinaryModule, // bool, when set, will only load
// precompiled modules if it is up-to-date with its source.
+ EmbedDXIL, // bool
CountOf,
};
@@ -4152,7 +4153,6 @@ namespace slang
int targetIndex,
SlangTargetFlags flags) = 0;
-
/*!
@brief Set the floating point mode (e.g., precise or fast) to use a target.
*/
@@ -4704,6 +4704,13 @@ namespace slang
// return a copy of internal profiling results, and if `shouldClear` is true, clear the internal profiling results before returning.
virtual SLANG_NO_THROW SlangResult SLANG_MCALL getCompileTimeProfile(ISlangProfiler** compileTimeProfile, bool shouldClear) = 0;
+ virtual SLANG_NO_THROW void SLANG_MCALL setTargetGenerateWholeProgram(
+ int targetIndex,
+ bool value) = 0;
+
+ virtual SLANG_NO_THROW void SLANG_MCALL setTargetEmbedDXIL(
+ int targetIndex,
+ bool value) = 0;
};
#define SLANG_UUID_ICompileRequest ICompileRequest::getTypeGuid()
diff --git a/source/compiler-core/slang-dxc-compiler.cpp b/source/compiler-core/slang-dxc-compiler.cpp
index aa005d47f..0ecfd0b30 100644
--- a/source/compiler-core/slang-dxc-compiler.cpp
+++ b/source/compiler-core/slang-dxc-compiler.cpp
@@ -381,18 +381,24 @@ SlangResult DXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
CompileOptions options = getCompatibleVersion(&inOptions);
- // This compiler can only deal with a single artifact
- if (options.sourceArtifacts.count != 1)
+ // This compiler can only deal at most, a single source code artifact
+ // Should be okay to link together multiple libraries without any source artifacts (assuming that means source code)
+ if (options.sourceArtifacts.count > 1)
{
return SLANG_FAIL;
}
- IArtifact* sourceArtifact = options.sourceArtifacts[0];
+ bool hasSource = options.sourceArtifacts.count > 0;
- if (options.sourceLanguage != SLANG_SOURCE_LANGUAGE_HLSL || options.targetType != SLANG_DXIL)
+ IArtifact* sourceArtifact = hasSource ? options.sourceArtifacts[0] : nullptr;
+
+ if (hasSource)
{
- SLANG_ASSERT(!"Can only compile HLSL to DXIL");
- return SLANG_FAIL;
+ if (options.sourceLanguage != SLANG_SOURCE_LANGUAGE_HLSL || options.targetType != SLANG_DXIL)
+ {
+ SLANG_ASSERT(!"Can only compile HLSL to DXIL");
+ return SLANG_FAIL;
+ }
}
// Find all of the libraries
@@ -416,16 +422,19 @@ SlangResult DXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
ComPtr<IDxcLibrary> dxcLibrary;
SLANG_RETURN_ON_FAIL(m_createInstance(CLSID_DxcLibrary, __uuidof(dxcLibrary), (LPVOID*)dxcLibrary.writeRef()));
+ ComPtr<IDxcBlobEncoding> dxcSourceBlob = nullptr;
ComPtr<ISlangBlob> sourceBlob;
- SLANG_RETURN_ON_FAIL(sourceArtifact->loadBlob(ArtifactKeep::Yes, sourceBlob.writeRef()));
+ if (hasSource)
+ {
+ SLANG_RETURN_ON_FAIL(sourceArtifact->loadBlob(ArtifactKeep::Yes, sourceBlob.writeRef()));
- // Create blob from the string
- ComPtr<IDxcBlobEncoding> dxcSourceBlob;
- SLANG_RETURN_ON_FAIL(dxcLibrary->CreateBlobWithEncodingFromPinned(
- (LPBYTE)sourceBlob->getBufferPointer(),
- (UINT32)sourceBlob->getBufferSize(),
- 0,
- dxcSourceBlob.writeRef()));
+ // Create blob from the string
+ SLANG_RETURN_ON_FAIL(dxcLibrary->CreateBlobWithEncodingFromPinned(
+ (LPBYTE)sourceBlob->getBufferPointer(),
+ (UINT32)sourceBlob->getBufferSize(),
+ 0,
+ dxcSourceBlob.writeRef()));
+ }
List<const WCHAR*> args;
@@ -508,7 +517,7 @@ SlangResult DXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
String profileName = asString(options.profileName);
// If we are going to link we have to compile in the lib profile style
- if (libraries.getCount())
+ if (libraries.getCount() && hasSource)
{
if (!profileName.startsWith("lib"))
{
@@ -561,28 +570,33 @@ SlangResult DXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
}
#endif
- String sourcePath = ArtifactUtil::findPath(sourceArtifact);
- OSString wideSourcePath = sourcePath.toWString();
+ String sourcePath;
+ ComPtr<IDxcBlob> dxcResultBlob = nullptr;
+ auto diagnostics = ArtifactDiagnostics::create();
+ ComPtr<IDxcOperationResult> dxcOperationResult = nullptr;
+ if (hasSource)
+ {
+ sourcePath = ArtifactUtil::findPath(sourceArtifact);
+ OSString wideSourcePath = sourcePath.toWString();
- DxcIncludeHandler includeHandler(&searchDirectories, options.fileSystemExt, options.sourceManager);
+ DxcIncludeHandler includeHandler(&searchDirectories, options.fileSystemExt, options.sourceManager);
- ComPtr<IDxcOperationResult> dxcOperationResult;
- SLANG_RETURN_ON_FAIL(dxcCompiler->Compile(dxcSourceBlob,
- wideSourcePath.begin(),
- wideEntryPointName.begin(),
- wideProfileName.begin(),
- args.getBuffer(),
- UINT32(args.getCount()),
- nullptr, // `#define`s
- 0, // `#define` count
- &includeHandler, // `#include` handler
- dxcOperationResult.writeRef()));
+ SLANG_RETURN_ON_FAIL(dxcCompiler->Compile(dxcSourceBlob,
+ wideSourcePath.begin(),
+ wideEntryPointName.begin(),
+ wideProfileName.begin(),
+ args.getBuffer(),
+ UINT32(args.getCount()),
+ nullptr, // `#define`s
+ 0, // `#define` count
+ &includeHandler, // `#include` handler
+ dxcOperationResult.writeRef()));
- auto diagnostics = ArtifactDiagnostics::create();
-
- ComPtr<IDxcBlob> dxcResultBlob;
+ SLANG_RETURN_ON_FAIL(_handleOperationResult(dxcOperationResult, diagnostics, dxcResultBlob));
- SLANG_RETURN_ON_FAIL(_handleOperationResult(dxcOperationResult, diagnostics, dxcResultBlob));
+ ComPtr<IDxcBlobEncoding> dxcResultBlob2 = nullptr;
+ dxcCompiler->Disassemble(dxcResultBlob, dxcResultBlob2.writeRef());
+ }
// If we have libraries then we need to link...
if (libraries.getCount())
@@ -604,26 +618,30 @@ SlangResult DXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
libraryNames.add(String(_addName(library, pool)).toWString());
}
- // Add the compiled blob name
- String name;
- if (options.modulePath.count)
- {
- name = Path::getFileNameWithoutExt(asString(options.modulePath));
- }
- else if (sourcePath.getLength())
+ if (hasSource)
{
- name = Path::getFileNameWithoutExt(sourcePath);
- }
+ // Add the compiled blob name
+ String name;
+ if (options.modulePath.count)
+ {
+ name = Path::getFileNameWithoutExt(asString(options.modulePath));
+ }
+ else if (sourcePath.getLength())
+ {
+ name = Path::getFileNameWithoutExt(sourcePath);
+ }
- // Add the blob with name
- {
- auto blob = (ISlangBlob*)dxcResultBlob.get();
- libraryBlobs.add(ComPtr<ISlangBlob>(blob));
- libraryNames.add(String(_addName(name.getUnownedSlice(), pool)).toWString());
+ // Add the blob with name
+ {
+ auto blob = (ISlangBlob*)dxcResultBlob.get();
+ libraryBlobs.add(ComPtr<ISlangBlob>(blob));
+ libraryNames.add(String(_addName(name.getUnownedSlice(), pool)).toWString());
+ }
}
const Index librariesCount = libraryNames.getCount();
SLANG_ASSERT(libraryBlobs.getCount() == librariesCount);
+ SLANG_ASSERT(libraryNames.getCount() == librariesCount);
List<const wchar_t*> linkLibraryNames;
diff --git a/source/slang/slang-capabilities.capdef b/source/slang/slang-capabilities.capdef
index 264748738..220e4a424 100644
--- a/source/slang/slang-capabilities.capdef
+++ b/source/slang/slang-capabilities.capdef
@@ -119,6 +119,8 @@ def _sm_6_7 : _sm_6_6;
def hlsl_nvapi : hlsl;
+alias dxil_lib = _sm_6_3;
+
// cuda versions
def _cuda_sm_1_0 : cuda;
def _cuda_sm_2_0 : _cuda_sm_1_0;
diff --git a/source/slang/slang-compiler-tu.cpp b/source/slang/slang-compiler-tu.cpp
new file mode 100644
index 000000000..b007e7b71
--- /dev/null
+++ b/source/slang/slang-compiler-tu.cpp
@@ -0,0 +1,115 @@
+// slang-compiler-tu.cpp: Compiles translation units to target language
+// and emit precompiled blobs into IR
+
+#include "../core/slang-basic.h"
+#include "slang-compiler.h"
+#include "slang-ir-insts.h"
+#include "slang-capability.h"
+
+namespace Slang
+{
+ SLANG_NO_THROW SlangResult SLANG_MCALL Module::precompileForTargets(
+ DiagnosticSink* sink,
+ EndToEndCompileRequest* endToEndReq,
+ TargetRequest* targetReq)
+ {
+ auto module = getIRModule();
+ Slang::Session* session = endToEndReq->getSession();
+ Slang::ASTBuilder* astBuilder = session->getGlobalASTBuilder();
+ Slang::Linkage* builtinLinkage = session->getBuiltinLinkage();
+ Slang::Linkage linkage(session, astBuilder, builtinLinkage);
+
+ CapabilityName precompileRequirement = CapabilityName::Invalid;
+ switch (targetReq->getTarget())
+ {
+ case CodeGenTarget::DXIL:
+ linkage.addTarget(Slang::CodeGenTarget::DXIL);
+ precompileRequirement = CapabilityName::dxil_lib;
+ break;
+ default:
+ assert(!"Unhandled target");
+ break;
+ }
+ SLANG_ASSERT(precompileRequirement != CapabilityName::Invalid);
+
+ // Ensure precompilation capability requirements are met.
+ auto targetCaps = targetReq->getTargetCaps();
+ auto precompileRequirementsCapabilitySet = CapabilitySet(precompileRequirement);
+ if (targetCaps.atLeastOneSetImpliedInOther(precompileRequirementsCapabilitySet) == CapabilitySet::ImpliesReturnFlags::NotImplied)
+ {
+ // If `RestrictiveCapabilityCheck` is true we will error, else we will warn.
+ // error ...: dxil libraries require $0, entry point compiled with $1.
+ // warn ...: dxil libraries require $0, entry point compiled with $1, implicitly upgrading capabilities.
+ maybeDiagnoseWarningOrError(
+ sink,
+ targetReq->getOptionSet(),
+ DiagnosticCategory::Capability,
+ SourceLoc(),
+ Diagnostics::incompatibleWithPrecompileLib,
+ Diagnostics::incompatibleWithPrecompileLibRestrictive,
+ precompileRequirementsCapabilitySet,
+ targetCaps);
+
+ // add precompile requirements to the cooked targetCaps
+ targetCaps.join(precompileRequirementsCapabilitySet);
+ if (targetCaps.isInvalid())
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::unknownCapability, targetCaps);
+ return SLANG_FAIL;
+ }
+ else
+ {
+ targetReq->setTargetCaps(targetCaps);
+ }
+ }
+
+ List<RefPtr<ComponentType>> allComponentTypes;
+ allComponentTypes.add(this); // Add Module as a component type
+
+ for (auto entryPoint : this->getEntryPoints())
+ {
+ allComponentTypes.add(entryPoint); // Add the entry point as a component type
+ }
+
+ auto composite = CompositeComponentType::create(
+ &linkage,
+ allComponentTypes);
+
+ TargetProgram tp(composite, targetReq);
+ tp.getOrCreateLayout(sink);
+ Slang::Index const entryPointCount = m_entryPoints.getCount();
+
+ CodeGenContext::EntryPointIndices entryPointIndices;
+
+ entryPointIndices.setCount(entryPointCount);
+ for (Index i = 0; i < entryPointCount; i++)
+ entryPointIndices[i] = i;
+ CodeGenContext::Shared sharedCodeGenContext(&tp, entryPointIndices, sink, endToEndReq);
+ CodeGenContext codeGenContext(&sharedCodeGenContext);
+
+ ComPtr<IArtifact> outArtifact;
+ SlangResult res = codeGenContext.emitTranslationUnit(outArtifact);
+ if (res != SLANG_OK)
+ {
+ return res;
+ }
+
+ ISlangBlob* blob;
+ outArtifact->loadBlob(ArtifactKeep::Yes, &blob);
+
+ auto builder = IRBuilder(module);
+ builder.setInsertInto(module);
+
+ switch (targetReq->getTarget())
+ {
+ case CodeGenTarget::DXIL:
+ builder.emitEmbeddedDXIL(blob);
+ break;
+ default:
+ assert(!"Unhandled target");
+ break;
+ }
+
+ return SLANG_OK;
+ }
+}
diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp
index 912e42572..1ef17df1d 100644
--- a/source/slang/slang-compiler.cpp
+++ b/source/slang/slang-compiler.cpp
@@ -762,6 +762,11 @@ namespace Slang
# pragma warning(pop)
#endif
+ SlangResult CodeGenContext::emitTranslationUnit(ComPtr<IArtifact>& outArtifact)
+ {
+ return emitWithDownstreamForEntryPoints(outArtifact);
+ }
+
String GetHLSLProfileName(Profile profile)
{
switch( profile.getFamily() )
@@ -1084,6 +1089,23 @@ namespace Slang
return SLANG_OK;
}
+ bool CodeGenContext::isPrecompiled()
+ {
+ auto program = getProgram();
+
+ bool allPrecompiled = true;
+ program->enumerateIRModules([&](IRModule* irModule)
+ {
+ // TODO: Conditionalize this on target
+ if (!irModule->precompiledDXIL)
+ {
+ allPrecompiled = false;
+ }
+ });
+
+ return allPrecompiled;
+ }
+
SlangResult CodeGenContext::emitWithDownstreamForEntryPoints(ComPtr<IArtifact>& outArtifact)
{
outArtifact.setNull();
@@ -1245,12 +1267,15 @@ namespace Slang
}
else
{
- CodeGenContext sourceCodeGenContext(this, sourceTarget, extensionTracker);
+ if (!isPrecompiled())
+ {
+ CodeGenContext sourceCodeGenContext(this, sourceTarget, extensionTracker);
- SLANG_RETURN_ON_FAIL(sourceCodeGenContext.emitEntryPointsSource(sourceArtifact));
- sourceCodeGenContext.maybeDumpIntermediate(sourceArtifact);
+ SLANG_RETURN_ON_FAIL(sourceCodeGenContext.emitEntryPointsSource(sourceArtifact));
+ sourceCodeGenContext.maybeDumpIntermediate(sourceArtifact);
- sourceLanguage = (SourceLanguage)TypeConvertUtil::getSourceLanguageFromTarget((SlangCompileTarget)sourceTarget);
+ sourceLanguage = (SourceLanguage)TypeConvertUtil::getSourceLanguageFromTarget((SlangCompileTarget)sourceTarget);
+ }
}
if (sourceArtifact)
@@ -1541,6 +1566,25 @@ namespace Slang
libraries.addRange(linkage->m_libModules.getBuffer(), linkage->m_libModules.getCount());
}
+ if (isPrecompiled())
+ {
+ auto program = getProgram();
+ program->enumerateIRModules([&](IRModule* irModule)
+ {
+ // TODO: conditionalize on target
+ if (irModule->precompiledDXIL)
+ {
+ ArtifactDesc desc = ArtifactDescUtil::makeDescForCompileTarget(SLANG_DXIL);
+ desc.kind = ArtifactKind::Library;
+
+ auto library = ArtifactUtil::createArtifact(desc);
+
+ library->addRepresentationUnknown(irModule->precompiledDXIL);
+ libraries.add(library);
+ }
+ });
+ }
+
options.compilerSpecificArguments = allocator.allocate(compilerSpecificArguments);
options.requiredCapabilityVersions = SliceUtil::asSlice(requiredCapabilityVersions);
options.libraries = SliceUtil::asSlice(libraries);
@@ -1991,7 +2035,10 @@ namespace Slang
{
if (auto artifact = targetProgram->getExistingWholeProgramResult())
{
- artifacts.add(ComPtr<IArtifact>(artifact));
+ if (!targetProgram->getOptionSet().getBoolOption(CompilerOptionName::EmbedDXIL))
+ {
+ artifacts.add(ComPtr<IArtifact>(artifact));
+ }
}
}
else
@@ -2251,7 +2298,7 @@ namespace Slang
void EndToEndCompileRequest::generateOutput()
{
generateOutput(getSpecializedGlobalAndEntryPointsComponentType());
-
+
// If we are in command-line mode, we might be expected to actually
// write output to one or more files here.
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index 4bae6c10d..8e1cb0a88 100755
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -1475,6 +1475,12 @@ namespace Slang
virtual SLANG_NO_THROW char const* SLANG_MCALL getDependencyFilePath(
SlangInt32 index) override;
+ /// Precompile TU to target language
+ virtual SLANG_NO_THROW SlangResult SLANG_MCALL precompileForTargets(
+ DiagnosticSink* sink,
+ EndToEndCompileRequest* endToEndReq,
+ TargetRequest* targetReq);
+
virtual void buildHash(DigestBuilder<SHA1>& builder) SLANG_OVERRIDE;
virtual slang::DeclReflection* getModuleReflection() SLANG_OVERRIDE;
@@ -2714,6 +2720,8 @@ namespace Slang
SlangResult emitEntryPoints(ComPtr<IArtifact>& outArtifact);
+ SlangResult emitTranslationUnit(ComPtr<IArtifact>& outArtifact);
+
void maybeDumpIntermediate(IArtifact* artifact);
protected:
@@ -2753,6 +2761,10 @@ namespace Slang
SlangResult _emitEntryPoints(ComPtr<IArtifact>& outArtifact);
+ /* Checks if all modules in the target program are already compiled to the
+ target language, indicating that a pass-through linking using the
+ downstream compiler is viable.*/
+ bool isPrecompiled();
private:
Shared* m_shared = nullptr;
};
@@ -2790,6 +2802,8 @@ namespace Slang
virtual SLANG_NO_THROW void SLANG_MCALL setTargetFloatingPointMode(int targetIndex, SlangFloatingPointMode mode) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL setTargetMatrixLayoutMode(int targetIndex, SlangMatrixLayoutMode mode) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL setTargetForceGLSLScalarBufferLayout(int targetIndex, bool value) SLANG_OVERRIDE;
+ virtual SLANG_NO_THROW void SLANG_MCALL setTargetGenerateWholeProgram(int targetIndex, bool value) SLANG_OVERRIDE;
+ virtual SLANG_NO_THROW void SLANG_MCALL setTargetEmbedDXIL(int targetIndex, bool value) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL setMatrixLayoutMode(SlangMatrixLayoutMode mode) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL setDebugInfoLevel(SlangDebugInfoLevel level) SLANG_OVERRIDE;
virtual SLANG_NO_THROW void SLANG_MCALL setOptimizationLevel(SlangOptimizationLevel level) SLANG_OVERRIDE;
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index 487f264d5..6d7be3f92 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -396,6 +396,9 @@ DIAGNOSTIC(36110, Error, stageIsIncompatibleWithCapabilityDefinition, "'$0' is d
DIAGNOSTIC(36111, Error, unexpectedCapability, "'$0' resolves into a disallowed `$1` Capability.")
DIAGNOSTIC(36112, Warning, entryPointAndProfileAreIncompatible, "'$0' is defined for stage '$1', which is incompatible with the declared profile '$2'.")
DIAGNOSTIC(36113, Warning, usingInternalCapabilityName, "'$0' resolves into a '_Internal' `_$1' Capability, use '$1' instead.")
+DIAGNOSTIC(36114, Warning, incompatibleWithPrecompileLib, "Precompiled library requires '$0', has `$1`, implicitly upgrading capabilities.")
+DIAGNOSTIC(36115, Error, incompatibleWithPrecompileLibRestrictive, "Precompiled library requires '$0', has `$1`.")
+
// Attributes
DIAGNOSTIC(31000, Warning, unknownAttributeName, "unknown attribute '$0'")
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 07694b066..0e84be2b2 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -294,6 +294,7 @@ INST(Block, block, 0, PARENT)
INST(FloatLit, float_constant, 0, 0)
INST(PtrLit, ptr_constant, 0, 0)
INST(StringLit, string_constant, 0, 0)
+ INST(BlobLit, string_constant, 0, 0)
INST(VoidLit, void_constant, 0, 0)
INST_RANGE(Constant, BoolLit, VoidLit)
@@ -1205,6 +1206,9 @@ INST(DebugLine, DebugLine, 5, 0)
INST(DebugVar, DebugVar, 4, 0)
INST(DebugValue, DebugValue, 2, 0)
+/* Embedded Precompiled Libraries */
+INST(EmbeddedDXIL, EmbeddedDXIL, 1, 0)
+
/* Inline assembly */
INST(SPIRVAsm, SPIRVAsm, 0, PARENT)
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 795a79c28..8a801e4e7 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -3431,6 +3431,7 @@ public:
IRInst* getIntValue(IRType* type, IRIntegerValue value);
IRInst* getFloatValue(IRType* type, IRFloatingPointValue value);
IRStringLit* getStringValue(const UnownedStringSlice& slice);
+ IRBlobLit* getBlobValue(ISlangBlob* blob);
IRPtrLit* _getPtrValue(void* ptr);
IRPtrLit* getNullPtrValue(IRType* type);
IRPtrLit* getNullVoidPtrValue() { return getNullPtrValue(getPtrType(getVoidType())); }
@@ -3947,6 +3948,8 @@ public:
IRInst* emitByteAddressBufferStore(IRInst* byteAddressBuffer, IRInst* offset, IRInst* value);
IRInst* emitByteAddressBufferStore(IRInst* byteAddressBuffer, IRInst* offset, IRInst* alignment, IRInst* value);
+ IRInst* emitEmbeddedDXIL(ISlangBlob* blob);
+
IRFunc* createFunc();
IRGlobalVar* createGlobalVar(
IRType* valueType);
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 1fc15f185..bfd6c20cf 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -2059,7 +2059,7 @@ namespace Slang
UnownedStringSlice IRConstant::getStringSlice()
{
- assert(getOp() == kIROp_StringLit);
+ assert(getOp() == kIROp_StringLit || getOp() == kIROp_BlobLit);
// If the transitory decoration is set, then this is uses the transitoryStringVal for the text storage.
// This is typically used when we are using a transitory IRInst held on the stack (such that it can be looked up in cached),
// that just points to a string elsewhere, and NOT the typical normal style, where the string is held after the instruction in memory.
@@ -2133,6 +2133,7 @@ namespace Slang
{
return value.ptrVal == rhs->value.ptrVal;
}
+ case kIROp_BlobLit:
case kIROp_StringLit:
{
return getStringSlice() == rhs->getStringSlice();
@@ -2174,6 +2175,7 @@ namespace Slang
{
return combineHash(code, Slang::getHashCode(value.ptrVal));
}
+ case kIROp_BlobLit:
case kIROp_StringLit:
{
const UnownedStringSlice slice = getStringSlice();
@@ -2264,6 +2266,7 @@ namespace Slang
irValue->value.ptrVal = keyInst.value.ptrVal;
break;
}
+ case kIROp_BlobLit:
case kIROp_StringLit:
{
const UnownedStringSlice slice = keyInst.getStringSlice();
@@ -2387,6 +2390,36 @@ namespace Slang
return static_cast<IRStringLit*>(_findOrEmitConstant(keyInst));
}
+ IRBlobLit* IRBuilder::getBlobValue(ISlangBlob* blob)
+ {
+ IRConstant keyInst;
+ memset(&keyInst, 0, sizeof(keyInst));
+
+ char* buffer = (char*)(getModule()->getMemoryArena().allocate(blob->getBufferSize()));
+ if (!buffer)
+ {
+ return nullptr;
+ }
+ memcpy(buffer, blob->getBufferPointer(), blob->getBufferSize());
+
+ UnownedStringSlice inSlice(buffer, blob->getBufferSize());
+
+ // Mark that this is on the stack...
+ IRDecoration stackDecoration;
+ memset(&stackDecoration, 0, sizeof(stackDecoration));
+ stackDecoration.m_op = kIROp_TransitoryDecoration;
+ stackDecoration.insertAtEnd(&keyInst);
+
+ keyInst.m_op = kIROp_BlobLit;
+ keyInst.typeUse.usedValue = nullptr; // not used
+
+ IRConstant::StringSliceValue& dstSlice = keyInst.value.transitoryStringVal;
+ dstSlice.chars = const_cast<char*>(inSlice.begin());
+ dstSlice.numChars = uint32_t(inSlice.getLength());
+
+ return static_cast<IRBlobLit*>(_findOrEmitConstant(keyInst));
+ }
+
IRPtrLit* IRBuilder::_getPtrValue(void* data)
{
auto type = getPtrType(getVoidType());
@@ -3800,6 +3833,13 @@ namespace Slang
return nullptr;
}
+ IRInst* IRBuilder::emitEmbeddedDXIL(ISlangBlob *blob)
+ {
+ IRInst* args[] = { getBlobValue(blob) };
+
+ return emitIntrinsicInst(getVoidType(), kIROp_EmbeddedDXIL, 1, args);
+ }
+
enum class TypeCastStyle
{
Unknown = -1,
@@ -7098,6 +7138,10 @@ namespace Slang
dump(context, irConst->value.intVal ? "true" : "false");
return;
+ case kIROp_BlobLit:
+ dump(context, "<binary blob>");
+ return;
+
case kIROp_StringLit:
dumpEncodeString(context, irConst->getStringSlice());
return;
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index cb8e83df8..719b383c3 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -1157,6 +1157,11 @@ struct IRStringLit : IRConstant
IR_LEAF_ISA(StringLit);
};
+struct IRBlobLit : IRConstant
+{
+ IR_LEAF_ISA(BlobLit);
+};
+
struct IRPtrLit : IRConstant
{
IR_LEAF_ISA(PtrLit);
@@ -2340,6 +2345,9 @@ public:
{
return m_containerPool;
}
+
+ // TODO: make a map with lookup by target?
+ ComPtr<ISlangBlob> precompiledDXIL;
private:
IRModule() = delete;
diff --git a/source/slang/slang-module-library.cpp b/source/slang/slang-module-library.cpp
index 02ace07d3..42b9ff77a 100644
--- a/source/slang/slang-module-library.cpp
+++ b/source/slang/slang-module-library.cpp
@@ -83,6 +83,15 @@ SlangResult loadModuleLibrary(const Byte* inBytes, size_t bytesCount, String pat
module, &sink);
if (!loadedModule)
return SLANG_FAIL;
+
+ for (auto inst : module.irModule->getModuleInst()->getChildren())
+ {
+ if (inst->getOp() == kIROp_EmbeddedDXIL)
+ {
+ auto slice = static_cast<IRBlobLit*>(inst->getOperand(0))->getStringSlice();
+ module.irModule->precompiledDXIL = StringBlob::create(slice);
+ }
+ }
library->m_modules.add(loadedModule);
}
}
diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp
index c3a0eeddc..33103442d 100644
--- a/source/slang/slang-options.cpp
+++ b/source/slang/slang-options.cpp
@@ -353,7 +353,9 @@ void initCommandOptions(CommandOptions& options)
"The language to be used for source embedding. Defaults to C/C++. Currently only C/C++ are supported"},
{ OptionKind::DisableShortCircuit, "-disable-short-circuit", nullptr, "Disable short-circuiting for \"&&\" and \"||\" operations" },
{ OptionKind::UnscopedEnum, "-unscoped-enum", nullptr, "Treat enums types as unscoped by default."},
- { OptionKind::PreserveParameters, "-preserve-params", nullptr, "Preserve all resource parameters in the output code, even if they are not used by the shader."}
+ { OptionKind::PreserveParameters, "-preserve-params", nullptr, "Preserve all resource parameters in the output code, even if they are not used by the shader."},
+ { OptionKind::EmbedDXIL, "-embed-dxil", nullptr,
+ "Embed DXIL into emitted slang-modules for faster linking" },
};
_addOptions(makeConstArrayView(generalOpts), options);
@@ -426,7 +428,6 @@ void initCommandOptions(CommandOptions& options)
"A path to a specific spirv.core.grammar.json to use when generating SPIR-V output" },
{ OptionKind::IncompleteLibrary, "-incomplete-library", nullptr,
"Allow generating code from incomplete libraries with unresolved external functions" },
-
};
_addOptions(makeConstArrayView(targetOpts), options);
@@ -699,6 +700,8 @@ struct OptionsParser
void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion);
void setProfile(RawTarget* rawTarget, Profile profile);
void addCapabilityAtom(RawTarget* rawTarget, CapabilityName atom);
+
+ SlangResult addEmbeddedLibrary(const CodeGenTarget format, CompilerOptionName option);
void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode);
@@ -1636,6 +1639,23 @@ SlangResult OptionsParser::_parseProfile(const CommandLineArg& arg)
return SLANG_OK;
}
+// Creates a target of the specified type whose output will be embedded as IR metadata
+SlangResult OptionsParser::addEmbeddedLibrary(const CodeGenTarget format, CompilerOptionName option)
+{
+ RawTarget rawTarget;
+ rawTarget.format = format;
+ // Silently allow redundant targets if it is the same as the last specified target.
+ if (m_rawTargets.getCount() == 0 || m_rawTargets.getLast().format != rawTarget.format)
+ {
+ m_rawTargets.add(rawTarget);
+ }
+
+ getCurrentTarget()->optionSet.add(option, true);
+ getCurrentTarget()->optionSet.add(CompilerOptionName::GenerateWholeProgram, true);
+
+ return SLANG_OK;
+}
+
SlangResult OptionsParser::_parse(
int argc,
char const* const* argv)
@@ -1927,6 +1947,7 @@ SlangResult OptionsParser::_parse(
linkage->m_optionSet.set(optionKind, compressionType);
break;
}
+ case OptionKind::EmbedDXIL: SLANG_RETURN_ON_FAIL(addEmbeddedLibrary(CodeGenTarget::DXIL, CompilerOptionName::EmbedDXIL)); break;
case OptionKind::Target:
{
CommandLineArg name;
@@ -2758,6 +2779,16 @@ SlangResult OptionsParser::_parse(
{
m_compileRequest->setTargetForceGLSLScalarBufferLayout(targetID, true);
}
+
+ if (rawTarget.optionSet.getBoolOption(CompilerOptionName::GenerateWholeProgram))
+ {
+ m_compileRequest->setTargetGenerateWholeProgram(targetID, true);
+ }
+
+ if (rawTarget.optionSet.getBoolOption(CompilerOptionName::EmbedDXIL))
+ {
+ m_compileRequest->setTargetEmbedDXIL(targetID, true);
+ }
}
// Next we need to sort out the output files specified with `-o`, and
diff --git a/source/slang/slang-serialize-container.cpp b/source/slang/slang-serialize-container.cpp
index 50b9061e3..2229c74ea 100644
--- a/source/slang/slang-serialize-container.cpp
+++ b/source/slang/slang-serialize-container.cpp
@@ -348,6 +348,12 @@ namespace Slang {
}
}
+ // TODO:
+ // Serialization of target component IR is causing the embedded precompiled binary
+ // feature to fail. The resulting data modules contain both TU IR and TC IR, with only
+ // one module header. Yong suggested to ignore the TC IR for now, though also that
+ // OV was using the feature, so disabling this might cause problems.
+#if 0
if (data.targetComponents.getCount() && (options.optionFlags & SerialOptionFlag::IRModule))
{
// TODO: in the case where we have specialization, we might need
@@ -365,6 +371,7 @@ namespace Slang {
SLANG_RETURN_ON_FAIL(IRSerialWriter::writeContainer(serialData, options.compressionType, container));
}
}
+#endif
}
if (data.entryPoints.getCount())
diff --git a/source/slang/slang-serialize-ir.cpp b/source/slang/slang-serialize-ir.cpp
index ccd5a2e7d..a2d05fc5b 100644
--- a/source/slang/slang-serialize-ir.cpp
+++ b/source/slang/slang-serialize-ir.cpp
@@ -195,6 +195,14 @@ Result IRSerialWriter::write(IRModule* module, SerialSourceLocWriter* sourceLocW
switch (srcInst->getOp())
{
// Special handling for the ir const derived types
+ case kIROp_BlobLit:
+ {
+ // Blobs are serialized into string table like strings
+ auto stringLit = static_cast<IRBlobLit*>(srcInst);
+ dstInst.m_payloadType = PayloadType::String_1;
+ dstInst.m_payload.m_stringIndices[0] = getStringIndex(stringLit->getStringSlice());
+ break;
+ }
case kIROp_StringLit:
{
auto stringLit = static_cast<IRStringLit*>(srcInst);
@@ -790,6 +798,7 @@ Result IRSerialReader::read(const IRSerialData& data, Session* session, SerialSo
op, operandCount, prefixSize));
break;
}
+ case kIROp_BlobLit:
case kIROp_StringLit:
{
SLANG_ASSERT(srcInst.m_payloadType == PayloadType::String_1);
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index 6f5e79458..cf8c19033 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -3149,6 +3149,24 @@ SlangResult EndToEndCompileRequest::executeActionsInner()
return SLANG_OK;
}
+ // If requested, attempt to compile the translation unit all the way down to the target language
+ // and stash the result blob in an IR op.
+ for (auto targetReq : getLinkage()->targets)
+ {
+ if (targetReq->getOptionSet().getBoolOption(CompilerOptionName::EmbedDXIL))
+ {
+ auto frontEndReq = getFrontEndReq();
+
+ for (auto translationUnit : frontEndReq->translationUnits)
+ {
+ translationUnit->getModule()->precompileForTargets(
+ getSink(),
+ this,
+ targetReq);
+ }
+ }
+ }
+
// If codegen is enabled, we need to move along to
// apply any generic specialization that the user asked for.
//
@@ -5758,6 +5776,16 @@ void EndToEndCompileRequest::setTargetMatrixLayoutMode(int targetIndex, SlangMat
getTargetOptionSet(targetIndex).setMatrixLayoutMode(MatrixLayoutMode(mode));
}
+void EndToEndCompileRequest::setTargetGenerateWholeProgram(int targetIndex, bool value)
+{
+ getTargetOptionSet(targetIndex).set(CompilerOptionName::GenerateWholeProgram, value);
+}
+
+void EndToEndCompileRequest::setTargetEmbedDXIL(int targetIndex, bool value)
+{
+ getTargetOptionSet(targetIndex).set(CompilerOptionName::EmbedDXIL, value);
+}
+
void EndToEndCompileRequest::setTargetLineDirectiveMode(
SlangInt targetIndex,
SlangLineDirectiveMode mode)
diff --git a/tests/library/precompiled-dxil.slang b/tests/library/precompiled-dxil.slang
new file mode 100644
index 000000000..19f67b075
--- /dev/null
+++ b/tests/library/precompiled-dxil.slang
@@ -0,0 +1,24 @@
+// precompiled-dxil.slang
+
+// A test that uses slang-modules with embedded precompiled DXIL.
+// The test compiles both library slang (export-library.slang) and entrypoint slang (this file) to two slang-modules, each with -embed-dxil options.
+// The result is two slang-modules that are then linked together in a third slangc invocation.
+// Internally, slang does not use the IR in the modules to link, but rather their embedded DXIL.
+// The test passes if there is no errror thrown.
+// TODO: Check if final linkage used only the precompiled dxil.
+
+//TEST(windows):COMPILE: tests/library/export-library.slang -o tests/library/export-library.slang-module -embed-dxil -profile lib_6_6 -incomplete-library
+//TEST(windows):COMPILE: tests/library/precompiled-dxil.slang -o tests/library/precompiled-dxil.slang-module -embed-dxil -profile lib_6_6 -incomplete-library
+//TEST(windows):COMPILE: tests/library/export-library.slang-module tests/library/precompiled-dxil.slang-module -target dxil -entry computeMain -profile cs_6_6 -o tests/library/linked.dxil
+
+extern int foo(int a);
+
+RWStructuredBuffer<int> outputBuffer;
+
+[shader("compute")]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int index = (int)dispatchThreadID.x;
+
+ outputBuffer[index] = foo(index);
+}