Skip to content

[embedded] Add support for (non-generic) classes in embedded Swift #68434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion include/swift/IRGen/Linking.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ inline bool isEmbedded(CanType t) {
return t->getASTContext().LangOpts.hasFeature(Feature::Embedded);
}

inline bool isMetadataAllowedInEmbedded(CanType t) {
return isa<ClassType>(t);
}

inline bool isEmbedded(Decl *d) {
return d->getASTContext().LangOpts.hasFeature(Feature::Embedded);
}
Expand Down Expand Up @@ -840,7 +844,7 @@ class LinkEntity {
static LinkEntity forTypeMetadata(CanType concreteType,
TypeMetadataAddress addr) {
assert(!isObjCImplementation(concreteType));
assert(!isEmbedded(concreteType));
assert(!isEmbedded(concreteType) || isMetadataAllowedInEmbedded(concreteType));
LinkEntity entity;
entity.setForType(Kind::TypeMetadata, concreteType);
entity.Data |= LINKENTITY_SET_FIELD(MetadataAddress, unsigned(addr));
Expand Down
23 changes: 23 additions & 0 deletions lib/IRGen/ClassMetadataVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ template <class Impl> class ClassMetadataVisitor
static_assert(MetadataAdjustmentIndex::Class == 3,
"Adjustment index must be synchronized with this layout");

if (IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
asImpl().noteAddressPoint();
asImpl().addSuperclass();
asImpl().addDestructorFunction();
addEmbeddedClassMembers(Target);
return;
}

// Pointer to layout string
asImpl().addLayoutStringPointer();

Expand Down Expand Up @@ -185,6 +193,21 @@ template <class Impl> class ClassMetadataVisitor
asImpl().addVTableEntries(theClass);
}

/// Add fields associated with the given class and its bases.
void addEmbeddedClassMembers(ClassDecl *theClass) {
// Visit the superclass first.
if (auto *superclassDecl = theClass->getSuperclassDecl()) {
addEmbeddedClassMembers(superclassDecl);
}

// Note that we have to emit a global variable storing the metadata
// start offset, or access remaining fields relative to one.
asImpl().noteStartOfImmediateMembers(theClass);

// Add vtable entries.
asImpl().addVTableEntries(theClass);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want the full vtable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we be more lazy and just emit the methods that are used? Or are we assuming that lto will strip them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually assuming that SILOptimizer (namely PruneVTables and DeadFunctionElimination) will take care of that, because we can internalize public declarations in embedded Swift.

}

friend SILVTableVisitor<Impl>;
void addMethod(SILDeclRef declRef) {
// Does this method require a reified runtime vtable entry?
Expand Down
2 changes: 2 additions & 0 deletions lib/IRGen/GenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,8 @@ void IRGenModule::emitClassDecl(ClassDecl *D) {
if (!D->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
emitClassMetadata(*this, D, fragileLayout, resilientLayout);
emitFieldDescriptor(D);
} else {
emitEmbeddedClassMetadata(*this, D, fragileLayout);
}

IRGen.addClassForEagerInitialization(D);
Expand Down
9 changes: 9 additions & 0 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4970,6 +4970,11 @@ llvm::GlobalValue *IRGenModule::defineTypeMetadata(
: LinkEntity::forTypeMetadata(
concreteType, TypeMetadataAddress::FullMetadata));

if (Context.LangOpts.hasFeature(Feature::Embedded)) {
entity = LinkEntity::forTypeMetadata(concreteType,
TypeMetadataAddress::AddressPoint);
}

auto DbgTy = DebugTypeInfo::getGlobalMetadata(
MetatypeType::get(concreteType),
entity.getDefaultDeclarationType(*this)->getPointerTo(), Size(0),
Expand All @@ -4991,6 +4996,10 @@ llvm::GlobalValue *IRGenModule::defineTypeMetadata(

LinkInfo link = LinkInfo::get(*this, entity, ForDefinition);
markGlobalAsUsedBasedOnLinkage(*this, link, var);

if (Context.LangOpts.hasFeature(Feature::Embedded)) {
return var;
}

/// For concrete metadata, we want to use the initializer on the
/// "full metadata", and define the "direct" address point as an alias.
Expand Down
36 changes: 36 additions & 0 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3949,6 +3949,12 @@ namespace {
}

void addDestructorFunction() {
if (IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
auto dtorRef = SILDeclRef(Target->getDestructor(), SILDeclRef::Kind::Deallocator);
addReifiedVTableEntry(dtorRef);
return;
}

if (asImpl().getFieldLayout().hasObjCImplementation())
return;

Expand Down Expand Up @@ -4967,6 +4973,36 @@ void irgen::emitClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
}
}

void irgen::emitEmbeddedClassMetadata(IRGenModule &IGM, ClassDecl *classDecl,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need class embedded class metadata at all?
The vtable + pointer to superclass should provide everything which is needed for method dispatch and conditional down casting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and that's what this function is trying to do (to emit a simple metadata record with a vtable + superclass pointer), I think the only extra leftover is metadataBuilder.createMetadataAccessFunction(); which I'm removing in #68461

const ClassLayout &fragileLayout) {
PrettyStackTraceDecl stackTraceRAII("emitting metadata for", classDecl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put this above the assertion? If the assertion fires, might be nice to have this diagnostic :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

assert(!classDecl->isForeign());

// Set up a dummy global to stand in for the metadata object while we produce
// relative references.
ConstantInitBuilder builder(IGM);
auto init = builder.beginStruct();
init.setPacked(true);

auto strategy = IGM.getClassMetadataStrategy(classDecl);
assert(strategy == ClassMetadataStrategy::FixedOrUpdate ||
strategy == ClassMetadataStrategy::Fixed);

FixedClassMetadataBuilder metadataBuilder(IGM, classDecl, init,
fragileLayout);
metadataBuilder.layout();
bool canBeConstant = metadataBuilder.canBeConstant();
metadataBuilder.createMetadataAccessFunction();

CanType declaredType = classDecl->getDeclaredType()->getCanonicalType();

StringRef section{};
bool isPattern = false;
auto var = IGM.defineTypeMetadata(declaredType, isPattern, canBeConstant,
init.finishAndCreateFuture(), section);
(void)var;
}

void irgen::emitSpecializedGenericClassMetadata(IRGenModule &IGM, CanType type,
ClassDecl &decl) {
assert(decl.isGenericContext());
Expand Down
5 changes: 5 additions & 0 deletions lib/IRGen/GenMeta.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ namespace irgen {
const ClassLayout &fragileLayout,
const ClassLayout &resilientLayout);

/// Emit "embedded Swift" class metadata (a simple vtable) for the given class
/// declaration.
void emitEmbeddedClassMetadata(IRGenModule &IGM, ClassDecl *theClass,
const ClassLayout &fragileLayout);

/// Emit the constant initializer of the type metadata candidate for
/// the given foreign class declaration.
llvm::Constant *emitForeignTypeMetadataInitializer(IRGenModule &IGM,
Expand Down
3 changes: 2 additions & 1 deletion lib/IRGen/MetadataRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3303,7 +3303,8 @@ llvm::Value *IRGenFunction::emitTypeMetadataRef(CanType type) {
MetadataResponse
IRGenFunction::emitTypeMetadataRef(CanType type,
DynamicMetadataRequest request) {
if (type->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
if (type->getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
!isMetadataAllowedInEmbedded(type)) {
llvm::errs() << "Metadata pointer requested in embedded Swift for type "
<< type << "\n";
assert(0 && "metadata used in embedded mode");
Expand Down
37 changes: 37 additions & 0 deletions test/embedded/classes-methods-no-stdlib.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %target-swift-emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -target arm64e-apple-none | %FileCheck %s

public class MyClass {
func foo() { }
func bar() { }
}

public class MySubClass: MyClass {
override func foo() { }
}

// CHECK: @"$s4main7MyClassCN" = {{.*}}<{ ptr, ptr, ptr, ptr, ptr }> <{ ptr null, ptr @"$s4main7MyClassCfD", ptr @"$s4main7MyClassC3fooyyF", ptr @"$s4main7MyClassC3baryyF", ptr @"$s4main7MyClassCACycfC" }>
// CHECK: @"$s4main10MySubClassCN" = {{.*}}<{ ptr, ptr, ptr, ptr, ptr }> <{ ptr @"$s4main7MyClassCN", ptr @"$s4main10MySubClassCfD", ptr @"$s4main10MySubClassC3fooyyF", ptr @"$s4main7MyClassC3baryyF", ptr @"$s4main10MySubClassCACycfC" }>

// CHECK: define {{.*}}void @"$s4main4test1xyAA7MyClassC_tF"(ptr %0)
public func test(x: MyClass) {

x.foo() // goes through the vtable
// CHECK: %1 = load ptr, ptr %0
// CHECK: %2 = getelementptr inbounds ptr, ptr %1, i64 2
// CHECK: %3 = load ptr, ptr %2
// CHECK: call swiftcc void %3(ptr swiftself %0)

x.bar() // does not go through the vtable
// CHECK: call swiftcc void @"$s4main7MyClassC3baryyF"

let y = MySubClass()
// CHECK: call swiftcc %swift.metadata_response @"$s4main10MySubClassCMa"
// CHECK: call swiftcc ptr @"$s4main10MySubClassCACycfC"

y.foo() // does not go through the vtable
// CHECK: call swiftcc void @"$s4main10MySubClassC3fooyyF"

y.bar() // does not go through the vtable
// CHECK: call swiftcc void @"$s4main7MyClassC3baryyF"

}
29 changes: 29 additions & 0 deletions test/embedded/classes-no-stdlib.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: %target-swift-emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -target arm64e-apple-none | %FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test for calling methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added!


public class MyClass {}

// CHECK-DAG: @"$s4main7MyClassCN" = {{.*}}<{ ptr, ptr, ptr }> <{ ptr null, ptr @"$s4main7MyClassCfD", ptr @"$s4main7MyClassCACycfC" }>
// CHECK-DAG: define {{.*}}ptr @"$s4main7MyClassCfd"
// CHECK-DAG: define {{.*}}void @"$s4main7MyClassCfD"
// CHECK-DAG: define {{.*}}ptr @"$s4main7MyClassCACycfC"
// CHECK-DAG: define {{.*}}ptr @"$s4main7MyClassCACycfc"
// CHECK-DAG: define {{.*}}%swift.metadata_response @"$s4main7MyClassCMa"

public func foo() -> MyClass {
return MyClass()
}
// CHECK-DAG: define {{.*}}ptr @"$s4main3fooAA7MyClassCyF"

public class MySubClass: MyClass {}

// CHECK-DAG: @"$s4main10MySubClassCN" = {{.*}}<{ ptr, ptr, ptr }> <{ ptr @"$s4main7MyClassCN", ptr @"$s4main10MySubClassCfD", ptr @"$s4main10MySubClassCACycfC" }>
// CHECK-DAG: define {{.*}}ptr @"$s4main10MySubClassCACycfC"
// CHECK-DAG: define {{.*}}ptr @"$s4main10MySubClassCACycfc"
// CHECK-DAG: define {{.*}}ptr @"$s4main10MySubClassCfd"
// CHECK-DAG: define {{.*}}void @"$s4main10MySubClassCfD"
// CHECK-DAG: define {{.*}}%swift.metadata_response @"$s4main10MySubClassCMa"

public func bar() -> MyClass {
return MySubClass()
}
// CHECK-DAG: define {{.*}}ptr @"$s4main3barAA7MyClassCyF"