Skip to content

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

Merged
merged 5 commits into from
Mar 31, 2020

Conversation

tbkka
Copy link
Contributor

@tbkka tbkka commented Mar 25, 2020

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 base EnumTypeInfo that is a sibling of RecordTypeInfo. 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

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.
@tbkka
Copy link
Contributor Author

tbkka commented Mar 25, 2020

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:

  1. Query the memory reader to get target-specific data. This would be a simple way to extend the current code to support all targets. If that's sufficient, it would be an excellent answer.
  2. Identify cases that cannot be handled correctly and divert them to UnsupportedEnumTypeInfo which fails all enum projection requests. That will allow the cases that do work to become available to consumers.
  3. Teach the compiler to add more metadata for use by the runtime so that the runtime can produce spare bit mask information for all cases. Of course, I'd like to reduce the memory impact of this.

@tbkka tbkka requested review from mikeash and slavapestov March 25, 2020 16:46
Copy link
Contributor Author

@tbkka tbkka left a 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);
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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.
Copy link
Contributor Author

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 {
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 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 {
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 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 {
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 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 {
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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.

@jckarter
Copy link
Contributor

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 __TEXT,__const, which should reduce the runtime memory impact of the added metadata.

@tbkka
Copy link
Contributor Author

tbkka commented Mar 25, 2020

@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).

@jckarter
Copy link
Contributor

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.

@tbkka
Copy link
Contributor Author

tbkka commented Mar 25, 2020

"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.

@jckarter
Copy link
Contributor

"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 Builtin.IntNN fields when NN is not a power of two. Of those, I guess only the Builtin.IntNN case is strictly not reconstructable from runtime information. However, it seems like we would have to reimplement a lot of IRGen in order to be able to reconstruct the spare bit info for enum/struct padding.

…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.
@tbkka
Copy link
Contributor Author

tbkka commented Mar 26, 2020

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - cfed291

@tbkka tbkka changed the title WIP: Implement MultiPayloadEnum support for projectEnumValue Implement MultiPayloadEnum support for projectEnumValue Mar 26, 2020
@tbkka
Copy link
Contributor Author

tbkka commented Mar 26, 2020

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.

@tbkka
Copy link
Contributor Author

tbkka commented Mar 26, 2020

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 2ad9bec

@tbkka
Copy link
Contributor Author

tbkka commented Mar 30, 2020

@swift-ci Please test Linux

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 2ad9bec

@tbkka
Copy link
Contributor Author

tbkka commented Mar 30, 2020

@swift-ci Please test Linux

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 2ad9bec

@tbkka
Copy link
Contributor Author

tbkka commented Mar 31, 2020

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 2ad9bec

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - c42e15e

@tbkka
Copy link
Contributor Author

tbkka commented Mar 31, 2020

@swift-ci Please smoke test Linux

@tbkka
Copy link
Contributor Author

tbkka commented Mar 31, 2020

@swift-ci Please test Linux

1 similar comment
@tbkka
Copy link
Contributor Author

tbkka commented Mar 31, 2020

@swift-ci Please test Linux

@tbkka tbkka merged commit 3c8fde7 into swiftlang:master Mar 31, 2020
@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 481d09a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants