Skip to content

Commit 3c8fde7

Browse files
authored
Implement MultiPayloadEnum support for projectEnumValue (#30635)
This code rearchitects and simplifies the projectEnumValue support by introducing a new `TypeInfo` subclass for each kind of enum, including trivial, no-payload, single-payload, and three different classes for multi-payload enums: * "UnsupportedEnum" that we don't understand. This returns "don't know" answers for all requests in cases where the runtime lacks enough information to accurately handle a particular enum. * MP Enums that only use a separate tag value. This includes generic enums and other dynamic layouts, as well as enums whose payloads have no spare bits. * MP Enums that use spare bits, possibly in addition to a separate tag. This logic can only be used, of course, if we can in fact compute a spare bit mask that agrees with the compiler. The final challenge is to choose one of the above three handlings for every MPE. Currently, we do not have an accurate source of information for the spare bit mask, so we never choose the third option above. We use the second option for dynamic MPE layouts (including generics) and the first for everything else. TODO: Once we can arrange for the compiler to expose spare bit mask data, we'll be able to use that to drive more MPE cases.
1 parent 8d68607 commit 3c8fde7

File tree

12 files changed

+1104
-548
lines changed

12 files changed

+1104
-548
lines changed

include/swift/Reflection/ReflectionContext.h

Lines changed: 18 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -711,176 +711,32 @@ class ReflectionContext
711711
}
712712
}
713713

714+
/// Projects the value of an enum.
715+
///
716+
/// Takes the address and typeref for an enum and determines the
717+
/// index of the currently-selected case within the enum.
718+
/// You can use this index with `swift_reflection_childOfTypeRef`
719+
/// to get detailed information about the specific case.
720+
///
721+
/// Returns true if the enum case could be successfully determined. In
722+
/// particular, note that this code may return false for valid in-memory data
723+
/// if the compiler used a strategy we do not yet understand.
714724
bool projectEnumValue(RemoteAddress EnumAddress,
715725
const TypeRef *EnumTR,
716726
int *CaseIndex) {
717-
if (EnumTR == nullptr)
727+
// Get the TypeInfo and sanity-check it
728+
if (EnumTR == nullptr) {
718729
return false;
719-
auto EnumTI = getTypeInfo(EnumTR);
720-
if (EnumTI == nullptr)
721-
return false;
722-
723-
auto EnumRecordTI = dyn_cast<const RecordTypeInfo>(EnumTI);
724-
if (EnumRecordTI == nullptr)
725-
return false;
726-
auto EnumSize = EnumRecordTI->getSize();
727-
728-
auto Fields = EnumRecordTI->getFields();
729-
auto FieldCount = Fields.size();
730-
if (FieldCount == 0) {
731-
return false; // No fields?
732-
}
733-
if (FieldCount == 1) {
734-
*CaseIndex = 0; // Only possible field
735-
return true;
736-
}
737-
738-
switch (EnumRecordTI->getRecordKind()) {
739-
740-
case RecordKind::NoPayloadEnum: {
741-
if (EnumSize == 0) {
742-
*CaseIndex = 0;
743-
return true;
744-
}
745-
return getReader().readInteger(EnumAddress, EnumSize, CaseIndex);
746-
}
747-
748-
case RecordKind::SinglePayloadEnum: {
749-
FieldInfo PayloadCase = Fields[0];
750-
if (!PayloadCase.TR)
751-
return false;
752-
unsigned long NonPayloadCaseCount = FieldCount - 1;
753-
unsigned long PayloadExtraInhabitants = PayloadCase.TI.getNumExtraInhabitants();
754-
unsigned Discriminator = 0;
755-
auto PayloadSize = PayloadCase.TI.getSize();
756-
if (NonPayloadCaseCount >= PayloadExtraInhabitants) {
757-
// There are more cases than inhabitants, we need a separate discriminator.
758-
auto TagInfo = getEnumTagCounts(PayloadSize, NonPayloadCaseCount, 1);
759-
auto TagSize = TagInfo.numTagBytes;
760-
auto TagAddress = RemoteAddress(EnumAddress.getAddressData() + PayloadSize);
761-
if (!getReader().readInteger(TagAddress, TagSize, &Discriminator)) {
762-
printf(">>>> readXI failed to read discriminator\n\n");
763-
return false;
764-
}
765-
}
766-
767-
if (PayloadSize == 0) {
768-
// Payload carries no information, so discriminator fully determines the case
769-
*CaseIndex = Discriminator;
770-
return true;
771-
} else if (Discriminator == 0) {
772-
// The payload area carries all the information...
773-
if (PayloadExtraInhabitants == 0) {
774-
*CaseIndex = 0;
775-
return true;
776-
}
777-
int XITag = 0;
778-
if (!PayloadCase.TI.readExtraInhabitantIndex(getReader(), EnumAddress, &XITag)) {
779-
return false;
780-
}
781-
if (XITag < 0) { // Valid (not extra) inhabitant
782-
*CaseIndex = 0; // Payload case is always #0
783-
return true;
784-
} else if ((unsigned)XITag <= NonPayloadCaseCount) {
785-
*CaseIndex = XITag + 1;
786-
return true;
787-
}
788-
return false;
789-
} else {
790-
// No payload: Payload area is reused for more cases
791-
uint32_t PayloadTag = 0;
792-
auto PayloadTagSize = std::min(PayloadSize, decltype(PayloadSize)(sizeof(PayloadTag)));
793-
if (!getReader().readInteger(EnumAddress, PayloadTagSize, &PayloadTag)) {
794-
return false;
795-
}
796-
auto XICases = 1U + PayloadExtraInhabitants; // Cases coded with XIs when discriminator = 0
797-
auto PayloadCases = 1U << (PayloadTagSize * 8U);
798-
*CaseIndex = XICases + (Discriminator - 1) * PayloadCases + PayloadTag;
799-
return true;
800-
}
801730
}
802-
803-
case RecordKind::MultiPayloadEnum: {
804-
// Collect basic statistics about the enum
805-
unsigned long PayloadCaseCount = 0;
806-
unsigned long NonPayloadCaseCount = 0;
807-
unsigned long PayloadSize = 0;
808-
for (auto Field : Fields) {
809-
if (Field.TR != 0) {
810-
PayloadCaseCount += 1;
811-
if (Field.TI.getSize() > PayloadSize) {
812-
PayloadSize = Field.TI.getSize();
813-
}
814-
} else {
815-
NonPayloadCaseCount += 1;
816-
}
817-
}
818-
if (EnumSize > PayloadSize) {
819-
// If the compiler laid this out with a separate tag, use that.
820-
unsigned tag = 0;
821-
auto TagSize = EnumSize - PayloadSize;
822-
auto TagAddress = remote::RemoteAddress(EnumAddress.getAddressData() + PayloadSize);
823-
if (!getReader().readInteger(TagAddress, TagSize, &tag)
824-
|| tag >= Fields.size()) {
825-
return false;
826-
}
827-
if (tag < PayloadCaseCount) {
828-
*CaseIndex = tag;
829-
return true;
830-
}
831-
auto PayloadTagSize = std::min(PayloadSize, 4UL);
832-
// Treat the tag as a page selector; payload carries the offset within the page
833-
auto Page = tag - PayloadCaseCount;
834-
// Zero for 32-bit because we'll never have more than one page
835-
auto PageSize = PayloadTagSize >= 4 ? 0 : 1 << (PayloadSize * 8U);
836-
auto PageStart = Page * PageSize;
837-
unsigned PayloadTag;
838-
if (!getReader().readInteger(EnumAddress, PayloadTagSize, &PayloadTag)) {
839-
return false;
840-
}
841-
*CaseIndex = PageStart + PayloadTag + PayloadCaseCount;
842-
return true;
843-
} else {
844-
// XXX TODO: If the payloads have common spare bits (e.g., all pointers)
845-
// then use those to decode the case.
846-
return false;
847-
}
848-
break;
849-
}
850-
851-
default:
852-
// Unknown record kind.
853-
break;
854-
}
855-
return false;
856-
}
857-
858-
bool getEnumCaseTypeRef(const TypeRef *EnumTR,
859-
unsigned CaseIndex,
860-
std::string &Name,
861-
const TypeRef **OutPayloadTR) {
862-
*OutPayloadTR = nullptr;
863-
864-
if (EnumTR == nullptr)
865-
return false;
866-
867-
auto EnumTI = getTypeInfo(EnumTR);
868-
if (EnumTI == nullptr)
731+
auto TI = getTypeInfo(EnumTR);
732+
if (TI == nullptr) {
869733
return false;
870-
871-
auto EnumRecordTI = dyn_cast<const RecordTypeInfo>(EnumTI);
872-
if (EnumRecordTI == nullptr)
873-
return false;
874-
875-
auto NumCases = EnumRecordTI->getNumFields();
876-
if (CaseIndex >= NumCases) {
734+
}
735+
auto EnumTI = dyn_cast<const EnumTypeInfo>(TI);
736+
if (EnumTI == nullptr){
877737
return false;
878-
} else {
879-
const auto Case = EnumRecordTI->getFields()[CaseIndex];
880-
Name = Case.Name;
881-
*OutPayloadTR = Case.TR;
882-
return true;
883738
}
739+
return EnumTI->projectEnumValue(getReader(), EnumAddress, CaseIndex);
884740
}
885741

886742
/// Return a description of the layout of a value with the given type.

include/swift/Reflection/TypeLowering.h

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ class EnumTypeInfoBuilder;
4242
class RecordTypeInfoBuilder;
4343
class ExistentialTypeInfoBuilder;
4444

45+
enum class EnumKind : unsigned {
46+
// An enum with no payload cases. The record will have no fields, but
47+
// will have the correct size.
48+
NoPayloadEnum,
49+
50+
// An enum with a single payload case and zero or more no-payload
51+
// cases. The no-payload cases may be encoded with an extra tag
52+
// byte or as invalid payload values ("extra inhabitants").
53+
SinglePayloadEnum,
54+
55+
// An enum with multiple payload cases and zero or more non-payload
56+
// cases. The selector that indicates what case is currently active
57+
// may be encoded in unused "spare bits" common to all payloads and/or
58+
// may use a separate tag byte.
59+
MultiPayloadEnum,
60+
};
61+
4562
enum class RecordKind : unsigned {
4663
Invalid,
4764

@@ -51,18 +68,6 @@ enum class RecordKind : unsigned {
5168
// A Swift struct type.
5269
Struct,
5370

54-
// An enum with no payload cases. The record will have no fields, but
55-
// will have the correct size.
56-
NoPayloadEnum,
57-
58-
// An enum with a single payload case. The record consists of a single
59-
// field, being the enum payload.
60-
SinglePayloadEnum,
61-
62-
// An enum with multiple payload cases. The record consists of a multiple
63-
// fields, one for each enum payload.
64-
MultiPayloadEnum,
65-
6671
// A Swift-native function is always a function pointer followed by a
6772
// retainable, nullable context pointer.
6873
ThickFunction,
@@ -107,6 +112,7 @@ enum class TypeInfoKind : unsigned {
107112
Record,
108113
Reference,
109114
Invalid,
115+
Enum,
110116
};
111117

112118
class TypeInfo {
@@ -209,6 +215,65 @@ class RecordTypeInfo : public TypeInfo {
209215
}
210216
};
211217

218+
/// Enums
219+
class EnumTypeInfo : public TypeInfo {
220+
EnumKind SubKind;
221+
std::vector<FieldInfo> Cases;
222+
223+
protected:
224+
EnumTypeInfo(unsigned Size, unsigned Alignment,
225+
unsigned Stride, unsigned NumExtraInhabitants,
226+
bool BitwiseTakable,
227+
EnumKind SubKind, const std::vector<FieldInfo> &Cases)
228+
: TypeInfo(TypeInfoKind::Enum, Size, Alignment, Stride,
229+
NumExtraInhabitants, BitwiseTakable),
230+
SubKind(SubKind), Cases(Cases) {}
231+
232+
public:
233+
EnumKind getEnumKind() const { return SubKind; }
234+
const std::vector<FieldInfo> &getCases() const { return Cases; }
235+
unsigned getNumCases() const { return Cases.size(); }
236+
unsigned getNumPayloadCases() const {
237+
auto Cases = getCases();
238+
return std::count_if(Cases.begin(), Cases.end(),
239+
[](const FieldInfo &Case){return Case.TR != 0;});
240+
}
241+
// Size of the payload area.
242+
unsigned getPayloadSize() const {
243+
return EnumTypeInfo::getPayloadSizeForCases(Cases);
244+
}
245+
246+
static unsigned getPayloadSizeForCases(const std::vector<FieldInfo> &Cases) {
247+
unsigned size = 0;
248+
for (auto Case : Cases) {
249+
if (Case.TR != 0 && Case.TI.getSize() > size) {
250+
size = Case.TI.getSize();
251+
}
252+
}
253+
return size;
254+
}
255+
256+
// Returns true if this enum is `Optional`
257+
// (This was factored out of a piece of code that was just
258+
// checking the EnumKind. This is vastly better than that,
259+
// but could probably be improved further.)
260+
bool isOptional() const {
261+
return
262+
SubKind == EnumKind::SinglePayloadEnum
263+
&& Cases.size() == 2
264+
&& Cases[0].Name == "some"
265+
&& Cases[1].Name == "none";
266+
}
267+
268+
virtual bool projectEnumValue(remote::MemoryReader &reader,
269+
remote::RemoteAddress address,
270+
int *CaseIndex) const = 0;
271+
272+
static bool classof(const TypeInfo *TI) {
273+
return TI->getKind() == TypeInfoKind::Enum;
274+
}
275+
};
276+
212277
/// References to classes, closure contexts and anything else with an
213278
/// 'isa' pointer
214279
class ReferenceTypeInfo : public TypeInfo {

include/swift/Remote/MemoryReader.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,29 @@ class MemoryReader {
6262
}
6363

6464
/// Attempts to read an integer of the specified size from the given
65-
/// address in the remote process.
65+
/// address in the remote process. Following `storeEnumElement`
66+
/// in EnumImpl.h, this reads arbitrary-size integers by ignoring
67+
/// high-order bits that are outside the range of `IntegerType`.
6668
///
67-
/// Returns false if the operation failed, or the request size is
68-
/// larger than the provided destination.
69+
/// Returns false if the operation failed.
6970
template <typename IntegerType>
7071
bool readInteger(RemoteAddress address, size_t bytes, IntegerType *dest) {
7172
*dest = 0;
72-
if ((bytes > sizeof(IntegerType))
73-
|| !readBytes(address, (uint8_t *)dest, bytes)) {
74-
return false;
75-
}
73+
size_t readSize = std::min(bytes, sizeof(IntegerType));
7674
// FIXME: Assumes host and target have the same endianness.
7775
// TODO: Query DLQ for endianness of target, compare to endianness of host.
7876
#if defined(__BIG_ENDIAN__)
79-
*dest >>= (sizeof(IntegerType) - bytes);
77+
// Read low-order bits of source ...
78+
if (!readBytes(address + (bytes - readSize), (uint8_t *)dest, readSize)) {
79+
return false;
80+
}
81+
// ... align result to low-order bits of *dest
82+
*dest >>= 8 * (sizeof(IntegerType) - readSize);
83+
#else
84+
// Read from low-order bytes of integer
85+
if (!readBytes(address, (uint8_t *)dest, readSize)) {
86+
return false;
87+
}
8088
#endif
8189
return true;
8290
}

include/swift/Remote/RemoteAddress.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ class RemoteAddress {
4747
uint64_t getAddressData() const {
4848
return Data;
4949
}
50+
51+
template<typename IntegerType>
52+
RemoteAddress& operator+=(const IntegerType& rhs) {
53+
Data += rhs;
54+
return *this;
55+
}
56+
57+
template<typename IntegerType>
58+
friend RemoteAddress operator+(RemoteAddress lhs,
59+
const IntegerType& rhs) {
60+
return lhs += rhs;
61+
}
5062
};
5163

5264
/// A symbolic relocated absolute pointer value.

include/swift/SwiftRemoteMirror/SwiftRemoteMirror.h

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ int swift_reflection_projectExistential(SwiftReflectionContextRef ContextRef,
241241
///
242242
/// Takes the address and typeref for an enum and determines the
243243
/// index of the currently-selected case within the enum.
244+
/// You can use this index with `swift_reflection_childOfTypeRef`
245+
/// to get detailed information about the specific case.
244246
///
245247
/// Returns true if the enum case could be successfully determined.
246248
/// In particular, note that this code may fail for valid in-memory data
@@ -251,21 +253,6 @@ int swift_reflection_projectEnumValue(SwiftReflectionContextRef ContextRef,
251253
swift_typeref_t EnumTypeRef,
252254
int *CaseIndex);
253255

254-
/// Finds information about a particular enum case.
255-
///
256-
/// Given an enum typeref and index of a case, returns:
257-
/// * Typeref of the associated payload or zero if there is no payload
258-
/// * Name of the case if known.
259-
///
260-
/// The Name points to a freshly-allocated C string on the heap. You
261-
/// are responsible for freeing the string when you are finished.
262-
SWIFT_REMOTE_MIRROR_LINKAGE
263-
int swift_reflection_getEnumCaseTypeRef(SwiftReflectionContextRef ContextRef,
264-
swift_typeref_t EnumTypeRef,
265-
int CaseIndex,
266-
char **CaseName,
267-
swift_typeref_t *PayloadTypeRef);
268-
269256
/// Dump a brief description of the typeref as a tree to stderr.
270257
SWIFT_REMOTE_MIRROR_LINKAGE
271258
void swift_reflection_dumpTypeRef(swift_typeref_t OpaqueTypeRef);

0 commit comments

Comments
 (0)