Skip to content

RemoteMirror: Project some multi-payload enums #30357

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 3 commits into from
Mar 12, 2020
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
47 changes: 45 additions & 2 deletions include/swift/Reflection/ReflectionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ class ReflectionContext
return false;
} else {
// No payload: Payload area is reused for more cases
unsigned PayloadTag = 0;
uint32_t PayloadTag = 0;
auto PayloadTagSize = std::min(PayloadSize, decltype(PayloadSize)(sizeof(PayloadTag)));
if (!getReader().readInteger(EnumAddress, PayloadTagSize, &PayloadTag)) {
return false;
Expand All @@ -801,7 +801,50 @@ class ReflectionContext
}

case RecordKind::MultiPayloadEnum: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This block of code actually identifies the current case for multi-payload enums. Today, it only handles enums that use a separate tag (as inferred from the size data exposed in the type information). For other enum layouts it returns false to indicate that it is unable to parse that particular enum.

// TODO: Support multipayload enums
// Collect basic statistics about the enum
unsigned long PayloadCaseCount = 0;
unsigned long NonPayloadCaseCount = 0;
unsigned long PayloadSize = 0;
for (auto Field : Fields) {
if (Field.TR != 0) {
PayloadCaseCount += 1;
if (Field.TI.getSize() > PayloadSize) {
PayloadSize = Field.TI.getSize();
}
} else {
NonPayloadCaseCount += 1;
}
}
if (EnumSize > PayloadSize) {
// If the compiler laid this out with a separate tag, use that.
unsigned tag = 0;
auto TagSize = EnumSize - PayloadSize;
auto TagAddress = remote::RemoteAddress(EnumAddress.getAddressData() + PayloadSize);
if (!getReader().readInteger(TagAddress, TagSize, &tag)
|| tag >= Fields.size()) {
return false;
}
if (tag < PayloadCaseCount) {
*CaseIndex = tag;
return true;
}
auto PayloadTagSize = std::min(PayloadSize, 4UL);
// Treat the tag as a page selector; payload carries the offset within the page
auto Page = tag - PayloadCaseCount;
// Zero for 32-bit because we'll never have more than one page
auto PageSize = PayloadTagSize >= 4 ? 0 : 1 << (PayloadSize * 8U);
auto PageStart = Page * PageSize;
unsigned PayloadTag;
if (!getReader().readInteger(EnumAddress, PayloadTagSize, &PayloadTag)) {
return false;
}
*CaseIndex = PageStart + PayloadTag + PayloadCaseCount;
return true;
} else {
// XXX TODO: If the payloads have common spare bits (e.g., all pointers)
// then use those to decode the case.
return false;
}
break;
}

Expand Down
44 changes: 10 additions & 34 deletions include/swift/Remote/MemoryReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,44 +64,20 @@ class MemoryReader {
/// Attempts to read an integer of the specified size from the given
/// address in the remote process.
///
/// Returns false if the operation failed, the request size is not
/// 1, 2, 4, or 8, or the request size is larger than the provided destination.
/// Returns false if the operation failed, or the request size is
/// larger than the provided destination.
template <typename IntegerType>
bool readInteger(RemoteAddress address, size_t bytes, IntegerType *dest) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A more flexible readInteger that can read integers with an arbitrary number of bytes, from 1 to sizeof(*dest). In particular, this is needed for handling 3-byte integers that can occur when storing enum tag indexes in the payload area.

if (bytes > sizeof(IntegerType))
return false;
switch (bytes) {
case 1: {
uint8_t n;
if (!readInteger(address, &n))
return false;
*dest = n;
break;
}
case 2: {
uint16_t n;
if (!readInteger(address, &n))
return false;
*dest = n;
break;
}
case 4: {
uint32_t n;
if (!readInteger(address, &n))
return false;
*dest = n;
break;
}
case 8: {
uint64_t n;
if (!readInteger(address, &n))
return false;
*dest = n;
break;
}
default:
*dest = 0;
if ((bytes > sizeof(IntegerType))
|| !readBytes(address, (uint8_t *)dest, bytes)) {
return false;
}
// FIXME: Assumes host and target have the same endianness.
// TODO: Query DLQ for endianness of target, compare to endianness of host.
#if defined(__BIG_ENDIAN__)
*dest >>= (sizeof(IntegerType) - bytes);
#endif
return true;
}

Expand Down
52 changes: 48 additions & 4 deletions stdlib/public/Reflection/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,53 @@ bool RecordTypeInfo::readExtraInhabitantIndex(remote::MemoryReader &reader,
return false;
}

case RecordKind::MultiPayloadEnum:// XXX TODO
return false;

case RecordKind::MultiPayloadEnum: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This block of code counts XIs in MultiPayloadEnums and returns the digested result for an enclosing single-payload enum to use.

// Multi payload enums can export XIs from the tag, if any.
// They never export XIs from their payloads.
auto Fields = getFields();
unsigned long PayloadCaseCount = 0;
unsigned long NonPayloadCaseCount = 0;
unsigned long PayloadSize = 0;
for (auto Field : Fields) {
if (Field.TR != 0) {
PayloadCaseCount += 1;
if (Field.TI.getSize() > PayloadSize) {
PayloadSize = Field.TI.getSize();
}
} else {
NonPayloadCaseCount += 1;
}
}
if (getSize() > PayloadSize) {
// Multipayload enums that do use a separate tag
// export XIs from that tag.
unsigned tag = 0;
auto TagSize = getSize() - PayloadSize;
auto TagAddress = remote::RemoteAddress(address.getAddressData() + PayloadSize);
if (!reader.readInteger(TagAddress, TagSize, &tag))
return false;
if (tag < Fields.size()) {
*extraInhabitantIndex = -1; // Valid payload, not an XI
} else if (TagSize >= 4) {
// This is really just the 32-bit 2s-complement negation of `tag`, but
// coded so as to ensure we cannot overflow or underflow.
*extraInhabitantIndex = static_cast<int>(
std::numeric_limits<uint32_t>::max()
- (tag & std::numeric_limits<uint32_t>::max())
+ 1);
} else {
// XIs are coded starting from the highest value that fits
// E.g., for 1-byte tag, tag 255 == XI #0, tag 254 == XI #1, etc.
unsigned maxTag = (1U << (TagSize * 8U)) - 1;
*extraInhabitantIndex = maxTag - tag;
}
return true;
} else {
// Multipayload enums that don't use a separate tag never
// export XIs.
return false;
}
}
}
return false;
}
Expand Down Expand Up @@ -1260,7 +1304,7 @@ class EnumTypeInfoBuilder {
Size += tagCounts.numTagBytes;
// Dynamic multi-payload enums use the tag representations not assigned
// to cases for extra inhabitants.
if (tagCounts.numTagBytes >= 32) {
if (tagCounts.numTagBytes >= 4) {
NumExtraInhabitants = ValueWitnessFlags::MaxNumExtraInhabitants;
} else {
NumExtraInhabitants =
Expand Down
24 changes: 24 additions & 0 deletions validation-test/Reflection/reflect_Enum_TwoCaseTwoPayloads.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,30 @@ reflect(object: ClassWithTwoCaseTwoPayloadsEnum())
// CHECK-32: (case name=none index=1)))
// CHECK-32: (case name=none index=1))))

reflect(enumValue: TwoCaseTwoPayloadsEnum.valid(Marker()))

// CHECK-64: Reflecting an enum value.
// CHECK-64-NEXT: Type reference:
// CHECK-64-NEXT: (enum reflect_Enum_TwoCaseTwoPayloads.TwoCaseTwoPayloadsEnum)
// CHECK-64-NEXT: Value: .valid(_)

// CHECK-32: Reflecting an enum value.
// CHECK-32-NEXT: Type reference:
// CHECK-32-NEXT: (enum reflect_Enum_TwoCaseTwoPayloads.TwoCaseTwoPayloadsEnum)
// CHECK-32-NEXT: Value: .valid(_)

reflect(enumValue: TwoCaseTwoPayloadsEnum.invalid(7))

// CHECK-64: Reflecting an enum value.
// CHECK-64-NEXT: Type reference:
// CHECK-64-NEXT: (enum reflect_Enum_TwoCaseTwoPayloads.TwoCaseTwoPayloadsEnum)
// CHECK-64-NEXT: Value: .invalid(_)

// CHECK-32: Reflecting an enum value.
// CHECK-32-NEXT: Type reference:
// CHECK-32-NEXT: (enum reflect_Enum_TwoCaseTwoPayloads.TwoCaseTwoPayloadsEnum)
// CHECK-32-NEXT: Value: .invalid(_)

doneReflecting()

// CHECK-64: Done.
Expand Down
101 changes: 101 additions & 0 deletions validation-test/Reflection/reflect_Enum_value.swift
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,107 @@ reflect(enumValue: OneIndirectPayload.leafF)
// CHECK-NEXT: (enum reflect_Enum_value.OneIndirectPayload)
// CHECK-NEXT: Value: .leafF

enum MultiPayload {
case stampA
case envelopeA(Int64)
case stampB
case envelopeB(Double)
case stampC
case envelopeC((Int32, Int32))
case stampD
case stampE
}

enum SinglePayloadEnumWithMultiPayloadEnumPayload {
case payloadA(MultiPayload)
case alsoA
case alsoB
case alsoC
case alsoD
}

reflect(enumValue: SinglePayloadEnumWithMultiPayloadEnumPayload.payloadA(.stampB))

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithMultiPayloadEnumPayload)
// CHECK-NEXT: Value: .payloadA(.stampB)

reflect(enumValue: SinglePayloadEnumWithMultiPayloadEnumPayload.payloadA(.envelopeC((1,2))))

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithMultiPayloadEnumPayload)
// CHECK-NEXT: Value: .payloadA(.envelopeC(_))

reflect(enumValue: SinglePayloadEnumWithMultiPayloadEnumPayload.alsoC)

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithMultiPayloadEnumPayload)
// CHECK-NEXT: Value: .alsoC

reflect(enumValue: Optional<Optional<SinglePayloadEnumWithMultiPayloadEnumPayload>>.some(.none))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test, in particular, verifies that XIs are being properly counted in the multi-payload enum for use by the surrounding single-payload optionals.


// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (bound_generic_enum Swift.Optional
// CHECK-NEXT: (bound_generic_enum Swift.Optional
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithMultiPayloadEnumPayload)))
// CHECK-NEXT: Value: .some(.none)

enum SmallMultiPayload {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code internally handles payloads with size >= 4 in a different path (to avoid overflow issues on 32-bit platforms). So the test above is repeated with a multi-payload enum that has a 2-byte payload area.

case stampA
case envelopeA(Int8)
case stampB
case envelopeB(Int16)
case stampC
case envelopeC((UInt8, Int8))
case stampD
case stampE
}

enum SinglePayloadEnumWithSmallMultiPayloadEnumPayload {
case payloadA(SmallMultiPayload)
case alsoA
case alsoB
case alsoC
case alsoD
}

reflect(enumValue: SinglePayloadEnumWithSmallMultiPayloadEnumPayload.payloadA(.stampB))

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithSmallMultiPayloadEnumPayload)
// CHECK-NEXT: Value: .payloadA(.stampB)

reflect(enumValue: SinglePayloadEnumWithSmallMultiPayloadEnumPayload.payloadA(.envelopeC((1,2))))

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithSmallMultiPayloadEnumPayload)
// CHECK-NEXT: Value: .payloadA(.envelopeC(_))

reflect(enumValue: SinglePayloadEnumWithSmallMultiPayloadEnumPayload.alsoC)

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithSmallMultiPayloadEnumPayload)
// CHECK-NEXT: Value: .alsoC

reflect(enumValue: Optional<Optional<SinglePayloadEnumWithSmallMultiPayloadEnumPayload>>.some(.none))

// CHECK: Reflecting an enum value.
// CHECK-NEXT: Type reference:
// CHECK-NEXT: (bound_generic_enum Swift.Optional
// CHECK-NEXT: (bound_generic_enum Swift.Optional
// CHECK-NEXT: (enum reflect_Enum_value.SinglePayloadEnumWithSmallMultiPayloadEnumPayload)))
// CHECK-NEXT: Value: .some(.none)


// XXX TODO: Multipayload enums with pointer payloads


// XXX TODO: test enum with thin function payload XXX

Expand Down
16 changes: 4 additions & 12 deletions validation-test/Reflection/reflect_Enum_values.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1575,12 +1575,9 @@ reflect(enum: ManyCasesOneStringPayload.payload("hello, world"))
// CHECK32-NEXT: (case name=otherC index=3))

// CHECKALL: Enum value:
// CHECK64-NEXT: (enum_value name=payload index=0
// CHECK64-NEXT: (struct Swift.String)
// CHECK64-NEXT: )

// XXX Note: 32-bit String contains a multi_payload_enum which projectEnumValue cannot yet handle.
// CHECK32-NEXT: swift_reflection_projectEnumValue failed.
// CHECKALL-NEXT: (enum_value name=payload index=0
// CHECKALL-NEXT: (struct Swift.String)
// CHECKALL-NEXT: )

reflect(enum: ManyCasesOneStringPayload.otherB)

Expand Down Expand Up @@ -1646,12 +1643,7 @@ reflect(enum: ManyCasesOneStringPayload.otherB)


// CHECKALL: Enum value:
// CHECK64-NEXT: (enum_value name=otherB index=2)

// XXX Note: 32-bit String contains a multi_payload_enum which projectEnumValue cannot yet handle.
// CHECK32-NEXT: swift_reflection_projectEnumValue failed.

//reflect(enum: ManyCasesManyPayloads.a("hi, world"))
// CHECKALL-NEXT: (enum_value name=otherB index=2)

doneReflecting()

Expand Down