[Embedded] Introduce DeferredCodeGen feature.

Introduce an experimental feature DeferredCodeGen, that defers the
generation of LLVM IR (and therefore object code) for all entities
within an Embedded Swift module unless they have explicitly requested
to not be emitted into the client (e.g., with
`@_neverEmitIntoClient`).

This feature is meant to generalize and subsume
-emit-empty-object-file, relying on lazy emission of entities rather
than abruptly ending the compilation pipeline before emitting any IR.

Part of rdar://158363967.
This commit is contained in:
Doug Gregor
2025-09-03 15:43:01 -07:00
parent 4f010f0fc7
commit ed93b46fa6
16 changed files with 97 additions and 24 deletions

View File

@@ -752,7 +752,7 @@ protected:
HasAnyUnavailableDuringLoweringValues : 1
);
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+8,
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+8,
/// If the module is compiled as static library.
StaticLibrary : 1,
@@ -821,7 +821,10 @@ protected:
SerializePackageEnabled : 1,
/// Whether this module has enabled strict memory safety checking.
StrictMemorySafety : 1
StrictMemorySafety : 1,
/// Whether this module uses deferred code generation in Embedded Swift.
DeferredCodeGen : 1
);
SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2,

View File

@@ -818,7 +818,7 @@ public:
Bits.ModuleDecl.IsConcurrencyChecked = value;
}
/// Whether this module has enable strict memory safety checking.
/// Whether this module has enabled strict memory safety checking.
bool strictMemorySafety() const {
return Bits.ModuleDecl.StrictMemorySafety;
}
@@ -827,6 +827,15 @@ public:
Bits.ModuleDecl.StrictMemorySafety = value;
}
/// Whether this module uses deferred code generation.
bool deferredCodeGen() const {
return Bits.ModuleDecl.DeferredCodeGen;
}
void setDeferredCodeGen(bool value = true) {
Bits.ModuleDecl.DeferredCodeGen = value;
}
bool isObjCNameLookupCachePopulated() const {
return Bits.ModuleDecl.ObjCNameLookupCachePopulated;
}

View File

@@ -324,6 +324,12 @@ EXPERIMENTAL_FEATURE(KeyPathWithMethodMembers, false)
// Whether to enable @_used and @_section attributes
EXPERIMENTAL_FEATURE(SymbolLinkageMarkers, true)
// Whether to emit an Embedded Swift module with "deferred" code generation,
// meaning that the only code that will be emitted into the object file is
// code that was marked as "never emit into client". For everything else,
// Swift still store the SIL and emit it into the client only when used.
EXPERIMENTAL_FEATURE(DeferredCodeGen, true)
// Whether to compile scripts lazily in immediate mode
EXPERIMENTAL_FEATURE(LazyImmediate, false)

View File

@@ -147,6 +147,7 @@ class ExtendedValidationInfo {
unsigned AllowNonResilientAccess: 1;
unsigned SerializePackageEnabled: 1;
unsigned StrictMemorySafety: 1;
unsigned DeferredCodeGen: 1;
} Bits;
public:
@@ -252,6 +253,13 @@ public:
Bits.StrictMemorySafety = val;
}
bool deferredCodeGen() const {
return Bits.DeferredCodeGen;
}
void setDeferredCodeGen(bool val = true) {
Bits.DeferredCodeGen = val;
}
bool hasCxxInteroperability() const { return Bits.HasCxxInteroperability; }
void setHasCxxInteroperability(bool val) {
Bits.HasCxxInteroperability = val;

View File

@@ -84,6 +84,7 @@ UNINTERESTING_FEATURE(CodeItemMacros)
UNINTERESTING_FEATURE(PreambleMacros)
UNINTERESTING_FEATURE(TupleConformances)
UNINTERESTING_FEATURE(SymbolLinkageMarkers)
UNINTERESTING_FEATURE(DeferredCodeGen)
UNINTERESTING_FEATURE(LazyImmediate)
UNINTERESTING_FEATURE(MoveOnlyClasses)
UNINTERESTING_FEATURE(NoImplicitCopy)

View File

@@ -784,6 +784,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx,
Bits.ModuleDecl.AllowNonResilientAccess = 0;
Bits.ModuleDecl.SerializePackageEnabled = 0;
Bits.ModuleDecl.StrictMemorySafety = 0;
Bits.ModuleDecl.DeferredCodeGen = 0;
// Populate the module's files.
SmallVector<FileUnit *, 2> files;

View File

@@ -1511,6 +1511,9 @@ ModuleDecl *CompilerInstance::getMainModule() const {
MainModule->setSerializePackageEnabled();
if (Invocation.getLangOptions().hasFeature(Feature::StrictMemorySafety))
MainModule->setStrictMemorySafety(true);
if (Invocation.getLangOptions().hasFeature(Feature::Embedded) &&
Invocation.getLangOptions().hasFeature(Feature::DeferredCodeGen))
MainModule->setDeferredCodeGen(true);
configureAvailabilityDomains(getASTContext(),
Invocation.getFrontendOptions(), MainModule);

View File

@@ -1112,10 +1112,20 @@ bool SILDeclRef::declHasNonUniqueDefinition(const ValueDecl *decl) {
if (decl->isNeverEmittedIntoClient())
return false;
// If the declaration is not from the main module, treat its definition as
// non-unique.
/// @_alwaysEmitIntoClient means that we have a non-unique definition.
if (decl->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
return true;
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->deferredCodeGen())
return true;
// If the declaration is not from the main module, treat its definition as
// non-unique.
return module != ctx.MainModule && ctx.MainModule;
}

View File

@@ -710,6 +710,9 @@ public:
/// \c true if this module was built with strict memory safety.
bool strictMemorySafety() const { return Core->strictMemorySafety(); }
/// \c true if this module uses deferred code generation.
bool deferredCodeGen() const { return Core->deferredCodeGen(); }
/// Associates this module file with the AST node representing it.
///
/// Checks that the file is compatible with the AST module it's being loaded

View File

@@ -226,6 +226,9 @@ static bool readOptionsBlock(llvm::BitstreamCursor &cursor,
case options_block::STRICT_MEMORY_SAFETY:
extendedInfo.setStrictMemorySafety(true);
break;
case options_block::DEFERRED_CODE_GEN:
extendedInfo.setDeferredCodeGen(true);
break;
default:
// Unknown options record, possibly for use by a future version of the
// module format.
@@ -1520,6 +1523,7 @@ ModuleFileSharedCore::ModuleFileSharedCore(
Bits.AllowNonResilientAccess = extInfo.allowNonResilientAccess();
Bits.SerializePackageEnabled = extInfo.serializePackageEnabled();
Bits.StrictMemorySafety = extInfo.strictMemorySafety();
Bits.DeferredCodeGen = extInfo.deferredCodeGen();
MiscVersion = info.miscVersion;
SDKVersion = info.sdkVersion;
ModuleABIName = extInfo.getModuleABIName();

View File

@@ -421,8 +421,11 @@ private:
/// Whether this module enabled strict memory safety.
unsigned StrictMemorySafety : 1;
/// Whether this module used deferred code generation.
unsigned DeferredCodeGen : 1;
// Explicitly pad out to the next word boundary.
unsigned : 2;
unsigned : 1;
} Bits = {};
static_assert(sizeof(ModuleBits) <= 8, "The bit set should be small");
@@ -696,6 +699,8 @@ public:
bool strictMemorySafety() const { return Bits.StrictMemorySafety; }
bool deferredCodeGen() const { return Bits.DeferredCodeGen; }
/// How should \p dependency be loaded for a transitive import via \c this?
///
/// If \p importNonPublicDependencies, more transitive dependencies

View File

@@ -987,7 +987,8 @@ namespace options_block {
CXX_STDLIB_KIND,
PUBLIC_MODULE_NAME,
SWIFT_INTERFACE_COMPILER_VERSION,
STRICT_MEMORY_SAFETY
STRICT_MEMORY_SAFETY,
DEFERRED_CODE_GEN,
};
using SDKPathLayout = BCRecordLayout<
@@ -1088,6 +1089,10 @@ namespace options_block {
STRICT_MEMORY_SAFETY
>;
using DeferredCodeGenLayout = BCRecordLayout<
DEFERRED_CODE_GEN
>;
using PublicModuleNameLayout = BCRecordLayout<
PUBLIC_MODULE_NAME,
BCBlob

View File

@@ -864,6 +864,7 @@ void Serializer::writeBlockInfoBlock() {
BLOCK_RECORD(options_block, ALLOW_NON_RESILIENT_ACCESS);
BLOCK_RECORD(options_block, SERIALIZE_PACKAGE_ENABLED);
BLOCK_RECORD(options_block, STRICT_MEMORY_SAFETY);
BLOCK_RECORD(options_block, DEFERRED_CODE_GEN);
BLOCK_RECORD(options_block, CXX_STDLIB_KIND);
BLOCK_RECORD(options_block, PUBLIC_MODULE_NAME);
BLOCK_RECORD(options_block, SWIFT_INTERFACE_COMPILER_VERSION);
@@ -1177,6 +1178,11 @@ void Serializer::writeHeader() {
StrictMemorySafety.emit(ScratchRecord);
}
if (M->deferredCodeGen()) {
options_block::DeferredCodeGenLayout DeferredCodeGen(Out);
DeferredCodeGen.emit(ScratchRecord);
}
if (M->hasCxxInteroperability()) {
options_block::HasCxxInteroperabilityEnabledLayout
CxxInteroperabilityEnabled(Out);

View File

@@ -976,6 +976,8 @@ LoadedFile *SerializedModuleLoaderBase::loadAST(
M.setIsConcurrencyChecked();
if (loadedModuleFile->strictMemorySafety())
M.setStrictMemorySafety();
if (loadedModuleFile->deferredCodeGen())
M.setDeferredCodeGen();
if (loadedModuleFile->hasCxxInteroperability()) {
M.setHasCxxInteroperability();
M.setCXXStdlibKind(loadedModuleFile->getCXXStdlibKind());

View File

@@ -32,7 +32,7 @@
// RUN: %target-run %t/Application | %FileCheck %s
// Test #2: Root is an "intermediate" library for everything
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientB.o %t/ClientB.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/Application.o %t/Application.swift -enable-experimental-feature Embedded -parse-as-library
@@ -41,26 +41,26 @@
// Test #3: ClientA as an "intermediate" library
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientB.o %t/ClientB.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/Application.o %t/Application.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-clang %target-clang-resource-dir-opt %t/Root.o %t/ClientA.o %t/ClientB.o %t/Application.o -o %t/Application
// RUN: %target-run %t/Application | %FileCheck %s
// Test #4: Root and ClientA as "intermediate" libraries
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientB.o %t/ClientB.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/Application.o %t/Application.swift -enable-experimental-feature Embedded -parse-as-library
// RUN: %target-clang %target-clang-resource-dir-opt %t/Root.o %t/ClientA.o %t/ClientB.o %t/Application.o -o %t/Application
// RUN: %target-run %t/Application | %FileCheck %s
// Test #%: All "intermediate", all the time. Main drives code generation
// TODO: This requires -emit-empty-object-file to still emit the main symbol.
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientB.o %t/ClientB.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/Application.o %t/Application.swift -enable-experimental-feature Embedded -emit-empty-object-file -parse-as-library
// TODO: @main needs to drive the generation of code here.
// RUN: %target-swift-frontend -c -emit-module -o %t/Root.o %t/Root.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientA.o %t/ClientA.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/ClientB.o %t/ClientB.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN: %target-swift-frontend -c -I %t -emit-module -o %t/Application.o %t/Application.swift -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -parse-as-library
// RUN-TODO: %target-clang %target-clang-resource-dir-opt %t/Root.o %t/ClientA.o %t/ClientB.o %t/Application.o -o %t/Application
// RUN-TODO: %target-run %t/Application | %FileCheck %s
@@ -68,6 +68,7 @@
// REQUIRES: swift_in_compiler
// REQUIRES: executable_test
// REQUIRES: swift_feature_Embedded
// REQUIRES: swift_feature_DeferredCodeGen
//--- Root.swift
struct Point {

View File

@@ -4,10 +4,10 @@
// Library module
// SIL checking
// RUN: %target-swift-frontend %t/Library.swift -parse-as-library -entry-point-function-name Library_main -enable-experimental-feature Embedded -emit-empty-object-file -emit-sil -emit-module-path %t/Modules/Library.swiftmodule -o - | %FileCheck -check-prefix LIBRARY-SIL %s
// RUN: %target-swift-frontend %t/Library.swift -parse-as-library -entry-point-function-name Library_main -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -emit-sil -emit-module-path %t/Modules/Library.swiftmodule -o - | %FileCheck -check-prefix LIBRARY-SIL %s
// IR checking to ensure we get the right weak symbols.
// RUN: %target-swift-frontend %t/Library.swift -parse-as-library -entry-point-function-name Library_main -enable-experimental-feature Embedded -emit-empty-object-file -emit-ir -o - | %FileCheck -check-prefix LIBRARY-IR --dump-input-filter all %s
// RUN: %target-swift-frontend %t/Library.swift -parse-as-library -entry-point-function-name Library_main -enable-experimental-feature Embedded -enable-experimental-feature DeferredCodeGen -emit-ir -o - | %FileCheck -check-prefix LIBRARY-IR --dump-input-filter all %s
// Application module
@@ -17,12 +17,14 @@
// REQUIRES: swift_in_compiler
// REQUIRES: swift_feature_Embedded
// REQUIRES: swift_feature_DeferredCodeGen
//--- Library.swift
// TODO: These are expected once "emit empty object file" becomes "be lazy".
// LIBRARY-IR-NOT: @"$es23_swiftEmptyArrayStorageSi_S3itvp" = linkonce_odr {{(protected |dllexport )?}}global
// LIBRARY-IR-NOT: @"$es16_emptyBoxStorageSi_Sitvp" = linkonce_odr {{(protected |dllexport )?}}global
// TODO: Once global variables can be emitted lazily, these should be -NOT
// again, then show up in the application binary if we use them.
// LIBRARY-IR: @"$es23_swiftEmptyArrayStorageSi_S3itvp" = weak_odr {{(protected |dllexport )?}}global
// LIBRARY-IR: @"$es16_emptyBoxStorageSi_Sitvp" = weak_odr {{(protected |dllexport )?}}global
// LIBRARY-IR-NOT: define {{.*}}@"$e7Library5helloSaySiGyF"()
public func hello() -> [Int] {
@@ -47,13 +49,17 @@ private func throughPrivate() -> [Int] {
// LIBRARY-IR-NOT: unnecessary
public func unnecessary() -> Int64 { 5 }
// LIBRARY-IR: define {{.*}} @"$e7Library14unusedYetThere
@_neverEmitIntoClient
public func unusedYetThere() -> Int64 { 5 }
// LIBRARY-IR-NOT: define swiftcc
// LIBRARY-IR-NOT: define hidden swiftcc
// LIBRARY-IR-NOT: define {{.*}} @"$es27_allocateUninitializedArrayySayxG_BptBwlFSi_Tg5"
// LIBRARY-SIL: sil [ossa] @$e7Library5helloSaySiGyF
// LIBRARY-SIL: sil [ossa] @$e7Library8getArraySaySiGyF : $@convention(thin) () -> @owned Array<Int> {
// LIBRARY-SIL: sil @$e7Library5helloSaySiGyF
// LIBRARY-SIL: sil @$e7Library8getArraySaySiGyF : $@convention(thin) () -> @owned Array<Int> {
//--- Application.swift
import Library