Skip to content

Commit 9c638ae

Browse files
authored
SR-5289: Teach Mirror how to handle unowned/unmanaged references (#28368)
* SR-5289: Support reflecting weak, unowned, and unmanaged refs This refactors how we handle reference ownership when reflecting fields of struct and class objects. There are now explicit paths for each type of reference and some simple exhaustiveness checks to fail the build if a new reference type is added in the future without updating this logic.
1 parent 8a3e4a2 commit 9c638ae

File tree

7 files changed

+335
-142
lines changed

7 files changed

+335
-142
lines changed

include/swift/ABI/Metadata.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ using TargetRelativeIndirectablePointer
160160

161161
struct HeapObject;
162162
class WeakReference;
163+
struct UnownedReference;
163164

164165
template <typename Runtime> struct TargetMetadata;
165166
using Metadata = TargetMetadata<InProcess>;
@@ -645,6 +646,9 @@ struct TargetMetadata {
645646
// NOTE: This *is* a box for copy-on-write existentials.
646647
OpaqueValue *allocateBoxForExistentialIn(ValueBuffer *Buffer) const;
647648

649+
// Deallocate an out-of-line buffer box if one is present.
650+
void deallocateBoxForExistentialIn(ValueBuffer *Buffer) const;
651+
648652
/// Get the nominal type descriptor if this metadata describes a nominal type,
649653
/// or return null if it does not.
650654
ConstTargetMetadataPointer<Runtime, TargetTypeContextDescriptor>

include/swift/ABI/MetadataValues.h

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -913,55 +913,6 @@ class TargetTupleTypeFlags {
913913
};
914914
using TupleTypeFlags = TargetTupleTypeFlags<size_t>;
915915

916-
/// Field types and flags as represented in a nominal type's field/case type
917-
/// vector.
918-
class FieldType {
919-
typedef uintptr_t int_type;
920-
// Type metadata is always at least pointer-aligned, so we get at least two
921-
// low bits to stash flags. We could use three low bits on 64-bit, and maybe
922-
// some high bits as well.
923-
enum : int_type {
924-
Indirect = 1,
925-
Weak = 2,
926-
927-
TypeMask = ((uintptr_t)-1) & ~(alignof(void*) - 1),
928-
};
929-
int_type Data;
930-
931-
constexpr FieldType(int_type Data) : Data(Data) {}
932-
public:
933-
constexpr FieldType() : Data(0) {}
934-
FieldType withType(const Metadata *T) const {
935-
return FieldType((Data & ~TypeMask) | (uintptr_t)T);
936-
}
937-
938-
constexpr FieldType withIndirect(bool indirect) const {
939-
return FieldType((Data & ~Indirect)
940-
| (indirect ? Indirect : 0));
941-
}
942-
943-
constexpr FieldType withWeak(bool weak) const {
944-
return FieldType((Data & ~Weak)
945-
| (weak ? Weak : 0));
946-
}
947-
948-
bool isIndirect() const {
949-
return bool(Data & Indirect);
950-
}
951-
952-
bool isWeak() const {
953-
return bool(Data & Weak);
954-
}
955-
956-
const Metadata *getType() const {
957-
return (const Metadata *)(Data & TypeMask);
958-
}
959-
960-
int_type getIntValue() const {
961-
return Data;
962-
}
963-
};
964-
965916
/// Flags for exclusivity-checking operations.
966917
enum class ExclusivityFlags : uintptr_t {
967918
Read = 0x0,

include/swift/Runtime/ExistentialContainer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ struct ClassExistentialContainerImpl {
9595
using ClassExistentialContainer = ClassExistentialContainerImpl<void *>;
9696
using WeakClassExistentialContainer =
9797
ClassExistentialContainerImpl<WeakReference>;
98+
using UnownedClassExistentialContainer =
99+
ClassExistentialContainerImpl<UnownedReference>;
98100

99101
} // end swift namespace
100102

stdlib/public/runtime/Metadata.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3862,6 +3862,13 @@ template <> OpaqueValue *Metadata::allocateBoxForExistentialIn(ValueBuffer *buff
38623862
return refAndValueAddr.buffer;
38633863
}
38643864

3865+
template <> void Metadata::deallocateBoxForExistentialIn(ValueBuffer *buffer) const {
3866+
auto *vwt = getValueWitnesses();
3867+
if (vwt->isValueInline())
3868+
return;
3869+
swift_deallocBox(reinterpret_cast<HeapObject *>(buffer->PrivateData[0]));
3870+
}
3871+
38653872
template <> OpaqueValue *Metadata::allocateBufferIn(ValueBuffer *buffer) const {
38663873
auto *vwt = getValueWitnesses();
38673874
if (vwt->isValueInline())

stdlib/public/runtime/Private.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ class TypeReferenceOwnership {
5050

5151
#define REF_STORAGE(Name, ...) \
5252
void set##Name() { Data |= Name; } \
53-
bool is##Name() const { return Data & Name; }
53+
bool is##Name() const { return Data == Name; }
5454
#include "swift/AST/ReferenceStorage.def"
55+
56+
bool isStrong() const { return Data == 0; }
5557
};
5658

5759
/// Type information consists of metadata and its ownership info,
@@ -76,9 +78,11 @@ class TypeInfo {
7678
const Metadata *getMetadata() const { return Response.Value; }
7779
MetadataResponse getResponse() const { return Response; }
7880

79-
bool isWeak() const { return ReferenceOwnership.isWeak(); }
80-
bool isUnowned() const { return ReferenceOwnership.isUnowned(); }
81-
bool isUnmanaged() const { return ReferenceOwnership.isUnmanaged(); }
81+
#define REF_STORAGE(Name, ...) \
82+
bool is##Name() const { return ReferenceOwnership.is##Name(); }
83+
#include "swift/AST/ReferenceStorage.def"
84+
85+
bool isStrong() const { return ReferenceOwnership.isStrong(); }
8286

8387
TypeReferenceOwnership getReferenceOwnership() const {
8488
return ReferenceOwnership;

stdlib/public/runtime/ReflectionMirror.mm

Lines changed: 87 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,29 @@ - (id)debugQuickLookObject;
8989

9090
namespace {
9191

92+
class FieldType {
93+
const Metadata *type;
94+
bool indirect;
95+
TypeReferenceOwnership referenceOwnership;
96+
public:
97+
98+
constexpr FieldType() : type(nullptr), indirect(false), referenceOwnership() { }
99+
constexpr FieldType(const Metadata *T) : type(T), indirect(false), referenceOwnership() { }
100+
101+
static constexpr FieldType untypedEnumCase(bool indirect) {
102+
FieldType type{};
103+
type.indirect = indirect;
104+
return type;
105+
}
106+
const Metadata *getType() const { return type; }
107+
const TypeReferenceOwnership getReferenceOwnership() const { return referenceOwnership; }
108+
bool isIndirect() const { return indirect; }
109+
void setIndirect(bool value) { indirect = value; }
110+
void setReferenceOwnership(TypeReferenceOwnership newOwnership) {
111+
referenceOwnership = newOwnership;
112+
}
113+
};
114+
92115
/// The layout of Any.
93116
using Any = OpaqueExistentialContainer;
94117

@@ -123,58 +146,63 @@ - (id)debugQuickLookObject;
123146
return std::make_tuple(T, Value);
124147
}
125148

126-
static bool loadSpecialReferenceStorage(OpaqueValue *fieldData,
127-
const FieldType fieldType,
128-
Any *outValue) {
129-
// isWeak() implies a reference type via Sema.
130-
if (!fieldType.isWeak())
131-
return false;
132-
133-
auto type = fieldType.getType();
149+
static void copyWeakFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
134150
assert(type->getKind() == MetadataKind::Optional);
151+
auto *srcContainer = reinterpret_cast<WeakClassExistentialContainer*>(fieldData);
152+
auto *destClassContainer = reinterpret_cast<ClassExistentialContainer*>(destContainer);
153+
destClassContainer->Value = swift_unknownObjectWeakLoadStrong(&srcContainer->Value);
154+
auto witnessTablesSize = type->vw_size() - sizeof(WeakClassExistentialContainer);
155+
memcpy(destClassContainer->getWitnessTables(), srcContainer->getWitnessTables(), witnessTablesSize);
156+
}
135157

136-
auto *weakField = reinterpret_cast<WeakReference *>(fieldData);
137-
auto *strongValue = swift_unknownObjectWeakLoadStrong(weakField);
138-
139-
// Now that we have a strong reference, we need to create a temporary buffer
140-
// from which to copy the whole value, which might be a native class-bound
141-
// existential, which means we also need to copy n witness tables, for
142-
// however many protocols are in the protocol composition. For example, if we
143-
// are copying a:
144-
// weak var myWeakProperty : (Protocol1 & Protocol2)?
145-
// then we need to copy three values:
146-
// - the instance
147-
// - the witness table for Protocol1
148-
// - the witness table for Protocol2
149-
150-
auto *weakContainer =
151-
reinterpret_cast<WeakClassExistentialContainer *>(fieldData);
152-
153-
// Create a temporary existential where we can put the strong reference.
154-
// The allocateBuffer value witness requires a ValueBuffer to own the
155-
// allocated storage.
156-
ValueBuffer temporaryBuffer;
157-
158-
auto *temporaryValue = reinterpret_cast<ClassExistentialContainer *>(
159-
type->allocateBufferIn(&temporaryBuffer));
160-
161-
// Now copy the entire value out of the parent, which will include the
162-
// witness tables.
163-
temporaryValue->Value = strongValue;
164-
auto valueWitnessesSize = type->getValueWitnesses()->getSize() -
165-
sizeof(WeakClassExistentialContainer);
166-
memcpy(temporaryValue->getWitnessTables(), weakContainer->getWitnessTables(),
167-
valueWitnessesSize);
168-
169-
outValue->Type = type;
170-
auto *opaqueValueAddr = type->allocateBoxForExistentialIn(&outValue->Buffer);
171-
type->vw_initializeWithCopy(opaqueValueAddr,
172-
reinterpret_cast<OpaqueValue *>(temporaryValue));
173-
174-
type->deallocateBufferIn(&temporaryBuffer);
175-
swift_unknownObjectRelease(strongValue);
176-
177-
return true;
158+
static void copyUnownedFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
159+
auto *srcContainer = reinterpret_cast<UnownedClassExistentialContainer*>(fieldData);
160+
auto *destClassContainer = reinterpret_cast<ClassExistentialContainer*>(destContainer);
161+
destClassContainer->Value = swift_unknownObjectUnownedLoadStrong(&srcContainer->Value);
162+
auto witnessTablesSize = type->vw_size() - sizeof(UnownedClassExistentialContainer);
163+
memcpy(destClassContainer->getWitnessTables(), srcContainer->getWitnessTables(), witnessTablesSize);
164+
}
165+
166+
static void copyUnmanagedFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
167+
// Also known as "unowned(unsafe)".
168+
// This is simpler than the unowned/weak cases because unmanaged
169+
// references are fundamentally the same as strong ones, so we
170+
// can use the regular strong reference support that already
171+
// knows how to handle existentials and Obj-C references.
172+
type->vw_initializeWithCopy(destContainer, fieldData);
173+
}
174+
175+
static AnyReturn copyFieldContents(OpaqueValue *fieldData,
176+
const FieldType fieldType) {
177+
Any outValue;
178+
auto *type = fieldType.getType();
179+
outValue.Type = type;
180+
auto ownership = fieldType.getReferenceOwnership();
181+
auto *destContainer = type->allocateBoxForExistentialIn(&outValue.Buffer);
182+
183+
if (ownership.isStrong()) {
184+
type->vw_initializeWithCopy(destContainer, fieldData);
185+
}
186+
187+
// Generate a conditional clause for every known ownership type.
188+
// If this causes errors, it's because someone added a new ownership type
189+
// to ReferenceStorage.def and missed some related updates.
190+
#define REF_STORAGE(Name, ...) \
191+
else if (ownership.is##Name()) { \
192+
copy##Name##FieldContents(destContainer, type, fieldData); \
193+
}
194+
#include "swift/AST/ReferenceStorage.def"
195+
196+
else {
197+
// The field was declared with a reference type we don't understand.
198+
warning(0, "Value with unrecognized reference type is reflected as ()");
199+
// Clean up the buffer allocated above
200+
type->deallocateBoxForExistentialIn(&outValue.Buffer);
201+
// Return an existential containing Void
202+
outValue.Type = &METADATA_SYM(EMPTY_TUPLE_MANGLING);
203+
}
204+
205+
return AnyReturn(outValue);
178206
}
179207

180208

@@ -310,11 +338,7 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
310338
"type '%*s' that claims to be reflectable. Its fields will show up as "
311339
"'unknown' in Mirrors\n",
312340
(int)typeName.length, typeName.data);
313-
return {"unknown",
314-
FieldType()
315-
.withType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))
316-
.withIndirect(false)
317-
.withWeak(false)};
341+
return {"unknown", FieldType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))};
318342
};
319343

320344
auto *baseDesc = base->getTypeContextDescriptor();
@@ -325,14 +349,13 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
325349
if (!fields)
326350
return failedToFindMetadata();
327351

328-
const FieldDescriptor &descriptor = *fields;
329-
auto &field = descriptor.getFields()[index];
352+
auto &field = fields->getFields()[index];
330353
// Bounds are always valid as the offset is constant.
331354
auto name = field.getFieldName();
332355

333356
// Enum cases don't always have types.
334357
if (!field.hasMangledTypeName())
335-
return {name, FieldType().withIndirect(field.isIndirectCase())};
358+
return {name, FieldType::untypedEnumCase(field.isIndirectCase())};
336359

337360
auto typeName = field.getMangledTypeName();
338361

@@ -360,10 +383,10 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
360383
(int)typeName.size(), typeName.data());
361384
}
362385

363-
return {name, FieldType()
364-
.withType(typeInfo.getMetadata())
365-
.withIndirect(field.isIndirectCase())
366-
.withWeak(typeInfo.isWeak())};
386+
auto fieldType = FieldType(typeInfo.getMetadata());
387+
fieldType.setIndirect(field.isIndirectCase());
388+
fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
389+
return {name, fieldType};
367390
}
368391

369392
// Implementation for structs.
@@ -397,7 +420,6 @@ AnyReturn subscript(intptr_t i, const char **outName,
397420
// Load the offset from its respective vector.
398421
auto fieldOffset = Struct->getFieldOffsets()[i];
399422

400-
Any result;
401423
StringRef name;
402424
FieldType fieldInfo;
403425
std::tie(name, fieldInfo) = getFieldAt(type, i);
@@ -409,15 +431,7 @@ AnyReturn subscript(intptr_t i, const char **outName,
409431
auto *bytes = reinterpret_cast<char*>(value);
410432
auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);
411433

412-
bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
413-
if (!didLoad) {
414-
result.Type = fieldInfo.getType();
415-
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
416-
result.Type->vw_initializeWithCopy(opaqueValueAddr,
417-
const_cast<OpaqueValue *>(fieldData));
418-
}
419-
420-
return AnyReturn(result);
434+
return copyFieldContents(fieldData, fieldInfo);
421435
}
422436
};
423437

@@ -559,7 +573,6 @@ AnyReturn subscript(intptr_t i, const char **outName,
559573
#endif
560574
}
561575

562-
Any result;
563576
StringRef name;
564577
FieldType fieldInfo;
565578
std::tie(name, fieldInfo) = getFieldAt(type, i);
@@ -571,15 +584,7 @@ AnyReturn subscript(intptr_t i, const char **outName,
571584
*outName = name.data();
572585
*outFreeFunc = nullptr;
573586

574-
bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
575-
if (!didLoad) {
576-
result.Type = fieldInfo.getType();
577-
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
578-
result.Type->vw_initializeWithCopy(opaqueValueAddr,
579-
const_cast<OpaqueValue *>(fieldData));
580-
}
581-
582-
return AnyReturn(result);
587+
return copyFieldContents(fieldData, fieldInfo);
583588
}
584589

585590
#if SWIFT_OBJC_INTEROP

0 commit comments

Comments
 (0)