From 0877b04efe38c656cfd0577ee0162164f88a2a3e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 12 May 2025 13:21:07 -0700 Subject: [PATCH] Merge pull request #81365 from Azoy/my-existentials [AST & Runtime] Correctly mangle extended existentials with inverse requirements --- include/swift/ABI/Metadata.h | 7 +- include/swift/AST/ExistentialLayout.h | 15 +++++ lib/AST/ASTMangler.cpp | 11 ++-- lib/AST/Type.cpp | 14 +++- lib/IRGen/GenMeta.cpp | 4 +- lib/IRGen/MetadataRequest.cpp | 41 +----------- stdlib/public/runtime/Demangle.cpp | 71 +++++++++++++++++++-- test/Interpreter/extended_existential.swift | 49 ++++++++++++++ 8 files changed, 154 insertions(+), 58 deletions(-) create mode 100644 test/Interpreter/extended_existential.swift diff --git a/include/swift/ABI/Metadata.h b/include/swift/ABI/Metadata.h index d2a14e21ca3bf..bede849059198 100644 --- a/include/swift/ABI/Metadata.h +++ b/include/swift/ABI/Metadata.h @@ -2352,12 +2352,7 @@ struct TargetExtendedExistentialTypeShape } bool isCopyable() const { - if (!hasGeneralizationSignature()) { - return true; - } - auto *reqts = getGenSigRequirements(); - for (unsigned i = 0, e = getNumGenSigRequirements(); i < e; ++i) { - auto &reqt = reqts[i]; + for (auto &reqt : getRequirementSignature().getRequirements()) { if (reqt.getKind() != GenericRequirementKind::InvertedProtocols) { continue; } diff --git a/include/swift/AST/ExistentialLayout.h b/include/swift/AST/ExistentialLayout.h index 7e4fa5e75189c..ad45c33a5e379 100644 --- a/include/swift/AST/ExistentialLayout.h +++ b/include/swift/AST/ExistentialLayout.h @@ -117,9 +117,24 @@ struct ExistentialLayout { LayoutConstraint getLayoutConstraint() const; + /// Whether this layout has any inverses within its signature. + bool hasInverses() const { + return !inverses.empty(); + } + + /// Whether this existential needs to have an extended existential shape. This + /// is relevant for the mangler to mangle as a symbolic link where possible + /// and for IRGen directly emitting some existentials. + /// + /// If 'allowInverses' is false, then regardless of if this existential layout + /// has inverse requirements those will not influence the need for having a + /// shape. + bool needsExtendedShape(bool allowInverses = true) const; + private: SmallVector protocols; SmallVector parameterized; + InvertibleProtocolSet inverses; }; } diff --git a/lib/AST/ASTMangler.cpp b/lib/AST/ASTMangler.cpp index 4284db42aaaaf..60361d73f9ccd 100644 --- a/lib/AST/ASTMangler.cpp +++ b/lib/AST/ASTMangler.cpp @@ -1620,7 +1620,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, // ExtendedExistentialTypeShapes consider existential metatypes to // be part of the existential, so if we're symbolically referencing // shapes, we need to handle that at this level. - if (EMT->hasParameterizedExistential()) { + if (EMT->getExistentialLayout().needsExtendedShape(AllowInverses)) { auto referent = SymbolicReferent::forExtendedExistentialTypeShape(EMT); if (canSymbolicReference(referent)) { appendSymbolicExtendedExistentialType(referent, EMT, sig, forDecl); @@ -1629,7 +1629,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, } if (EMT->getInstanceType()->isExistentialType() && - EMT->hasParameterizedExistential()) + EMT->getExistentialLayout().needsExtendedShape(AllowInverses)) appendConstrainedExistential(EMT->getInstanceType(), sig, forDecl); else appendType(EMT->getInstanceType(), sig, forDecl); @@ -1685,8 +1685,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, return appendType(strippedTy, sig, forDecl); } - if (PCT->hasParameterizedExistential() - || (PCT->hasInverse() && AllowInverses)) + if (PCT->getExistentialLayout().needsExtendedShape(AllowInverses)) return appendConstrainedExistential(PCT, sig, forDecl); // We mangle ProtocolType and ProtocolCompositionType using the @@ -1700,7 +1699,8 @@ void ASTMangler::appendType(Type type, GenericSignature sig, case TypeKind::Existential: { auto *ET = cast(tybase); - if (ET->hasParameterizedExistential()) { + + if (ET->getExistentialLayout().needsExtendedShape(AllowInverses)) { auto referent = SymbolicReferent::forExtendedExistentialTypeShape(ET); if (canSymbolicReference(referent)) { appendSymbolicExtendedExistentialType(referent, ET, sig, forDecl); @@ -1710,6 +1710,7 @@ void ASTMangler::appendType(Type type, GenericSignature sig, return appendConstrainedExistential(ET->getConstraintType(), sig, forDecl); } + return appendType(ET->getConstraintType(), sig, forDecl); } diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index c76a5ed6965fa..01782ca1be589 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -320,6 +320,8 @@ ExistentialLayout::ExistentialLayout(CanProtocolType type) { !protoDecl->isMarkerProtocol()); representsAnyObject = false; + inverses = InvertibleProtocolSet(); + protocols.push_back(protoDecl); expandDefaults(protocols, InvertibleProtocolSet(), type->getASTContext()); } @@ -353,7 +355,7 @@ ExistentialLayout::ExistentialLayout(CanProtocolCompositionType type) { protocols.push_back(protoDecl); } - auto inverses = type->getInverses(); + inverses = type->getInverses(); expandDefaults(protocols, inverses, type->getASTContext()); representsAnyObject = [&]() { @@ -433,6 +435,16 @@ Type ExistentialLayout::getSuperclass() const { return Type(); } +bool ExistentialLayout::needsExtendedShape(bool allowInverses) const { + if (!getParameterizedProtocols().empty()) + return true; + + if (allowInverses && hasInverses()) + return true; + + return false; +} + bool TypeBase::isObjCExistentialType() { return getCanonicalType().isObjCExistentialType(); } diff --git a/lib/IRGen/GenMeta.cpp b/lib/IRGen/GenMeta.cpp index 47bd2db5509d0..f52b85106b27a 100644 --- a/lib/IRGen/GenMeta.cpp +++ b/lib/IRGen/GenMeta.cpp @@ -7857,8 +7857,10 @@ irgen::emitExtendedExistentialTypeShape(IRGenModule &IGM, // You can have a superclass with a generic parameter pack in a composition, // like `C & P` - assert(genHeader->GenericPackArguments.empty() && + if (genSig) { + assert(genHeader->GenericPackArguments.empty() && "Generic parameter packs not supported here yet"); + } return b.finishAndCreateFuture(); }, [&](llvm::GlobalVariable *var) { diff --git a/lib/IRGen/MetadataRequest.cpp b/lib/IRGen/MetadataRequest.cpp index e4a94f15c9d9a..945996b7e00f6 100644 --- a/lib/IRGen/MetadataRequest.cpp +++ b/lib/IRGen/MetadataRequest.cpp @@ -264,41 +264,6 @@ MetadataDependency MetadataDependencyCollector::finish(IRGenFunction &IGF) { return result; } -static bool usesExtendedExistentialMetadata(CanType type) { - auto layout = type.getExistentialLayout(); - // If there are parameterized protocol types that we want to - // treat as equal to unparameterized protocol types (maybe - // something like `P`?), then AST type canonicalization - // should turn them into unparameterized protocol types. If the - // structure makes it to IRGen, we have to honor that decision that - // they represent different types. - return !layout.getParameterizedProtocols().empty(); -} - -static std::optional> -usesExtendedExistentialMetadata(CanExistentialMetatypeType type) { - unsigned depth = 1; - auto cur = type.getInstanceType(); - while (auto metatype = dyn_cast(cur)) { - cur = metatype.getInstanceType(); - depth++; - } - - // The only existential types that don't currently use ExistentialType - // are Any and AnyObject, which don't use extended metadata. - if (usesExtendedExistentialMetadata(cur)) { - // HACK: The AST for an existential metatype of a (parameterized) protocol - // still directly wraps the existential type as its instance, which means - // we need to reconstitute the enclosing ExistentialType. - assert(cur->isExistentialType()); - if (!cur->is()) { - cur = ExistentialType::get(cur)->getCanonicalType(); - } - return std::make_pair(cast(cur), depth); - } - return std::nullopt; -} - llvm::Constant *IRGenModule::getAddrOfStringForMetadataRef( StringRef symbolName, unsigned alignment, @@ -1998,7 +1963,7 @@ namespace { // Existential metatypes for extended existentials don't use // ExistentialMetatypeMetadata. - if (usesExtendedExistentialMetadata(type)) { + if (type->getExistentialLayout().needsExtendedShape()) { auto metadata = emitExtendedExistentialTypeMetadata(type); return setLocal(type, MetadataResponse::forComplete(metadata)); } @@ -3110,8 +3075,8 @@ static bool shouldAccessByMangledName(IRGenModule &IGM, CanType type) { void visitExistentialMetatypeType(CanExistentialMetatypeType meta) { // Extended existential metatypes just emit a different shape // and don't do any wrapping. - if (auto typeAndDepth = usesExtendedExistentialMetadata(meta)) { - return visit(typeAndDepth.first); + if (meta->getExistentialLayout().needsExtendedShape()) { + // return visit(unwrapExistentialMetatype(meta)); } // The number of accesses turns out the same as the instance type, diff --git a/stdlib/public/runtime/Demangle.cpp b/stdlib/public/runtime/Demangle.cpp index 0befba2969756..9fd0edd57977b 100644 --- a/stdlib/public/runtime/Demangle.cpp +++ b/stdlib/public/runtime/Demangle.cpp @@ -465,6 +465,69 @@ _buildDemanglingForNominalType(const Metadata *type, Demangle::Demangler &Dem) { return _buildDemanglingForContext(description, demangledGenerics, Dem); } +static Demangle::NodePointer +_replaceGeneralizationArg(Demangle::NodePointer type, + SubstGenericParametersFromMetadata substitutions, + Demangle::Demangler &Dem) { + assert(type->getKind() == Node::Kind::Type); + auto genericParam = type->getChild(0); + + if (genericParam->getKind() != Node::Kind::DependentGenericParamType) + return type; + + auto depth = genericParam->getChild(0)->getIndex(); + auto index = genericParam->getChild(1)->getIndex(); + + auto arg = substitutions.getMetadata(depth, index); + assert(arg.isMetadata()); + return _swift_buildDemanglingForMetadata(arg.getMetadata(), Dem); +} + +static Demangle::NodePointer +_buildDemanglingForExtendedExistential(const Metadata *type, + Demangle::Demangler &Dem) { + auto ee = cast(type); + + auto demangledExistential = Dem.demangleType(ee->Shape->ExistentialType.get(), + ResolveToDemanglingForContext(Dem)); + + if (!ee->Shape->hasGeneralizationSignature()) + return demangledExistential; + + SubstGenericParametersFromMetadata substitutions(ee->Shape, + ee->getGeneralizationArguments()); + + // Dig out the requirement list. + auto constrainedExistential = demangledExistential->getChild(0); + assert(constrainedExistential->getKind() == Node::Kind::ConstrainedExistential); + auto reqList = constrainedExistential->getChild(1); + assert(reqList->getKind() == Node::Kind::ConstrainedExistentialRequirementList); + + auto newReqList = Dem.createNode(Node::Kind::ConstrainedExistentialRequirementList); + + for (auto req : *reqList) { + // Currently, the only requirements that can create generalization arguments + // are same types. + if (req->getKind() != Node::Kind::DependentGenericSameTypeRequirement) { + newReqList->addChild(req, Dem); + continue; + } + + auto lhs = _replaceGeneralizationArg(req->getChild(0), substitutions, Dem); + auto rhs = _replaceGeneralizationArg(req->getChild(1), substitutions, Dem); + + auto newReq = Dem.createNode(Node::Kind::DependentGenericSameTypeRequirement); + newReq->addChild(lhs, Dem); + newReq->addChild(rhs, Dem); + + newReqList->addChild(newReq, Dem); + } + + constrainedExistential->replaceChild(1, newReqList); + + return demangledExistential; +} + // Build a demangled type tree for a type. // // FIXME: This should use MetadataReader.h. @@ -596,13 +659,7 @@ swift::_swift_buildDemanglingForMetadata(const Metadata *type, return proto_list; } case MetadataKind::ExtendedExistential: { - // FIXME: Implement this by demangling the extended existential and - // substituting the generalization arguments into the demangle tree. - // For now, unconditional casts will report '<<< invalid type >>>' when - // they fail. - // TODO: for clients that need to guarantee round-tripping, demangle - // to a SymbolicExtendedExistentialType. - return nullptr; + return _buildDemanglingForExtendedExistential(type, Dem); } case MetadataKind::ExistentialMetatype: { auto metatype = static_cast(type); diff --git a/test/Interpreter/extended_existential.swift b/test/Interpreter/extended_existential.swift new file mode 100644 index 0000000000000..020977aaa9ed3 --- /dev/null +++ b/test/Interpreter/extended_existential.swift @@ -0,0 +1,49 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -O -target %target-cpu-apple-macos15.0 %s -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s + +// REQUIRES: OS=macosx +// REQUIRES: executable_test + +protocol A: ~Copyable { + associatedtype B +} + +protocol B: ~Copyable {} + +let a: Any = (any ~Copyable).self +// CHECK: any Any +print(a) + +let b: Any = (any ~Escapable).self +// CHECK: any Any +print(b) + +let c: Any = (any ~Copyable & ~Escapable).self +// CHECK: any Any +print(c) + +let d: Any = (any A).self +// CHECK: A +print(d) + +let e: Any = (any B).self +// CHECK: B +print(e) + +let f: Any = (any A & B).self +// CHECK: A & B +print(f) + +let g: Any = (any A & ~Copyable).self +// CHECK: any A +print(g) + +let h: Any = (any B & ~Copyable).self +// CHECK: any B +print(h) + +let i: Any = (any A & B & ~Copyable).self +// CHECK: any A & B +print(i)