-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Implement MultiPayloadEnum support for projectEnumValue #30635
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
Conversation
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 will allow us to return correct "don't know" answers 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. The current code that computes the spare bit mask for the third type above has some flaws that need to be understood. Next step: Figure out spare bit mask computation. Any cases we can compute correctly based on the information available at runtime can be handled by one of the above three TI classes. For the remaining cases, we'll probably have to divert to "Unsupported" until we can arrange for the compiler to provide us with augmented data.
This is "WIP" because there's still some work needed around the spare bit mask computation. The basic implementation I have here is sufficient to test everything on x86_64 and 32-bit platforms. In particular, there seems to be some question about whether visibility information that's only known to the compiler needs to be considered here. There are a number of possible ways forward:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've peppered some expository comments throughout the code to help orient reviewers.
} | ||
return EnumTI->projectEnumValue(getReader(), EnumAddress, CaseIndex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@slavapestov Note that I've pushed all the real knowledge down out of the ReflectionContext and into new TypeInfo classes that are private to TypeLowering.cpp. I believe this is essentially the approach you were suggesting.
@@ -209,6 +215,65 @@ class RecordTypeInfo : public TypeInfo { | |||
} | |||
}; | |||
|
|||
/// Enums | |||
class EnumTypeInfo : public TypeInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided it made the most sense for the new EnumTypeInfo
to be a sibling of RecordTypeInfo
, rather than a subclass. Enums have cases and need to be inspected to determine the active case, which makes them different than records.
@@ -231,6 +231,8 @@ int swift_reflection_projectExistential(SwiftReflectionContextRef ContextRef, | |||
/// | |||
/// Takes the address and typeref for an enum and determines the | |||
/// index of the currently-selected case within the enum. | |||
/// You can use this index with `swift_reflection_childOfTypeRef` | |||
/// to get detailed information about the specific case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @slavapestov pointed out, the getEnumCaseTypeRef
wasn't needed, so it's been dropped.
return false; | ||
} | ||
|
||
class UnsupportedEnumTypeInfo: public EnumTypeInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first of several private subclasses of EnumTypeInfo
that contain decoding knowledge for specific type of enum. I experimented with trying to unify enum decoding but that got unduly complex. Splitting out the different cases here dramatically simplified things.
} | ||
}; | ||
|
||
class EmptyEnumTypeInfo: public EnumTypeInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Enum type info classes are broken more finely than just no/single/multi payload. Separating out "unsupported", "empty", and "trivial" enums into their own classes eliminated checks for those cases from other parts of the code.
template<typename IntegerType> | ||
bool readMaskedInteger(remote::MemoryReader &reader, | ||
remote::RemoteAddress address, | ||
IntegerType *dest) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This uses a memory reader to gather bits from a payload into a single integer.
|
||
// General multi-payload enum support for enums that do use spare | ||
// bits in the payload. | ||
class MultiPayloadEnumTypeInfo: public EnumTypeInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MP enums that do not use spare bits can rely on the much simpler SimpleMultiPayloadEnumTypeInfo
above.
BitMask spareBitsMask(PayloadSize); | ||
auto validSpareBitsMask = populateSpareBitsMask(Cases, spareBitsMask); | ||
|
||
if (!validSpareBitsMask) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the critical logic that determines how to handle a particular MPE based on our current understanding.
@@ -1522,21 +2144,19 @@ class LowerType | |||
if (auto *ReferenceTI = dyn_cast<ReferenceTypeInfo>(TI)) | |||
return TC.getReferenceTypeInfo(Kind, ReferenceTI->getReferenceCounting()); | |||
|
|||
if (auto *EnumTI = dyn_cast<EnumTypeInfo>(TI)) { | |||
if (EnumTI->isOptional() && Kind == ReferenceKind::Weak) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the previous version of this code just used SubKind == SinglePayloadEnum
as a test for something being an Optional. I've factored that out in order to make it easier to refine this decision.
Rather than have the runtime try to recompute spare bit information from the payload types, it might be simpler to encode the spare bits used by a particular enum in its metadata, since we never use spare bits for runtime type layout and always know them at compile time. Since this also means that the spare bit mask would never vary among different generic instantiations of a generic enum, we could add the information to the enum's type context descriptor, which is shared among all generic instantiations and lives in |
@jckarter I understood that generic MPEs never used the spare bit strategy, so that part already doesn't vary with different instantiations. @slavapestov also suggested adding spare bit info to the type metadata for fixed-layout MPEs that use it. I'm not sure if that would be simpler or not; my current code to walk the type tree and accumulate spare bit info is only a couple dozen lines of code, though I need to do some more tests to see if I missed any cases. My only real concern revolves around whether the compiler chooses MPE strategies based on information that's currently completely missing from the runtime metadata (such as visibility information). |
I'm pretty sure the layout in its full generality does rely on information that's not available to the runtime. Encoding the spare bit info in the enum metadata would also be more robust if we decide to rev the layout algorithm for newly-defined enums eventually too. |
"information that's not available to the runtime" -- Can you be more specific? I'd like to have a test case to verify that the RemoteMirror code fails on cases that it should not be able to handle. |
To my best recollection, we get spare bits from the high byte of 64-bit object or function pointers, from the unused bits in enums, from padding between struct/tuple elements, and from the unused high bits of |
…depended on it The experimental spare bits calculation helped to validate the structure of this code and the handling of various multi-payload enum details, but it cannot be done here in a way that will always agree with the compiler. So I've backed that out: The result can still handle certain multi-payload enums (those with dynamic layouts that can never utilize spare bits) but the remainder will require some additional work for the compiler to expose the real spare bit data for use in the runtime.
@swift-ci Please test |
Build failed |
I believe I've resolved the spare bits question sufficient to make this mergeable in its current state. As such, I've removed the "WIP" from the PR title and plan to merge it as soon as it can pass tests. |
@swift-ci Please test |
Build failed |
@swift-ci Please test Linux |
Build failed |
@swift-ci Please test Linux |
Build failed |
@swift-ci Please test |
Build failed |
Build failed |
@swift-ci Please smoke test Linux |
@swift-ci Please test Linux |
1 similar comment
@swift-ci Please test Linux |
Build failed |
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 multi-payload enums. These classes are mostly private to the TypeLowering.cpp source file; the only change visible outside of that is a new baseEnumTypeInfo
that is a sibling ofRecordTypeInfo
. Three of these new classes are used for MultiPayload enum support:"Unsupported" enums that we don't know how to project. This TI returns "don't know" answers for any request to project an enum value or an XI but otherwise has correct layout and case data.
"Simple" MP Enums that only use a separate tag value. This includes dynamically-laid-out generic enums, as well as enums whose payloads have no spare bits.
The general case of 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. In particular, there is still some work to robustly compute the spare bit mask for the third type above.
Resolves rdar://31666592