Skip to content

Commit 4b456ff

Browse files
committed
Support link time constants in embedded SPIR-V
When modules contain extern references to globals they don't define, or when the contain exports of globals they may or may not use, utilize SPIR-V linkage attributes to define and use the variables in embedded SPIR-V IR. Fixes shader-slang#6524
1 parent 5d6578b commit 4b456ff

10 files changed

+225
-58
lines changed

source/slang/slang-compiler-tu.cpp

+68-34
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,51 @@
1010

1111
namespace Slang
1212
{
13-
// Only attempt to precompile functions:
13+
// Only attempt to precompile functions and global variables:
1414
// 1) With function bodies (not just empty decls)
1515
// 2) Not marked with unsafeForceInlineDecoration
1616
// 3) Have a simple HLSL data type as the return or parameter type
1717
static bool attemptPrecompiledExport(IRInst* inst)
1818
{
19-
if (inst->getOp() != kIROp_Func)
19+
if (inst->getOp() != kIROp_Func && inst->getOp() != kIROp_GlobalVar && inst->getOp() != kIROp_GlobalConstant)
2020
{
2121
return false;
2222
}
2323

24-
// Skip functions with no body
25-
bool hasBody = false;
26-
for (auto child : inst->getChildren())
24+
if (inst->getOp() == kIROp_GlobalVar ||
25+
inst->getOp() == kIROp_GlobalConstant)
2726
{
28-
if (child->getOp() == kIROp_Block)
27+
printf("GlobalVar or GlobalConstant\n");
28+
if (inst->findDecoration<IRExportDecoration>())
2929
{
30-
hasBody = true;
31-
break;
30+
printf("Exporting global variable\n");
31+
return true;
3232
}
33-
}
34-
if (!hasBody)
35-
{
33+
printf("Not exporting global variable\n");
3634
return false;
3735
}
38-
39-
// Skip functions marked with unsafeForceInlineDecoration
40-
if (inst->findDecoration<IRUnsafeForceInlineEarlyDecoration>())
36+
else if (inst->getOp() == kIROp_Func)
4137
{
42-
return false;
38+
// Skip functions with no body
39+
bool hasBody = false;
40+
for (auto child : inst->getChildren())
41+
{
42+
if (child->getOp() == kIROp_Block)
43+
{
44+
hasBody = true;
45+
break;
46+
}
47+
}
48+
if (!hasBody)
49+
{
50+
return false;
51+
}
52+
53+
// Skip functions marked with unsafeForceInlineDecoration
54+
if (inst->findDecoration<IRUnsafeForceInlineEarlyDecoration>())
55+
{
56+
return false;
57+
}
4358
}
4459

4560
// Skip non-simple HLSL data types, filters out generics
@@ -51,6 +66,18 @@ static bool attemptPrecompiledExport(IRInst* inst)
5166
return true;
5267
}
5368

69+
static bool needsImport(IRInst* inst)
70+
{
71+
if (inst->getOp() == kIROp_GlobalVar || inst->getOp() == kIROp_GlobalConstant)
72+
{
73+
if (inst->findDecoration<IRUserExternDecoration>())
74+
{
75+
return true;
76+
}
77+
}
78+
return false;
79+
}
80+
5481
/*
5582
* Precompile the module for the given target.
5683
*
@@ -67,28 +94,28 @@ static bool attemptPrecompiledExport(IRInst* inst)
6794
* done during target generation in between IR linking+legalization and
6895
* target source emission.
6996
*
70-
* Functions which can be rejected up front:
97+
* Language features which can be rejected up front:
7198
* - Functions with no body
7299
* - Functions marked with unsafeForceInlineDecoration
73100
* - Functions that define or use generics
74101
*
75-
* The functions not rejected up front are marked with
76-
* DownstreamModuleExportDecoration which indicates functions we're trying to
77-
* export for precompilation, and this also helps to identify the functions
102+
* The instructions not rejected up front are marked with
103+
* DownstreamModuleExportDecoration which indicates what we're trying to
104+
* export for precompilation, and this also helps to identify the instructions
78105
* in the linked IR which survived the additional pruning.
79106
*
80-
* Functions that are rejected after linking+legalization (inside
107+
* Instructions that are rejected after linking+legalization (inside
81108
* emitPrecompiledDownstreamIR):
82109
* - (DXIL) Functions that return or take a HLSLStructuredBufferType
83110
* - (DXIL) Functions that return or take a Matrix type
84111
*
85112
* emitPrecompiled* produces the output artifact containing target language
86-
* blob, and as metadata, the list of functions which survived the second
113+
* blob, and as metadata, the list of instructions which survived the second
87114
* phase of filtering.
88115
*
89-
* The original module IR functions matching those are then marked with
116+
* The original module IR instructions matching those are then marked with
90117
* "AvailableInDownstreamIRDecoration" to indicate to future
91-
* module users which functions are present in the precompiled blob.
118+
* module users which instructions are present in the precompiled blob.
92119
*/
93120
SLANG_NO_THROW SlangResult SLANG_MCALL
94121
Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnostics)
@@ -107,6 +134,9 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
107134
}
108135
}
109136

137+
if (getEntryPoints().getCount() != 0)
138+
return SLANG_OK;
139+
110140
auto module = getIRModule();
111141
auto linkage = getLinkage();
112142
auto builder = IRBuilder(module);
@@ -160,20 +190,26 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
160190
// the linked result to see which functions survived the pruning and are included in the
161191
// precompiled blob.
162192
Dictionary<String, IRInst*> nameToFunction;
163-
bool hasAtLeastOneFunction = false;
193+
bool hasAtLeastOneExport = false;
194+
bool hasAtLeastOneImport = false;
164195
for (auto inst : module->getGlobalInsts())
165196
{
166197
if (attemptPrecompiledExport(inst))
167198
{
168-
hasAtLeastOneFunction = true;
199+
hasAtLeastOneExport = true;
169200
builder.addDecoration(inst, kIROp_DownstreamModuleExportDecoration);
170201
nameToFunction[inst->findDecoration<IRExportDecoration>()->getMangledName()] = inst;
171202
}
203+
if (needsImport(inst))
204+
{
205+
hasAtLeastOneImport = true;
206+
builder.addDecoration(inst, kIROp_DownstreamModuleImportDecoration);
207+
}
172208
}
173209

174-
// Bail if there are no functions to export. That's not treated as an error
175-
// because it's possible that the module just doesn't have any simple HLSL.
176-
if (!hasAtLeastOneFunction)
210+
// Bail if there is nothing to import/export. That's not treated as an error
211+
// because it's possible that the module just doesn't have any simple code.
212+
if (!hasAtLeastOneExport && !hasAtLeastOneImport)
177213
{
178214
return SLANG_OK;
179215
}
@@ -201,19 +237,17 @@ Module::precompileForTarget(SlangCompileTarget target, slang::IBlob** outDiagnos
201237
kIROp_AvailableInDownstreamIRDecoration,
202238
builder.getIntValue(builder.getIntType(), (int)targetReq->getTarget()));
203239
auto moduleDec = moduleInst->findDecoration<IRDownstreamModuleExportDecoration>();
204-
moduleDec->removeAndDeallocate();
240+
if (moduleDec)
241+
moduleDec->removeAndDeallocate();
205242
}
206243

207244
// Finally, clean up the transient export decorations left over in the module. These are
208245
// represent functions that were pruned from the IR after linking, before target generation.
209246
for (auto moduleInst : module->getGlobalInsts())
210247
{
211-
if (moduleInst->getOp() == kIROp_Func)
248+
if (auto dec = moduleInst->findDecoration<IRDownstreamModuleExportDecoration>())
212249
{
213-
if (auto dec = moduleInst->findDecoration<IRDownstreamModuleExportDecoration>())
214-
{
215-
dec->removeAndDeallocate();
216-
}
250+
dec->removeAndDeallocate();
217251
}
218252
}
219253

source/slang/slang-emit-spirv.cpp

+67-13
Original file line numberDiff line numberDiff line change
@@ -1793,6 +1793,8 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
17931793
return emitGlobalParam(as<IRGlobalParam>(inst));
17941794
case kIROp_GlobalVar:
17951795
return emitGlobalVar(as<IRGlobalVar>(inst));
1796+
case kIROp_GlobalConstant:
1797+
return emitGlobalConstant(as<IRGlobalConstant>(inst));
17961798
case kIROp_SPIRVAsmOperandBuiltinVar:
17971799
return emitBuiltinVar(inst);
17981800
case kIROp_Var:
@@ -2727,15 +2729,61 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
27272729
if (ptrType->hasAddressSpace())
27282730
storageClass = addressSpaceToStorageClass(ptrType->getAddressSpace());
27292731
}
2730-
auto varInst = emitOpVariable(
2731-
getSection(SpvLogicalSectionID::GlobalVariables),
2732-
globalVar,
2733-
globalVar->getDataType(),
2734-
storageClass);
2732+
2733+
// iterate through block children looking for constant initializers
2734+
// TODO: This is a bit of a hack.
2735+
IRInst* initializer = nullptr;
2736+
for (auto child : globalVar->getChildren())
2737+
{
2738+
if (auto blockInst = as<IRBlock>(child))
2739+
{
2740+
for (auto inst : blockInst->getChildren())
2741+
{
2742+
if (auto var = as<IRReturn>(inst))
2743+
{
2744+
initializer = var->getVal();
2745+
}
2746+
}
2747+
}
2748+
}
2749+
SpvInst* varInst;
2750+
if (initializer)
2751+
{
2752+
varInst = emitOpVariable(
2753+
getSection(SpvLogicalSectionID::GlobalVariables),
2754+
globalVar,
2755+
globalVar->getDataType(),
2756+
storageClass,
2757+
initializer);
2758+
}
2759+
else
2760+
{
2761+
varInst = emitOpVariable(
2762+
getSection(SpvLogicalSectionID::GlobalVariables),
2763+
globalVar,
2764+
globalVar->getDataType(),
2765+
storageClass);
2766+
}
27352767
maybeEmitPointerDecoration(varInst, globalVar);
27362768
if (layout)
27372769
emitVarLayout(globalVar, varInst, layout);
27382770
emitDecorations(globalVar, getID(varInst));
2771+
2772+
return varInst;
2773+
}
2774+
2775+
SpvInst* emitGlobalConstant(IRGlobalConstant* globalConstant)
2776+
{
2777+
auto storageClass = SpvStorageClassUniform;
2778+
if (auto ptrType = as<IRPtrTypeBase>(globalConstant->getDataType()))
2779+
{
2780+
if (ptrType->hasAddressSpace())
2781+
storageClass = addressSpaceToStorageClass(ptrType->getAddressSpace());
2782+
}
2783+
SpvInst* varInst = emitOpConstant(
2784+
globalConstant,
2785+
globalConstant->getDataType(),
2786+
getLiteralBits(globalConstant->getDataType(), globalConstant));
27392787
return varInst;
27402788
}
27412789

@@ -4041,6 +4089,8 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
40414089
operands.getArrayView());
40424090
}
40434091
break;
4092+
case kIROp_GlobalConstant:
4093+
return emitGlobalConstant(as<IRGlobalConstant>(inst));
40444094
}
40454095
if (result)
40464096
emitDecorations(inst, getID(result));
@@ -4860,8 +4910,15 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
48604910
case kIROp_DownstreamModuleImportDecoration:
48614911
{
48624912
requireSPIRVCapability(SpvCapabilityLinkage);
4863-
auto name =
4864-
decoration->getParent()->findDecoration<IRExportDecoration>()->getMangledName();
4913+
UnownedStringSlice name;
4914+
if (decoration->getParent()->findDecoration<IRImportDecoration>())
4915+
{
4916+
name = decoration->getParent()->findDecoration<IRImportDecoration>()->getMangledName();
4917+
}
4918+
else
4919+
{
4920+
name = decoration->getParent()->findDecoration<IRExportDecoration>()->getMangledName();
4921+
}
48654922
emitInst(
48664923
getSection(SpvLogicalSectionID::Annotations),
48674924
decoration,
@@ -8329,13 +8386,10 @@ SlangResult emitSPIRVFromIR(
83298386
}
83308387
if (generateWholeProgram)
83318388
{
8332-
if (auto func = as<IRFunc>(inst))
8389+
if (inst->findDecoration<IRDownstreamModuleExportDecoration>())
83338390
{
8334-
if (func->findDecoration<IRDownstreamModuleExportDecoration>())
8335-
{
8336-
context.ensureInst(inst);
8337-
symbolsEmitted = true;
8338-
}
8391+
context.ensureInst(inst);
8392+
symbolsEmitted = true;
83398393
}
83408394
}
83418395
}

source/slang/slang-emit.cpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,15 @@ SlangResult emitSPIRVForEntryPointsDirectly(
20672067
// Outside because we want to keep IR in scope whilst we are processing emits
20682068
LinkedIR linkedIR;
20692069
LinkingAndOptimizationOptions linkingAndOptimizationOptions;
2070+
2071+
bool isPrecompilation = codeGenContext->getTargetProgram()->getOptionSet().getBoolOption(
2072+
CompilerOptionName::EmbedDownstreamIR);
2073+
2074+
if (!isPrecompilation)
2075+
{
2076+
codeGenContext->removeAvailableInDownstreamIR = true;
2077+
}
2078+
20702079
SLANG_RETURN_ON_FAIL(
20712080
linkAndOptimizeIR(codeGenContext, linkingAndOptimizationOptions, linkedIR));
20722081

@@ -2098,9 +2107,6 @@ SlangResult emitSPIRVForEntryPointsDirectly(
20982107
compiler->disassemble((uint32_t*)spirv.getBuffer(), int(spirv.getCount() / 4));
20992108
#endif
21002109

2101-
bool isPrecompilation = codeGenContext->getTargetProgram()->getOptionSet().getBoolOption(
2102-
CompilerOptionName::EmbedDownstreamIR);
2103-
21042110
if (!isPrecompilation && !codeGenContext->shouldSkipDownstreamLinking())
21052111
{
21062112
ComPtr<IArtifact> linkedArtifact;
@@ -2191,8 +2197,8 @@ SlangResult emitSPIRVForEntryPointsDirectly(
21912197
downstreamOptions.optimizationLevel = DownstreamCompileOptions::OptimizationLevel::None;
21922198
break;
21932199
case OptimizationLevel::Default:
2194-
downstreamOptions.optimizationLevel =
2195-
DownstreamCompileOptions::OptimizationLevel::Default;
2200+
downstreamOptions.optimizationLevel = DownstreamCompileOptions::OptimizationLevel::None;
2201+
//Default;
21962202
break;
21972203
case OptimizationLevel::High:
21982204
downstreamOptions.optimizationLevel = DownstreamCompileOptions::OptimizationLevel::High;

source/slang/slang-ir-explicit-global-init.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,22 @@ struct MoveGlobalVarInitializationToEntryPointsPass
7272
m_module = module;
7373
m_targetProgram = targetProgram;
7474

75+
// Hack. If there are no entrypoint functions, we don't need to do anything.
76+
bool hasEntryPoints = false;
77+
for (auto inst : m_module->getGlobalInsts())
78+
{
79+
auto func = as<IRFunc>(inst);
80+
if (!func)
81+
continue;
82+
83+
if (!func->findDecoration<IREntryPointDecoration>())
84+
continue;
85+
86+
hasEntryPoints = true;
87+
}
88+
if (!hasEntryPoints)
89+
return;
90+
7591
// We start by looking for global variables with
7692
// initialization logic in the IR, and processing
7793
// each to produce a split variable (now without

source/slang/slang-ir-link.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1720,7 +1720,7 @@ static void diagnoseUnresolvedSymbols(TargetRequest* req, DiagnosticSink* sink,
17201720
{
17211721
for (auto globalSym : module->getGlobalInsts())
17221722
{
1723-
if (globalSym->findDecoration<IRImportDecoration>())
1723+
if (globalSym->findDecoration<IRImportDecoration>() && !globalSym->findDecoration<IRDownstreamModuleImportDecoration>())
17241724
{
17251725
for (;;)
17261726
{

source/slang/slang-ir-metadata.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ void collectMetadata(const IRModule* irModule, ArtifactPostEmitMetadata& outMeta
118118
// and exported functions.
119119
for (const auto& inst : irModule->getGlobalInsts())
120120
{
121+
if (inst->findDecoration<IRDownstreamModuleExportDecoration>())
122+
{
123+
auto name = inst->findDecoration<IRExportDecoration>()->getMangledName();
124+
outMetadata.m_exportedFunctionMangledNames.add(name);
125+
}
126+
121127
if (auto func = as<IRFunc>(inst))
122128
{
123-
if (func->findDecoration<IRDownstreamModuleExportDecoration>())
124-
{
125-
auto name = func->findDecoration<IRExportDecoration>()->getMangledName();
126-
outMetadata.m_exportedFunctionMangledNames.add(name);
127-
}
128129

129130
// Collect metadata from entrypoint params.
130131
for (auto param : func->getParams())

0 commit comments

Comments
 (0)