summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2024-02-26 17:00:31 -0800
committerGitHub <noreply@github.com>2024-02-26 17:00:31 -0800
commit39522159c245e32a99cfdc47f03236f7028f5c61 (patch)
tree4ae93fb32f267f7caa5ce55a6a52aac9f1f33bdd
parent1d8e93cd434f0c7acbb6db747b32c3a3720c5c2e (diff)
Allow default values for `extern` symbols. (#3632)
* Allow default values for `extern` symbols. * Fix. * Fix test.
-rw-r--r--build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj1
-rw-r--r--build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters3
-rw-r--r--slang.h1
-rw-r--r--source/slang/slang-ast-decl.h1
-rw-r--r--source/slang/slang-check-conformance.cpp12
-rw-r--r--source/slang/slang-check-decl.cpp17
-rw-r--r--source/slang/slang-check-impl.h2
-rw-r--r--source/slang/slang-diagnostic-defs.h2
-rw-r--r--source/slang/slang-ir-inst-defs.h3
-rw-r--r--source/slang/slang-ir-insts.h11
-rw-r--r--source/slang/slang-ir-link.cpp100
-rw-r--r--source/slang/slang-ir-peephole.cpp11
-rw-r--r--source/slang/slang-ir-peephole.h13
-rw-r--r--source/slang/slang-ir-ssa-simplification.cpp2
-rw-r--r--source/slang/slang-ir-ssa-simplification.h3
-rw-r--r--source/slang/slang-ir.cpp8
-rw-r--r--source/slang/slang-ir.h2
-rw-r--r--source/slang/slang-lower-to-ir.cpp71
-rw-r--r--source/slang/slang-options.cpp3
-rw-r--r--source/slang/slang-parser.cpp3
-rw-r--r--source/slang/slang-serialize-container.cpp2
-rw-r--r--source/slang/slang.cpp3
-rw-r--r--tests/diagnostics/unresolved-symbol.slang18
-rw-r--r--tests/ir/string-literal-hash-reflection.slang5
-rw-r--r--tests/library/export-test.slang6
-rw-r--r--tests/library/library-test.slang6
-rw-r--r--tools/gfx-unit-test/link-time-default.cpp237
27 files changed, 469 insertions, 77 deletions
diff --git a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj
index 2be387a2a..3b3df0754 100644
--- a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj
+++ b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj
@@ -304,6 +304,7 @@
<ClCompile Include="..\..\..\tools\gfx-unit-test\gfx-test-util.cpp" />
<ClCompile Include="..\..\..\tools\gfx-unit-test\instanced-draw-tests.cpp" />
<ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-constant.cpp" />
+ <ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-default.cpp" />
<ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-options.cpp" />
<ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-type.cpp" />
<ClCompile Include="..\..\..\tools\gfx-unit-test\mutable-shader-object.cpp" />
diff --git a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters
index 162ea49b1..9b98ce1c8 100644
--- a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters
+++ b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters
@@ -68,6 +68,9 @@
<ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-constant.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-default.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\tools\gfx-unit-test\link-time-options.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/slang.h b/slang.h
index e217f9ab4..4513be03b 100644
--- a/slang.h
+++ b/slang.h
@@ -884,6 +884,7 @@ extern "C"
EmitSpirvViaGLSL, // bool
EmitSpirvDirectly, // bool
SPIRVCoreGrammarJSON, // stringValue0: json path
+ IncompleteLibrary, // bool, when set, will not issue an error when the linked program has unresolved extern function symbols.
// Downstream
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h
index c516b15c7..61e1b751f 100644
--- a/source/slang/slang-ast-decl.h
+++ b/source/slang/slang-ast-decl.h
@@ -146,6 +146,7 @@ class AggTypeDecl : public AggTypeDeclBase
// Used if this type declaration is a wrapper, i.e. struct FooWrapper:IFoo = Foo;
TypeExp wrappedType;
+ bool hasBody = true;
void unionTagsWith(TypeTag other);
void addTag(TypeTag tag);
diff --git a/source/slang/slang-check-conformance.cpp b/source/slang/slang-check-conformance.cpp
index 726572d08..e73c0723b 100644
--- a/source/slang/slang-check-conformance.cpp
+++ b/source/slang/slang-check-conformance.cpp
@@ -279,18 +279,16 @@ namespace Slang
}
- Type* SemanticsVisitor::getBufferElementType(Type* type)
+ Type* SemanticsVisitor::getConstantBufferElementType(Type* type)
{
if (auto arrType = as<ArrayExpressionType>(type))
- return getBufferElementType(arrType->getElementType());
+ return getConstantBufferElementType(arrType->getElementType());
if (auto modifiedType = as<ModifiedType>(type))
- return getBufferElementType(modifiedType->getBase());
+ return getConstantBufferElementType(modifiedType->getBase());
if (auto constantBuffer = as<ConstantBufferType>(type))
return constantBuffer->getElementType();
- if (auto structuredBuffer = as<HLSLStructuredBufferTypeBase>(type))
- return structuredBuffer->getElementType();
- if (auto storageBuffer = as<GLSLShaderStorageBufferType>(type))
- return storageBuffer->getElementType();
+ if (auto parameterBlock = as<ParameterBlockType>(type))
+ return parameterBlock->getElementType();
return nullptr;
}
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index 25f535825..5a9559ce6 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -1478,11 +1478,6 @@ namespace Slang
}
else
{
- if (varDecl->hasModifier<ExternModifier>())
- {
- getSink()->diagnose(initExpr, Diagnostics::externValueCannotHaveInitializer);
- }
-
initExpr = CheckExpr(initExpr);
// TODO: We might need some additional steps here to ensure
@@ -1850,7 +1845,7 @@ namespace Slang
varDecl->initExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate);
}
}
- if (auto elementType = getBufferElementType(varDecl->getType()))
+ if (auto elementType = getConstantBufferElementType(varDecl->getType()))
{
if (doesTypeHaveTag(elementType, TypeTag::Incomplete))
{
@@ -5166,6 +5161,13 @@ namespace Slang
return false;
}
+ static bool _doesTypeDeclHaveDefinition(ContainerDecl* decl)
+ {
+ if (auto aggTypeDecl = as<AggTypeDecl>(decl))
+ return aggTypeDecl->hasBody;
+ return false;
+ }
+
bool SemanticsVisitor::checkConformance(
Type* subType,
InheritanceDecl* inheritanceDecl,
@@ -5244,7 +5246,8 @@ namespace Slang
witnessTable = new WitnessTable();
witnessTable->baseType = superType;
witnessTable->witnessedType = subType;
- witnessTable->isExtern = parentDecl->hasModifier<ExternModifier>();
+ witnessTable->isExtern = (!_doesTypeDeclHaveDefinition(parentDecl)
+ && parentDecl->hasModifier<ExternModifier>());
inheritanceDecl->witnessTable = witnessTable;
}
diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h
index 8abf06d6f..d91bbb75b 100644
--- a/source/slang/slang-check-impl.h
+++ b/source/slang/slang-check-impl.h
@@ -1978,7 +1978,7 @@ namespace Slang
TypeTag getTypeTags(Type* type);
- Type* getBufferElementType(Type* type);
+ Type* getConstantBufferElementType(Type* type);
/// Check whether `subType` is a sub-type of `superTypeDeclRef`,
/// and return a witness to the sub-type relationship if it holds
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index c90dc12e8..d638f7be6 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -484,7 +484,6 @@ DIAGNOSTIC(30504, Error, cannotUseInitializerListForType, "cannot use initialize
// 3062x: variables
DIAGNOSTIC(30620, Error, varWithoutTypeMustHaveInitializer, "a variable declaration without an initial-value expression must be given an explicit type")
-DIAGNOSTIC(30621, Error, externValueCannotHaveInitializer, "an 'extern' variable declaration cannot have a value.")
DIAGNOSTIC(30622, Error, ambiguousDefaultInitializerForType, "more than one default initializer was found for type '$0'")
// 307xx: parameters
@@ -713,6 +712,7 @@ DIAGNOSTIC(41904, Error, unableToAlignOf, "alignof could not be performed for ty
DIAGNOSTIC(42001, Error, invalidUseOfTorchTensorTypeInDeviceFunc, "invalid use of TorchTensor type in device/kernel functions. use `TensorView` instead.")
+DIAGNOSTIC(45001, Error, unresolvedSymbol, "unresolved external symbol '$0'.")
//
// 5xxxx - Target code generation.
//
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 0c962b7a4..cb89b265b 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -787,6 +787,9 @@ INST(HighLevelDeclDecoration, highLevelDecl, 1, 0)
INST(ExportDecoration, export, 1, 0)
INST_RANGE(LinkageDecoration, ImportDecoration, ExportDecoration)
+ /// Marks an inst as coming from an `extern` symbol defined in the user code.
+ INST(UserExternDecoration, UserExtern, 0, 0)
+
/// An extern_cpp decoration marks the inst to emit its name without mangling for C++ interop.
INST(ExternCppDecoration, externCpp, 1, 0)
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 82d891459..f1160c9a6 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -526,6 +526,12 @@ struct IRLinkageDecoration : IRDecoration
}
};
+struct IRUserExternDecoration : IRDecoration
+{
+ enum { kOp = kIROp_UserExternDecoration };
+ IR_LEAF_ISA(UserExternDecoration)
+};
+
struct IRImportDecoration : IRLinkageDecoration
{
enum { kOp = kIROp_ImportDecoration };
@@ -4346,6 +4352,11 @@ public:
addDecoration(value, kIROp_ExportDecoration, getStringValue(mangledName));
}
+ void addUserExternDecoration(IRInst* value)
+ {
+ addDecoration(value, kIROp_UserExternDecoration);
+ }
+
void addExternCppDecoration(IRInst* value, UnownedStringSlice const& mangledName)
{
addDecoration(value, kIROp_ExternCppDecoration, getStringValue(mangledName));
diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp
index be0b87b60..e81eddab7 100644
--- a/source/slang/slang-ir-link.cpp
+++ b/source/slang/slang-ir-link.cpp
@@ -1425,6 +1425,100 @@ static bool _isHLSLExported(IRInst* inst)
return false;
}
+static bool doesFuncHaveDefinition(IRFunc* func)
+{
+ if (func->getFirstBlock() != nullptr)
+ return true;
+ for (auto decor : func->getDecorations())
+ {
+ switch (decor->getOp())
+ {
+ case kIROp_IntrinsicOpDecoration:
+ case kIROp_TargetIntrinsicDecoration:
+ return true;
+ default:
+ continue;
+ }
+ }
+ return false;
+}
+
+static bool doesWitnessTableHaveDefinition(IRWitnessTable* wt)
+{
+ auto interfaceType = as<IRInterfaceType>(wt->getConformanceType());
+ if (!interfaceType)
+ return true;
+ auto interfaceRequirementCount = interfaceType->getRequirementCount();
+ if (interfaceRequirementCount == 0)
+ return true;
+ for (auto entry : wt->getChildren())
+ {
+ if (as<IRWitnessTableEntry>(entry))
+ return true;
+ }
+ return false;
+}
+
+static bool doesTargetAllowUnresolvedFuncSymbol(TargetRequest* req)
+{
+ switch (req->getTarget())
+ {
+ case CodeGenTarget::HLSL:
+ case CodeGenTarget::DXIL:
+ case CodeGenTarget::DXILAssembly:
+ case CodeGenTarget::HostCPPSource:
+ case CodeGenTarget::PyTorchCppBinding:
+ case CodeGenTarget::ShaderHostCallable:
+ case CodeGenTarget::ShaderSharedLibrary:
+ case CodeGenTarget::HostHostCallable:
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::CUDASource:
+ case CodeGenTarget::SPIRV:
+ if (req->getOptionSet().getBoolOption(CompilerOptionName::IncompleteLibrary))
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void diagnoseUnresolvedSymbols(TargetRequest* req, DiagnosticSink* sink, IRModule* module)
+{
+ for (auto globalSym : module->getGlobalInsts())
+ {
+ if (globalSym->findDecoration<IRImportDecoration>())
+ {
+ for (;;)
+ {
+ if (auto constant = as<IRGlobalConstant>(globalSym))
+ {
+ if (constant->getOperandCount() == 0)
+ sink->diagnose(globalSym->sourceLoc, Diagnostics::unresolvedSymbol, globalSym);
+ }
+ else if (auto genericSym = as<IRGeneric>(globalSym))
+ {
+ globalSym = findGenericReturnVal(genericSym);
+ continue;
+ }
+ else if (auto funcSym = as<IRFunc>(globalSym))
+ {
+ if (!doesFuncHaveDefinition(funcSym) && !doesTargetAllowUnresolvedFuncSymbol(req))
+ sink->diagnose(globalSym->sourceLoc, Diagnostics::unresolvedSymbol, globalSym);
+ }
+ else if (auto witnessSym = as<IRWitnessTable>(globalSym))
+ {
+ if (!doesWitnessTableHaveDefinition(witnessSym))
+ {
+ sink->diagnose(globalSym->sourceLoc, Diagnostics::unresolvedSymbol, witnessSym);
+ if (auto concreteType = witnessSym->getConcreteType())
+ sink->diagnose(concreteType->sourceLoc, Diagnostics::seeDeclarationOf, concreteType);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
LinkedIR linkIR(
CodeGenContext* codeGenContext)
{
@@ -1627,6 +1721,12 @@ LinkedIR linkIR(
// Specialize target_switch branches to use the best branch for the target.
specializeTargetSwitch(targetReq, state->irModule);
+ // Diagnose on unresolved symbols if we are compiling into a target that does
+ // not allow incomplete symbols.
+ // At this point, we should not see any [import] symbols that does not have a
+ // definition.
+ diagnoseUnresolvedSymbols(targetReq, codeGenContext->getSink(), state->irModule);
+
// TODO: *technically* we should consider the case where
// we have global variables with initializers, since
// these should get run whether or not the entry point
diff --git a/source/slang/slang-ir-peephole.cpp b/source/slang/slang-ir-peephole.cpp
index f9fca35e3..d8327a580 100644
--- a/source/slang/slang-ir-peephole.cpp
+++ b/source/slang/slang-ir-peephole.cpp
@@ -17,6 +17,7 @@ struct PeepholeContext : InstPassBase
FloatingPointMode floatingPointMode = FloatingPointMode::Precise;
bool removeOldInst = true;
bool isInGeneric = false;
+ bool isPrelinking = false;
TargetProgram* targetProgram;
@@ -695,6 +696,13 @@ struct PeepholeContext : InstPassBase
{
if (inst->getOperand(0)->getOp() == kIROp_WitnessTable)
{
+ // Don't fold witness lookups prelinking if the witness table is `extern`.
+ // These witness tables provides `default`s in case they are not
+ // explicitly specialized via other linked modules, therefore we don't want
+ // to resolve them too soon before linking.
+ if (isPrelinking && inst->getOperand(0)->findDecoration<IRUserExternDecoration>())
+ break;
+
auto wt = as<IRWitnessTable>(inst->getOperand(0));
auto key = inst->getOperand(1);
for (auto item : wt->getChildren())
@@ -1069,10 +1077,11 @@ struct PeepholeContext : InstPassBase
}
};
-bool peepholeOptimize(TargetProgram* target, IRModule* module)
+bool peepholeOptimize(TargetProgram* target, IRModule* module, PeepholeOptimizationOptions options)
{
PeepholeContext context = PeepholeContext(module);
context.targetProgram = target;
+ context.isPrelinking = options.isPrelinking;
return context.processModule();
}
diff --git a/source/slang/slang-ir-peephole.h b/source/slang/slang-ir-peephole.h
index 9aa2d6164..411267072 100644
--- a/source/slang/slang-ir-peephole.h
+++ b/source/slang/slang-ir-peephole.h
@@ -8,8 +8,19 @@ namespace Slang
struct IRInst;
class TargetProgram;
+ struct PeepholeOptimizationOptions
+ {
+ bool isPrelinking = false;
+ static PeepholeOptimizationOptions getPrelinking()
+ {
+ PeepholeOptimizationOptions result;
+ result.isPrelinking = true;
+ return result;
+ }
+ };
+
/// Apply peephole optimizations.
- bool peepholeOptimize(TargetProgram* target, IRModule* module);
+ bool peepholeOptimize(TargetProgram* target, IRModule* module, PeepholeOptimizationOptions options);
bool peepholeOptimize(TargetProgram* target, IRInst* func);
bool peepholeOptimizeGlobalScope(TargetProgram* target, IRModule* module);
bool tryReplaceInstUsesWithSimplifiedValue(TargetProgram* target, IRModule* module, IRInst* inst);
diff --git a/source/slang/slang-ir-ssa-simplification.cpp b/source/slang/slang-ir-ssa-simplification.cpp
index 77cad9f6c..6a0fb620e 100644
--- a/source/slang/slang-ir-ssa-simplification.cpp
+++ b/source/slang/slang-ir-ssa-simplification.cpp
@@ -76,7 +76,7 @@ namespace Slang
while (changed && iterationCounter < kMaxIterations)
{
changed = false;
- changed |= peepholeOptimize(target, module);
+ changed |= peepholeOptimize(target, module, options.peepholeOptions);
changed |= removeRedundancy(module);
changed |= simplifyCFG(module, options.cfgOptions);
diff --git a/source/slang/slang-ir-ssa-simplification.h b/source/slang/slang-ir-ssa-simplification.h
index c15ffc822..4ae8e8e6e 100644
--- a/source/slang/slang-ir-ssa-simplification.h
+++ b/source/slang/slang-ir-ssa-simplification.h
@@ -2,6 +2,7 @@
#pragma once
#include "slang-ir-simplify-cfg.h"
+#include "slang-ir-peephole.h"
namespace Slang
{
@@ -13,6 +14,8 @@ namespace Slang
struct IRSimplificationOptions
{
CFGSimplificationOptions cfgOptions;
+ PeepholeOptimizationOptions peepholeOptions;
+
static IRSimplificationOptions getDefault()
{
IRSimplificationOptions result;
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 035b2aade..a016679e7 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -32,7 +32,15 @@ namespace Slang
if (!irObject)
return;
if (auto nameHint = irObject->findDecoration<IRNameHintDecoration>())
+ {
sb << nameHint->getName();
+ return;
+ }
+ if (auto linkage = irObject->findDecoration<IRLinkageDecoration>())
+ {
+ sb << linkage->getMangledName();
+ return;
+ }
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index bbb9dfeeb..3a459a501 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -1878,6 +1878,8 @@ struct IRInterfaceRequirementEntry : IRInst
struct IRInterfaceType : IRType
{
IR_LEAF_ISA(InterfaceType)
+
+ UInt getRequirementCount() { return getOperandCount(); }
};
struct IRConjunctionType : IRType
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index 9d010ec1c..8b9a3c377 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -654,47 +654,30 @@ bool isFromStdLib(Decl* decl)
return false;
}
-bool isImportedDecl(IRGenContext* context, Decl* decl)
+bool isImportedDecl(IRGenContext* context, Decl* decl, bool& outIsExplicitExtern)
{
// If the declaration has the extern attribute then it must be imported
// from another module.
// Note that `extern` declarations will have a mangled name that does not
// include the module name so the linking step can resolve them correctly.
//
+ outIsExplicitExtern = false;
if (decl->findModifier<ExternAttribute>() || decl->findModifier<ExternModifier>())
{
+ outIsExplicitExtern = true;
return true;
}
- ModuleDecl* moduleDecl = findModuleDecl(decl);
- if (!moduleDecl)
- return false;
-
-#if 0
- // HACK: don't treat standard library code as
- // being imported for right now, just because
- // we don't load its IR in the same way as
- // for other imports.
- //
- // TODO: Fix this the right way, by having standard
- // library declarations have IR modules that we link
- // in via the normal means.
- if (isFromStdLib(decl))
- return false;
-#endif
-
- if (moduleDecl != context->getMainModuleDecl())
- return true;
-
- return false;
-}
-
- /// Is `decl` a function that should be force-inlined early in compilation (before linking)?
-static bool isForceInlineEarly(Decl* decl)
-{
- if(decl->hasModifier<UnsafeForceInlineEarlyAttribute>())
- return true;
-
+ for (auto parent = decl; parent; parent = parent->parentDecl)
+ {
+ if (as<ModuleDecl>(parent) && parent != context->getMainModuleDecl())
+ return true;
+ if (parent->findModifier<ExternAttribute>() || parent->findModifier<ExternModifier>())
+ {
+ outIsExplicitExtern = true;
+ return true;
+ }
+ }
return false;
}
@@ -1325,9 +1308,12 @@ static void addLinkageDecoration(
inst = outerGeneric;
}
- if (isImportedDecl(context, decl))
+ bool explicitExtern = false;
+ if (isImportedDecl(context, decl, explicitExtern))
{
builder->addImportDecoration(inst, mangledName);
+ if (explicitExtern)
+ builder->addUserExternDecoration(inst);
}
else
{
@@ -7597,12 +7583,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
// the underlying storage.
context->setGlobalValue(decl, globalVal);
- if (isImportedDecl(decl))
- {
- // Always emit imported declarations as declarations,
- // and not definitions.
- }
- else if( auto initExpr = decl->initExpr )
+ if( auto initExpr = decl->initExpr )
{
IRBuilder subBuilderStorage = *getBuilder();
IRBuilder* subBuilder = &subBuilderStorage;
@@ -8410,11 +8391,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
return LoweredValInfo::simple(irFieldKey);
}
- bool isImportedDecl(Decl* decl)
- {
- return Slang::isImportedDecl(context, decl);
- }
-
IRType* maybeGetConstExprType(IRType* type, Decl* decl)
{
return Slang::maybeGetConstExprType(getBuilder(), type, decl);
@@ -9105,12 +9081,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
// pre-generated IR for the module that defines it (or do some kind
// of minimal linking to bring in the inline functions).
//
- if (isImportedDecl(decl) && !isForceInlineEarly(decl))
- {
- // Always emit imported declarations as declarations,
- // and not definitions.
- }
- else if (!decl->body)
+ if (!decl->body)
{
// This is a function declaration without a body.
// In Slang we currently try not to support forward declarations
@@ -10374,7 +10345,7 @@ RefPtr<IRModule> generateIRForTranslationUnit(
constructSSA(module);
simplifyCFG(module, CFGSimplificationOptions::getDefault());
applySparseConditionalConstantPropagation(module, compileRequest->getSink());
- peepholeOptimize(nullptr, module);
+ peepholeOptimize(nullptr, module, PeepholeOptimizationOptions::getPrelinking());
for (auto inst : module->getGlobalInsts())
{
@@ -10423,7 +10394,7 @@ RefPtr<IRModule> generateIRForTranslationUnit(
changed |= constructSSA(module);
simplifyCFG(module, CFGSimplificationOptions::getDefault());
changed |= applySparseConditionalConstantPropagation(module, compileRequest->getSink());
- changed |= peepholeOptimize(nullptr, module);
+ changed |= peepholeOptimize(nullptr, module, PeepholeOptimizationOptions::getPrelinking());
for (auto inst : module->getGlobalInsts())
{
if (auto func = as<IRGlobalValueWithCode>(inst))
diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp
index 39578a1ea..f8231210c 100644
--- a/source/slang/slang-options.cpp
+++ b/source/slang/slang-options.cpp
@@ -417,6 +417,8 @@ void initCommandOptions(CommandOptions& options)
"Generate SPIR-V output direclty rather than by compiling generated GLSL with glslang" },
{ OptionKind::SPIRVCoreGrammarJSON, "-spirv-core-grammar", nullptr,
"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" },
#endif
};
@@ -1681,6 +1683,7 @@ SlangResult OptionsParser::_parse(
case OptionKind::OutputIncludes:
case OptionKind::PreprocessorOutput:
case OptionKind::DumpAst:
+ case OptionKind::IncompleteLibrary:
linkage->m_optionSet.set(optionKind, true); break;
break;
case OptionKind::NoCodeGen:
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index 7ce710469..5555647f4 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -4744,7 +4744,10 @@ namespace Slang
return rs;
}
if (AdvanceIf(this, TokenType::Semicolon))
+ {
+ rs->hasBody = false;
return rs;
+ }
parseDeclBody(this, rs);
return rs;
});
diff --git a/source/slang/slang-serialize-container.cpp b/source/slang/slang-serialize-container.cpp
index 0622c2bd4..5b382f03d 100644
--- a/source/slang/slang-serialize-container.cpp
+++ b/source/slang/slang-serialize-container.cpp
@@ -81,6 +81,8 @@ namespace Slang {
SLANG_ASSERT(dstModule.irModule);
}
DigestBuilder<SHA1> digestBuilder;
+ auto version = String(getBuildTagString());
+ digestBuilder.append(version);
module->getOptionSet().buildHash(digestBuilder);
auto fileDependencies = module->getFileDependencies();
String canonicalModulePath, moduleDir;
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index bc91622f0..9a653aded 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -2907,7 +2907,6 @@ SlangResult EndToEndCompileRequest::executeActionsInner()
//
for (auto targetReq : getLinkage()->targets)
{
- targetReq->getOptionSet().inheritFrom(getLinkage()->m_optionSet);
auto targetProgram = m_specializedGlobalAndEntryPointsComponentType->getTargetProgram(targetReq);
targetProgram->getOrCreateLayout(getSink());
}
@@ -3559,6 +3558,8 @@ bool Linkage::isBinaryModuleUpToDate(String fromPath, RiffContainer* container)
auto& moduleHeader = containerData.modules[0];
DigestBuilder<SHA1> digestBuilder;
+ auto version = String(getBuildTagString());
+ digestBuilder.append(version);
m_optionSet.buildHash(digestBuilder);
for (auto file : moduleHeader.dependentFiles)
{
diff --git a/tests/diagnostics/unresolved-symbol.slang b/tests/diagnostics/unresolved-symbol.slang
new file mode 100644
index 000000000..d5c8444fc
--- /dev/null
+++ b/tests/diagnostics/unresolved-symbol.slang
@@ -0,0 +1,18 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target hlsl -entry main -profile cs_6_0
+interface IFoo
+{
+ int doThing();
+}
+
+// CHECK: ([[#@LINE+1]]): error 45001:
+extern struct Foo : IFoo;
+// CHECK: ([[#@LINE+1]]): error 45001:
+extern static const int c;
+
+RWStructuredBuffer<int> output;
+
+void main()
+{
+ Foo f;
+ output[0] = c + f.doThing();
+} \ No newline at end of file
diff --git a/tests/ir/string-literal-hash-reflection.slang b/tests/ir/string-literal-hash-reflection.slang
index a0a28876b..44a0f36e9 100644
--- a/tests/ir/string-literal-hash-reflection.slang
+++ b/tests/ir/string-literal-hash-reflection.slang
@@ -1,4 +1,7 @@
-//TEST:REFLECTION:-stage compute -entry computeMain -target hlsl -no-codegen
+//TEST:REFLECTION(filecheck=CHECK):-stage compute -entry computeMain -target hlsl -no-codegen
+
+// CHECK-DAG: "Hello \t\n\0x083 World": -215446506
+// CHECK-DAG: "Try another": 900483678
import string_literal_module;
diff --git a/tests/library/export-test.slang b/tests/library/export-test.slang
index a85396126..c2bb7810e 100644
--- a/tests/library/export-test.slang
+++ b/tests/library/export-test.slang
@@ -5,12 +5,12 @@
// This didn't work for lib_6_2 when compiling via DXC (!). Even though it's stated elsewhere the feature is available from 6.1
-//TEST:COMPILE: tests/library/export-library.slang -profile lib_6_3 -target dxil -o tests/library/export-library.dxil
-//TEST:COMPILE: tests/library/export-test.slang -entry computeMain -profile cs_6_3 -target dxil -r tests/library/export-library.dxil -o tests/library/export-test.dxil
+//TEST:COMPILE: tests/library/export-library.slang -incomplete-library -profile lib_6_3 -target dxil -o tests/library/export-library.dxil
+//TEST:COMPILE: tests/library/export-test.slang -incomplete-library -entry computeMain -profile cs_6_3 -target dxil -r tests/library/export-library.dxil -o tests/library/export-test.dxil
// Test the produced kernel.
-//TEST:COMPARE_COMPUTE_EX:-slang -compute -profile cs_6_3 -dx12 -use-dxil -shaderobj -Xslang... -r tests/library/export-library.dxil -X.
+//TEST:COMPARE_COMPUTE_EX:-slang -compute -profile cs_6_3 -dx12 -use-dxil -shaderobj -Xslang... -r tests/library/export-library.dxil -incomplete-library -X.
extern int foo(int a);
diff --git a/tests/library/library-test.slang b/tests/library/library-test.slang
index 3543ced10..e128b6c7c 100644
--- a/tests/library/library-test.slang
+++ b/tests/library/library-test.slang
@@ -5,12 +5,12 @@
// This didn't work for lib_6_2 when compiling via DXC (!). Even though it's stated elsewhere the feature is available from 6.1
-//TEST:COMPILE: tests/library/library.slang -profile lib_6_3 -target dxil -o tests/library/library.dxil
-//TEST:COMPILE: tests/library/library-test.slang -entry computeMain -profile cs_6_3 -target dxil -r tests/library/library.dxil -o tests/library/library-test.dxil
+//TEST:COMPILE: tests/library/library.slang -incomplete-library -profile lib_6_3 -target dxil -o tests/library/library.dxil
+//TEST:COMPILE: tests/library/library-test.slang -incomplete-library -entry computeMain -profile cs_6_3 -target dxil -r tests/library/library.dxil -o tests/library/library-test.dxil
// Test the produced kernel.
-//TEST:COMPARE_COMPUTE_EX:-slang -compute -profile cs_6_3 -dx12 -use-dxil -shaderobj -Xslang... -r tests/library/library.dxil -X.
+//TEST:COMPARE_COMPUTE_EX:-slang -compute -profile cs_6_3 -dx12 -use-dxil -shaderobj -Xslang... -r tests/library/library.dxil -incomplete-library -X.
extern int foo(int a);
diff --git a/tools/gfx-unit-test/link-time-default.cpp b/tools/gfx-unit-test/link-time-default.cpp
new file mode 100644
index 000000000..be9198b82
--- /dev/null
+++ b/tools/gfx-unit-test/link-time-default.cpp
@@ -0,0 +1,237 @@
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "slang-gfx.h"
+#include "gfx-test-util.h"
+#include "tools/gfx-util/shader-cursor.h"
+#include "source/core/slang-basic.h"
+#include "source/core/slang-blob.h"
+
+using namespace gfx;
+
+namespace gfx_test
+{
+ static Slang::Result loadProgram(
+ gfx::IDevice* device,
+ Slang::ComPtr<gfx::IShaderProgram>& outShaderProgram,
+ slang::ProgramLayout*& slangReflection,
+ bool linkSpecialization = false)
+ {
+ const char* moduleInterfaceSrc = R"(
+ interface IFoo
+ {
+ static const int offset;
+ [mutating] void setValue(float v);
+ float getValue();
+ property float val2{get;set;}
+ }
+ struct FooImpl : IFoo
+ {
+ float val;
+ static const int offset = -1;
+ [mutating] void setValue(float v) { val = v; }
+ float getValue() { return val + 1.0; }
+ property float val2 {
+ get { return val + 2.0; }
+ set { val = newValue; }
+ }
+ };
+ struct BarImpl : IFoo
+ {
+ float val;
+ static const int offset = 2;
+ [mutating] void setValue(float v) { val = v; }
+ float getValue() { return val + 1.0; }
+ property float val2 {
+ get { return val; }
+ set { val = newValue; }
+ }
+ };
+ )";
+ const char* module0Src = R"(
+ import ifoo;
+ extern struct Foo : IFoo = FooImpl;
+ extern static const float c = 0.0;
+ [numthreads(1,1,1)]
+ void computeMain(uniform RWStructuredBuffer<float> buffer)
+ {
+ Foo foo;
+ foo.setValue(3.0);
+ buffer[0] = foo.getValue() + foo.val2 + Foo.offset + c;
+ }
+ )";
+ const char* module1Src = R"(
+ import ifoo;
+ export struct Foo : IFoo = BarImpl;
+ export static const float c = 1.0;
+ )";
+ Slang::ComPtr<slang::ISession> slangSession;
+ SLANG_RETURN_ON_FAIL(device->getSlangSession(slangSession.writeRef()));
+ Slang::ComPtr<slang::IBlob> diagnosticsBlob;
+ auto moduleInterfaceBlob = Slang::UnownedRawBlob::create(moduleInterfaceSrc, strlen(moduleInterfaceSrc));
+ auto module0Blob = Slang::UnownedRawBlob::create(module0Src, strlen(module0Src));
+ auto module1Blob = Slang::UnownedRawBlob::create(module1Src, strlen(module1Src));
+ slang::IModule* moduleInterface = slangSession->loadModuleFromSource("ifoo", "ifoo.slang",
+ moduleInterfaceBlob);
+ slang::IModule* module0 = slangSession->loadModuleFromSource("module0", "path0",
+ module0Blob);
+ slang::IModule* module1 = slangSession->loadModuleFromSource("module1", "path1",
+ module1Blob);
+ ComPtr<slang::IEntryPoint> computeEntryPoint;
+ SLANG_RETURN_ON_FAIL(
+ module0->findEntryPointByName("computeMain", computeEntryPoint.writeRef()));
+
+ Slang::List<slang::IComponentType*> componentTypes;
+ componentTypes.add(moduleInterface);
+ componentTypes.add(module0);
+ if (linkSpecialization)
+ componentTypes.add(module1);
+ componentTypes.add(computeEntryPoint);
+
+ Slang::ComPtr<slang::IComponentType> composedProgram;
+ SlangResult result = slangSession->createCompositeComponentType(
+ componentTypes.getBuffer(),
+ componentTypes.getCount(),
+ composedProgram.writeRef(),
+ diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+
+ ComPtr<slang::IComponentType> linkedProgram;
+ result = composedProgram->link(linkedProgram.writeRef(), diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+
+ composedProgram = linkedProgram;
+ slangReflection = composedProgram->getLayout();
+
+ gfx::IShaderProgram::Desc programDesc = {};
+ programDesc.slangGlobalScope = composedProgram.get();
+
+ auto shaderProgram = device->createProgram(programDesc);
+
+ outShaderProgram = shaderProgram;
+ return SLANG_OK;
+ }
+
+ void linkTimeDefaultTestImpl(IDevice* device, UnitTestContext* context)
+ {
+ Slang::ComPtr<ITransientResourceHeap> transientHeap;
+ ITransientResourceHeap::Desc transientHeapDesc = {};
+ transientHeapDesc.constantBufferSize = 4096;
+ GFX_CHECK_CALL_ABORT(
+ device->createTransientResourceHeap(transientHeapDesc, transientHeap.writeRef()));
+
+ // Create pipeline without linking a specialization override module, so we should
+ // see the default value of `extern Foo`.
+ ComPtr<IShaderProgram> shaderProgram;
+ slang::ProgramLayout* slangReflection;
+ GFX_CHECK_CALL_ABORT(loadProgram(device, shaderProgram, slangReflection, false));
+
+ ComputePipelineStateDesc pipelineDesc = {};
+ pipelineDesc.program = shaderProgram.get();
+ ComPtr<gfx::IPipelineState> pipelineState;
+ GFX_CHECK_CALL_ABORT(
+ device->createComputePipelineState(pipelineDesc, pipelineState.writeRef()));
+
+ // Create pipeline with a specialization override module linked in, so we should
+ // see the result of using `Bar` for `extern Foo`.
+ ComPtr<IShaderProgram> shaderProgram1;
+ GFX_CHECK_CALL_ABORT(loadProgram(device, shaderProgram1, slangReflection, true));
+
+ ComputePipelineStateDesc pipelineDesc1 = {};
+ pipelineDesc1.program = shaderProgram1.get();
+ ComPtr<gfx::IPipelineState> pipelineState1;
+ GFX_CHECK_CALL_ABORT(
+ device->createComputePipelineState(pipelineDesc1, pipelineState1.writeRef()));
+
+ const int numberCount = 4;
+ float initialData[] = { 0.0f, 0.0f, 0.0f, 0.0f };
+ IBufferResource::Desc bufferDesc = {};
+ bufferDesc.sizeInBytes = numberCount * sizeof(float);
+ bufferDesc.format = gfx::Format::Unknown;
+ bufferDesc.elementSize = sizeof(float);
+ bufferDesc.allowedStates = ResourceStateSet(
+ ResourceState::ShaderResource,
+ ResourceState::UnorderedAccess,
+ ResourceState::CopyDestination,
+ ResourceState::CopySource);
+ bufferDesc.defaultState = ResourceState::UnorderedAccess;
+ bufferDesc.memoryType = MemoryType::DeviceLocal;
+
+ ComPtr<IBufferResource> numbersBuffer;
+ GFX_CHECK_CALL_ABORT(device->createBufferResource(
+ bufferDesc,
+ (void*)initialData,
+ numbersBuffer.writeRef()));
+
+ ComPtr<IResourceView> bufferView;
+ IResourceView::Desc viewDesc = {};
+ viewDesc.type = IResourceView::Type::UnorderedAccess;
+ viewDesc.format = Format::Unknown;
+ GFX_CHECK_CALL_ABORT(
+ device->createBufferView(numbersBuffer, nullptr, viewDesc, bufferView.writeRef()));
+
+ ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics };
+ auto queue = device->createCommandQueue(queueDesc);
+
+ // We have done all the set up work, now it is time to start recording a command buffer for
+ // GPU execution.
+ {
+ auto commandBuffer = transientHeap->createCommandBuffer();
+ auto encoder = commandBuffer->encodeComputeCommands();
+
+ auto rootObject = encoder->bindPipeline(pipelineState);
+
+ ShaderCursor entryPointCursor(
+ rootObject->getEntryPoint(0)); // get a cursor the the first entry-point.
+ // Bind buffer view to the entry point.
+ entryPointCursor.getPath("buffer").setResource(bufferView);
+
+ encoder->dispatchCompute(1, 1, 1);
+ encoder->endEncoding();
+ commandBuffer->close();
+ queue->executeCommandBuffer(commandBuffer);
+ queue->waitOnHost();
+ }
+
+ compareComputeResult(
+ device,
+ numbersBuffer,
+ Slang::makeArray<float>(8.0));
+
+ // Now run again with the overrided program.
+ {
+ auto commandBuffer = transientHeap->createCommandBuffer();
+ auto encoder = commandBuffer->encodeComputeCommands();
+
+ auto rootObject = encoder->bindPipeline(pipelineState1);
+
+ ShaderCursor entryPointCursor(
+ rootObject->getEntryPoint(0)); // get a cursor the the first entry-point.
+ // Bind buffer view to the entry point.
+ entryPointCursor.getPath("buffer").setResource(bufferView);
+
+ encoder->dispatchCompute(1, 1, 1);
+ encoder->endEncoding();
+ commandBuffer->close();
+ queue->executeCommandBuffer(commandBuffer);
+ queue->waitOnHost();
+ }
+
+ compareComputeResult(
+ device,
+ numbersBuffer,
+ Slang::makeArray<float>(10.0));
+ }
+
+ SLANG_UNIT_TEST(linkTimeDefaultD3D12)
+ {
+ runTestImpl(linkTimeDefaultTestImpl, unitTestContext, Slang::RenderApiFlag::D3D12);
+ }
+
+ SLANG_UNIT_TEST(linkTimeDefaultVulkan)
+ {
+ runTestImpl(linkTimeDefaultTestImpl, unitTestContext, Slang::RenderApiFlag::Vulkan);
+ }
+
+}