From 14bc0baecfdfd648f71f13d6ea161c40e1320c6a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 17 May 2026 13:37:51 -0700 Subject: [PATCH] Introduce the notion of an "effective" code generation model The code generation model for a particular declaration or conformance can be defined explicitly with `@export(interface)`, `@export(implementation)`, or `@inlinable` (for declarations), indicating where the definition will occur. Embedded Swift also has some limitations on what can be emitted into IR. For example, a generic function cannot be `@export(interface)` because Embedded Swift does not support unspecialized generics. Compute the effective code generation model based on what was explicitly specified, the limitations of the model, and the default code generation model for the given module, which defaults to "inlinable" but can be made "implementation" by the DeferredCodeGen feature. Use the effective code generation model for IR- and SIL-level determinations of linkage and where to emit symbols. [WIP] Start computing and using the "effective" code generation model FIXUP linkage of the alias symbol --- include/swift/AST/ProtocolConformance.h | 6 ++++ lib/AST/Decl.cpp | 37 ++++++++++++++++++- lib/AST/ProtocolConformance.cpp | 38 ++++++++++++++++++++ lib/IRGen/GenClass.cpp | 4 +-- lib/IRGen/GenDecl.cpp | 10 ++---- lib/IRGen/GenMeta.cpp | 6 ++-- lib/IRGen/IRGenModule.cpp | 12 +++++-- lib/IRGen/Linking.cpp | 38 +++++++++++++------- lib/SIL/IR/SIL.cpp | 9 +++-- lib/SIL/IR/SILDeclRef.cpp | 18 ++++++---- lib/SIL/IR/SILFunctionBuilder.cpp | 5 ++- test/embedded/linkage/leaf_application.swift | 8 ++--- test/embedded/serialization.swift | 2 +- 13 files changed, 151 insertions(+), 42 deletions(-) diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 3284519033c..07ba9d119bf 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -817,6 +817,12 @@ public: std::optional getExplicitCodeGenerationModel() const; + /// Compute the code generation model for the conformance, combining the + /// explicitly-specified information from attributes with defaults + /// based on Embedded Swift or feature flags. + CodeGenerationModel + getEffectiveCodeGenerationModel() const; + /// Whether this conformance represents the conformance of one protocol's /// conforming types to another protocol. /// diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 851bb665bb6..9d461e0c570 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2369,6 +2369,37 @@ Decl::getExplicitCodeGenerationModel() const { return std::nullopt; } +/// Determine the code generation model that is required by the given +/// declaration. +/// +/// This accounts for limitations of the code generation model. For example, +/// a generic declaration can only be treated as @export(implementation) in +/// Embedded Swift, because there are no unspecialized generics. +static std::optional +getRequiredCodeGenerationModel(const Decl *decl) { + bool isEmbedded = decl->getASTContext().LangOpts.hasFeature(Feature::Embedded); + + // A generic declaration must be @export(implementation) in Embedded Swift. + auto dc = decl->getInnermostDeclContext(); + if (auto sig = dc->getGenericSignatureOfContext()) { + if (!sig->areAllParamsConcrete() && isEmbedded) + return CodeGenerationModel::Implementation; + } + + // Foreign types are always @export(implementation). + if (auto nominal = dyn_cast(decl)) { + if (isa(nominal->getModuleScopeContext())) + return CodeGenerationModel::Implementation; + } + + // Types must be @export(interface) in non-Embedded Swift, because the type + // metadata symbols need to be unique. + if (isa(decl) && !isEmbedded) + return CodeGenerationModel::Interface; + + return std::nullopt; +} + CodeGenerationModel Decl::getEffectiveCodeGenerationModel() const { // If there is an explicit attribute that specifies the model for this @@ -2376,7 +2407,11 @@ Decl::getEffectiveCodeGenerationModel() const { if (auto explicitModel = getExplicitCodeGenerationModel()) return *explicitModel; - // Otherwise, apply the model-level defaults. + // If there is a required code generation model, return that. + if (auto required = getRequiredCodeGenerationModel(this)) + return *required; + + // Otherwise, apply the module-level default. return getModuleContext()->codeGenerationModel(); } diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 75b66a168eb..ed96f6d5f6a 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -32,6 +32,7 @@ #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Types.h" #include "swift/Basic/Assertions.h" +#include "swift/Basic/CodeGenerationModel.h" #include "swift/Basic/Statistic.h" #include "swift/ClangImporter/ClangImporter.h" #include "llvm/ADT/Statistic.h" @@ -432,6 +433,43 @@ NormalProtocolConformance::getExplicitCodeGenerationModel() const { return std::nullopt; } +static std::optional +getRequiredCodeGenerationModel(const NormalProtocolConformance *conformance) { + auto dc = conformance->getDeclContext(); + bool isEmbedded = dc->getASTContext().LangOpts.hasFeature(Feature::Embedded); + + // A conformance in a generic context must be @export(implementation) in + // Embedded Swift. + if (auto sig = dc->getGenericSignatureOfContext()) { + if (!sig->areAllParamsConcrete() && isEmbedded) + return CodeGenerationModel::Implementation; + } + + // Synthesized conformances are always @export(implementation). + if (conformance->isSynthesized()) + return CodeGenerationModel::Implementation; + + // Other onformances must be @export(interface) in non-Embedded Swift, + // because the witness table symbols must be unique. + if (!isEmbedded) { + return CodeGenerationModel::Interface; + } + + return std::nullopt; +} + +CodeGenerationModel +NormalProtocolConformance::getEffectiveCodeGenerationModel() const { + if (auto explicitModel = getExplicitCodeGenerationModel()) + return *explicitModel; + + if (auto required = getRequiredCodeGenerationModel(this)) + return *required; + + // Otherwise, apply the module-level default. + return getDeclContext()->getParentModule()->codeGenerationModel(); +} + bool NormalProtocolConformance::isConformanceOfProtocol() const { return getDeclContext()->getSelfProtocolDecl() != nullptr; } diff --git a/lib/IRGen/GenClass.cpp b/lib/IRGen/GenClass.cpp index 4a29ff60dac..a60b2a79fb3 100644 --- a/lib/IRGen/GenClass.cpp +++ b/lib/IRGen/GenClass.cpp @@ -1067,8 +1067,8 @@ void IRGenModule::emitClassDecl(ClassDecl *D) { emitClassMetadata(*this, D, fragileLayout, resilientLayout); emitFieldDescriptor(D); } else { - bool isExportInterface = !D->isGenericContext() && - D->getExplicitCodeGenerationModel() == CodeGenerationModel::Interface; + bool isExportInterface = + D->getEffectiveCodeGenerationModel() == CodeGenerationModel::Interface; if (isExportInterface) { emitEmbeddedClassMetadata(*this, D); } else { diff --git a/lib/IRGen/GenDecl.cpp b/lib/IRGen/GenDecl.cpp index ebeda9079ed..2a4ae5cc8f4 100644 --- a/lib/IRGen/GenDecl.cpp +++ b/lib/IRGen/GenDecl.cpp @@ -1591,16 +1591,12 @@ bool IRGenerator::hasLazyMetadata(TypeDecl *type) { if (langOpts.hasFeature(Feature::Embedded) && (isa(type) || isa(type))) { auto *nominal = cast(type); - bool isGeneric = nominal->isGenericContext(); // @export(interface) types have a unique definition in their defining // module; importing modules reference them as external symbols rather than // lazily emitting their own copy. - bool isExportInterface = false; - if (!isGeneric) { - if (auto model = nominal->getExplicitCodeGenerationModel()) - isExportInterface = *model == CodeGenerationModel::Interface; - } - bool isLazy = !isGeneric && !isExportInterface; + bool isExportInterface = + nominal->getEffectiveCodeGenerationModel() == CodeGenerationModel::Interface; + bool isLazy = !nominal->isGenericContext() && !isExportInterface; HasLazyMetadata[type] = isLazy; return isLazy; } diff --git a/lib/IRGen/GenMeta.cpp b/lib/IRGen/GenMeta.cpp index ca9f77aa803..0b9c695ef39 100644 --- a/lib/IRGen/GenMeta.cpp +++ b/lib/IRGen/GenMeta.cpp @@ -5727,9 +5727,9 @@ void irgen::emitLazyClassMetadata(IRGenModule &IGM, CanType classTy) { // lazily emitting their own copy. if (IGM.isEmbeddedWithExistentials()) { if (auto *classDecl = classTy->getClassOrBoundGenericClass()) { - if (auto model = classDecl->getExplicitCodeGenerationModel()) - if (*model == CodeGenerationModel::Interface) - return; + if (classDecl->getEffectiveCodeGenerationModel() + == CodeGenerationModel::Interface) + return; } } diff --git a/lib/IRGen/IRGenModule.cpp b/lib/IRGen/IRGenModule.cpp index bbb2edec9fc..89d0db606c7 100644 --- a/lib/IRGen/IRGenModule.cpp +++ b/lib/IRGen/IRGenModule.cpp @@ -1443,10 +1443,16 @@ bool IRGenerator::canEmitWitnessTableLazily(SILWitnessTable *wt) { // @export(interface) conformances, which have a unique strong definition // in the owning module. if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) { - if (auto *normal = dyn_cast(wt->getConformance())) - if (auto model = normal->getExplicitCodeGenerationModel()) - if (*model == CodeGenerationModel::Interface) + if (auto *normal = dyn_cast(wt->getConformance())) { + switch (normal->getEffectiveCodeGenerationModel()) { + case CodeGenerationModel::Interface: return false; + case CodeGenerationModel::Implementation: + case CodeGenerationModel::Inlinable: + return true; + } + } + return true; } diff --git a/lib/IRGen/Linking.cpp b/lib/IRGen/Linking.cpp index dcc120cb877..381eae24445 100644 --- a/lib/IRGen/Linking.cpp +++ b/lib/IRGen/Linking.cpp @@ -718,14 +718,16 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const { if (isForcedShared()) return SILLinkage::Shared; - // In embedded existenitals mode we generate lazy public metadata on demand - // which makes it non unique. - if (isLazyEmissionOfPublicSymbolInMultipleModulesPossible(getType())) - return SILLinkage::Shared; - auto *nominal = getType().getAnyNominal(); switch (getMetadataAddress()) { case TypeMetadataAddress::FullMetadata: { + // In embedded existentials mode we generate lazy public metadata on + // demand which makes the full metadata non-unique. (The address-point + // alias still uses the formal declaration linkage so that it survives + // GlobalDCE under -internalize-at-link.) + if (isLazyEmissionOfPublicSymbolInMultipleModulesPossible(getType())) + return SILLinkage::Shared; + // For imported types, the full metadata object is a candidate // for uniquing. if (getDeclLinkage(nominal) == FormalLinkage::PublicNonUnique) @@ -740,11 +742,10 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const { // module, so the FullMetadata must be externally linkable. bool isEmbedded = nominal->getASTContext().LangOpts.hasFeature(Feature::Embedded); - if (isEmbedded && !nominal->isGenericContext()) { - if (auto model = nominal->getExplicitCodeGenerationModel()) { - if (*model == CodeGenerationModel::Interface) { - return getSILLinkage(FormalLinkage::PublicUnique, forDefinition); - } + if (isEmbedded) { + if (nominal->getEffectiveCodeGenerationModel() + == CodeGenerationModel::Interface) { + return getSILLinkage(FormalLinkage::PublicUnique, forDefinition); } } @@ -1816,6 +1817,13 @@ bool LinkEntity::hasNonUniqueDefinition() const { if (getKind() == Kind::TypeMetadata || getKind() == Kind::ValueWitnessTable) { + // The address-point alias of type metadata is uniquely defined per + // binary even when the full metadata it references is shared, so it + // gets the formal declaration linkage rather than linkonce_odr. + if (getKind() == Kind::TypeMetadata && + getMetadataAddress() == TypeMetadataAddress::AddressPoint) + return false; + // For a nominal type, check its declaration. CanType type = getType(); if (auto nominal = type->getAnyNominal()) { @@ -1835,10 +1843,16 @@ bool LinkEntity::hasNonUniqueDefinition() const { Feature::Embedded)) { if (auto *normal = dyn_cast( getProtocolConformance()->getRootConformance())) { - if (auto model = normal->getExplicitCodeGenerationModel()) - if (*model == CodeGenerationModel::Interface) + switch (normal->getEffectiveCodeGenerationModel()) { + case CodeGenerationModel::Interface: return false; + + case CodeGenerationModel::Implementation: + case CodeGenerationModel::Inlinable: + return true; + } } + return true; } } diff --git a/lib/SIL/IR/SIL.cpp b/lib/SIL/IR/SIL.cpp index c7a11ee31b0..094fcee32bd 100644 --- a/lib/SIL/IR/SIL.cpp +++ b/lib/SIL/IR/SIL.cpp @@ -104,9 +104,14 @@ swift::getLinkageForProtocolConformance(const ProtocolConformance *C, // externally. if (auto *normal = dyn_cast( C->getRootConformance())) { - if (auto model = normal->getExplicitCodeGenerationModel()) - if (*model == CodeGenerationModel::Interface) + switch (normal->getEffectiveCodeGenerationModel()) { + case CodeGenerationModel::Interface: return (definition ? SILLinkage::Public : SILLinkage::PublicExternal); + + case CodeGenerationModel::Implementation: + case CodeGenerationModel::Inlinable: + return SILLinkage::Shared; + } } // Other embedded conformances are emitted lazily with shared linkage, diff --git a/lib/SIL/IR/SILDeclRef.cpp b/lib/SIL/IR/SILDeclRef.cpp index fbb566d683f..5d200d8bdc0 100644 --- a/lib/SIL/IR/SILDeclRef.cpp +++ b/lib/SIL/IR/SILDeclRef.cpp @@ -1250,14 +1250,20 @@ bool SILDeclRef::declHasNonUniqueDefinition(const ValueDecl *decl) { auto module = decl->getModuleContext(); auto &ctx = module->getASTContext(); - /// With deferred code generation, declarations are emitted as late as - /// possible, so they must have non-unique definitions. - if (module->codeGenerationModel() == CodeGenerationModel::Implementation) + switch (decl->getEffectiveCodeGenerationModel()) { + case CodeGenerationModel::Implementation: + /// When deferring all code generation, declarations are emitted as late + /// as possible, so they must have non-unique definitions. return true; - // If the declaration is not from the main module, treat its definition as - // non-unique. - return module != ctx.MainModule && ctx.MainModule; + case CodeGenerationModel::Inlinable: + // If the declaration is not from the main module, treat its definition as + // non-unique. + return module != ctx.MainModule && ctx.MainModule; + + case CodeGenerationModel::Interface: + return false; + } } bool SILDeclRef::isForeignToNativeThunk() const { diff --git a/lib/SIL/IR/SILFunctionBuilder.cpp b/lib/SIL/IR/SILFunctionBuilder.cpp index bf5fa7f3c1a..3a83ac2e693 100644 --- a/lib/SIL/IR/SILFunctionBuilder.cpp +++ b/lib/SIL/IR/SILFunctionBuilder.cpp @@ -391,7 +391,10 @@ SILFunction *SILFunctionBuilder::getOrCreateFunction( F->setAvailabilityForLinkage(*availability); F->setIsAlwaysWeakImported(decl->isAlwaysWeakImported()); - if (auto cgModel = decl->getExplicitCodeGenerationModel()) { + auto cgModel = decl->getExplicitCodeGenerationModel(); + if (!cgModel && mod.getOptions().EmbeddedSwift) + cgModel = decl->getEffectiveCodeGenerationModel(); + if (cgModel) { switch (*cgModel) { case CodeGenerationModel::Interface: case CodeGenerationModel::Implementation: diff --git a/test/embedded/linkage/leaf_application.swift b/test/embedded/linkage/leaf_application.swift index 54a720af7fd..7271d404dea 100644 --- a/test/embedded/linkage/leaf_application.swift +++ b/test/embedded/linkage/leaf_application.swift @@ -92,8 +92,8 @@ public func createsExistential() -> any Reflectable { // LIBRARY-IR: define linkonce_odr hidden void @_swift_dead_method_stub -// LIBRARY-SIL: sil @$e7Library5helloSaySiGyF -// LIBRARY-SIL: sil @$e7Library8getArraySaySiGyF : $@convention(thin) () -> @owned Array { +// LIBRARY-SIL: sil [export_implementation] @$e7Library5helloSaySiGyF +// LIBRARY-SIL: sil [export_implementation] @$e7Library8getArraySaySiGyF : $@convention(thin) () -> @owned Array { //--- Application.swift import Library @@ -105,10 +105,10 @@ public func testMe() { // APPLICATION-IR: define {{(protected |dllexport )?}}swiftcc void @"$e11Application6testMeyyF"() -// APPLICATION-SIL: sil public_external @$e7Library5helloSaySiGyF : $@convention(thin) () -> @owned Array { +// APPLICATION-SIL: sil public_external [export_implementation] @$e7Library5helloSaySiGyF : $@convention(thin) () -> @owned Array { // APPLICATION-IR: define linkonce_odr hidden swiftcc ptr @"$e7Library5helloSaySiGyF"() -// APPLICATION-SIL: sil public_external @$e7Library8getArraySaySiGyF : $@convention(thin) () -> @owned Array { +// APPLICATION-SIL: sil public_external [export_implementation] @$e7Library8getArraySaySiGyF : $@convention(thin) () -> @owned Array { // APPLICATION-IR: define linkonce_odr hidden swiftcc ptr @"$e7Library8getArraySaySiGyF"() // APPLICATION-IR: define {{(protected |dllexport )?}}i32 @Application_main diff --git a/test/embedded/serialization.swift b/test/embedded/serialization.swift index ac0160295d0..d6ef1fd5500 100644 --- a/test/embedded/serialization.swift +++ b/test/embedded/serialization.swift @@ -16,7 +16,7 @@ func internalFunc() { } -// LIBRARY-SIL: sil [asmname "swift_dosomething"] @$e7Library17swift_dosomethingyyFTo : $@convention(c) () -> () { +// LIBRARY-SIL: sil [export_implementation] [asmname "swift_dosomething"] @$e7Library17swift_dosomethingyyFTo : $@convention(c) () -> () { @c public func swift_dosomething() { internalFunc()