Skip to content

Don't let reflection handle noncopyable types yet. #64295

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 1 commit into from
Mar 14, 2023
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
72 changes: 68 additions & 4 deletions lib/IRGen/GenReflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,54 @@ getTypeRefByFunction(IRGenModule &IGM,
? 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->isPureMoveOnly()) {
// 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
// generics. On other platforms, a new Swift compiler and runtime
// require recompilation anyway, so this dance is unnecessary, and
// for now, we can unconditionally lie.
bool useForwardCompatibility =
IGM.Context.LangOpts.Target.isOSDarwin();

llvm::Instruction *br = nullptr;
llvm::BasicBlock *supportedBB = nullptr;
if (useForwardCompatibility) {
auto runtimeSupportsNoncopyableTypesSymbol
= IGM.Module.getOrInsertGlobal("swift_runtimeSupportsNoncopyableTypes",
IGM.Int8Ty);
cast<llvm::GlobalVariable>(runtimeSupportsNoncopyableTypesSymbol)
->setLinkage(llvm::GlobalValue::ExternalWeakLinkage);

auto runtimeSupportsNoncopyableTypes
= IGF.Builder.CreateIsNotNull(runtimeSupportsNoncopyableTypesSymbol,
"supports.noncopyable");
supportedBB = IGF.createBasicBlock("does.support.noncopyable");
auto unsupportedBB = IGF.createBasicBlock("does.not.support.noncopyable");
br = IGF.Builder.CreateCondBr(runtimeSupportsNoncopyableTypes,
supportedBB,
unsupportedBB);

IGF.Builder.emitBlock(unsupportedBB);
}

// If the runtime does not yet support noncopyable types, lie that the
// field is an empty tuple, so the runtime doesn't try to do anything
// with the actual value.
auto phonyRet = IGF.emitTypeMetadataRef(IGM.Context.TheEmptyTupleType);
IGF.Builder.CreateRet(phonyRet);

if (!useForwardCompatibility) {
goto done_building_function;
}

// Emit the type metadata normally otherwise.
IGF.Builder.SetInsertPoint(br);
IGF.Builder.emitBlock(supportedBB);
}

bindFromGenericRequirementsBuffer(
IGF, requirements,
Address(bindingsBufPtr, IGM.Int8Ty, IGM.getPointerAlignment()),
Expand All @@ -276,6 +324,7 @@ getTypeRefByFunction(IRGenModule &IGM,
auto ret = IGF.emitTypeMetadataRef(substT);
IGF.Builder.CreateRet(ret);
}
done_building_function:
// Form the mangled name with its relative reference.
auto S = B.beginStruct();
S.setPacked(true);
Expand Down Expand Up @@ -315,6 +364,21 @@ getTypeRefImpl(IRGenModule &IGM,
case MangledTypeRefRole::FlatUnique:
useFlatUnique = true;
break;

case MangledTypeRefRole::FieldMetadata:
// We want to keep fields of noncopyable type from being exposed to
// in-process runtime reflection libraries in older Swift runtimes, since
// they more than likely assume they can copy field values, and the language
// support for noncopyable types as dynamic or generic types isn't yet
// implemented as of the writing of this comment. If the type is
// noncopyable, use a function to emit the type ref which will look for a
// signal from future runtimes whether they support noncopyable types before
// exposing their metadata to them.
if (type->isPureMoveOnly()) {
IGM.IRGen.noteUseOfTypeMetadata(type);
return getTypeRefByFunction(IGM, sig, type);
}
LLVM_FALLTHROUGH;

case MangledTypeRefRole::DefaultAssociatedTypeWitness:
case MangledTypeRefRole::Metadata:
Expand Down Expand Up @@ -724,14 +788,14 @@ class FieldTypeMetadataBuilder : public ReflectionMetadataBuilder {
if (type->isForeignReferenceType()) {
auto opaqueType = type->getASTContext().getOpaquePointerType();
// The standard library's Mirror demangles metadata from field
// descriptors, so use MangledTypeRefRole::Metadata to ensure
// descriptors, so use MangledTypeRefRole::FieldMetadata to ensure
// runtime metadata is available.
addTypeRef(opaqueType, genericSig, MangledTypeRefRole::Metadata);
addTypeRef(opaqueType, genericSig, MangledTypeRefRole::FieldMetadata);
} else {
// The standard library's Mirror demangles metadata from field
// descriptors, so use MangledTypeRefRole::Metadata to ensure
// descriptors, so use MangledTypeRefRole::FieldMetadata to ensure
// runtime metadata is available.
addTypeRef(type, genericSig, MangledTypeRefRole::Metadata);
addTypeRef(type, genericSig, MangledTypeRefRole::FieldMetadata);
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/IRGen/IRGenMangler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ mangleSymbolNameForSymbolicMangling(const SymbolicMangling &mangling,
prefix = "default assoc type ";
break;

case MangledTypeRefRole::FieldMetadata:
case MangledTypeRefRole::Metadata:
case MangledTypeRefRole::Reflection:
prefix = "symbolic ";
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/IRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,10 @@ class TypeEntityReference {

/// Describes the role of a mangled type reference string.
enum class MangledTypeRefRole {
/// The mangled type reference is used for field metadata, which is used
/// by both in-process and out-of-process reflection, so still requires
/// referenced types' metadata to be fully emitted.
FieldMetadata,
/// The mangled type reference is used for normal metadata.
Metadata,
/// The mangled type reference is used for reflection metadata.
Expand Down
1 change: 1 addition & 0 deletions lib/IRGen/MetadataRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ llvm::Constant *IRGenModule::getAddrOfStringForTypeRef(

case MangledTypeRefRole::Metadata:
case MangledTypeRefRole::Reflection:
case MangledTypeRefRole::FieldMetadata:
break;
}

Expand Down
13 changes: 13 additions & 0 deletions stdlib/public/runtime/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ extern "C" void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler)
using namespace swift;
using namespace metadataimpl;

#if defined(__APPLE__)
// Binaries using noncopyable types check the address of the symbol
// `swift_runtimeSupportsNoncopyableTypes` before exposing any noncopyable
// type metadata through in-process reflection, to prevent existing code
// that expects all types to be copyable from crashing or causing bad behavior
// by copying noncopyable types. The runtime does not yet support noncopyable
// types, so we explicitly define this symbol to be zero for now. Binaries
// weak-import this symbol so they will resolve it to a zero address on older
// runtimes as well.
__asm__(" .globl _swift_runtimeSupportsNoncopyableTypes\n");
__asm__(".set _swift_runtimeSupportsNoncopyableTypes, 0\n");
#endif

// GenericParamDescriptor is a single byte, so while it's difficult to
// imagine needing even a quarter this many generic params, there's very
// little harm in doing it.
Expand Down
44 changes: 44 additions & 0 deletions test/Interpreter/moveonly_field_in_class_reflection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-move-only)
// REQUIRES: executable_test

// Verify that iterating through the fields of an object whose class has
// a move-only field does not trap from trying to reflect and copy those
// move-only fields.

@_moveOnly
public struct MO {
var x: Int8 = 0
var y: Int8 = 0
var z: Int8 = 0

deinit { print("destroyed MO") }
}

public class MOHaver {
var s: String = "hello"
var mo: MO = MO()
var b: Int8 = 42
var c: Any.Type = MOHaver.self
}

// CHECK-LABEL: doing nongeneric
print("doing nongeneric")
do {
let k = MOHaver()

let mirror = Mirror(reflecting: k)
// CHECK-NEXT: s: hello
// Whether this actually prints the value of `k.mo` or not is irrelevant
// to the test; we care that attempting to reflect the field does not trap
// copying a noncopyable field.
// CHECK-NEXT: mo:
// CHECK-NEXT: b: 42
// CHECK-NEXT: c: {{.*}}.MOHaver
for c in mirror.children {
print("\(c.label!): \(c.value)")
}
// CHECK-NEXT: destroyed MO
}
// CHECK-NEXT: done with nongeneric
print("done with nongeneric")