diff options
| author | Yong He <yonghe@outlook.com> | 2024-02-26 17:00:31 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-26 17:00:31 -0800 |
| commit | 39522159c245e32a99cfdc47f03236f7028f5c61 (patch) | |
| tree | 4ae93fb32f267f7caa5ce55a6a52aac9f1f33bdd | |
| parent | 1d8e93cd434f0c7acbb6db747b32c3a3720c5c2e (diff) | |
Allow default values for `extern` symbols. (#3632)
* Allow default values for `extern` symbols.
* Fix.
* Fix test.
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>
@@ -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); + } + +} |
