Skip to content

SR-5289: Teach Mirror how to handle unowned/unmanaged references #28368

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 21 commits into from
Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6c1b920
SR-5289: Test using Mirror to inspect weak, unowned, and unmanaged refs
tbkka Nov 19, 2019
ca53953
Expose Unowned reference types similar to Weak types.
tbkka Nov 19, 2019
36972a1
Be stricter about identifying reference types.
tbkka Nov 19, 2019
be3a3f9
SR-5289: Implement reflection for unowned and unmanaged fields
tbkka Nov 19, 2019
5398cef
Fix various warnings in Mirror.swift test source
tbkka Nov 19, 2019
663783f
Move FieldType into anonymous namespace
tbkka Nov 20, 2019
e66c901
Comment updates suggested by @rjmccall
tbkka Nov 20, 2019
28f662b
Reuse the same outValue to avoid breaking NRVO
tbkka Nov 21, 2019
9b33aa2
Add `deallocateBoxForExistentialIn` to match `allocateBoxForExistenti…
tbkka Nov 22, 2019
a96f094
Make the verification a little more detailed to get more data about a…
tbkka Dec 3, 2019
0e86b31
Support casting Any<Optional<P>> => P
tbkka Dec 6, 2019
6b07581
Minor cleanup
tbkka Dec 6, 2019
60fff2e
Merge branch 'master' into SR-5289
tbkka Dec 6, 2019
5db3f0a
Fix the handling of a null type
tbkka Dec 7, 2019
0995991
Try to make this copy of the unwrap loop the same as the other copy
tbkka Dec 7, 2019
2753ac1
Merge branch 'master' into SR-5289
tbkka Dec 9, 2019
16150d0
Revert "Support casting Any<Optional<P>> => P"
tbkka Dec 11, 2019
efa65d5
Revert "Support casting Any<Optional<P>> => P"
tbkka Dec 11, 2019
64b0e6f
Merge branch 'SR-5289' of github.com:tbkka/swift into SR-5289
tbkka Dec 12, 2019
52667c1
Merge branch 'master' into SR-5289
tbkka Dec 13, 2019
7a7a00a
In the test, avoid relying on casts that don't work
tbkka Dec 13, 2019
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
4 changes: 4 additions & 0 deletions include/swift/ABI/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ using TargetRelativeIndirectablePointer

struct HeapObject;
class WeakReference;
struct UnownedReference;

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

// Deallocate an out-of-line buffer box if one is present.
void deallocateBoxForExistentialIn(ValueBuffer *Buffer) const;

/// Get the nominal type descriptor if this metadata describes a nominal type,
/// or return null if it does not.
ConstTargetMetadataPointer<Runtime, TargetTypeContextDescriptor>
Expand Down
49 changes: 0 additions & 49 deletions include/swift/ABI/MetadataValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -913,55 +913,6 @@ class TargetTupleTypeFlags {
};
using TupleTypeFlags = TargetTupleTypeFlags<size_t>;

/// Field types and flags as represented in a nominal type's field/case type
/// vector.
class FieldType {
typedef uintptr_t int_type;
// Type metadata is always at least pointer-aligned, so we get at least two
// low bits to stash flags. We could use three low bits on 64-bit, and maybe
// some high bits as well.
enum : int_type {
Indirect = 1,
Weak = 2,

TypeMask = ((uintptr_t)-1) & ~(alignof(void*) - 1),
};
int_type Data;

constexpr FieldType(int_type Data) : Data(Data) {}
public:
constexpr FieldType() : Data(0) {}
FieldType withType(const Metadata *T) const {
return FieldType((Data & ~TypeMask) | (uintptr_t)T);
}

constexpr FieldType withIndirect(bool indirect) const {
return FieldType((Data & ~Indirect)
| (indirect ? Indirect : 0));
}

constexpr FieldType withWeak(bool weak) const {
return FieldType((Data & ~Weak)
| (weak ? Weak : 0));
}

bool isIndirect() const {
return bool(Data & Indirect);
}

bool isWeak() const {
return bool(Data & Weak);
}

const Metadata *getType() const {
return (const Metadata *)(Data & TypeMask);
}

int_type getIntValue() const {
return Data;
}
};

/// Flags for exclusivity-checking operations.
enum class ExclusivityFlags : uintptr_t {
Read = 0x0,
Expand Down
2 changes: 2 additions & 0 deletions include/swift/Runtime/ExistentialContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ struct ClassExistentialContainerImpl {
using ClassExistentialContainer = ClassExistentialContainerImpl<void *>;
using WeakClassExistentialContainer =
ClassExistentialContainerImpl<WeakReference>;
using UnownedClassExistentialContainer =
ClassExistentialContainerImpl<UnownedReference>;

} // end swift namespace

Expand Down
7 changes: 7 additions & 0 deletions stdlib/public/runtime/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3862,6 +3862,13 @@ template <> OpaqueValue *Metadata::allocateBoxForExistentialIn(ValueBuffer *buff
return refAndValueAddr.buffer;
}

template <> void Metadata::deallocateBoxForExistentialIn(ValueBuffer *buffer) const {
auto *vwt = getValueWitnesses();
if (vwt->isValueInline())
return;
swift_deallocBox(reinterpret_cast<HeapObject *>(buffer->PrivateData[0]));
}

template <> OpaqueValue *Metadata::allocateBufferIn(ValueBuffer *buffer) const {
auto *vwt = getValueWitnesses();
if (vwt->isValueInline())
Expand Down
12 changes: 8 additions & 4 deletions stdlib/public/runtime/Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ class TypeReferenceOwnership {

#define REF_STORAGE(Name, ...) \
void set##Name() { Data |= Name; } \
bool is##Name() const { return Data & Name; }
bool is##Name() const { return Data == Name; }
#include "swift/AST/ReferenceStorage.def"

bool isStrong() const { return Data == 0; }
};

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

bool isWeak() const { return ReferenceOwnership.isWeak(); }
bool isUnowned() const { return ReferenceOwnership.isUnowned(); }
bool isUnmanaged() const { return ReferenceOwnership.isUnmanaged(); }
#define REF_STORAGE(Name, ...) \
bool is##Name() const { return ReferenceOwnership.is##Name(); }
#include "swift/AST/ReferenceStorage.def"

bool isStrong() const { return ReferenceOwnership.isStrong(); }

TypeReferenceOwnership getReferenceOwnership() const {
return ReferenceOwnership;
Expand Down
169 changes: 87 additions & 82 deletions stdlib/public/runtime/ReflectionMirror.mm
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ - (id)debugQuickLookObject;

namespace {

class FieldType {
const Metadata *type;
bool indirect;
TypeReferenceOwnership referenceOwnership;
public:

constexpr FieldType() : type(nullptr), indirect(false), referenceOwnership() { }
constexpr FieldType(const Metadata *T) : type(T), indirect(false), referenceOwnership() { }

static constexpr FieldType untypedEnumCase(bool indirect) {
FieldType type{};
type.indirect = indirect;
return type;
}
const Metadata *getType() const { return type; }
const TypeReferenceOwnership getReferenceOwnership() const { return referenceOwnership; }
bool isIndirect() const { return indirect; }
void setIndirect(bool value) { indirect = value; }
void setReferenceOwnership(TypeReferenceOwnership newOwnership) {
referenceOwnership = newOwnership;
}
};

/// The layout of Any.
using Any = OpaqueExistentialContainer;

Expand Down Expand Up @@ -123,58 +146,63 @@ - (id)debugQuickLookObject;
return std::make_tuple(T, Value);
}

static bool loadSpecialReferenceStorage(OpaqueValue *fieldData,
const FieldType fieldType,
Any *outValue) {
// isWeak() implies a reference type via Sema.
if (!fieldType.isWeak())
return false;

auto type = fieldType.getType();
static void copyWeakFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
assert(type->getKind() == MetadataKind::Optional);
auto *srcContainer = reinterpret_cast<WeakClassExistentialContainer*>(fieldData);
auto *destClassContainer = reinterpret_cast<ClassExistentialContainer*>(destContainer);
destClassContainer->Value = swift_unknownObjectWeakLoadStrong(&srcContainer->Value);
auto witnessTablesSize = type->vw_size() - sizeof(WeakClassExistentialContainer);
memcpy(destClassContainer->getWitnessTables(), srcContainer->getWitnessTables(), witnessTablesSize);
}

auto *weakField = reinterpret_cast<WeakReference *>(fieldData);
auto *strongValue = swift_unknownObjectWeakLoadStrong(weakField);

// Now that we have a strong reference, we need to create a temporary buffer
// from which to copy the whole value, which might be a native class-bound
// existential, which means we also need to copy n witness tables, for
// however many protocols are in the protocol composition. For example, if we
// are copying a:
// weak var myWeakProperty : (Protocol1 & Protocol2)?
// then we need to copy three values:
// - the instance
// - the witness table for Protocol1
// - the witness table for Protocol2

auto *weakContainer =
reinterpret_cast<WeakClassExistentialContainer *>(fieldData);

// Create a temporary existential where we can put the strong reference.
// The allocateBuffer value witness requires a ValueBuffer to own the
// allocated storage.
ValueBuffer temporaryBuffer;

auto *temporaryValue = reinterpret_cast<ClassExistentialContainer *>(
type->allocateBufferIn(&temporaryBuffer));

// Now copy the entire value out of the parent, which will include the
// witness tables.
temporaryValue->Value = strongValue;
auto valueWitnessesSize = type->getValueWitnesses()->getSize() -
sizeof(WeakClassExistentialContainer);
memcpy(temporaryValue->getWitnessTables(), weakContainer->getWitnessTables(),
valueWitnessesSize);

outValue->Type = type;
auto *opaqueValueAddr = type->allocateBoxForExistentialIn(&outValue->Buffer);
type->vw_initializeWithCopy(opaqueValueAddr,
reinterpret_cast<OpaqueValue *>(temporaryValue));

type->deallocateBufferIn(&temporaryBuffer);
swift_unknownObjectRelease(strongValue);

return true;
static void copyUnownedFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
auto *srcContainer = reinterpret_cast<UnownedClassExistentialContainer*>(fieldData);
auto *destClassContainer = reinterpret_cast<ClassExistentialContainer*>(destContainer);
destClassContainer->Value = swift_unknownObjectUnownedLoadStrong(&srcContainer->Value);
auto witnessTablesSize = type->vw_size() - sizeof(UnownedClassExistentialContainer);
memcpy(destClassContainer->getWitnessTables(), srcContainer->getWitnessTables(), witnessTablesSize);
}

static void copyUnmanagedFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
// Also known as "unowned(unsafe)".
// This is simpler than the unowned/weak cases because unmanaged
// references are fundamentally the same as strong ones, so we
// can use the regular strong reference support that already
// knows how to handle existentials and Obj-C references.
type->vw_initializeWithCopy(destContainer, fieldData);
}

static AnyReturn copyFieldContents(OpaqueValue *fieldData,
const FieldType fieldType) {
Any outValue;
auto *type = fieldType.getType();
outValue.Type = type;
auto ownership = fieldType.getReferenceOwnership();
auto *destContainer = type->allocateBoxForExistentialIn(&outValue.Buffer);

if (ownership.isStrong()) {
type->vw_initializeWithCopy(destContainer, fieldData);
}

// Generate a conditional clause for every known ownership type.
// If this causes errors, it's because someone added a new ownership type
// to ReferenceStorage.def and missed some related updates.
#define REF_STORAGE(Name, ...) \
else if (ownership.is##Name()) { \
copy##Name##FieldContents(destContainer, type, fieldData); \
}
#include "swift/AST/ReferenceStorage.def"

else {
// The field was declared with a reference type we don't understand.
warning(0, "Value with unrecognized reference type is reflected as ()");
// Clean up the buffer allocated above
type->deallocateBoxForExistentialIn(&outValue.Buffer);
// Return an existential containing Void
outValue.Type = &METADATA_SYM(EMPTY_TUPLE_MANGLING);
}

return AnyReturn(outValue);
}


Expand Down Expand Up @@ -310,11 +338,7 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
"type '%*s' that claims to be reflectable. Its fields will show up as "
"'unknown' in Mirrors\n",
(int)typeName.length, typeName.data);
return {"unknown",
FieldType()
.withType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))
.withIndirect(false)
.withWeak(false)};
return {"unknown", FieldType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))};
};

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

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

// Enum cases don't always have types.
if (!field.hasMangledTypeName())
return {name, FieldType().withIndirect(field.isIndirectCase())};
return {name, FieldType::untypedEnumCase(field.isIndirectCase())};

auto typeName = field.getMangledTypeName();

Expand Down Expand Up @@ -360,10 +383,10 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
(int)typeName.size(), typeName.data());
}

return {name, FieldType()
.withType(typeInfo.getMetadata())
.withIndirect(field.isIndirectCase())
.withWeak(typeInfo.isWeak())};
auto fieldType = FieldType(typeInfo.getMetadata());
fieldType.setIndirect(field.isIndirectCase());
fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
return {name, fieldType};
}

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

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

bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
if (!didLoad) {
result.Type = fieldInfo.getType();
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
result.Type->vw_initializeWithCopy(opaqueValueAddr,
const_cast<OpaqueValue *>(fieldData));
}

return AnyReturn(result);
return copyFieldContents(fieldData, fieldInfo);
}
};

Expand Down Expand Up @@ -559,7 +573,6 @@ AnyReturn subscript(intptr_t i, const char **outName,
#endif
}

Any result;
StringRef name;
FieldType fieldInfo;
std::tie(name, fieldInfo) = getFieldAt(type, i);
Expand All @@ -571,15 +584,7 @@ AnyReturn subscript(intptr_t i, const char **outName,
*outName = name.data();
*outFreeFunc = nullptr;

bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
if (!didLoad) {
result.Type = fieldInfo.getType();
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
result.Type->vw_initializeWithCopy(opaqueValueAddr,
const_cast<OpaqueValue *>(fieldData));
}

return AnyReturn(result);
return copyFieldContents(fieldData, fieldInfo);
}

#if SWIFT_OBJC_INTEROP
Expand Down
Loading