Skip to content

NCGenerics: New Inverse Mangling 3DS XL #71878

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 98 additions & 6 deletions docs/ABI/Mangling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,92 @@ An ``extension`` mangling is used whenever an entity's declaration context is
an extension *and* the entity being extended is in a different module. In this
case the extension's module is mangled first, followed by the entity being
extended. If the extension and the extended entity are in the same module, the
plain ``entity`` mangling is preferred. If the extension is constrained, the
constraints on the extension are mangled in its generic signature.
plain ``entity`` mangling is preferred, but not always used. An extension is
considered "constrained" if it:

- Has any requirements not already satisfied by the extended nominal,
excluding conformance requirements for invertible protocols.
- Has any generic parameters with an inverse requirement.

Those requirements included in any of the above are included in the extension's
generic signature. The reason for this additional complexity is that we do not
mangle conformance req's for invertible protocols, only their absence.

::

struct S<A: ~Copyable, B: ~Copyable> {}

// An unconstrained extension.
extension S {}

// Also an unconstrained extension, because there are no inverses to mangle.
// This extension is exactly the same as the previous.
extension S where A: Copyable, B: Copyable {}

// A constrained extension, because of the added requirement `B: P` that is
// not already present in S.
extension S where B: P {}

// A constrained extension, because of the absence of `A: Copyable`.
// Despite also being absent in `S`, absences of invertible protocols
// are always mangled.
extension S where A: ~Copyable {}

Some entities, like computed properties, rely on the generic signature in their
`context`, so in order to disambiguate between those properties and
those in a context where a generic type requires Copyable, which is not mangled,
we have the following rule:

If the innermost type declaration for an entity has any inverses in its generic
signature, then extension mangling is used. This strategy is used to ensure
that moving a declaration between a nominal type and one of its extensions does
not cause an ABI break if the generic signature of the entity is equivalent in
both circumstances. For example:

::

struct R<A: ~Copyable> {
func f1() {} // uses extension mangling, just like `f3`

func f2() where A: Copyable {}
}

extension R where A: ~Copyable {
func f3() {}

func f4() where A: Copyable {} // uses entity mangling, just like `f2`
}

extension R where A: Copyable {
// 'f5' is mangled equivalent to 'f2' and 'f4' modulo its identifier.
func f5() {}
}

For intermediate nested types, i.e., those between the top level and the entity,
any inverses that remain in at the signature of the entity are mangled into
that entity's generic signature:

::

struct X<A: ~Copyable> {
struct Y<B: ~Copyable> {
// 'g1' uses 'entity' context mangling with and has no mangled signatures.
func g1() where A: Copyable, B: Copyable {}

// 'g2' uses 'entity' context mangling. The requirement `B: ~Copyable` is
//mangled into the generic signature for 'g2'.
func g2() where A: Copyable {}

// 'g3' uses extension mangling with generic signature 'A: ~Copyable'.
// The mangled generic signature of 'g3' is empty.
func g3() where B: Copyable {}

// 'g4' uses extension mangling with generic signature 'A: ~Copyable'.
// The mangled generic signature of 'g4' contains 'B: ~Copyable'.
func g4() {}
}
}


When mangling the context of a local entity within a constructor or
destructor, the non-allocating or non-deallocating variant is used.
Expand Down Expand Up @@ -680,12 +764,14 @@ Types
METATYPE-REPR ::= 'T' // Thick metatype representation
METATYPE-REPR ::= 'o' // ObjC metatype representation

existential-layout ::= protocol-list 'p' // existential layout
existential-layout ::= protocol-list superclass 'Xc' // existential layout with superclass
existential-layout ::= protocol-list 'Xl' // existential layout with AnyObject

type ::= associated-type
type ::= any-generic-type
type ::= protocol-list 'p' // existential type
type ::= protocol-list superclass 'Xc' // existential type with superclass
type ::= protocol-list 'Xl' // existential type with AnyObject
type ::= protocol-list requirement* '_' 'XP' // constrained existential type
type ::= existential-layout // existential type
type ::= existential-layout requirement '_' requirement* 'XP' // constrained existential type
type ::= type-list 't' // tuple
type ::= type generic-signature 'u' // generic type
type ::= 'x' // generic param, depth=0, idx=0
Expand Down Expand Up @@ -925,13 +1011,19 @@ now codified into the ABI; the index 0 is therefore reserved.

generic-param-pack-marker ::= 'Rv' GENERIC_PARAM-INDEX // generic parameter pack marker

INVERTIBLE-KIND ::= 'c' // Copyable
INVERTIBLE-KIND ::= 'e' // Escapable
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should bake the invertible protocols we know about into the mangling scheme. Rather, I think we should mangle an "inverted conformance requirement" and add standard substitutions for Copyable and Escapable to keep it smaller.

Copy link
Member Author

@kavon kavon Mar 4, 2024

Choose a reason for hiding this comment

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

I considered that approach and would support it. I just went with this current approach as it was simple to sketch out.


GENERIC-PARAM-COUNT ::= 'z' // zero parameters
GENERIC-PARAM-COUNT ::= INDEX // N+1 parameters

requirement ::= protocol 'R' GENERIC-PARAM-INDEX // protocol requirement
requirement ::= protocol assoc-type-name 'Rp' GENERIC-PARAM-INDEX // protocol requirement on associated type
requirement ::= protocol assoc-type-list 'RP' GENERIC-PARAM-INDEX // protocol requirement on associated type at depth
requirement ::= protocol substitution 'RQ' // protocol requirement with substitution
#if SWIFT_RUNTIME_VERSION >= 6.0
requirement ::= 'Ri' INVERTIBLE-KIND GENERIC-PARAM-INDEX // inverse requirement
Copy link
Member

Choose a reason for hiding this comment

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

We'll also need a version that works with associated types, I presume?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes I believe we'll need them.

Copy link
Contributor

Choose a reason for hiding this comment

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

@slavapestov seems to think we won’t need them

Copy link
Contributor

Choose a reason for hiding this comment

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

Currently the only place in the language where you can state T: ~Copyable with T a dependent member type is inside of a protocol, eg

protocol P {
  associatedtype A: ~Copyable
}

I was thinking that for symbol mangling, we don't ever need this because the requirement signature is not mangled as part of any symbol. However, now that I think about it I do believe we mangle the requirement signature in the protocol's context descriptor. So perhaps we need a mangling here after all.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we do mangle the requirement signature as part of the protocol's context descriptor.

#endif
requirement ::= type 'Rb' GENERIC-PARAM-INDEX // base class requirement
requirement ::= type assoc-type-name 'Rc' GENERIC-PARAM-INDEX // base class requirement on associated type
requirement ::= type assoc-type-list 'RC' GENERIC-PARAM-INDEX // base class requirement on associated type at depth
Expand Down
100 changes: 89 additions & 11 deletions include/swift/AST/ASTMangler.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,8 @@ class ASTMangler : public Mangler {
/// If enabled, marker protocols can be encoded in the mangled name.
bool AllowMarkerProtocols = true;

/// Whether the mangling predates concurrency, and therefore shouldn't
/// include concurrency features such as global actors or @Sendable
/// function types.
bool Preconcurrency = false;
/// If enabled, inverses will not be mangled into generic signatures.
bool AllowInverses = true;

/// If enabled, declarations annotated with @_originallyDefinedIn are mangled
/// as if they're part of their original module. Disabled for debug mangling,
Expand Down Expand Up @@ -455,16 +453,54 @@ class ASTMangler : public Mangler {
GenericSignature sig,
const ValueDecl *forDecl);

void appendContextOf(const ValueDecl *decl);
// A "base entity" is a function, property, subscript, or any other
// declaration that can appear in an extension.
struct BaseEntitySignature {
private:
GenericSignature sig;
bool innermostTypeDecl;
bool extension;
std::optional<unsigned> mangledDepth; // for inverses
public:
bool reachedInnermostTypeDecl() {
bool answer = innermostTypeDecl;
innermostTypeDecl = false;
return answer;
}
bool reachedExtension() const { return extension; }
void setReachedExtension() { assert(!extension); extension = true; }
GenericSignature getSignature() const { return sig; }
// The depth of the inverses mangled so far.
std::optional<unsigned> getDepth() const { return mangledDepth; }
void setDepth(unsigned depth) {
assert(!mangledDepth || *mangledDepth <= depth);
mangledDepth = depth;
}
BaseEntitySignature(const Decl *decl);
};

void appendContextOf(const ValueDecl *decl, BaseEntitySignature &base);
void appendContextualInverses(const GenericTypeDecl *contextDecl,
BaseEntitySignature &base,
const ModuleDecl *module,
StringRef useModuleName);

void appendContext(const DeclContext *ctx, StringRef useModuleName);
void appendContext(const DeclContext *ctx,
BaseEntitySignature &base,
StringRef useModuleName);

void appendModule(const ModuleDecl *module, StringRef useModuleName);

void appendExtension(const ExtensionDecl *ext,
BaseEntitySignature &base,
StringRef useModuleName);

void appendProtocolName(const ProtocolDecl *protocol,
bool allowStandardSubstitution = true);

void appendAnyGenericType(const GenericTypeDecl *decl);
void appendAnyGenericType(const GenericTypeDecl *decl,
BaseEntitySignature &base);

enum FunctionManglingKind {
NoFunctionMangling,
Expand Down Expand Up @@ -508,17 +544,42 @@ class ASTMangler : public Mangler {
GenericSignature sig,
const ValueDecl *forDecl = nullptr);

struct GenericSignatureParts {
ArrayRef<CanGenericTypeParamType> params;
unsigned initialParamDepth = 0;
SmallVector<Requirement, 2> requirements;
SmallVector<InverseRequirement, 2> inverses;
bool isNull() const; // Is there anything to mangle?
bool hasRequirements() const; // Are there any requirements to mangle?
void clear();
};

/// Append a generic signature to the mangling.
///
/// \param sig The generic signature.
///
/// \returns \c true if a generic signature was appended, \c false
/// if it was empty.
bool appendGenericSignature(GenericSignature sig);

/// Append a generic signature to the mangling.
///
/// \param sig The generic signature.
///
/// \param contextSig The signature of the known context. This function
/// will only mangle the difference between \c sig and \c contextSig.
///
/// \param base The signature of the base entity whose generic signature we're
/// mangling. This function will only mangle the inverses on generic
/// parameter in \c sig that are not eliminated by conformance requirements in
/// \c base.
///
///
/// \returns \c true if a generic signature was appended, \c false
/// if it was empty.
bool appendGenericSignature(GenericSignature sig,
GenericSignature contextSig = nullptr);
GenericSignature contextSig,
BaseEntitySignature &base);

/// Append a requirement to the mangling.
///
Expand All @@ -532,10 +593,23 @@ class ASTMangler : public Mangler {
void appendRequirement(const Requirement &reqt, GenericSignature sig,
bool lhsBaseIsProtocolSelf = false);

/// Append an inverse requirement into the mangling.
///
/// Instead of mangling the presence of an invertible protocol, we mangle
/// their absence, which is what an inverse represents.
///
/// \param req The inverse requirement to mangle.
void appendInverseRequirement(const InverseRequirement &req,
GenericSignature sig,
bool lhsBaseIsProtocolSelf = false);

void gatherGenericSignatureParts(GenericSignature sig,
GenericSignature contextSig,
BaseEntitySignature &base,
GenericSignatureParts &parts);

void appendGenericSignatureParts(GenericSignature sig,
ArrayRef<CanTypeWrapper<GenericTypeParamType>> params,
unsigned initialParamDepth,
ArrayRef<Requirement> requirements);
GenericSignatureParts const& parts);

DependentMemberType *dropProtocolFromAssociatedType(DependentMemberType *dmt,
GenericSignature sig);
Expand Down Expand Up @@ -565,6 +639,7 @@ class ASTMangler : public Mangler {


void appendDeclType(const ValueDecl *decl,
BaseEntitySignature &base,
FunctionManglingKind functionMangling = NoFunctionMangling);

bool tryAppendStandardSubstitution(const GenericTypeDecl *type);
Expand All @@ -580,7 +655,10 @@ class ASTMangler : public Mangler {
void appendAccessorEntity(StringRef accessorKindCode,
const AbstractStorageDecl *decl, bool isStatic);

void appendEntity(const ValueDecl *decl, StringRef EntityOp, bool isStatic);
void appendEntity(const ValueDecl *decl,
BaseEntitySignature &base,
StringRef EntityOp,
bool isStatic);

void appendEntity(const ValueDecl *decl);

Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,10 @@ class ExtensionDecl final : public GenericContext, public Decl,
/// resiliently moved into the original protocol itself.
bool isEquivalentToExtendedContext() const;

/// Determine whether this extension context is in the same defining module as
/// the original nominal type context.
bool isInSameDefiningModule() const;

/// Returns the name of the category specified by the \c \@_objcImplementation
/// attribute, or \c None if the name is invalid or
/// \c isObjCImplementation() is false.
Expand Down
5 changes: 4 additions & 1 deletion include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,10 @@ DECL_ATTR(_distributedThunkTarget, DistributedThunkTarget,
DECL_ATTR(_allowFeatureSuppression, AllowFeatureSuppression,
OnAnyDecl | UserInaccessible | NotSerialized | ABIStableToAdd | APIStableToAdd | ABIStableToRemove | APIStableToRemove,
157)
LAST_DECL_ATTR(AllowFeatureSuppression)
SIMPLE_DECL_ATTR(_preInverseGenerics, PreInverseGenerics,
OnAbstractFunction | OnSubscript | OnVar | UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove,
158)
LAST_DECL_ATTR(PreInverseGenerics)

#undef DECL_ATTR_ALIAS
#undef CONTEXTUAL_DECL_ATTR_ALIAS
Expand Down
10 changes: 5 additions & 5 deletions include/swift/AST/GenericSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,11 @@ void validateGenericSignaturesInModule(ModuleDecl *module);
/// required to be minimal or canonical, and may contain unresolved
/// DependentMemberTypes.
///
/// If \p baseSignature is non-null, the new parameters and requirements
/// are added on; existing requirements of the base signature might become
/// redundant.
///
/// If \p baseSignature is null, build a new signature from scratch.
/// \param baseSignature if non-null, the new parameters and requirements
///// are added on; existing requirements of the base signature might become
///// redundant. Otherwise if null, build a new signature from scratch.
/// \param allowInverses if true, default requirements to Copyable/Escapable are
/// expanded for generic parameters.
GenericSignature buildGenericSignature(
ASTContext &ctx,
GenericSignature baseSignature,
Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/Requirement.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ class Requirement {
llvm_unreachable("Unhandled RequirementKind in switch");
}

friend bool operator!=(const Requirement &lhs,
const Requirement &rhs) {
return !(lhs == rhs);
}

/// Whether this requirement's types contain ErrorTypes.
bool hasError() const;

Expand Down Expand Up @@ -257,6 +262,9 @@ struct InverseRequirement {

InvertibleProtocolKind getKind() const;

/// Linear order on inverse requirements in a generic signature.
int compare(const InverseRequirement &other) const;

/// Appends additional requirements corresponding to defaults for the given
/// generic parameters.
static void expandDefaults(ASTContext &ctx,
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -6007,6 +6007,7 @@ class ProtocolCompositionType final : public TypeBase,
}

InvertibleProtocolSet getInverses() const { return Inverses; }
bool hasInverse() const { return !Inverses.empty(); }

void Profile(llvm::FoldingSetNodeID &ID) {
Profile(ID, getMembers(), getInverses(), hasExplicitAnyObject());
Expand Down
2 changes: 2 additions & 0 deletions include/swift/Demangling/DemangleNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -393,5 +393,7 @@ NODE(OutlinedAssignWithTakeNoValueWitness)
NODE(OutlinedAssignWithCopyNoValueWitness)
NODE(OutlinedDestroyNoValueWitness)

NODE(DependentGenericInverseConformanceRequirement)

#undef CONTEXT_NODE
#undef NODE
6 changes: 6 additions & 0 deletions include/swift/Demangling/TypeDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,14 @@ void decodeRequirement(NodePointer node,
child->getChild(1), /*forRequirement=*/false);
if (!constraintType)
return;
} else if (child->getKind() ==
Demangle::Node::Kind::DependentGenericInverseConformanceRequirement) {
// FIXME(kavon): this is unimplemented! We should build a PCT here with
// the inverse in it.
return;
}


switch (child->getKind()) {
case Demangle::Node::Kind::DependentGenericConformanceRequirement: {
requirements.push_back(BuiltRequirement(
Expand Down
Loading