Merge pull request #85871 from aschwaighofer/lazy_metadata_emission_embedded_exist

[embedded] Lazily emit class metadata in embedded mode and apply shared linkage
This commit is contained in:
Arnold Schwaighofer
2025-12-08 18:13:38 -08:00
committed by GitHub
5 changed files with 151 additions and 13 deletions

View File

@@ -1051,16 +1051,22 @@ void IRGenModule::emitClassDecl(ClassDecl *D) {
auto &resilientLayout =
classTI.getClassLayout(*this, selfType, /*forBackwardDeployment=*/false);
auto isEmbeddedWithExistentials =
Context.LangOpts.hasFeature(Feature::EmbeddedExistentials);
// As a matter of policy, class metadata is never emitted lazily for now.
assert(!IRGen.hasLazyMetadata(D));
assert(isEmbeddedWithExistentials || !IRGen.hasLazyMetadata(D));
// Emit the class metadata.
if (!D->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
emitClassMetadata(*this, D, fragileLayout, resilientLayout);
emitFieldDescriptor(D);
} else {
if (!D->isGenericContext()) {
emitEmbeddedClassMetadata(*this, D, fragileLayout);
if (!isEmbeddedWithExistentials && !D->isGenericContext()) {
emitEmbeddedClassMetadata(*this, D);
} else {
// We create all metadata lazily in embedded with existentials mode.
return;
}
}

View File

@@ -5565,8 +5565,7 @@ static void emitEmbeddedVTable(IRGenModule &IGM, CanType classTy,
(void)var;
}
void irgen::emitEmbeddedClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
const ClassLayout &fragileLayout) {
void irgen::emitEmbeddedClassMetadata(IRGenModule &IGM, ClassDecl *classDecl) {
PrettyStackTraceDecl stackTraceRAII("emitting metadata for", classDecl);
assert(!classDecl->isForeign());
CanType declaredType = classDecl->getDeclaredType()->getCanonicalType();
@@ -5578,7 +5577,9 @@ void irgen::emitLazyClassMetadata(IRGenModule &IGM, CanType classTy) {
// Might already be emitted, skip if that's the case.
auto entity =
LinkEntity::forTypeMetadata(classTy, TypeMetadataAddress::AddressPoint);
if (IGM.Context.LangOpts.hasFeature(Feature::EmbeddedExistentials)) {
auto isEmbeddedWithExistentials = IGM.Context.LangOpts.hasFeature(Feature::EmbeddedExistentials);
if (isEmbeddedWithExistentials) {
entity = LinkEntity::forTypeMetadata(classTy, TypeMetadataAddress::FullMetadata);
}
auto *existingVar = cast<llvm::GlobalVariable>(
@@ -5587,6 +5588,11 @@ void irgen::emitLazyClassMetadata(IRGenModule &IGM, CanType classTy) {
return;
}
if (isEmbeddedWithExistentials) {
emitEmbeddedClassMetadata(IGM, classTy->getClassOrBoundGenericClass());
return;
}
auto &context = classTy->getNominalOrBoundGenericNominal()->getASTContext();
PrettyStackTraceType stackTraceRAII(
context, "emitting lazy class metadata for", classTy);

View File

@@ -61,8 +61,7 @@ namespace irgen {
/// Emit "embedded Swift" class metadata (a simple vtable) for the given class
/// declaration.
void emitEmbeddedClassMetadata(IRGenModule &IGM, ClassDecl *theClass,
const ClassLayout &fragileLayout);
void emitEmbeddedClassMetadata(IRGenModule &IGM, ClassDecl *theClass);
/// Emit the constant initializer of the type metadata candidate for
/// the given foreign class declaration.

View File

@@ -629,6 +629,21 @@ SILDeclRef LinkEntity::getSILDeclRef() const {
return ref;
}
static bool isLazyEmissionOfPublicSymbolInMultipleModulesPossible(CanType ty) {
// In embedded existenitals mode we generate lazy public metadata on demand
// which makes it non unique.
if (ty->getASTContext().LangOpts.hasFeature(Feature::EmbeddedExistentials)) {
if (auto nominal = ty->getAnyNominal()) {
if (SILDeclRef::declHasNonUniqueDefinition(nominal)) {
return true;
}
} else {
return true;
}
}
return false;
}
SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const {
// For when `this` is a protocol conformance of some kind.
auto getLinkageAsConformance = [&] {
@@ -658,6 +673,11 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const {
case Kind::ValueWitnessTable: {
auto type = getType();
// In embedded existenitals mode we generate lazy public metadata on demand
// which makes it non unique.
if (isLazyEmissionOfPublicSymbolInMultipleModulesPossible(type))
return SILLinkage::Shared;
// Builtin types, (), () -> () and so on are in the runtime.
if (!type.getAnyNominal())
return getSILLinkage(FormalLinkage::PublicUnique, forDefinition);
@@ -696,12 +716,10 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const {
if (isForcedShared())
return SILLinkage::Shared;
// In embedded existenitals mode we generate metadata for tuple types.
if (getType()->getASTContext().LangOpts.hasFeature(Feature::EmbeddedExistentials) &&
(isa<TupleType>(getType()) ||
isa<FunctionType>(getType()))) {
// 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()) {

View File

@@ -0,0 +1,109 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t
// RUN: %target-swift-frontend -wmo -module-name A -emit-irgen -o %t/A1.ll %t/A.swift -enable-experimental-feature Embedded -enable-experimental-feature EmbeddedExistentials -parse-as-library
// RUN: %FileCheck --check-prefix=A1 %s < %t/A1.ll
// RUN: %target-swift-frontend -wmo -module-name A -num-threads 1 -emit-ir -o %t/A2.ll -o %t/B2.ll %t/A.swift %t/B.swift -enable-experimental-feature Embedded -enable-experimental-feature EmbeddedExistentials -parse-as-library
// RUN: %FileCheck --check-prefix=A2 %s < %t/A2.ll
// RUN: %FileCheck --check-prefix=B2 %s < %t/B2.ll
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/A.swiftmodule -wmo -module-name A -emit-ir -o %t/A3.ll %t/A.swift %t/B.swift -enable-experimental-feature Embedded -enable-experimental-feature EmbeddedExistentials -parse-as-library
// RUN: %FileCheck --check-prefix=A3 %s < %t/A3.ll
// RUN: %target-swift-frontend -wmo -I %t -module-name C -emit-irgen -o %t/C4.ll %t/C.swift -enable-experimental-feature Embedded -enable-experimental-feature EmbeddedExistentials -parse-as-library
// RUN: %FileCheck --check-prefix=C4 %s < %t/C4.ll
// REQUIRES: swift_in_compiler
// REQUIRES: optimized_stdlib
// REQUIRES: swift_feature_Embedded
// REQUIRES: swift_feature_EmbeddedExistentials
//--- A.swift
public class SomeClass {
}
public struct SomeStruct {
var x = 1
var y = 2
}
public enum SomeEnum {
case a(Int)
case b(Float)
}
//--- B.swift
public func getSomeClass() -> SomeClass {
return SomeClass()
}
public func getSomeStructAsAny() -> Any {
return SomeStruct()
}
public func getSomeStruct() -> SomeStruct {
return SomeStruct()
}
public func getSomeEnumAsAny() -> Any {
return SomeEnum.a(1)
}
public func getSomeEnum()-> SomeEnum {
return SomeEnum.b(2.0)
}
//--- C.swift
import A
public func useMetadata() -> Any {
return getSomeClass()
}
public func useMetadata2() -> Any {
return getSomeStruct()
}
public func useMetadata3() -> Any {
return getSomeEnum()
}
// Test no reference of metadata.
// A1-NOT: CMf
// Test reference of metadata. Because we are the defining module metadata is
// visible outside of the image per current policy.
// In multiple llvm modules per SIL module mode "Private" SIL linkage becomes
// hidden linkonce_odr.
// A2: @"$e1A8SomeEnumOMf" = linkonce_odr hidden constant
// A2: @"$e1A10SomeStructVMf" = linkonce_odr hidden constant
// A2: @"$e1A9SomeClassCMf" = linkonce_odr hidden global
// A2: @"$e1A8SomeEnumON" = {{.*}}alias{{.*}}e1A8SomeEnumOMf
// A2: @"$e1A10SomeStructVN" = {{.*}}alias{{.*}}e1A10SomeStructVMf
// A2: @"$e1A9SomeClassCN" = {{.*}}alias{{.*}}e1A9SomeClassCMf
// B2: @"$e1A9SomeClassCN" = {{.*}}external{{.*}} global
// B2: @"$e1A10SomeStructVN" = {{.*}}external{{.*}} global
// B2: @"$e1A8SomeEnumON" = {{.*}}external{{.*}} global
// B2: call {{.*}} @"$e1A9SomeClassCACycfC"(ptr swiftself @"$e1A9SomeClassCN")
// B2: store{{.*}}e1A10SomeStructVN
// B2: store{{.*}}e1A8SomeEnumON
// Test reference of metadata. Because we are the defining module metadata is
// visible outside of the image per current policy.
// In single llvm module per SIL module "Private" SIL linkage makes more
// intuitive sense.
// A3: @"$e1A8SomeEnumOMf" = {{.*}}internal constant
// A3: @"$e1A10SomeStructVMf" = {{.*}}internal constant
// A3: @"$e1A9SomeClassCMf" = {{.*}}internal global
// A3: @"$e1A9SomeClassCN" = {{.*}}alias{{.*}}e1A9SomeClassCMf
// A3: call {{.*}} @"$e1A9SomeClassCACycfC"({{.*}}getelementptr{{.*}}@"$e1A9SomeClassCMf"
// Test "external" reference of metadata (defines metadata from another module
// in current module as linkonce).
// C4: @"$e1A8SomeEnumOMf" = linkonce_odr hidden constant
// C4: @"$e1A10SomeStructVMf" = linkonce_odr hidden constant
// C4: @"$e1A9SomeClassCMf" = linkonce_odr hidden global