Skip to content

Commit ddf4a32

Browse files
authored
Support mixture of precompiled and non-precompiled modules (shader-slang#4860)
* Support mixture of precompiled and non-precompiled modules This changes the implementation of precompile DXIL modules to accept combinations of modules with precompiled DXIL, ones without, and ones with a mixture of precompiled DXIL and Slang IR. During precompilation, module IR is analyzed to find public functions which appear to be capable of being compiled as HLSL, and those functions are given a HLSLExport decoration, ensuring they are emitted as HLSL and preserved in the precompiled DXIL blob. The IR for those functions is then tagged with a new decoration AvailableInDXIL, which marks that their implementation is present in the embedded DXIL blob. The DXIL blob is attached to the IR as before, inside a EmbeddedDXIL BlobLit instruction. The logic that determines whether or not functions should be precompiled to DXIL is a placeholder at this point, returning true always. A subsequent change will add selection criteria. During module linking, the full module IR is available, as well as the optional EmbeddedDXIL blob. The IR for functions implemented by the blob are tagged with AvailableInDXIL in the module IR. After linking the IR for all modules to program level IR, the IR for the functions marked AvailableInDXIL are deleted from the linked IR, prior to emitting HLSL and compiling linking the result. This change also changes the point of time when the module IR is checked for EmbeddedDXIL blobs. Instead of happening at load time as before, it happens during immediately before final linking, meaning that the blob does not need to be independently stored with the module separate from the IR as was done previously. Work on shader-slang#4792 * Clean up debug prints * Call isSimpleHLSLDataType stub * Address feedback on precompiled dxil support Allow for IR filtering both before and after linking. Only mark AvailableInDXIL those functions which pass both filtering stages. Functions are corrlated using mangled function names. Rather than delete functions entirely when linking with libraries that include precompiled DXIL, instead convert the IR function definitions to declarations by gutting them, removing child blocks. * Use artifact metadata and name list instead of linkedir hack * Use String instead of UnownedStringSlice * Update tests * Renaming * Minor edits * Don't fully remove functions post-link * Unexport before collecting metadata
1 parent 87d3d4f commit ddf4a32

26 files changed

+425
-102
lines changed

include/slang.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -4929,9 +4929,7 @@ namespace slang
49294929
int targetIndex,
49304930
bool value) = 0;
49314931

4932-
virtual SLANG_NO_THROW void SLANG_MCALL setTargetEmbedDXIL(
4933-
int targetIndex,
4934-
bool value) = 0;
4932+
virtual SLANG_NO_THROW void SLANG_MCALL setEmbedDXIL(bool value) = 0;
49354933

49364934
virtual SLANG_NO_THROW void SLANG_MCALL setTargetForceDXLayout(int targetIndex, bool value) = 0;
49374935
};

source/compiler-core/slang-artifact-associated-impl.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -308,4 +308,9 @@ Slice<ShaderBindingRange> ArtifactPostEmitMetadata::getUsedBindingRanges()
308308
return Slice<ShaderBindingRange>(m_usedBindings.getBuffer(), m_usedBindings.getCount());
309309
}
310310

311+
Slice<String> ArtifactPostEmitMetadata::getExportedFunctionMangledNames()
312+
{
313+
return Slice<String>(m_exportedFunctionMangledNames.getBuffer(), m_exportedFunctionMangledNames.getCount());
314+
}
315+
311316
} // namespace Slang

source/compiler-core/slang-artifact-associated-impl.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,15 @@ class ArtifactPostEmitMetadata : public ComBaseObject, public IArtifactPostEmitM
155155

156156
// IArtifactPostEmitMetadata
157157
SLANG_NO_THROW virtual Slice<ShaderBindingRange> SLANG_MCALL getUsedBindingRanges() SLANG_OVERRIDE;
158-
158+
SLANG_NO_THROW virtual Slice<String> SLANG_MCALL getExportedFunctionMangledNames() SLANG_OVERRIDE;
159+
159160
void* getInterface(const Guid& uuid);
160161
void* getObject(const Guid& uuid);
161162

162163
static ComPtr<IArtifactPostEmitMetadata> create() { return ComPtr<IArtifactPostEmitMetadata>(new ThisType); }
163164

164165
List<ShaderBindingRange> m_usedBindings;
166+
List<String> m_exportedFunctionMangledNames;
165167
};
166168

167169
} // namespace Slang

source/compiler-core/slang-artifact-associated.h

+3
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ class IArtifactPostEmitMetadata : public ICastable
124124

125125
/// Get the binding ranges
126126
SLANG_NO_THROW virtual Slice<ShaderBindingRange> SLANG_MCALL getUsedBindingRanges() = 0;
127+
128+
/// Get the list of functions that were exported in the linked IR
129+
SLANG_NO_THROW virtual Slice<String> SLANG_MCALL getExportedFunctionMangledNames() = 0;
127130
};
128131

129132
} // namespace Slang

source/slang/slang-compiler-tu.cpp

+135-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,91 @@
44
#include "../core/slang-basic.h"
55
#include "slang-compiler.h"
66
#include "slang-ir-insts.h"
7+
#include "slang-ir-util.h"
78
#include "slang-capability.h"
89

910
namespace Slang
1011
{
12+
// Only attempt to precompile functions:
13+
// 1) With function bodies (not just empty decls)
14+
// 2) Not marked with unsafeForceInlineDecoration
15+
// 3) Have a simple HLSL data type as the return or parameter type
16+
static bool attemptPrecompiledExport(IRInst* inst)
17+
{
18+
if (inst->getOp() != kIROp_Func)
19+
{
20+
return false;
21+
}
22+
23+
// Skip functions with no body
24+
bool hasBody = false;
25+
for (auto child : inst->getChildren())
26+
{
27+
if (child->getOp() == kIROp_Block)
28+
{
29+
hasBody = true;
30+
break;
31+
}
32+
}
33+
if (!hasBody)
34+
{
35+
return false;
36+
}
37+
38+
// Skip functions marked with unsafeForceInlineDecoration
39+
if (inst->findDecoration<IRUnsafeForceInlineEarlyDecoration>())
40+
{
41+
return false;
42+
}
43+
44+
// Skip non-simple HLSL data types, filters out generics
45+
if (!isSimpleHLSLDataType(inst))
46+
{
47+
return false;
48+
}
49+
50+
return true;
51+
}
52+
53+
/*
54+
* Precompile the module for the given target.
55+
*
56+
* This function creates a target program and emits the precompiled blob as
57+
* an embedded blob in the module IR, e.g. DXIL.
58+
* Because the IR for the Slang Module may violate the restrictions of the
59+
* target language, the emitted target blob may not be able to include the
60+
* full module, but rather only the subset that can be precompiled. For
61+
* example, DXIL libraries do not allow resources like structured buffers
62+
* to appear in the library interface. Also, no target languages allow
63+
* generics to be precompiled.
64+
*
65+
* Some restrictions can be enforced up front before linking, but some are
66+
* done during target generation in between IR linking+legalization and
67+
* target source emission.
68+
*
69+
* Functions which can be rejected up front:
70+
* - Functions with no body
71+
* - Functions marked with unsafeForceInlineDecoration
72+
* - Functions that define or use generics
73+
*
74+
* The functions not rejected up front are marked with
75+
* DownstreamModuleExportDecoration which indicates functions we're trying to
76+
* export for precompilation, and this also helps to identify the functions
77+
* in the linked IR which survived the additional pruning.
78+
*
79+
* Functions that are rejected after linking+legalization (inside
80+
* emitPrecompiled*):
81+
* - (DXIL) Functions that return or take a HLSLStructuredBufferType
82+
* - (DXIL) Functions that return or take a Matrix type
83+
*
84+
* emitPrecompiled* produces the output artifact containing target language
85+
* blob, and as metadata, the list of functions which survived the second
86+
* phase of filtering.
87+
*
88+
* The original module IR functions matching those are then marked with
89+
* "AvailableIn*" (e.g. AvailableInDXILDecoration) to indicate to future
90+
* module users which functions are present in the precompiled blob.
91+
*/
1192
SLANG_NO_THROW SlangResult SLANG_MCALL Module::precompileForTarget(
1293
SlangCompileTarget target,
1394
slang::IBlob** outDiagnostics)
@@ -20,6 +101,7 @@ namespace Slang
20101

21102
auto module = getIRModule();
22103
auto linkage = getLinkage();
104+
auto builder = IRBuilder(module);
23105

24106
DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer);
25107
applySettingsToDiagnosticSink(&sink, &sink, linkage->m_optionSet);
@@ -48,6 +130,7 @@ namespace Slang
48130
{
49131
case CodeGenTarget::DXIL:
50132
tp.getOptionSet().add(CompilerOptionName::Profile, Profile::RawEnum::DX_Lib_6_6);
133+
tp.getOptionSet().add(CompilerOptionName::EmbedDXIL, true);
51134
break;
52135
}
53136

@@ -59,20 +142,69 @@ namespace Slang
59142
CodeGenContext::Shared sharedCodeGenContext(&tp, entryPointIndices, &sink, nullptr);
60143
CodeGenContext codeGenContext(&sharedCodeGenContext);
61144

145+
// Mark all public functions as exported, ensure there's at least one. Store a mapping
146+
// of function name to IRInst* for later reference. After linking is done, we'll scan
147+
// the linked result to see which functions survived the pruning and are included in the
148+
// precompiled blob.
149+
Dictionary<String, IRInst*> nameToFunction;
150+
bool hasAtLeastOneFunction = false;
151+
for (auto inst : module->getGlobalInsts())
152+
{
153+
if (attemptPrecompiledExport(inst))
154+
{
155+
hasAtLeastOneFunction = true;
156+
builder.addDecoration(inst, kIROp_DownstreamModuleExportDecoration);
157+
nameToFunction[inst->findDecoration<IRExportDecoration>()->getMangledName()] = inst;
158+
}
159+
}
160+
161+
// Bail if there are no functions to export. That's not treated as an error
162+
// because it's possible that the module just doesn't have any simple HLSL.
163+
if (!hasAtLeastOneFunction)
164+
{
165+
return SLANG_OK;
166+
}
167+
62168
ComPtr<IArtifact> outArtifact;
63-
SlangResult res = codeGenContext.emitTranslationUnit(outArtifact);
169+
SlangResult res = codeGenContext.emitPrecompiledDXIL(outArtifact);
64170

65171
sink.getBlobIfNeeded(outDiagnostics);
66-
67172
if (res != SLANG_OK)
68173
{
69174
return res;
70175
}
71176

177+
auto metadata = findAssociatedRepresentation<IArtifactPostEmitMetadata>(outArtifact);
178+
if (!metadata)
179+
{
180+
return SLANG_E_NOT_AVAILABLE;
181+
}
182+
183+
for (const auto& mangledName : metadata->getExportedFunctionMangledNames())
184+
{
185+
auto moduleInst = nameToFunction[mangledName];
186+
builder.addDecoration(moduleInst, kIROp_AvailableInDXILDecoration);
187+
auto moduleDec = moduleInst->findDecoration<IRDownstreamModuleExportDecoration>();
188+
moduleDec->removeAndDeallocate();
189+
}
190+
191+
// Finally, clean up the transient export decorations left over in the module. These are
192+
// represent functions that were pruned from the IR after linking, before target generation.
193+
for (auto moduleInst : module->getGlobalInsts())
194+
{
195+
if (moduleInst->getOp() == kIROp_Func)
196+
{
197+
if (auto dec = moduleInst->findDecoration<IRDownstreamModuleExportDecoration>())
198+
{
199+
dec->removeAndDeallocate();
200+
}
201+
}
202+
}
203+
72204
ISlangBlob* blob;
73205
outArtifact->loadBlob(ArtifactKeep::Yes, &blob);
74206

75-
auto builder = IRBuilder(module);
207+
// Add the precompiled blob to the module
76208
builder.setInsertInto(module);
77209

78210
switch (targetReq->getTarget())

source/slang/slang-compiler.cpp

+23-33
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ namespace Slang
350350
Profile Profile::lookUp(UnownedStringSlice const& name)
351351
{
352352
#define PROFILE(TAG, NAME, STAGE, VERSION) if(name == UnownedTerminatedStringSlice(#NAME)) return Profile::TAG;
353-
#define PROFILE_ALIAS(TAG, DEF, NAME) if(name == UnownedTerminatedStringSlice(#NAME)) return Profile::TAG;
353+
#define PROFILE_ALIAS(TAG, DEF, NAME) if(name == UnownedTerminatedStringSlice(#NAME)) return Profile::TAG;
354354
#include "slang-profile-defs.h"
355355

356356
return Profile::Unknown;
@@ -767,7 +767,7 @@ namespace Slang
767767
# pragma warning(pop)
768768
#endif
769769

770-
SlangResult CodeGenContext::emitTranslationUnit(ComPtr<IArtifact>& outArtifact)
770+
SlangResult CodeGenContext::emitPrecompiledDXIL(ComPtr<IArtifact>& outArtifact)
771771
{
772772
return emitWithDownstreamForEntryPoints(outArtifact);
773773
}
@@ -1094,23 +1094,6 @@ namespace Slang
10941094
return SLANG_OK;
10951095
}
10961096

1097-
bool CodeGenContext::isPrecompiled()
1098-
{
1099-
auto program = getProgram();
1100-
1101-
bool allPrecompiled = true;
1102-
program->enumerateIRModules([&](IRModule* irModule)
1103-
{
1104-
// TODO: Conditionalize this on target
1105-
if (!irModule->precompiledDXIL)
1106-
{
1107-
allPrecompiled = false;
1108-
}
1109-
});
1110-
1111-
return allPrecompiled;
1112-
}
1113-
11141097
SlangResult CodeGenContext::emitWithDownstreamForEntryPoints(ComPtr<IArtifact>& outArtifact)
11151098
{
11161099
outArtifact.setNull();
@@ -1272,15 +1255,17 @@ namespace Slang
12721255
}
12731256
else
12741257
{
1275-
if (!isPrecompiled())
1258+
CodeGenContext sourceCodeGenContext(this, sourceTarget, extensionTracker);
1259+
1260+
if (target == CodeGenTarget::DXILAssembly || target == CodeGenTarget::DXIL)
12761261
{
1277-
CodeGenContext sourceCodeGenContext(this, sourceTarget, extensionTracker);
1262+
sourceCodeGenContext.removeAvailableInDXIL = true;
1263+
}
12781264

1279-
SLANG_RETURN_ON_FAIL(sourceCodeGenContext.emitEntryPointsSource(sourceArtifact));
1280-
sourceCodeGenContext.maybeDumpIntermediate(sourceArtifact);
1265+
SLANG_RETURN_ON_FAIL(sourceCodeGenContext.emitEntryPointsSource(sourceArtifact));
1266+
sourceCodeGenContext.maybeDumpIntermediate(sourceArtifact);
12811267

1282-
sourceLanguage = (SourceLanguage)TypeConvertUtil::getSourceLanguageFromTarget((SlangCompileTarget)sourceTarget);
1283-
}
1268+
sourceLanguage = (SourceLanguage)TypeConvertUtil::getSourceLanguageFromTarget((SlangCompileTarget)sourceTarget);
12841269
}
12851270

12861271
if (sourceArtifact)
@@ -1571,24 +1556,29 @@ namespace Slang
15711556
libraries.addRange(linkage->m_libModules.getBuffer(), linkage->m_libModules.getCount());
15721557
}
15731558

1574-
if (isPrecompiled())
1559+
auto program = getProgram();
1560+
1561+
// Load embedded precompiled libraries from IR into library artifacts
1562+
program->enumerateIRModules([&](IRModule* irModule)
15751563
{
1576-
auto program = getProgram();
1577-
program->enumerateIRModules([&](IRModule* irModule)
1564+
for (auto inst : irModule->getModuleInst()->getChildren())
1565+
{
1566+
if (target == CodeGenTarget::DXILAssembly || target == CodeGenTarget::DXIL)
15781567
{
1579-
// TODO: conditionalize on target
1580-
if (irModule->precompiledDXIL)
1568+
if (inst->getOp() == kIROp_EmbeddedDXIL)
15811569
{
1570+
auto slice = static_cast<IRBlobLit*>(inst->getOperand(0))->getStringSlice();
15821571
ArtifactDesc desc = ArtifactDescUtil::makeDescForCompileTarget(SLANG_DXIL);
15831572
desc.kind = ArtifactKind::Library;
15841573

15851574
auto library = ArtifactUtil::createArtifact(desc);
15861575

1587-
library->addRepresentationUnknown(irModule->precompiledDXIL);
1576+
library->addRepresentationUnknown(StringBlob::create(slice));
15881577
libraries.add(library);
15891578
}
1590-
});
1591-
}
1579+
}
1580+
}
1581+
});
15921582

15931583
options.compilerSpecificArguments = allocator.allocate(compilerSpecificArguments);
15941584
options.requiredCapabilityVersions = SliceUtil::asSlice(requiredCapabilityVersions);

source/slang/slang-compiler.h

+6-8
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,6 @@ namespace Slang
555555
/// and parsing via Slang reflection, and is not recommended for future APIs to use.
556556
///
557557
Scope* _getOrCreateScopeForLegacyLookup(ASTBuilder* astBuilder);
558-
559558
protected:
560559
ComponentType(Linkage* linkage);
561560

@@ -2732,10 +2731,14 @@ namespace Slang
27322731

27332732
SlangResult emitEntryPoints(ComPtr<IArtifact>& outArtifact);
27342733

2735-
SlangResult emitTranslationUnit(ComPtr<IArtifact>& outArtifact);
2734+
SlangResult emitPrecompiledDXIL(ComPtr<IArtifact>& outArtifact);
27362735

27372736
void maybeDumpIntermediate(IArtifact* artifact);
27382737

2738+
// Used to cause instructions available in precompiled DXIL to be
2739+
// removed between IR linking and target source generation.
2740+
bool removeAvailableInDXIL = false;
2741+
27392742
protected:
27402743
CodeGenTarget m_targetFormat = CodeGenTarget::Unknown;
27412744
ExtensionTracker* m_extensionTracker = nullptr;
@@ -2772,11 +2775,6 @@ namespace Slang
27722775

27732776

27742777
SlangResult _emitEntryPoints(ComPtr<IArtifact>& outArtifact);
2775-
2776-
/* Checks if all modules in the target program are already compiled to the
2777-
target language, indicating that a pass-through linking using the
2778-
downstream compiler is viable.*/
2779-
bool isPrecompiled();
27802778
private:
27812779
Shared* m_shared = nullptr;
27822780
};
@@ -2816,7 +2814,7 @@ namespace Slang
28162814
virtual SLANG_NO_THROW void SLANG_MCALL setTargetForceGLSLScalarBufferLayout(int targetIndex, bool value) SLANG_OVERRIDE;
28172815
virtual SLANG_NO_THROW void SLANG_MCALL setTargetForceDXLayout(int targetIndex, bool value) SLANG_OVERRIDE;
28182816
virtual SLANG_NO_THROW void SLANG_MCALL setTargetGenerateWholeProgram(int targetIndex, bool value) SLANG_OVERRIDE;
2819-
virtual SLANG_NO_THROW void SLANG_MCALL setTargetEmbedDXIL(int targetIndex, bool value) SLANG_OVERRIDE;
2817+
virtual SLANG_NO_THROW void SLANG_MCALL setEmbedDXIL(bool value) SLANG_OVERRIDE;
28202818
virtual SLANG_NO_THROW void SLANG_MCALL setMatrixLayoutMode(SlangMatrixLayoutMode mode) SLANG_OVERRIDE;
28212819
virtual SLANG_NO_THROW void SLANG_MCALL setDebugInfoLevel(SlangDebugInfoLevel level) SLANG_OVERRIDE;
28222820
virtual SLANG_NO_THROW void SLANG_MCALL setOptimizationLevel(SlangOptimizationLevel level) SLANG_OVERRIDE;

0 commit comments

Comments
 (0)