From a62933318bfc3526a9c57864f667cd451511f281 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 30 Sep 2024 19:42:44 +0200 Subject: [PATCH 01/13] SwiftCompilerSources: add `Context.getSpecializedConformance` --- .../Sources/Optimizer/PassManager/Context.swift | 8 ++++++++ include/swift/SILOptimizer/OptimizerBridging.h | 4 ++++ include/swift/SILOptimizer/OptimizerBridgingImpl.h | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index 802a2f527eb0b..b2c031c5f6c86 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -74,6 +74,14 @@ extension Context { return _bridged.lookupSpecializedVTable(classType.bridged).vTable } + func getSpecializedConformance(of genericConformance: Conformance, + for type: AST.`Type`, + substitutions: SubstitutionMap) -> Conformance + { + let c = _bridged.getSpecializedConformance(genericConformance.bridged, type.bridged, substitutions.bridged) + return Conformance(bridged: c) + } + func notifyNewFunction(function: Function, derivedFrom: Function) { _bridged.addFunctionToPassManagerWorklist(function.bridged, derivedFrom.bridged) } diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index 27ceafbd76873..b3278fb769db6 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -332,6 +332,10 @@ struct BridgedPassContext { SWIFT_IMPORT_UNSAFE BRIDGED_INLINE OptionalBridgedVTable lookupSpecializedVTable(BridgedType classType) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE + BridgedConformance getSpecializedConformance(BridgedConformance genericConformance, + BridgedASTType type, + BridgedSubstitutionMap substitutions) const; + SWIFT_IMPORT_UNSAFE BRIDGED_INLINE OptionalBridgedWitnessTable lookupWitnessTable(BridgedConformance conformance) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedWitnessTable createWitnessTable(BridgedLinkage linkage, bool serialized, diff --git a/include/swift/SILOptimizer/OptimizerBridgingImpl.h b/include/swift/SILOptimizer/OptimizerBridgingImpl.h index a05844391909e..7e3be2c4d52f9 100644 --- a/include/swift/SILOptimizer/OptimizerBridgingImpl.h +++ b/include/swift/SILOptimizer/OptimizerBridgingImpl.h @@ -417,6 +417,16 @@ OptionalBridgedVTable BridgedPassContext::lookupSpecializedVTable(BridgedType cl return {mod->lookUpSpecializedVTable(classType.unbridged())}; } +BridgedConformance BridgedPassContext::getSpecializedConformance( + BridgedConformance genericConformance, + BridgedASTType type, + BridgedSubstitutionMap substitutions) const { + auto &ctxt = invocation->getPassManager()->getModule()->getASTContext(); + auto *genConf = llvm::cast(genericConformance.unbridged().getConcrete()); + auto *c = ctxt.getSpecializedConformance(type.unbridged(), genConf, substitutions.unbridged()); + return swift::ProtocolConformanceRef(c); +} + OptionalBridgedWitnessTable BridgedPassContext::lookupWitnessTable(BridgedConformance conformance) const { swift::ProtocolConformanceRef ref = conformance.unbridged(); if (!ref.isConcrete()) { From ee1166646d5309b51ef387bffe0f9cf9ee8e9182 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 30 Sep 2024 19:48:29 +0200 Subject: [PATCH 02/13] GenericSpecialization: change how new specialized witness tables are added to MandatoryPerformanceOptimization's worklist Do it by passing a closure instead of returning the new witness table. This allows to add more than one new witness table to the worklist --- .../ModulePasses/MandatoryPerformanceOptimizations.swift | 5 +++-- .../Optimizer/Utilities/GenericSpecialization.swift | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift index 10cd4e211c963..062a3f997fc22 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift @@ -271,8 +271,9 @@ private func specializeWitnessTables(for initExRef: InitExistentialRefInst, _ co let origWitnessTable = context.lookupWitnessTable(for: conformance) if conformance.isSpecialized { if origWitnessTable == nil { - let wt = specializeWitnessTable(forConformance: conformance, errorLocation: initExRef.location, context) - worklist.addWitnessMethods(of: wt) + specializeWitnessTable(forConformance: conformance, errorLocation: initExRef.location, context) { + worklist.addWitnessMethods(of: $0) + } } } else if let origWitnessTable { checkForGenericMethods(in: origWitnessTable, errorLocation: initExRef.location, context) diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift index a5a660c29c157..76d644a4b60c1 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift @@ -78,7 +78,8 @@ func specializeVTablesOfSuperclasses(_ moduleContext: ModulePassContext) { func specializeWitnessTable(forConformance conformance: Conformance, errorLocation: Location, - _ context: ModulePassContext) -> WitnessTable + _ context: ModulePassContext, + _ notifyNewWitnessTable: (WitnessTable) -> ()) { let genericConformance = conformance.genericConformance guard let witnessTable = context.lookupWitnessTable(for: genericConformance) else { @@ -107,5 +108,7 @@ func specializeWitnessTable(forConformance conformance: Conformance, } return origEntry } - return context.createWitnessTable(entries: newEntries, conformance: conformance, linkage: .shared, serialized: false) + let newWT = context.createWitnessTable(entries: newEntries,conformance: conformance, + linkage: .shared, serialized: false) + notifyNewWitnessTable(newWT) } From c84a289e5aa27e3e422ba634245665c7e60f2967 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 27 Sep 2024 13:45:55 +0200 Subject: [PATCH 03/13] IRGen: allow specialized witness tables also in regular swift --- lib/IRGen/GenProto.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index d56b625422572..779c0357f29e8 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -2485,8 +2485,11 @@ IRGenModule::getConformanceInfo(const ProtocolDecl *protocol, const ConformanceInfo *info; - if (Context.LangOpts.hasFeature(Feature::Embedded)) { - if (auto *sc = dyn_cast(conformance)) { + // If there is a specialized SILWitnessTable for the specialized conformance, + // directly use it. + if (auto *sc = dyn_cast(conformance)) { + SILWitnessTable *wt = getSILModule().lookUpWitnessTable(conformance); + if (wt && wt->getConformance() == sc) { info = new SpecializedConformanceInfo(sc); Conformances.try_emplace(conformance, info); return *info; From 2cc95fef56b64ef2e4f173eb974b25d4bb002989 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 30 Sep 2024 19:50:28 +0200 Subject: [PATCH 04/13] embedded: Support class existentials with inherited protocols For example: ``` protocol Base: AnyObject {} protocol Derived: Base {} class C: Derived {} let e: Derived = C() ``` --- .../Utilities/GenericSpecialization.swift | 7 ++++++- lib/IRGen/GenProto.cpp | 14 ++++++++------ test/embedded/existential-class-bound4.swift | 5 ++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift index 76d644a4b60c1..2281508c00557 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift @@ -102,11 +102,16 @@ func specializeWitnessTable(forConformance conformance: Conformance, return origEntry } return .method(requirement: requirement, witness: specializedMethod) + case .baseProtocol(let requirement, let witness): + let baseConf = context.getSpecializedConformance(of: witness, + for: conformance.type, + substitutions: conformance.specializedSubstitutions) + specializeWitnessTable(forConformance: baseConf, errorLocation: errorLocation, context, notifyNewWitnessTable) + return .baseProtocol(requirement: requirement, witness: baseConf) default: // TODO: handle other witness table entry kinds fatalError("unsupported witness table etnry") } - return origEntry } let newWT = context.createWitnessTable(entries: newEntries,conformance: conformance, linkage: .shared, serialized: false) diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index 779c0357f29e8..05a8537f5669e 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -1563,12 +1563,9 @@ class AccessorConformanceInfo : public ConformanceInfo { /// A base protocol is witnessed by a pointer to the conformance /// of this type to that protocol. void addOutOfLineBaseProtocol(ProtocolDecl *baseProto) { -#ifndef NDEBUG auto &entry = SILEntries.front(); -#endif SILEntries = SILEntries.slice(1); -#ifndef NDEBUG assert(entry.getKind() == SILWitnessTable::BaseProtocol && "sil witness table does not match protocol"); assert(entry.getBaseProtocolWitness().Requirement == baseProto @@ -1577,13 +1574,18 @@ class AccessorConformanceInfo : public ConformanceInfo { assert((size_t)piIndex.getValue() == Table.size() - WitnessTableFirstRequirementOffset && "offset doesn't match ProtocolInfo layout"); -#endif // TODO: Use the witness entry instead of falling through here. // Look for conformance info. - auto *astConf = ConformanceInContext.getInheritedConformance(baseProto); - assert(astConf->getType()->isEqual(ConcreteType)); + ProtocolConformance *astConf = nullptr; + if (isa(SILWT->getConformance())) { + astConf = entry.getBaseProtocolWitness().Witness; + ASSERT(isa(astConf)); + } else { + astConf = ConformanceInContext.getInheritedConformance(baseProto); + assert(astConf->getType()->isEqual(ConcreteType)); + } const ConformanceInfo &conf = IGM.getConformanceInfo(baseProto, astConf); // If we can emit the base witness table as a constant, do so. diff --git a/test/embedded/existential-class-bound4.swift b/test/embedded/existential-class-bound4.swift index 3959d30e9e72f..d11c3f26b891b 100644 --- a/test/embedded/existential-class-bound4.swift +++ b/test/embedded/existential-class-bound4.swift @@ -5,8 +5,11 @@ // REQUIRES: optimized_stdlib // REQUIRES: OS=macosx || OS=linux-gnu -protocol ClassBound: AnyObject { +public protocol Base: AnyObject { func foo() +} + +protocol ClassBound: Base { func bar() } From 64535df5bfff2a5f799aaecf9ebd8aad6c96f56a Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 16:12:21 +0200 Subject: [PATCH 05/13] SIL: make SubstitutionMap CustomStringConvertible --- .../Sources/AST/SubstitutionMap.swift | 6 +++++- include/swift/AST/ASTBridging.h | 1 + lib/AST/ASTBridging.cpp | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/SwiftCompilerSources/Sources/AST/SubstitutionMap.swift b/SwiftCompilerSources/Sources/AST/SubstitutionMap.swift index 7576030308d2c..41b50aa94aa4a 100644 --- a/SwiftCompilerSources/Sources/AST/SubstitutionMap.swift +++ b/SwiftCompilerSources/Sources/AST/SubstitutionMap.swift @@ -18,7 +18,7 @@ import ASTBridging /// /// Substitution maps are primarily used when performing substitutions into any entity that /// can reference type parameters and conformances. -public struct SubstitutionMap { +public struct SubstitutionMap: CustomStringConvertible { public let bridged: BridgedSubstitutionMap public init(bridged: BridgedSubstitutionMap) { @@ -29,6 +29,10 @@ public struct SubstitutionMap { self.bridged = BridgedSubstitutionMap() } + public var description: String { + return String(taking: bridged.getDebugDescription()) + } + public var isEmpty: Bool { bridged.isEmpty() } public var hasAnySubstitutableParams: Bool { bridged.hasAnySubstitutableParams() } diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index c8969888d31c4..21a38144882cd 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -2060,6 +2060,7 @@ struct BridgedSubstitutionMap { BRIDGED_INLINE BridgedSubstitutionMap(swift::SubstitutionMap map); BRIDGED_INLINE swift::SubstitutionMap unbridged() const; BRIDGED_INLINE BridgedSubstitutionMap(); + BridgedOwnedString getDebugDescription() const; BRIDGED_INLINE bool isEmpty() const; BRIDGED_INLINE bool hasAnySubstitutableParams() const; BRIDGED_INLINE SwiftInt getNumConformances() const; diff --git a/lib/AST/ASTBridging.cpp b/lib/AST/ASTBridging.cpp index 37d9f337aa3b6..e490580a56015 100644 --- a/lib/AST/ASTBridging.cpp +++ b/lib/AST/ASTBridging.cpp @@ -2744,6 +2744,10 @@ void BridgedTypeRepr_dump(void *type) { static_cast(type)->dump(); } #pragma clang diagnostic pop +//===----------------------------------------------------------------------===// +// MARK: Conformance +//===----------------------------------------------------------------------===// + BridgedOwnedString BridgedConformance::getDebugDescription() const { std::string str; llvm::raw_string_ostream os(str); @@ -2751,9 +2755,20 @@ BridgedOwnedString BridgedConformance::getDebugDescription() const { return str; } +//===----------------------------------------------------------------------===// +// MARK: SubstitutionMap +//===----------------------------------------------------------------------===// + static_assert(sizeof(BridgedSubstitutionMap) >= sizeof(swift::SubstitutionMap), "BridgedSubstitutionMap has wrong size"); +BridgedOwnedString BridgedSubstitutionMap::getDebugDescription() const { + std::string str; + llvm::raw_string_ostream os(str); + unbridged().dump(os); + return str; +} + //===----------------------------------------------------------------------===// // MARK: Plugins //===----------------------------------------------------------------------===// From 9964513189dd68d0ba4cb52974941f1666467c81 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 16:32:54 +0200 Subject: [PATCH 06/13] SwiftCompilerSources: add `Type.subst(with substitutionMap: SubstitutionMap) -> Type` API --- SwiftCompilerSources/Sources/AST/Type.swift | 8 ++++++++ include/swift/AST/ASTBridging.h | 1 + include/swift/AST/ASTBridgingImpl.h | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/SwiftCompilerSources/Sources/AST/Type.swift b/SwiftCompilerSources/Sources/AST/Type.swift index 4519920c7a694..533cb94d22456 100644 --- a/SwiftCompilerSources/Sources/AST/Type.swift +++ b/SwiftCompilerSources/Sources/AST/Type.swift @@ -37,6 +37,10 @@ public struct Type: CustomStringConvertible, NoReflectionChildren { public var isEscapable: Bool { bridged.isEscapable() } public var isNoEscape: Bool { bridged.isNoEscape() } public var isInteger: Bool { bridged.isInteger() } + + public func subst(with substitutionMap: SubstitutionMap) -> Type { + return Type(bridged: bridged.subst(substitutionMap.bridged)) + } } /// A Type that is statically known to be canonical. @@ -55,4 +59,8 @@ public struct CanonicalType: CustomStringConvertible, NoReflectionChildren { public var isEscapable: Bool { type.isEscapable } public var isNoEscape: Bool { type.isNoEscape } public var isInteger: Bool { type.isInteger } + + public func subst(with substitutionMap: SubstitutionMap) -> CanonicalType { + return type.subst(with: substitutionMap).canonical + } } diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 21a38144882cd..948c15d9e6cad 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -2004,6 +2004,7 @@ struct BridgedASTType { BRIDGED_INLINE bool isEscapable() const; BRIDGED_INLINE bool isNoEscape() const; BRIDGED_INLINE bool isInteger() const; + SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedASTType subst(BridgedSubstitutionMap substMap) const; }; class BridgedCanType { diff --git a/include/swift/AST/ASTBridgingImpl.h b/include/swift/AST/ASTBridgingImpl.h index aaf7105b6bdbd..0dbb9f10094ca 100644 --- a/include/swift/AST/ASTBridgingImpl.h +++ b/include/swift/AST/ASTBridgingImpl.h @@ -126,6 +126,10 @@ bool BridgedASTType::isInteger() const { return unbridged()->is(); } +BridgedASTType BridgedASTType::subst(BridgedSubstitutionMap substMap) const { + return {unbridged().subst(substMap.unbridged()).getPointer()}; +} + //===----------------------------------------------------------------------===// // MARK: BridgedCanType //===----------------------------------------------------------------------===// From af3d505015507816a5b4fd508c4ca16a5f3d3d31 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 16:34:59 +0200 Subject: [PATCH 07/13] SILCloner: fix creating a wrong type for cloned witness_method instructions The type needs to be mapped. --- include/swift/SIL/SILCloner.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 27f96c1f44654..ebc8d3bb029a5 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -2682,7 +2682,7 @@ SILCloner::visitWitnessMethodInst(WitnessMethodInst *Inst) { recordClonedInstruction(Inst, getBuilder().createWitnessMethod( getOpLocation(Inst->getLoc()), newLookupType, - conformance, Inst->getMember(), Inst->getType())); + conformance, Inst->getMember(), getOpType(Inst->getType()))); } template From ebfd4bbc5ce5f6222ac9b170a2972b84c7641982 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 16:44:32 +0200 Subject: [PATCH 08/13] SIL: support specialized witness_method instructions In Embedded Swift, witness method lookup is done from specialized witness tables. For this to work, the type of witness_method must be specialized as well. Otherwise the method call would be done with wrong parameter conventions (indirect instead of direct). --- include/swift/SIL/InstructionUtils.h | 5 ++- include/swift/SIL/SILInstruction.h | 7 +++ include/swift/SIL/SILModule.h | 1 + lib/SIL/IR/SILModule.cpp | 8 +++- lib/SIL/Utils/CalleeCache.cpp | 4 +- lib/SIL/Utils/InstructionUtils.cpp | 8 ++++ lib/SIL/Verifier/SILVerifier.cpp | 44 ++++++++++--------- .../Mandatory/DiagnoseInfiniteRecursion.cpp | 3 +- .../SILCombiner/SILCombinerApplyVisitors.cpp | 2 - lib/SILOptimizer/Utils/ConstExpr.cpp | 2 +- lib/SILOptimizer/Utils/Devirtualize.cpp | 6 +-- 11 files changed, 56 insertions(+), 34 deletions(-) diff --git a/include/swift/SIL/InstructionUtils.h b/include/swift/SIL/InstructionUtils.h index 88a2621c172b5..eba86f73c55b9 100644 --- a/include/swift/SIL/InstructionUtils.h +++ b/include/swift/SIL/InstructionUtils.h @@ -15,7 +15,7 @@ #include "swift/SIL/InstWrappers.h" #include "swift/SIL/RuntimeEffect.h" -#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILModule.h" namespace swift { @@ -233,6 +233,9 @@ bool visitExplodedTupleType(SILType type, bool visitExplodedTupleValue(SILValue value, llvm::function_ref)> callback); +std::pair +lookUpFunctionInWitnessTable(WitnessMethodInst *wmi, SILModule::LinkingMode linkingMode); + } // end namespace swift #endif diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 42a0685e0b566..5eab2091d6e68 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -7715,6 +7715,13 @@ class WitnessMethodInst final return getMember().getDecl()->getDeclContext()->getSelfProtocolDecl(); } + // Returns true if it's expected that the witness method is looked up up from + // a specialized witness table. + // This is the case in Embedded Swift. + bool isSpecialized() const { + return !getType().castTo()->isPolymorphic(); + } + ProtocolConformanceRef getConformance() const { return Conformance; } }; diff --git a/include/swift/SIL/SILModule.h b/include/swift/SIL/SILModule.h index 2d113dcdbbdd2..e3dc5d8c2539f 100644 --- a/include/swift/SIL/SILModule.h +++ b/include/swift/SIL/SILModule.h @@ -853,6 +853,7 @@ class SILModule { std::pair lookUpFunctionInWitnessTable(ProtocolConformanceRef C, SILDeclRef Requirement, + bool lookupInSpecializedWitnessTable, SILModule::LinkingMode linkingMode); /// Look up the SILDefaultWitnessTable representing the default witnesses diff --git a/lib/SIL/IR/SILModule.cpp b/lib/SIL/IR/SILModule.cpp index 04a7e6227be46..3e0b33ff4dadf 100644 --- a/lib/SIL/IR/SILModule.cpp +++ b/lib/SIL/IR/SILModule.cpp @@ -531,6 +531,7 @@ SerializedSILLoader *SILModule::getSILLoader() { std::pair SILModule::lookUpFunctionInWitnessTable(ProtocolConformanceRef C, SILDeclRef Requirement, + bool lookupInSpecializedWitnessTable, SILModule::LinkingMode linkingMode) { if (!C.isConcrete()) return {nullptr, nullptr}; @@ -539,7 +540,12 @@ SILModule::lookUpFunctionInWitnessTable(ProtocolConformanceRef C, SILLinkerVisitor linker(*this, linkingMode); linker.processConformance(C); } - SILWitnessTable *wt = lookUpWitnessTable(C.getConcrete()->getRootConformance()); + ProtocolConformance *conf = C.getConcrete(); + if (!isa(conf) || !lookupInSpecializedWitnessTable) { + conf = conf->getRootConformance(); + } + + SILWitnessTable *wt = lookUpWitnessTable(conf); if (!wt) { LLVM_DEBUG(llvm::dbgs() << " Failed speculative lookup of " diff --git a/lib/SIL/Utils/CalleeCache.cpp b/lib/SIL/Utils/CalleeCache.cpp index 625900c950e37..2ed1f1fa536e5 100644 --- a/lib/SIL/Utils/CalleeCache.cpp +++ b/lib/SIL/Utils/CalleeCache.cpp @@ -12,6 +12,7 @@ #include "swift/SIL/CalleeCache.h" #include "swift/SIL/SILModule.h" +#include "swift/SIL/InstructionUtils.h" #include "swift/AST/ProtocolConformance.h" #include "swift/Basic/Assertions.h" #include "llvm/Support/Compiler.h" @@ -256,8 +257,7 @@ CalleeCache::getSingleCalleeForWitnessMethod(WitnessMethodInst *WMI) const { SILWitnessTable *WT; // Attempt to find a specific callee for the given conformance and member. - std::tie(CalleeFn, WT) = WMI->getModule().lookUpFunctionInWitnessTable( - WMI->getConformance(), WMI->getMember(), SILModule::LinkingMode::LinkNormal); + std::tie(CalleeFn, WT) = lookUpFunctionInWitnessTable(WMI, SILModule::LinkingMode::LinkNormal); return CalleeFn; } diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 3b684d0c43bcd..71d3d0c066c90 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -1389,3 +1389,11 @@ bool swift::visitExplodedTupleValue( return true; } + +std::pair +swift::lookUpFunctionInWitnessTable(WitnessMethodInst *wmi, + SILModule::LinkingMode linkingMode) { + SILModule &mod = wmi->getModule(); + return mod.lookUpFunctionInWitnessTable(wmi->getConformance(), wmi->getMember(), + wmi->isSpecialized(), linkingMode); +} diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 9e8ee08646a3d..fb44246efc3eb 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -4114,29 +4114,31 @@ class SILVerifier : public SILVerifierBase { == F.getModule().Types.getProtocolWitnessRepresentation(protocol), "result of witness_method must have correct representation for protocol"); - require(methodType->isPolymorphic(), - "result of witness_method must be polymorphic"); - - auto genericSig = methodType->getInvocationGenericSignature(); - - auto selfGenericParam = genericSig.getGenericParams()[0]; - require(selfGenericParam->getDepth() == 0 - && selfGenericParam->getIndex() == 0, - "method should be polymorphic on Self parameter at depth 0 index 0"); - std::optional selfRequirement; - for (auto req : genericSig.getRequirements()) { - if (req.getKind() != RequirementKind::SameType) { - selfRequirement = req; - break; + if (methodType->isPolymorphic()) { + require(methodType->isPolymorphic(), + "result of witness_method must be polymorphic"); + + auto genericSig = methodType->getInvocationGenericSignature(); + + auto selfGenericParam = genericSig.getGenericParams()[0]; + require(selfGenericParam->getDepth() == 0 + && selfGenericParam->getIndex() == 0, + "method should be polymorphic on Self parameter at depth 0 index 0"); + std::optional selfRequirement; + for (auto req : genericSig.getRequirements()) { + if (req.getKind() != RequirementKind::SameType) { + selfRequirement = req; + break; + } } - } - require(selfRequirement && - selfRequirement->getKind() == RequirementKind::Conformance, - "first non-same-typerequirement should be conformance requirement"); - const auto protos = genericSig->getRequiredProtocols(selfGenericParam); - require(std::find(protos.begin(), protos.end(), protocol) != protos.end(), - "requirement Self parameter must conform to called protocol"); + require(selfRequirement && + selfRequirement->getKind() == RequirementKind::Conformance, + "first non-same-typerequirement should be conformance requirement"); + const auto protos = genericSig->getRequiredProtocols(selfGenericParam); + require(std::find(protos.begin(), protos.end(), protocol) != protos.end(), + "requirement Self parameter must conform to called protocol"); + } auto lookupType = AMI->getLookupType(); if (getLocalArchetypeOf(lookupType) || lookupType->hasDynamicSelfType()) { diff --git a/lib/SILOptimizer/Mandatory/DiagnoseInfiniteRecursion.cpp b/lib/SILOptimizer/Mandatory/DiagnoseInfiniteRecursion.cpp index d35aeb2ed4a0f..f822213f66164 100644 --- a/lib/SILOptimizer/Mandatory/DiagnoseInfiniteRecursion.cpp +++ b/lib/SILOptimizer/Mandatory/DiagnoseInfiniteRecursion.cpp @@ -122,8 +122,7 @@ static bool isRecursiveCall(FullApplySite applySite) { } if (auto *WMI = dyn_cast(callee)) { - auto funcAndTable = parentFunc->getModule().lookUpFunctionInWitnessTable( - WMI->getConformance(), WMI->getMember(), SILModule::LinkingMode::LinkNormal); + auto funcAndTable = lookUpFunctionInWitnessTable(WMI, SILModule::LinkingMode::LinkNormal); return funcAndTable.first == parentFunc; } return false; diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp index 5acc3107985d9..778aff211aac2 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp @@ -1134,8 +1134,6 @@ SILInstruction *SILCombiner::createApplyWithConcreteType( FullApplySite Apply, const llvm::SmallDenseMap &COAIs, SILBuilderContext &BuilderCtx) { - // Ensure that the callee is polymorphic. - assert(Apply.getOrigCalleeType()->isPolymorphic()); // Create the new set of arguments to apply including their substitutions. SubstitutionMap NewCallSubs = Apply.getSubstitutionMap(); diff --git a/lib/SILOptimizer/Utils/ConstExpr.cpp b/lib/SILOptimizer/Utils/ConstExpr.cpp index 31e3d5f81fd35..e7c4e96f1ed2f 100644 --- a/lib/SILOptimizer/Utils/ConstExpr.cpp +++ b/lib/SILOptimizer/Utils/ConstExpr.cpp @@ -433,7 +433,7 @@ SymbolicValue ConstExprFunctionState::computeConstantValue(SILValue value) { UnknownReason::UnknownWitnessMethodConformance); auto &module = wmi->getModule(); SILFunction *fn = - module.lookUpFunctionInWitnessTable(conf, wmi->getMember(), + module.lookUpFunctionInWitnessTable(conf, wmi->getMember(), wmi->isSpecialized(), SILModule::LinkingMode::LinkAll).first; // If we were able to resolve it, then we can proceed. if (fn) diff --git a/lib/SILOptimizer/Utils/Devirtualize.cpp b/lib/SILOptimizer/Utils/Devirtualize.cpp index d0975437877b0..4e0f206c420e7 100644 --- a/lib/SILOptimizer/Utils/Devirtualize.cpp +++ b/lib/SILOptimizer/Utils/Devirtualize.cpp @@ -1239,8 +1239,7 @@ static bool canDevirtualizeWitnessMethod(ApplySite applySite, bool isMandatory) } } - std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable( - wmi->getConformance(), wmi->getMember(), SILModule::LinkingMode::LinkAll); + std::tie(f, wt) = lookUpFunctionInWitnessTable(wmi, SILModule::LinkingMode::LinkAll); if (!f) return false; @@ -1351,8 +1350,7 @@ swift::tryDevirtualizeWitnessMethod(SILPassManager *pm, ApplySite applySite, auto *wmi = cast(applySite.getCallee()); - std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable( - wmi->getConformance(), wmi->getMember(), SILModule::LinkingMode::LinkAll); + std::tie(f, wt) = lookUpFunctionInWitnessTable(wmi, SILModule::LinkingMode::LinkAll); return devirtualizeWitnessMethod(pm, applySite, f, wmi->getConformance(), ore); } From db7068e1e0fee0ef139604f7f5ab8051891839c3 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 17:08:00 +0200 Subject: [PATCH 09/13] IRGen: support associated-type and associated-conformance witness table entries Associated-type entries are simply skipped. Associated-conformance entries point to the specialized conformance witness tables. --- include/swift/AST/IRGenOptions.h | 2 + lib/IRGen/GenProto.cpp | 63 ++++++++++++++++++++++++++++---- lib/IRGen/GenProto.h | 4 ++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/include/swift/AST/IRGenOptions.h b/include/swift/AST/IRGenOptions.h index 813bae2befc0c..53db6c8e98b93 100644 --- a/include/swift/AST/IRGenOptions.h +++ b/include/swift/AST/IRGenOptions.h @@ -120,6 +120,8 @@ struct PointerAuthOptions : clang::PointerAuthOptions { /// Swift protocol witness table associated conformance witness table /// access functions. + /// In Embedded Swift used for associated conformance witness table + /// pointers. PointerAuthSchema ProtocolAssociatedTypeWitnessTableAccessFunctions; /// Swift class v-table functions. diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index 05a8537f5669e..a1f85af84f67f 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -946,6 +946,9 @@ namespace { } void addAssociatedType(AssociatedType requirement) { + // In Embedded Swift witness tables don't have associated-types entries. + if (requirement.getAssociation()->getASTContext().LangOpts.hasFeature(Feature::Embedded)) + return; Entries.push_back(WitnessTableEntry::forAssociatedType(requirement)); } @@ -1671,6 +1674,10 @@ class AccessorConformanceInfo : public ConformanceInfo { auto &entry = SILEntries.front(); SILEntries = SILEntries.slice(1); + // In Embedded Swift witness tables don't have associated-types entries. + if (IGM.Context.LangOpts.hasFeature(Feature::Embedded)) + return; + #ifndef NDEBUG assert(entry.getKind() == SILWitnessTable::AssociatedType && "sil witness table does not match protocol"); @@ -1735,6 +1742,16 @@ class AccessorConformanceInfo : public ConformanceInfo { "offset doesn't match ProtocolInfo layout"); #endif + if (IGM.Context.LangOpts.hasFeature(Feature::Embedded)) { + // In Embedded Swift associated-conformance entries simply point to the witness table + // of the associated conformance. + llvm::Constant *witnessEntry = IGM.getAddrOfWitnessTable(associatedConformance.getConcrete()); + auto &schema = IGM.getOptions().PointerAuth + .ProtocolAssociatedTypeWitnessTableAccessFunctions; + Table.addSignedPointer(witnessEntry, schema, requirement); + return; + } + llvm::Constant *witnessEntry = getAssociatedConformanceWitness(requirement, associate, associatedConformance); @@ -2670,11 +2687,11 @@ void IRGenModule::emitSILWitnessTable(SILWitnessTable *wt) { // Produce the initializer value. auto initializer = wtableContents.finishAndCreateFuture(); + auto *normalConf = dyn_cast(conf); global = cast( (isDependent && conf->getDeclContext()->isGenericContext() && - !useRelativeProtocolWitnessTable) - ? getAddrOfWitnessTablePattern(cast(conf), - initializer) + !useRelativeProtocolWitnessTable && normalConf) + ? getAddrOfWitnessTablePattern(normalConf, initializer) : getAddrOfWitnessTable(conf, initializer)); // Eelative protocol witness tables are always constant. They don't cache // results in the table. @@ -3321,6 +3338,18 @@ MetadataResponse MetadataPath::followComponent(IRGenFunction &IGF, if (!source) return MetadataResponse(); + AssociatedConformance associatedConformanceRef(sourceProtocol, + association, + associatedRequirement); + + if (IGF.IGM.Context.LangOpts.hasFeature(Feature::Embedded)) { + // In Embedded Swift associated-conformance entries simply point to the witness table + // of the associated conformance. + llvm::Value *sourceWTable = source.getMetadata(); + llvm::Value *associatedWTable = emitAssociatedConformanceValue(IGF, sourceWTable, associatedConformanceRef); + return MetadataResponse::forComplete(associatedWTable); + } + auto *sourceMetadata = IGF.emitAbstractTypeMetadataRef(sourceType); @@ -3394,10 +3423,7 @@ MetadataResponse MetadataPath::followComponent(IRGenFunction &IGF, auto sourceWTable = source.getMetadata(); - AssociatedConformance associatedConformanceRef(sourceProtocol, - association, - associatedRequirement); - auto associatedWTable = + auto associatedWTable = emitAssociatedTypeWitnessTableRef(IGF, sourceMetadata, sourceWTable, associatedConformanceRef, associatedMetadata); @@ -4329,6 +4355,29 @@ FunctionPointer irgen::emitWitnessMethodValue(IRGenFunction &IGF, signature); } +llvm::Value *irgen::emitAssociatedConformanceValue(IRGenFunction &IGF, + llvm::Value *wtable, + const AssociatedConformance &conf) { + auto proto = conf.getSourceProtocol(); + assert(!IGF.IGM.isResilient(proto, ResilienceExpansion::Maximal)); + + // Find the witness we're interested in. + auto &fnProtoInfo = IGF.IGM.getProtocolInfo(proto, ProtocolInfoKind::Full); + auto index = fnProtoInfo.getAssociatedConformanceIndex(conf); + assert(!IGF.IGM.IRGen.Opts.UseRelativeProtocolWitnessTables); + + wtable = IGF.optionallyLoadFromConditionalProtocolWitnessTable(wtable); + auto slot = + slotForLoadOfOpaqueWitness(IGF, wtable, index.forProtocolWitnessTable(), + false/*isRelativeTable*/); + llvm::Value *confPointer = IGF.emitInvariantLoad(slot); + if (auto &schema = IGF.getOptions().PointerAuth.ProtocolAssociatedTypeWitnessTableAccessFunctions) { + auto authInfo = PointerAuthInfo::emit(IGF, schema, slot.getAddress(), conf); + confPointer = emitPointerAuthAuth(IGF, confPointer, authInfo); + } + return confPointer; +} + FunctionPointer irgen::emitWitnessMethodValue( IRGenFunction &IGF, CanType baseTy, llvm::Value **baseMetadataCache, SILDeclRef member, ProtocolConformanceRef conformance) { diff --git a/lib/IRGen/GenProto.h b/lib/IRGen/GenProto.h index 51b8a73dd7380..8904550f52e98 100644 --- a/lib/IRGen/GenProto.h +++ b/lib/IRGen/GenProto.h @@ -73,6 +73,10 @@ namespace irgen { SILDeclRef member, ProtocolConformanceRef conformance); + llvm::Value *emitAssociatedConformanceValue(IRGenFunction &IGF, + llvm::Value *wtable, + const AssociatedConformance &conf); + /// Compute the index into a witness table for a resilient protocol given /// a reference to a descriptor of one of the requirements in that witness /// table. From 2a6dc9660c34cf284f11dd7ce6247b2171674708 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 17:27:44 +0200 Subject: [PATCH 10/13] MandatoryPerformanceOptimizations: specialize witness_method instructions In Embedded Swift, witness method lookup is done from specialized witness tables. For this to work, the type of witness_method must be specialized as well. Otherwise the method call would be done with wrong parameter conventions (indirect instead of direct). --- .../MandatoryPerformanceOptimizations.swift | 4 ++ .../Optimizer/PassManager/Context.swift | 9 ++++ .../swift/SILOptimizer/OptimizerBridging.h | 1 + .../swift/SILOptimizer/Utils/InstOptUtils.h | 1 + lib/SILOptimizer/PassManager/PassManager.cpp | 4 ++ lib/SILOptimizer/Utils/Generics.cpp | 54 ++++++++++++++++++- 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift index 062a3f997fc22..4a7ba15650748 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift @@ -116,6 +116,10 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ modu if context.options.enableEmbeddedSwift { _ = context.specializeClassMethodInst(classMethod) } + case let witnessMethod as WitnessMethodInst: + if context.options.enableEmbeddedSwift { + _ = context.specializeWitnessMethodInst(witnessMethod) + } case let initExRef as InitExistentialRefInst: if context.options.enableEmbeddedSwift { diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index b2c031c5f6c86..6fcac80f5002e 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -357,6 +357,15 @@ struct FunctionPassContext : MutatingContext { return false } + func specializeWitnessMethodInst(_ wm: WitnessMethodInst) -> Bool { + if _bridged.specializeWitnessMethodInst(wm.bridged) { + notifyInstructionsChanged() + notifyCallsChanged() + return true + } + return false + } + func specializeApplies(in function: Function, isMandatory: Bool) -> Bool { if _bridged.specializeAppliesInFunction(function.bridged, isMandatory) { notifyInstructionsChanged() diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index b3278fb769db6..0d0644e627cde 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -256,6 +256,7 @@ struct BridgedPassContext { BridgedSubstitutionMap substitutions) const; void deserializeAllCallees(BridgedFunction function, bool deserializeAll) const; bool specializeClassMethodInst(BridgedInstruction cm) const; + bool specializeWitnessMethodInst(BridgedInstruction wm) const; bool specializeAppliesInFunction(BridgedFunction function, bool isMandatory) const; BridgedOwnedString mangleOutlinedVariable(BridgedFunction function) const; BridgedOwnedString mangleAsyncRemoved(BridgedFunction function) const; diff --git a/include/swift/SILOptimizer/Utils/InstOptUtils.h b/include/swift/SILOptimizer/Utils/InstOptUtils.h index dd8adda01264c..95731d4862c69 100644 --- a/include/swift/SILOptimizer/Utils/InstOptUtils.h +++ b/include/swift/SILOptimizer/Utils/InstOptUtils.h @@ -607,6 +607,7 @@ bool optimizeMemoryAccesses(SILFunction *fn); bool eliminateDeadAllocations(SILFunction *fn, DominanceInfo *domInfo); bool specializeClassMethodInst(ClassMethodInst *cm); +bool specializeWitnessMethodInst(WitnessMethodInst *wm); bool specializeAppliesInFunction(SILFunction &F, SILTransform *transform, diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index 021a09dfb2b79..e1f430dda7569 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -1771,6 +1771,10 @@ bool BridgedPassContext::specializeClassMethodInst(BridgedInstruction cm) const return ::specializeClassMethodInst(cm.getAs()); } +bool BridgedPassContext::specializeWitnessMethodInst(BridgedInstruction wm) const { + return ::specializeWitnessMethodInst(wm.getAs()); +} + bool BridgedPassContext::specializeAppliesInFunction(BridgedFunction function, bool isMandatory) const { return ::specializeAppliesInFunction(*function.getFunction(), invocation->getTransform(), isMandatory); } diff --git a/lib/SILOptimizer/Utils/Generics.cpp b/lib/SILOptimizer/Utils/Generics.cpp index 2b0666d5bb8bf..7c469a3ba139b 100644 --- a/lib/SILOptimizer/Utils/Generics.cpp +++ b/lib/SILOptimizer/Utils/Generics.cpp @@ -725,8 +725,6 @@ void ReabstractionInfo::createSubstitutedAndSpecializedTypes() { SubstitutedType = createSubstitutedType(Callee, CallerInterfaceSubs, HasUnboundGenericParams); } - assert(!SubstitutedType->hasArchetype() && - "Substituted function type should not contain archetypes"); // Check which parameters and results can be converted from // indirect to direct ones. @@ -2363,6 +2361,58 @@ bool swift::specializeClassMethodInst(ClassMethodInst *cm) { return true; } +bool swift::specializeWitnessMethodInst(WitnessMethodInst *wm) { + SILFunction *f = wm->getFunction(); + SILModule &m = f->getModule(); + + CanType astType = wm->getLookupType(); + if (!isa(astType)) + return false; + + if (wm->isSpecialized()) + return false; + + // This should not happen. Just to be on the safe side. + if (wm->use_empty()) + return false; + + Operand *firstUse = *wm->use_begin(); + ApplySite AI = ApplySite::isa(firstUse->getUser()); + assert(AI && AI.getCalleeOperand() == firstUse && "wrong use of witness_method instruction"); + + SubstitutionMap subs = AI.getSubstitutionMap(); + + SILType funcTy = wm->getType(); + SILType substitutedType = + funcTy.substGenericArgs(m, subs, TypeExpansionContext::minimal()); + + ReabstractionInfo reInfo(substitutedType.getAs(), wm->getMember(), m); + reInfo.createSubstitutedAndSpecializedTypes(); + CanSILFunctionType finalFuncTy = reInfo.getSpecializedType(); + SILType finalSILTy = SILType::getPrimitiveObjectType(finalFuncTy); + + SILBuilder builder(wm); + auto *newWM = builder.createWitnessMethod(wm->getLoc(), wm->getLookupType(), + wm->getConformance(), wm->getMember(), finalSILTy); + + while (!wm->use_empty()) { + Operand *use = *wm->use_begin(); + SILInstruction *user = use->getUser(); + ApplySite AI = ApplySite::isa(user); + if (AI && AI.getCalleeOperand() == use) { + replaceWithSpecializedCallee(AI, newWM, reInfo); + AI.getInstruction()->eraseFromParent(); + continue; + } + llvm::errs() << "unsupported use of witness method " + << newWM->getMember().getDecl()->getName() << " in function " + << newWM->getFunction()->getName() << '\n'; + llvm::report_fatal_error("unsupported witness method"); + } + + return true; +} + /// Create a new apply based on an old one, but with a different /// function being applied. ApplySite From ac44508a60a6fcf788cd07afbb97be2ce87c5514 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 17:28:57 +0200 Subject: [PATCH 11/13] MandatoryPerformanceOptimizations: handle all kind of witness-table entries when specializing witness-tables Support associated-type and associated-conformance entries. This enable existentials with associated types. --- .../Utilities/GenericSpecialization.swift | 13 +++- test/embedded/existential-class-bound1.swift | 60 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift index 2281508c00557..b730a18ea4f22 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/GenericSpecialization.swift @@ -89,6 +89,8 @@ func specializeWitnessTable(forConformance conformance: Conformance, let newEntries = witnessTable.entries.map { origEntry in switch origEntry { + case .invalid: + return WitnessTable.Entry.invalid case .method(let requirement, let witness): guard let origMethod = witness else { return origEntry @@ -108,9 +110,14 @@ func specializeWitnessTable(forConformance conformance: Conformance, substitutions: conformance.specializedSubstitutions) specializeWitnessTable(forConformance: baseConf, errorLocation: errorLocation, context, notifyNewWitnessTable) return .baseProtocol(requirement: requirement, witness: baseConf) - default: - // TODO: handle other witness table entry kinds - fatalError("unsupported witness table etnry") + case .associatedType(let requirement, let witness): + let substType = witness.subst(with: conformance.specializedSubstitutions) + return .associatedType(requirement: requirement, witness: substType) + case .associatedConformance(let requirement, let proto, let witness): + if witness.isSpecialized { + specializeWitnessTable(forConformance: witness, errorLocation: errorLocation, context, notifyNewWitnessTable) + } + return .associatedConformance(requirement: requirement, protocol: proto, witness: witness) } } let newWT = context.createWitnessTable(entries: newEntries,conformance: conformance, diff --git a/test/embedded/existential-class-bound1.swift b/test/embedded/existential-class-bound1.swift index bba4ef51d1dcc..76a877588e594 100644 --- a/test/embedded/existential-class-bound1.swift +++ b/test/embedded/existential-class-bound1.swift @@ -29,6 +29,61 @@ func test(existential: any ClassBound) { existential.bar() } +public protocol ProtoWithAssocType: AnyObject { + associatedtype T + func foo(t: T) +} + +final public class GenClass: ProtoWithAssocType { + public func foo(t: T) { + print(t) + } +} + +public func createExWithAssocType() -> any ProtoWithAssocType { + return GenClass() +} + +public func callExWithAssocType(_ p: any ProtoWithAssocType) { + p.foo(t: 27) +} + +public protocol Q: AnyObject { + func bar() +} + +public protocol ProtoWithAssocConf: AnyObject { + associatedtype A: Q + func foo() -> A +} + +public class GenClass2: Q { + var t: T + + init(t : T) { self.t = t } + + public func bar() { + print("bar") + } +} + +final public class GenClass3: ProtoWithAssocConf { + public func foo() -> GenClass2 { + print("foo") + return GenClass2(t: 27) + } +} + + +public func createExWithAssocConf() -> any ProtoWithAssocConf { + return GenClass3() +} + +public func callExWithAssocConf(_ p: any ProtoWithAssocConf) { + let x = p.foo() + x.bar() +} + @main struct Main { static func main() { @@ -38,6 +93,11 @@ struct Main { test(existential: MyOtherClass()) // CHECK: MyOtherClass.foo() // CHECK: MyOtherClass.bar() + callExWithAssocType(createExWithAssocType()) + // CHECK: 27 + callExWithAssocConf(createExWithAssocConf()) + // CHECK: foo + // CHECK: bar } } From 2966d3e32dfc20164008de70884a9307f40bd902 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 17:45:44 +0200 Subject: [PATCH 12/13] docs: document the witness table ABI for Embedded Swift --- docs/EmbeddedSwift/ABI.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/EmbeddedSwift/ABI.md b/docs/EmbeddedSwift/ABI.md index a2e8c701ae2b0..d868f2c2fddb3 100644 --- a/docs/EmbeddedSwift/ABI.md +++ b/docs/EmbeddedSwift/ABI.md @@ -20,7 +20,11 @@ The compiler respects the ABIs and calling conventions of C and C++ when interop ## Metadata ABI of Embedded Swift -Embedded Swift eliminates almost all metadata compared to full Swift. However, class metadata is still used, because those serve as vtables for dynamic dispatch of methods to implement runtime polymorphism. The layout of Embedded Swift's class metadata is *different* from full Swift: +Embedded Swift eliminates almost all metadata compared to full Swift. However, class and existential metadata are still used, because those serve as vtables and witness tables for dynamic dispatch of methods to implement runtime polymorphism with classes and existentials. + +### Class Metadata ABI + +The layout of Embedded Swift's class metadata is *different* from full Swift: - The **super pointer** pointing to the class metadata record for the superclass is stored at **offset 0**. If the class is a root class, it is null. - The **destructor pointer** is stored at **offset 1**. This function is invoked by Swift's deallocator when the class instance is destroyed. @@ -28,6 +32,19 @@ Embedded Swift eliminates almost all metadata compared to full Swift. However, c - Lastly, the **vtable** is stored at **offset 3**: For each Swift class in the class's inheritance hierarchy, in order starting from the root class and working down to the most derived class, the function pointers to the implementation of every method of the class in declaration order in stored. +### Witness Tables ABI + +The layout of Embedded Swift's witness tables is *different* from full Swift: + +- The first word is always a null pointer (TODO: it can be eliminated) +- The following words are witness table entries which can be one of the following: + - A method witness: a pointer to the witness function. + - An associated conformance witness: a pointer to the witness table of the associated conformance + +Note that witness tables in Embedded Swift do not contain associated type entries. + +Witness functions are always specialized for concrete types. This also means that parameters and return values are passed directly (if possible). + ## Heap object layout in Embedded Swift Heap objects have the following layout in Embedded Swift: From 7beae5a99fdf393c6ec82e59c9ff03dba158d764 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 4 Oct 2024 20:55:23 +0200 Subject: [PATCH 13/13] Embedded: support existentials with inherited conformances That means: derived classes where the base conforms to a protocol --- .../Sources/AST/Conformance.swift | 10 +++++ .../MandatoryPerformanceOptimizations.swift | 3 +- include/swift/AST/ASTBridging.h | 2 + include/swift/AST/ASTBridgingImpl.h | 9 ++++ lib/IRGen/GenProto.cpp | 3 ++ test/embedded/existential-class-bound1.swift | 41 ++++++++++++++++++- 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/SwiftCompilerSources/Sources/AST/Conformance.swift b/SwiftCompilerSources/Sources/AST/Conformance.swift index bbef4d7324a4d..7d10ef6d5b2ed 100644 --- a/SwiftCompilerSources/Sources/AST/Conformance.swift +++ b/SwiftCompilerSources/Sources/AST/Conformance.swift @@ -47,6 +47,16 @@ public struct Conformance: CustomStringConvertible, NoReflectionChildren { return bridged.getGenericConformance().conformance } + public var isInherited: Bool { + assert(isConcrete) + return bridged.isInheritedConformance() + } + + public var inheritedConformance: Conformance { + assert(isInherited) + return bridged.getInheritedConformance().conformance + } + public var specializedSubstitutions: SubstitutionMap { assert(isSpecialized) return SubstitutionMap(bridged: bridged.getSpecializedSubstitutions()) diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift index 4a7ba15650748..70b5a5905650f 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift @@ -271,7 +271,8 @@ private func shouldInline(apply: FullApplySite, callee: Function, alreadyInlined private func specializeWitnessTables(for initExRef: InitExistentialRefInst, _ context: ModulePassContext, _ worklist: inout FunctionWorklist) { - for conformance in initExRef.conformances where conformance.isConcrete { + for c in initExRef.conformances where c.isConcrete { + let conformance = c.isInherited ? c.inheritedConformance : c let origWitnessTable = context.lookupWitnessTable(for: conformance) if conformance.isSpecialized { if origWitnessTable == nil { diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 948c15d9e6cad..c60f09dffd7d8 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -2032,8 +2032,10 @@ struct BridgedConformance { BRIDGED_INLINE bool isConcrete() const; BRIDGED_INLINE bool isValid() const; BRIDGED_INLINE bool isSpecializedConformance() const; + BRIDGED_INLINE bool isInheritedConformance() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedASTType getType() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedConformance getGenericConformance() const; + SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedConformance getInheritedConformance() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedSubstitutionMap getSpecializedSubstitutions() const; }; diff --git a/include/swift/AST/ASTBridgingImpl.h b/include/swift/AST/ASTBridgingImpl.h index 0dbb9f10094ca..e45d453715d3e 100644 --- a/include/swift/AST/ASTBridgingImpl.h +++ b/include/swift/AST/ASTBridgingImpl.h @@ -163,6 +163,10 @@ bool BridgedConformance::isSpecializedConformance() const { return swift::isa(unbridged().getConcrete()); } +bool BridgedConformance::isInheritedConformance() const { + return swift::isa(unbridged().getConcrete()); +} + BridgedASTType BridgedConformance::getType() const { return {unbridged().getConcrete()->getType().getPointer()}; } @@ -172,6 +176,11 @@ BridgedConformance BridgedConformance::getGenericConformance() const { return {swift::ProtocolConformanceRef(specPC->getGenericConformance())}; } +BridgedConformance BridgedConformance::getInheritedConformance() const { + auto *inheritedConf = swift::cast(unbridged().getConcrete()); + return {swift::ProtocolConformanceRef(inheritedConf->getInheritedConformance())}; +} + BridgedSubstitutionMap BridgedConformance::getSpecializedSubstitutions() const { auto *specPC = swift::cast(unbridged().getConcrete()); return {specPC->getSubstitutionMap()}; diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index a1f85af84f67f..bb08f209fbe9d 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -2504,6 +2504,9 @@ IRGenModule::getConformanceInfo(const ProtocolDecl *protocol, const ConformanceInfo *info; + if (auto *inheritedC = dyn_cast(conformance)) + conformance = inheritedC->getInheritedConformance(); + // If there is a specialized SILWitnessTable for the specialized conformance, // directly use it. if (auto *sc = dyn_cast(conformance)) { diff --git a/test/embedded/existential-class-bound1.swift b/test/embedded/existential-class-bound1.swift index 76a877588e594..d530e783d86b1 100644 --- a/test/embedded/existential-class-bound1.swift +++ b/test/embedded/existential-class-bound1.swift @@ -58,7 +58,7 @@ public protocol ProtoWithAssocConf: AnyObject { } public class GenClass2: Q { - var t: T + final var t: T init(t : T) { self.t = t } @@ -67,6 +67,14 @@ public class GenClass2: Q { } } +public class DerivedFromGenClass2: GenClass2 { + init() { super.init(t: 42) } + + public override func bar() { + print("derived-bar") + } +} + final public class GenClass3: ProtoWithAssocConf { public func foo() -> GenClass2 { print("foo") @@ -74,6 +82,13 @@ final public class GenClass3: ProtoWithAssocConf { } } +final public class OtherClass: ProtoWithAssocConf { + public func foo() -> GenClass2 { + print("other-foo") + return DerivedFromGenClass2() + } +} + public func createExWithAssocConf() -> any ProtoWithAssocConf { return GenClass3() @@ -84,6 +99,21 @@ public func callExWithAssocConf(_ p: any ProtoWithAssocConf) { x.bar() } +public class Base: ClassBound { + public func foo() { print("Base.foo()") } + public func bar() { print("Base.bar()") } +} + +public class Derived1: Base { + public override func foo() { print("Derived1.foo()") } + public override func bar() { print("Derived1.bar()") } +} + +public class Derived2: Base { + public override func foo() { print("Derived2.foo()") } + public override func bar() { print("Derived2.bar()") } +} + @main struct Main { static func main() { @@ -98,6 +128,15 @@ struct Main { callExWithAssocConf(createExWithAssocConf()) // CHECK: foo // CHECK: bar + callExWithAssocConf(OtherClass()) + // CHECK: other-foo + // CHECK: derived-bar + test(existential: Derived1()) + // CHECK: Derived1.foo() + // CHECK: Derived1.bar() + test(existential: Derived2()) + // CHECK: Derived2.foo() + // CHECK: Derived2.bar() } }