From b4d7e98a58bc136dbd246e0e6b78764e84ead9ff Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Mon, 6 Oct 2025 18:23:49 -0700 Subject: [PATCH] IRGen: Don't hide noncopyable metadata from access functions in load-bearing mangled names. There is a compatibility hack where fallback metadata access functions used in field type metadata lie and say a field's type is the empty tuple when it's noncopyable and the runtime doesn't advertise compatibility with noncopyable types. Unfortunately, this hack applied to *all* fallback metadata access functions, causing us to return wrong metadata in situations we really need the correct metadata, such as in associated type substitutions. Constrain the hack to only apply to reflection metadata. Fixes rdar://161562839. --- lib/IRGen/GenReflection.cpp | 21 ++++++++-- lib/IRGen/IRGenMangler.cpp | 37 ++++++++++++++-- lib/IRGen/IRGenMangler.h | 3 +- lib/IRGen/TypeLayout.cpp | 9 ++-- .../IRGen/noncopyable_field_descriptors.swift | 36 ++++++++-------- .../noncopyable_runtime_back_compat.swift | 42 +++++++++++++++++++ 6 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 test/IRGen/noncopyable_runtime_back_compat.swift diff --git a/lib/IRGen/GenReflection.cpp b/lib/IRGen/GenReflection.cpp index 7d2e40db88fc7..848731fe1088e 100644 --- a/lib/IRGen/GenReflection.cpp +++ b/lib/IRGen/GenReflection.cpp @@ -320,7 +320,7 @@ getTypeRefByFunction(IRGenModule &IGM, CanGenericSignature sig, CanType t, IRGenMangler mangler(IGM.Context); std::string symbolName = mangler.mangleSymbolNameForMangledMetadataAccessorString( - "get_type_metadata", sig, t); + "get_type_metadata", sig, t, role); auto constant = IGM.getAddrOfStringForMetadataRef(symbolName, /*align*/2, /*low bit*/false, [&](ConstantInitBuilder &B) { @@ -351,9 +351,22 @@ getTypeRefByFunction(IRGenModule &IGM, CanGenericSignature sig, CanType t, ? genericEnv->mapTypeIntoContext(t)->getCanonicalType() : t; - // If a type is noncopyable, lie about the resolved type unless the - // runtime is sufficiently aware of noncopyable types. - if (substT->isNoncopyable()) { + // If a type is noncopyable, lie about the resolved type to reflection + // APIs unless the runtime is sufficiently aware of noncopyable types. + bool shouldHideNoncopyableTypeFromOldRuntimes; + switch (role) { + case MangledTypeRefRole::Metadata: + case MangledTypeRefRole::DefaultAssociatedTypeWitness: + case MangledTypeRefRole::FlatUnique: + shouldHideNoncopyableTypeFromOldRuntimes = false; + break; + case MangledTypeRefRole::FieldMetadata: + case MangledTypeRefRole::Reflection: + shouldHideNoncopyableTypeFromOldRuntimes = true; + break; + } + if (shouldHideNoncopyableTypeFromOldRuntimes + && substT->isNoncopyable()) { // Darwin-based platforms have ABI stability, and we want binaries // that use noncopyable types nongenerically today to be forward // compatible with a future OS runtime that supports noncopyable diff --git a/lib/IRGen/IRGenMangler.cpp b/lib/IRGen/IRGenMangler.cpp index 6ae760fbabe2d..a8a68b585c229 100644 --- a/lib/IRGen/IRGenMangler.cpp +++ b/lib/IRGen/IRGenMangler.cpp @@ -13,7 +13,9 @@ #include "IRGenMangler.h" #include "ExtendedExistential.h" #include "GenClass.h" +#include "IRGenModule.h" #include "swift/AST/ExistentialLayout.h" +#include "swift/AST/GenericEnvironment.h" #include "swift/AST/IRGenOptions.h" #include "swift/AST/ProtocolAssociations.h" #include "swift/AST/ProtocolConformance.h" @@ -414,15 +416,44 @@ std::string IRGenMangler::mangleSymbolNameForAssociatedConformanceWitness( std::string IRGenMangler::mangleSymbolNameForMangledMetadataAccessorString( const char *kind, CanGenericSignature genericSig, - CanType type) { + CanType type, + MangledTypeRefRole role) { beginManglingWithoutPrefix(); Buffer << kind << " "; - if (genericSig) + if (genericSig) { appendGenericSignature(genericSig); + } - if (type) + if (type) { appendType(type, genericSig); + } + + // Noncopyable types get additional runtime capability checks before we reveal + // their metadata to reflection APIs, while core metadata queries always provide + // the metadata. So we need a separate symbol mangling for the two variants in + // this case. + switch (role) { + case MangledTypeRefRole::DefaultAssociatedTypeWitness: + case MangledTypeRefRole::FlatUnique: + case MangledTypeRefRole::Metadata: + // Core metadata, never conditionalized. + break; + + case MangledTypeRefRole::Reflection: + case MangledTypeRefRole::FieldMetadata: { + // Reflection metadata is conditionalized for noncopyable types. + CanType contextType = type; + if (genericSig) { + contextType = genericSig.getGenericEnvironment()->mapTypeIntoContext(contextType) + ->getReducedType(genericSig); + } + if (contextType->isNoncopyable()) { + Buffer << " noncopyable"; + } + } + } + return finalize(); } diff --git a/lib/IRGen/IRGenMangler.h b/lib/IRGen/IRGenMangler.h index 60d20a40d8ff9..d3121c56a7e1c 100644 --- a/lib/IRGen/IRGenMangler.h +++ b/lib/IRGen/IRGenMangler.h @@ -685,7 +685,8 @@ class IRGenMangler : public Mangle::ASTMangler { std::string mangleSymbolNameForMangledMetadataAccessorString( const char *kind, CanGenericSignature genericSig, - CanType type); + CanType type, + MangledTypeRefRole role); std::string mangleSymbolNameForMangledConformanceAccessorString( const char *kind, diff --git a/lib/IRGen/TypeLayout.cpp b/lib/IRGen/TypeLayout.cpp index b88fa9939f1a9..61de9e4fb9251 100644 --- a/lib/IRGen/TypeLayout.cpp +++ b/lib/IRGen/TypeLayout.cpp @@ -472,7 +472,8 @@ llvm::Function *createMetatypeAccessorFunction(IRGenModule &IGM, SILType ty, std::string symbolName = mangler.mangleSymbolNameForMangledMetadataAccessorString( "get_type_metadata_for_layout_string", sig, - fieldType->mapTypeOutOfContext()->getCanonicalType()); + fieldType->mapTypeOutOfContext()->getCanonicalType(), + MangledTypeRefRole::Metadata); auto helperFn = IGM.getOrCreateHelperFunction( symbolName, IGM.TypeMetadataPtrTy /*retTy*/, @@ -1738,7 +1739,8 @@ AlignedGroupEntry::layoutString(IRGenModule &IGM, std::string symbolName = mangler.mangleSymbolNameForMangledMetadataAccessorString( "type_layout_string", genericSig.getCanonicalSignature(), - ty.getASTType()->mapTypeOutOfContext()->getCanonicalType()); + ty.getASTType()->mapTypeOutOfContext()->getCanonicalType(), + MangledTypeRefRole::Metadata); auto *global = SB.finishAndCreateGlobal(symbolName, IGM.getPointerAlignment(), /*constant*/ true); @@ -2339,7 +2341,8 @@ EnumTypeLayoutEntry::layoutString(IRGenModule &IGM, std::string symbolName = mangler.mangleSymbolNameForMangledMetadataAccessorString( "type_layout_string", genericSig.getCanonicalSignature(), - ty.getASTType()->mapTypeOutOfContext()->getCanonicalType()); + ty.getASTType()->mapTypeOutOfContext()->getCanonicalType(), + MangledTypeRefRole::Metadata); auto *global = SB.finishAndCreateGlobal(symbolName, IGM.getPointerAlignment(), /*constant*/ true); diff --git a/test/IRGen/noncopyable_field_descriptors.swift b/test/IRGen/noncopyable_field_descriptors.swift index bdfc8b36f5c09..eb62fbf590419 100644 --- a/test/IRGen/noncopyable_field_descriptors.swift +++ b/test/IRGen/noncopyable_field_descriptors.swift @@ -39,16 +39,16 @@ public struct NonCopyable: ~Copyable { } // 'MF' constant as a separator that precedes each field descriptor. // NEW: @"$s4test8CC_TestsCMF" = -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG.{{[0-9]+}}" +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG noncopyable // NEW-SAME: @"symbolic _____yq_G 4test21ConditionallyCopyableOAARi_zrlE" -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG.{{[0-9]+}}" +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG noncopyable // NEW-SAME: @"symbolic _____ySSG 4test21ConditionallyCopyableOAARi_zrlE" // OLD: @"$s4test8CC_TestsCMF" = -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG.{{[0-9]+}}" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyq_G.{{[0-9]+}}" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG.{{[0-9]+}}" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOySSG.{{[0-9]+}}" +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG noncopyable +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyq_G +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG noncopyable +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOySSG public class CC_Tests { var ccNCG: ConditionallyCopyable = .none var ccT: ConditionallyCopyable = .none @@ -62,16 +62,16 @@ public class CC_Tests { /// fields until a future runtime says they're safe to reflect. // NEW: @"$s4test8NC_TestsCMF" = -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.{{[0-9]+}}" -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G.{{[0-9]+}}" -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG.{{[0-9]+}}" -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG.{{[0-9]+}}" +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG noncopyable +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G noncopyable +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG noncopyable +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG noncopyable // OLD: @"$s4test8NC_TestsCMF" = -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.{{[0-9]+}}" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G.{{[0-9]+}}" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG.{{[0-9]+}}" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG.{{[0-9]+}}" +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG noncopyable +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G noncopyable +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG noncopyable +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG noncopyable public class NC_Tests { var ncNCG: NeverCopyable = .none var ncT: NeverCopyable = .none @@ -83,7 +83,7 @@ public class NC_Tests { // NEW: @"$s4test17StdlibTypes_TestsCMF" = // NEW-SAME: @"symbolic xSg" // NEW-SAME: @"symbolic q_Sg" -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg.{{[0-9]+}}" +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg noncopyable // NEW-SAME: @"symbolic SSSg" // NEW-SAME: @"symbolic SPyxG" // NEW-SAME: @"symbolic SPyq_G" @@ -93,7 +93,7 @@ public class NC_Tests { // OLD: @"$s4test17StdlibTypes_TestsCMF" = // OLD-SAME: @"symbolic xSg" // OLD-SAME: @"symbolic q_Sg" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg.{{[0-9]+}}" +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg noncopyable // OLD-SAME: @"symbolic SSSg" // OLD-SAME: @"symbolic SPyxG" // OLD-SAME: @"symbolic SPyq_G" @@ -115,13 +115,13 @@ public class StdlibTypes_Tests { // NEW: @"$s4test19PlainlyStored_TestsCMF" = // NEW-SAME: @"symbolic x" // NEW-SAME: @"symbolic q_" -// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV.{{[0-9]+}}" +// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV noncopyable // NEW-SAME: @"symbolic SS" // OLD: @"$s4test19PlainlyStored_TestsCMF" = // OLD-SAME: @"symbolic x" // OLD-SAME: @"symbolic q_" -// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV.{{[0-9]+}}" +// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV noncopyable // OLD-SAME: @"symbolic SS" public class PlainlyStored_Tests { var ncg: NCG diff --git a/test/IRGen/noncopyable_runtime_back_compat.swift b/test/IRGen/noncopyable_runtime_back_compat.swift new file mode 100644 index 0000000000000..c99f631bac202 --- /dev/null +++ b/test/IRGen/noncopyable_runtime_back_compat.swift @@ -0,0 +1,42 @@ +// RUN: %target-run-simple-swift(-enable-experimental-feature SuppressedAssociatedTypes) +// REQUIRES: executable_test +// REQUIRES: swift_feature_SuppressedAssociatedTypes + +// Noncopyable types are suppressed in field metadata in runtimes lacking +// reflection support for them (currently all runtimes). However, we want +// to ensure that noncopyable type metadata is still produced where it is +// "load-bearing" to the generics system, such as in associated types in +// protocol conformances. + +protocol Barrable: ~Copyable { +} + +struct Bar: Barrable & ~Copyable { + var x: Int = 0 +} + +protocol F: ~Copyable { + associatedtype B: Barrable & ~Copyable +} + +struct Foo: F & ~Copyable { + typealias B = Bar +} + +func test(_: T.Type) { + print("\(T.B.self)") +} + +// CHECK: Bar +test(Foo.self) + +class Mirrored { + var noncopyableField: Bar + + init() { + self.noncopyableField = Bar() + } +} + +// CHECK: noncopyableField (0 elements) +dump(Mirrored())