diff --git a/include/swift/IRGen/Linking.h b/include/swift/IRGen/Linking.h index 67d2c4f3dd6..ba7e6057a7f 100644 --- a/include/swift/IRGen/Linking.h +++ b/include/swift/IRGen/Linking.h @@ -89,9 +89,23 @@ inline bool isEmbedded(CanType t) { 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) { - return isa(t) || isa(t) || - isa(t); + if (isa(t) || isa(t) || + isa(t)) { + return true; + } + if (auto existentialTy = dyn_cast(t)) { + if (existentialTy->requiresClass()) + return true; + } + if (auto archeTy = dyn_cast(t)) { + if (archeTy->requiresClass()) + return true; + } + return false; } inline bool isEmbedded(Decl *d) { @@ -1062,7 +1076,10 @@ public: } static LinkEntity forProtocolWitnessTable(const RootProtocolConformance *C) { - assert(!isEmbedded(C)); + if (isEmbedded(C)) { + assert(C->getProtocol()->requiresClass()); + } + LinkEntity entity; entity.setForProtocolConformance(Kind::ProtocolWitnessTable, C); return entity; diff --git a/include/swift/SIL/RuntimeEffect.h b/include/swift/SIL/RuntimeEffect.h index 1b85b368901..d59fb6b1a03 100644 --- a/include/swift/SIL/RuntimeEffect.h +++ b/include/swift/SIL/RuntimeEffect.h @@ -54,6 +54,9 @@ enum class RuntimeEffect : unsigned { /// Witness methods, boxing, unboxing, initializing, etc. Existential = 0x80, + + /// Class-bound only existential + ExistentialClassBound = 0x200, /// Not modelled currently. Concurrency = 0x0, diff --git a/lib/IRGen/GenDecl.cpp b/lib/IRGen/GenDecl.cpp index d7b65ebbbf4..2f69af00273 100644 --- a/lib/IRGen/GenDecl.cpp +++ b/lib/IRGen/GenDecl.cpp @@ -1142,8 +1142,9 @@ void IRGenModule::emitGlobalLists() { // Eagerly emit functions that are externally visible. Functions that are // dynamic replacements must also be eagerly emitted. static bool isLazilyEmittedFunction(SILFunction &f, SILModule &m) { - // Embedded Swift only emits specialized function, so don't emit generic - // functions, even if they're externally visible. + // Embedded Swift only emits specialized function (except when they are + // protocol witness methods). So don't emit generic functions, even if they're + // externally visible. if (f.getASTContext().LangOpts.hasFeature(Feature::Embedded) && f.getLoweredFunctionType()->getSubstGenericSignature()) { return true; @@ -1332,7 +1333,7 @@ void IRGenerator::emitLazyDefinitions() { assert(LazyFieldDescriptors.empty()); // LazyFunctionDefinitions are allowed, but they must not be generic for (SILFunction *f : LazyFunctionDefinitions) { - assert(!f->isGeneric()); + assert(hasValidSignatureForEmbedded(f)); } assert(LazyWitnessTables.empty()); assert(LazyCanonicalSpecializedMetadataAccessors.empty()); @@ -1482,7 +1483,7 @@ void IRGenerator::addLazyFunction(SILFunction *f) { // Embedded Swift doesn't expect any generic functions to be referenced. if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) { - assert(!f->isGeneric()); + assert(hasValidSignatureForEmbedded(f)); } assert(!FinishedEmittingLazyDefinitions); @@ -3473,6 +3474,24 @@ llvm::CallBase *swift::irgen::emitCXXConstructorCall( 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(mappedParam)) { + if (archeTy->requiresClass()) + continue; + } + return false; + } + return true; +} + StackProtectorMode IRGenModule::shouldEmitStackProtector(SILFunction *f) { const SILOptions &opts = IRGen.SIL.getOptions(); return (opts.EnableStackProtection && f->needsStackProtection()) ? @@ -4352,7 +4371,10 @@ static bool conformanceIsVisibleViaMetadata( void IRGenModule::addProtocolConformance(ConformanceDescription &&record) { - + if (Context.LangOpts.hasFeature(Feature::Embedded)) { + return; + } + emitProtocolConformance(record); if (conformanceIsVisibleViaMetadata(record.conformance)) { diff --git a/lib/IRGen/GenDecl.h b/lib/IRGen/GenDecl.h index e85fa7782f5..f5463be7146 100644 --- a/lib/IRGen/GenDecl.h +++ b/lib/IRGen/GenDecl.h @@ -76,6 +76,8 @@ namespace irgen { llvm::FunctionType *ctorFnType, llvm::Constant *ctorAddress, llvm::ArrayRef args); + + bool hasValidSignatureForEmbedded(SILFunction *f); } } diff --git a/lib/IRGen/GenExistential.cpp b/lib/IRGen/GenExistential.cpp index 2fda5bdc5b0..e6651bb61dd 100644 --- a/lib/IRGen/GenExistential.cpp +++ b/lib/IRGen/GenExistential.cpp @@ -1791,11 +1791,6 @@ static void forEachProtocolWitnessTable( assert(protocols.size() == witnessConformances.size() && "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) { assert(protocols[i] == witnessConformances[i].getRequirement()); auto table = emitWitnessTableRef(IGF, srcType, srcMetadataCache, diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index 49fe80a01ad..5277036feb1 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -1512,6 +1512,13 @@ public: /// Add reference to the protocol conformance descriptor that generated /// this table. 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 = IGM.getAddrOfProtocolConformanceDescriptor(&Conformance); if (isRelative) @@ -2560,7 +2567,9 @@ static void addWTableTypeMetadata(IRGenModule &IGM, void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) { 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. @@ -3560,9 +3569,13 @@ llvm::Value *irgen::emitWitnessTableRef(IRGenFunction &IGF, CanType srcType, llvm::Value **srcMetadataCache, ProtocolConformanceRef conformance) { - assert(!srcType->getASTContext().LangOpts.hasFeature(Feature::Embedded)); - 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) && "protocol does not have witness tables?!"); diff --git a/lib/IRGen/IRGenModule.cpp b/lib/IRGen/IRGenModule.cpp index 013944bfdac..adbe6c43d9b 100644 --- a/lib/IRGen/IRGenModule.cpp +++ b/lib/IRGen/IRGenModule.cpp @@ -1407,7 +1407,10 @@ bool IRGenerator::canEmitWitnessTableLazily(SILWitnessTable *wt) { } 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)) { // Add it to the queue if it hasn't already been put there. diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index d706346f373..27a027f635f 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -2478,10 +2478,6 @@ void IRGenModule::emitSILFunction(SILFunction *f) { if (f->isExternalDeclaration()) return; - if (Context.LangOpts.hasFeature(Feature::Embedded) && - f->getLoweredFunctionType()->isPolymorphic()) - return; - // Do not emit bodies of public_external or package_external functions. if (hasPublicOrPackageVisibility(f->getLinkage(), f->getASTContext().SILOpts.EnableSerializePackage) && diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 59e73283a30..2fb232ceefe 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -671,9 +671,13 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType) RuntimeEffect::MetaData | RuntimeEffect::Existential; case SILInstructionKind::InitExistentialRefInst: + impactType = inst->getOperand(0)->getType(); + return RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound; + case SILInstructionKind::InitExistentialMetatypeInst: impactType = inst->getOperand(0)->getType(); return RuntimeEffect::MetaData | RuntimeEffect::Existential; + case SILInstructionKind::ObjCToThickMetatypeInst: impactType = inst->getOperand(0)->getType(); return RuntimeEffect::MetaData; @@ -693,14 +697,8 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType) return RuntimeEffect::Existential; case SILInstructionKind::OpenExistentialRefInst: { - SILType opType = cast(inst)->getOperand()->getType(); - impactType = opType; - if (opType.getASTType()->isObjCExistentialType()) { - return RuntimeEffect::MetaData | RuntimeEffect::Existential; - } - return RuntimeEffect::MetaData | RuntimeEffect::Existential; - // TODO: should be Existential - //return RuntimeEffect::Existential; + impactType = inst->getOperand(0)->getType(); + return RuntimeEffect::MetaData | RuntimeEffect::ExistentialClassBound; } case SILInstructionKind::UnconditionalCheckedCastInst: @@ -963,7 +961,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType) rt |= RuntimeEffect::ObjectiveC | RuntimeEffect::MetaData; break; case SILFunctionTypeRepresentation::WitnessMethod: - rt |= RuntimeEffect::MetaData | RuntimeEffect::Existential; + rt |= RuntimeEffect::MetaData; // ??? break; case SILFunctionTypeRepresentation::CFunctionPointer: case SILFunctionTypeRepresentation::CXXMethod: diff --git a/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp b/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp index 0c1b078ca62..68553958fd7 100644 --- a/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp +++ b/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp @@ -534,7 +534,8 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst, LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc); if (perfConstr == PerformanceConstraints::NoExistentials && - (impact & RuntimeEffect::Existential)) { + ((impact & RuntimeEffect::Existential) || + (impact & RuntimeEffect::ExistentialClassBound))) { PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst); if (impactType) { diagnose(loc, diag::perf_diag_existential_type, impactType.getASTType()); @@ -556,6 +557,8 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst, } if (module.getOptions().EmbeddedSwift) { + // Explicitly don't detect RuntimeEffect::ExistentialClassBound - those are + // allowed in Embedded Swift. if (impact & RuntimeEffect::Existential) { PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst); if (impactType) { diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index 7dbb2d723f2..7c63998578f 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -457,7 +457,13 @@ void addFunctionPasses(SILPassPipelinePlan &P, P.addMem2Reg(); // 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. P.addPerformanceConstantPropagation(); diff --git a/test/embedded/existential-class-bound1.swift b/test/embedded/existential-class-bound1.swift new file mode 100644 index 00000000000..bba4ef51d1d --- /dev/null +++ b/test/embedded/existential-class-bound1.swift @@ -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() + } +} + diff --git a/test/embedded/existential-class-bound2.swift b/test/embedded/existential-class-bound2.swift new file mode 100644 index 00000000000..7a4e4ad84ac --- /dev/null +++ b/test/embedded/existential-class-bound2.swift @@ -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() + } +} + diff --git a/test/embedded/existential-class-bound3.swift b/test/embedded/existential-class-bound3.swift new file mode 100644 index 00000000000..dda66630135 --- /dev/null +++ b/test/embedded/existential-class-bound3.swift @@ -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() + } +} + diff --git a/test/embedded/existential-class-bound4.swift b/test/embedded/existential-class-bound4.swift new file mode 100644 index 00000000000..68a380c987f --- /dev/null +++ b/test/embedded/existential-class-bound4.swift @@ -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 { + 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(typ: "Int")) + array.append(MyGenericClass(typ: "String")) + + for e in array { + e.foo() + e.bar() + } + + // CHECK: MyGenericClass.foo() + // CHECK: MyGenericClass.bar() + + // CHECK: MyGenericClass.foo() + // CHECK: MyGenericClass.bar() + } +} + diff --git a/test/embedded/existential-class-bound5.swift b/test/embedded/existential-class-bound5.swift new file mode 100644 index 00000000000..4917310c690 --- /dev/null +++ b/test/embedded/existential-class-bound5.swift @@ -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}} + } +}