Merge pull request #76572 from kubamracek/embedded-class-bound-existentials

[embedded] Introduce class-bound existentials into Embedded Swift
This commit is contained in:
Kuba (Brecka) Mracek
2024-09-20 13:58:06 -07:00
committed by GitHub
16 changed files with 314 additions and 33 deletions

View File

@@ -89,9 +89,23 @@ inline bool isEmbedded(CanType t) {
return t->getASTContext().LangOpts.hasFeature(Feature::Embedded); return t->getASTContext().LangOpts.hasFeature(Feature::Embedded);
} }
// Metadata is not generated and not allowed to be referenced in Embedded Swift,
// expect for classes (both generic and non-generic), dynamic self, and
// class-bound existentials.
inline bool isMetadataAllowedInEmbedded(CanType t) { inline bool isMetadataAllowedInEmbedded(CanType t) {
return isa<ClassType>(t) || isa<BoundGenericClassType>(t) || if (isa<ClassType>(t) || isa<BoundGenericClassType>(t) ||
isa<DynamicSelfType>(t); isa<DynamicSelfType>(t)) {
return true;
}
if (auto existentialTy = dyn_cast<ExistentialType>(t)) {
if (existentialTy->requiresClass())
return true;
}
if (auto archeTy = dyn_cast<ArchetypeType>(t)) {
if (archeTy->requiresClass())
return true;
}
return false;
} }
inline bool isEmbedded(Decl *d) { inline bool isEmbedded(Decl *d) {
@@ -1062,7 +1076,10 @@ public:
} }
static LinkEntity forProtocolWitnessTable(const RootProtocolConformance *C) { static LinkEntity forProtocolWitnessTable(const RootProtocolConformance *C) {
assert(!isEmbedded(C)); if (isEmbedded(C)) {
assert(C->getProtocol()->requiresClass());
}
LinkEntity entity; LinkEntity entity;
entity.setForProtocolConformance(Kind::ProtocolWitnessTable, C); entity.setForProtocolConformance(Kind::ProtocolWitnessTable, C);
return entity; return entity;

View File

@@ -55,6 +55,9 @@ enum class RuntimeEffect : unsigned {
/// Witness methods, boxing, unboxing, initializing, etc. /// Witness methods, boxing, unboxing, initializing, etc.
Existential = 0x80, Existential = 0x80,
/// Class-bound only existential
ExistentialClassBound = 0x200,
/// Not modelled currently. /// Not modelled currently.
Concurrency = 0x0, Concurrency = 0x0,

View File

@@ -1142,8 +1142,9 @@ void IRGenModule::emitGlobalLists() {
// Eagerly emit functions that are externally visible. Functions that are // Eagerly emit functions that are externally visible. Functions that are
// dynamic replacements must also be eagerly emitted. // dynamic replacements must also be eagerly emitted.
static bool isLazilyEmittedFunction(SILFunction &f, SILModule &m) { static bool isLazilyEmittedFunction(SILFunction &f, SILModule &m) {
// Embedded Swift only emits specialized function, so don't emit generic // Embedded Swift only emits specialized function (except when they are
// functions, even if they're externally visible. // protocol witness methods). So don't emit generic functions, even if they're
// externally visible.
if (f.getASTContext().LangOpts.hasFeature(Feature::Embedded) && if (f.getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
f.getLoweredFunctionType()->getSubstGenericSignature()) { f.getLoweredFunctionType()->getSubstGenericSignature()) {
return true; return true;
@@ -1332,7 +1333,7 @@ void IRGenerator::emitLazyDefinitions() {
assert(LazyFieldDescriptors.empty()); assert(LazyFieldDescriptors.empty());
// LazyFunctionDefinitions are allowed, but they must not be generic // LazyFunctionDefinitions are allowed, but they must not be generic
for (SILFunction *f : LazyFunctionDefinitions) { for (SILFunction *f : LazyFunctionDefinitions) {
assert(!f->isGeneric()); assert(hasValidSignatureForEmbedded(f));
} }
assert(LazyWitnessTables.empty()); assert(LazyWitnessTables.empty());
assert(LazyCanonicalSpecializedMetadataAccessors.empty()); assert(LazyCanonicalSpecializedMetadataAccessors.empty());
@@ -1482,7 +1483,7 @@ void IRGenerator::addLazyFunction(SILFunction *f) {
// Embedded Swift doesn't expect any generic functions to be referenced. // Embedded Swift doesn't expect any generic functions to be referenced.
if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) { if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
assert(!f->isGeneric()); assert(hasValidSignatureForEmbedded(f));
} }
assert(!FinishedEmittingLazyDefinitions); assert(!FinishedEmittingLazyDefinitions);
@@ -3472,6 +3473,24 @@ llvm::CallBase *swift::irgen::emitCXXConstructorCall(
return result; return result;
} }
// For a SILFunction to be legal in Embedded Swift, it must be either
// - non-generic
// - generic with parameters thar are either
// - fully specialized (concrete)
// - a class-bound archetype (class-bound existential)
bool swift::irgen::hasValidSignatureForEmbedded(SILFunction *f) {
auto s = f->getLoweredFunctionType()->getInvocationGenericSignature();
for (auto genParam : s.getGenericParams()) {
auto mappedParam = f->getGenericEnvironment()->mapTypeIntoContext(genParam);
if (auto archeTy = dyn_cast<ArchetypeType>(mappedParam)) {
if (archeTy->requiresClass())
continue;
}
return false;
}
return true;
}
StackProtectorMode IRGenModule::shouldEmitStackProtector(SILFunction *f) { StackProtectorMode IRGenModule::shouldEmitStackProtector(SILFunction *f) {
const SILOptions &opts = IRGen.SIL.getOptions(); const SILOptions &opts = IRGen.SIL.getOptions();
return (opts.EnableStackProtection && f->needsStackProtection()) ? return (opts.EnableStackProtection && f->needsStackProtection()) ?
@@ -4351,6 +4370,9 @@ static bool conformanceIsVisibleViaMetadata(
void IRGenModule::addProtocolConformance(ConformanceDescription &&record) { void IRGenModule::addProtocolConformance(ConformanceDescription &&record) {
if (Context.LangOpts.hasFeature(Feature::Embedded)) {
return;
}
emitProtocolConformance(record); emitProtocolConformance(record);

View File

@@ -76,6 +76,8 @@ namespace irgen {
llvm::FunctionType *ctorFnType, llvm::FunctionType *ctorFnType,
llvm::Constant *ctorAddress, llvm::Constant *ctorAddress,
llvm::ArrayRef<llvm::Value *> args); llvm::ArrayRef<llvm::Value *> args);
bool hasValidSignatureForEmbedded(SILFunction *f);
} }
} }

View File

@@ -1791,11 +1791,6 @@ static void forEachProtocolWitnessTable(
assert(protocols.size() == witnessConformances.size() && assert(protocols.size() == witnessConformances.size() &&
"mismatched protocol conformances"); "mismatched protocol conformances");
// Don't emit witness tables in embedded Swift.
if (srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
return;
}
for (unsigned i = 0, e = protocols.size(); i < e; ++i) { for (unsigned i = 0, e = protocols.size(); i < e; ++i) {
assert(protocols[i] == witnessConformances[i].getRequirement()); assert(protocols[i] == witnessConformances[i].getRequirement());
auto table = emitWitnessTableRef(IGF, srcType, srcMetadataCache, auto table = emitWitnessTableRef(IGF, srcType, srcMetadataCache,

View File

@@ -1516,6 +1516,13 @@ public:
/// Add reference to the protocol conformance descriptor that generated /// Add reference to the protocol conformance descriptor that generated
/// this table. /// this table.
void addProtocolConformanceDescriptor() { void addProtocolConformanceDescriptor() {
// In Embedded Swift, there are no protocol conformance descriptors. Emit
// a null pointer instead to keep the same layout as regular Swift.
if (IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
Table.addNullPointer(IGM.Int8PtrTy);
return;
}
auto descriptor = auto descriptor =
IGM.getAddrOfProtocolConformanceDescriptor(&Conformance); IGM.getAddrOfProtocolConformanceDescriptor(&Conformance);
if (isRelative) if (isRelative)
@@ -2564,7 +2571,9 @@ static void addWTableTypeMetadata(IRGenModule &IGM,
void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) { void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) {
if (Context.LangOpts.hasFeature(Feature::Embedded)) { if (Context.LangOpts.hasFeature(Feature::Embedded)) {
return; // In Embedded Swift, only class-bound wtables are allowed.
if (!wt->getConformance()->getProtocol()->requiresClass())
return;
} }
// Don't emit a witness table if it is a declaration. // Don't emit a witness table if it is a declaration.
@@ -3564,9 +3573,13 @@ llvm::Value *irgen::emitWitnessTableRef(IRGenFunction &IGF,
CanType srcType, CanType srcType,
llvm::Value **srcMetadataCache, llvm::Value **srcMetadataCache,
ProtocolConformanceRef conformance) { ProtocolConformanceRef conformance) {
assert(!srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded));
auto proto = conformance.getRequirement(); auto proto = conformance.getRequirement();
// In Embedded Swift, only class-bound wtables are allowed.
if (srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
assert(proto->requiresClass());
}
assert(Lowering::TypeConverter::protocolRequiresWitnessTable(proto) assert(Lowering::TypeConverter::protocolRequiresWitnessTable(proto)
&& "protocol does not have witness tables?!"); && "protocol does not have witness tables?!");

View File

@@ -1407,7 +1407,10 @@ bool IRGenerator::canEmitWitnessTableLazily(SILWitnessTable *wt) {
} }
void IRGenerator::addLazyWitnessTable(const ProtocolConformance *Conf) { void IRGenerator::addLazyWitnessTable(const ProtocolConformance *Conf) {
assert(!SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)); // In Embedded Swift, only class-bound wtables are allowed.
if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
assert(Conf->getProtocol()->requiresClass());
}
if (auto *wt = SIL.lookUpWitnessTable(Conf)) { if (auto *wt = SIL.lookUpWitnessTable(Conf)) {
// Add it to the queue if it hasn't already been put there. // Add it to the queue if it hasn't already been put there.

View File

@@ -2478,10 +2478,6 @@ void IRGenModule::emitSILFunction(SILFunction *f) {
if (f->isExternalDeclaration()) if (f->isExternalDeclaration())
return; return;
if (Context.LangOpts.hasFeature(Feature::Embedded) &&
f->getLoweredFunctionType()->isPolymorphic())
return;
// Do not emit bodies of public_external or package_external functions. // Do not emit bodies of public_external or package_external functions.
if (hasPublicOrPackageVisibility(f->getLinkage(), if (hasPublicOrPackageVisibility(f->getLinkage(),
f->getASTContext().SILOpts.EnableSerializePackage) && f->getASTContext().SILOpts.EnableSerializePackage) &&

View File

@@ -671,9 +671,13 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
RuntimeEffect::MetaData | RuntimeEffect::Existential; RuntimeEffect::MetaData | RuntimeEffect::Existential;
case SILInstructionKind::InitExistentialRefInst: case SILInstructionKind::InitExistentialRefInst:
impactType = inst->getOperand(0)->getType();
return RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound;
case SILInstructionKind::InitExistentialMetatypeInst: case SILInstructionKind::InitExistentialMetatypeInst:
impactType = inst->getOperand(0)->getType(); impactType = inst->getOperand(0)->getType();
return RuntimeEffect::MetaData | RuntimeEffect::Existential; return RuntimeEffect::MetaData | RuntimeEffect::Existential;
case SILInstructionKind::ObjCToThickMetatypeInst: case SILInstructionKind::ObjCToThickMetatypeInst:
impactType = inst->getOperand(0)->getType(); impactType = inst->getOperand(0)->getType();
return RuntimeEffect::MetaData; return RuntimeEffect::MetaData;
@@ -693,14 +697,8 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
return RuntimeEffect::Existential; return RuntimeEffect::Existential;
case SILInstructionKind::OpenExistentialRefInst: { case SILInstructionKind::OpenExistentialRefInst: {
SILType opType = cast<OpenExistentialRefInst>(inst)->getOperand()->getType(); impactType = inst->getOperand(0)->getType();
impactType = opType; return RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound;
if (opType.getASTType()->isObjCExistentialType()) {
return RuntimeEffect::MetaData | RuntimeEffect::Existential;
}
return RuntimeEffect::MetaData | RuntimeEffect::Existential;
// TODO: should be Existential
//return RuntimeEffect::Existential;
} }
case SILInstructionKind::UnconditionalCheckedCastInst: case SILInstructionKind::UnconditionalCheckedCastInst:
@@ -962,9 +960,16 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
case SILFunctionTypeRepresentation::Block: case SILFunctionTypeRepresentation::Block:
rt |= RuntimeEffect::ObjectiveC | RuntimeEffect::MetaData; rt |= RuntimeEffect::ObjectiveC | RuntimeEffect::MetaData;
break; break;
case SILFunctionTypeRepresentation::WitnessMethod: case SILFunctionTypeRepresentation::WitnessMethod: {
rt |= RuntimeEffect::MetaData | RuntimeEffect::Existential; auto conformance =
as.getOrigCalleeType()->getWitnessMethodConformanceOrInvalid();
if (conformance.getRequirement()->requiresClass()) {
rt |= RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound;
} else {
rt |= RuntimeEffect::MetaData | RuntimeEffect::Existential;
}
break; break;
}
case SILFunctionTypeRepresentation::CFunctionPointer: case SILFunctionTypeRepresentation::CFunctionPointer:
case SILFunctionTypeRepresentation::CXXMethod: case SILFunctionTypeRepresentation::CXXMethod:
case SILFunctionTypeRepresentation::Thin: case SILFunctionTypeRepresentation::Thin:

View File

@@ -534,7 +534,8 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc); LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc);
if (perfConstr == PerformanceConstraints::NoExistentials && if (perfConstr == PerformanceConstraints::NoExistentials &&
(impact & RuntimeEffect::Existential)) { ((impact & RuntimeEffect::Existential) ||
(impact & RuntimeEffect::ExistentialClassBound))) {
PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst); PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst);
if (impactType) { if (impactType) {
diagnose(loc, diag::perf_diag_existential_type, impactType.getASTType()); diagnose(loc, diag::perf_diag_existential_type, impactType.getASTType());
@@ -556,6 +557,8 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
} }
if (module.getOptions().EmbeddedSwift) { if (module.getOptions().EmbeddedSwift) {
// Explicitly don't detect RuntimeEffect::ExistentialClassBound - those are
// allowed in Embedded Swift.
if (impact & RuntimeEffect::Existential) { if (impact & RuntimeEffect::Existential) {
PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst); PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst);
if (impactType) { if (impactType) {

View File

@@ -457,7 +457,13 @@ void addFunctionPasses(SILPassPipelinePlan &P,
P.addMem2Reg(); P.addMem2Reg();
// Run the existential specializer Pass. // Run the existential specializer Pass.
P.addExistentialSpecializer(); if (!P.getOptions().EmbeddedSwift) {
// MandatoryPerformanceOptimizations already took care of all specializations
// in embedded Swift mode, running the existential specializer might introduce
// more generic calls from non-generic functions, which breaks the assumptions
// of embedded Swift.
P.addExistentialSpecializer();
}
// Cleanup, which is important if the inliner has restarted the pass pipeline. // Cleanup, which is important if the inliner has restarted the pass pipeline.
P.addPerformanceConstantPropagation(); P.addPerformanceConstantPropagation();

View File

@@ -0,0 +1,43 @@
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo -O) | %FileCheck %s
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo -Osize) | %FileCheck %s
// REQUIRES: swift_in_compiler
// REQUIRES: executable_test
// REQUIRES: optimized_stdlib
// REQUIRES: OS=macosx || OS=linux-gnu
protocol ClassBound: AnyObject {
func foo()
func bar()
}
class MyClass {}
extension MyClass: ClassBound {
func foo() { print("MyClass.foo()") }
func bar() { print("MyClass.bar()") }
}
class MyOtherClass {}
extension MyOtherClass: ClassBound {
func foo() { print("MyOtherClass.foo()") }
func bar() { print("MyOtherClass.bar()") }
}
func test(existential: any ClassBound) {
existential.foo()
existential.bar()
}
@main
struct Main {
static func main() {
test(existential: MyClass())
// CHECK: MyClass.foo()
// CHECK: MyClass.bar()
test(existential: MyOtherClass())
// CHECK: MyOtherClass.foo()
// CHECK: MyOtherClass.bar()
}
}

View File

@@ -0,0 +1,50 @@
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
// REQUIRES: swift_in_compiler
// REQUIRES: executable_test
// REQUIRES: optimized_stdlib
// REQUIRES: OS=macosx || OS=linux-gnu
protocol ClassBound: AnyObject {
func foo()
func bar()
}
extension ClassBound {
func extensionMethod() {
self.foo()
self.bar()
}
}
class MyClass {}
extension MyClass: ClassBound {
func foo() { print("MyClass.foo()") }
func bar() { print("MyClass.bar()") }
}
class MyOtherClass {}
extension MyOtherClass: ClassBound {
func foo() { print("MyOtherClass.foo()") }
func bar() { print("MyOtherClass.bar()") }
}
@main
struct Main {
static func main() {
var array: [any ClassBound] = []
array.append(MyClass())
array.append(MyOtherClass())
for e in array {
e.extensionMethod()
}
// CHECK: MyClass.foo()
// CHECK: MyClass.bar()
// CHECK: MyOtherClass.foo()
// CHECK: MyOtherClass.bar()
}
}

View File

@@ -0,0 +1,44 @@
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
// REQUIRES: swift_in_compiler
// REQUIRES: executable_test
// REQUIRES: optimized_stdlib
// REQUIRES: OS=macosx || OS=linux-gnu
protocol ClassBound: AnyObject {
func foo()
func bar()
}
class MyClass {}
extension MyClass: ClassBound {
func foo() { print("MyClass.foo()") }
func bar() { print("MyClass.bar()") }
}
class MyOtherClass {}
extension MyOtherClass: ClassBound {
func foo() { print("MyOtherClass.foo()") }
func bar() { print("MyOtherClass.bar()") }
}
@main
struct Main {
static func main() {
var array: [any ClassBound] = []
array.append(MyClass())
array.append(MyOtherClass())
for e in array {
e.foo()
e.bar()
}
// CHECK: MyClass.foo()
// CHECK: MyClass.bar()
// CHECK: MyOtherClass.foo()
// CHECK: MyOtherClass.bar()
}
}

View File

@@ -0,0 +1,44 @@
// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -parse-as-library -wmo) | %FileCheck %s
// REQUIRES: swift_in_compiler
// REQUIRES: executable_test
// REQUIRES: optimized_stdlib
// REQUIRES: OS=macosx || OS=linux-gnu
// Generic classes don't work yet.
// XFAIL: *
protocol ClassBound: AnyObject {
func foo()
func bar()
}
class MyGenericClass<T> {
var typ: String
init(typ: String) { self.typ = typ }
}
extension MyGenericClass: ClassBound {
func foo() { print("MyGenericClass<\(typ)>.foo()") }
func bar() { print("MyGenericClass<\(typ)>.bar()") }
}
@main
struct Main {
static func main() {
var array: [any ClassBound] = []
array.append(MyGenericClass<Int>(typ: "Int"))
array.append(MyGenericClass<String>(typ: "String"))
for e in array {
e.foo()
e.bar()
}
// CHECK: MyGenericClass<Int>.foo()
// CHECK: MyGenericClass<Int>.bar()
// CHECK: MyGenericClass<String>.foo()
// CHECK: MyGenericClass<String>.bar()
}
}

View File

@@ -0,0 +1,35 @@
// RUN: %target-swift-emit-ir -parse-as-library -module-name main -verify %s -enable-experimental-feature Embedded -wmo
// REQUIRES: swift_in_compiler
// REQUIRES: optimized_stdlib
// REQUIRES: OS=macosx || OS=linux-gnu
protocol ClassBound: AnyObject {
func foo()
}
protocol NotClassBound {
func foo()
}
class MyClass {}
extension MyClass: ClassBound, NotClassBound {
func foo() { print("MyClass.foo()") }
}
func test(existential: any ClassBound) {
existential.foo() // ok
}
func test(existential: any NotClassBound) {
existential.foo() // expected-error {{cannot use a value of protocol type in embedded Swift}}
// expected-note@+3 {{called from here}}
}
@main
struct Main {
static func main() {
test(existential: MyClass() as (any ClassBound)) // ok
test(existential: MyClass() as (any NotClassBound)) // expected-error {{cannot use a value of protocol type 'any NotClassBound' in embedded Swift}}
}
}