-
Notifications
You must be signed in to change notification settings - Fork 10.5k
In-place class metadata initialization #18694
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
In-place class metadata initialization #18694
Conversation
6879294
to
153db56
Compare
@rjmccall This is still a work in progress but it's coming together. I think I got the new in-place classes working with two-phase initialization also. I'm still trying to figure out how to port classes with fully resilient ancestry to the two-phase scheme. For the in-place case I cargo-culted InPlaceValueMetadataInitialization and made it work with classes as well. For resilient classes, should I add a new ResilientClassMetadataInitialization that will do something special somehow? Once that is done, I can remove |
I've been thinking of this as a special case of "in-place" initialization that happens to need allocation — maybe that means "in-place" is the wrong word and it should really be "singleton" or something. Anyway, I'd prefer if this didn't need a new MetadataInitializationKind because we're already using 3 of 4 values for that and the last should really be future-proofed. |
lib/IRGen/GenClass.cpp
Outdated
|
||
bool irgen::doesClassMetadataRequireInitialization(IRGenModule &IGM, | ||
ClassDecl *theClass) { | ||
// Classes imported from Objective-C never requires dynamic initialization. |
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 comment (like the place you copied it from 😄) has a grammar error.
cc8b52e
to
49ba088
Compare
@swift-ci Please test |
@swift-ci Please test source compatibility |
@swift-ci Please test |
@swift-ci Please test source compatibility |
include/swift/ABI/Metadata.h
Outdated
/// The completion function. The pattern will always be null. | ||
TargetRelativeDirectPointer<Runtime, MetadataCompleter> | ||
CompletionFunction; | ||
}; |
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.
If you're going to use these for classes, maybe Value
should be dropped from the class name.
Should the link to the resilient class pattern just be unioned with IncompleteMetadata
?
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.
Unioning the resilient pattern with IncompleteMetadata sounds like a good idea. I'll do that.
I also plan on two follow-up cleanups here:
- renaming 'InPlaceValueMetadata' and similar to 'SingletonMetadata' throughout.
- switching non-generic resilient classes to start from a true-const pattern and not a partial class metadata.
Is it OK if I do the second one in a follow-up PR?
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.
Sure. Do you consider this PR ready for a more complete review?
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.
Yes, except I'm redoing the IncompleteMetadata field part.
Build failed |
@jrose-apple You might be interested in the behavior change with categories, and the fact that there are now two kinds of resilient classes. |
lib/IRGen/GenClass.cpp
Outdated
return (ClassHasMissingMembers || | ||
ClassHasResilientMembers || | ||
ClassHasResilientAncestry || | ||
ClassHasGenericAncestry); | ||
} | ||
|
||
bool doesMetadataRequireRelocation() const { | ||
return ClassHasResilientAncestry; |
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.
Your commit comment implies that the class itself being generic would affect this too. Does the question just not get asked in that 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.
Generic classes go down a totally separate code path. Note that the top-level irgen::doesClassMetadataRequireRelocation() method does return true for generic classes. I can add a comment here.
let y = Derived(t: 100) | ||
|
||
// CHECK: 100 | ||
print(y.dynamicMethod()) |
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'm not sure I trust this test to invoke the method dynamically. Can you add a respondsToSelector check?
// CHECK: respondsToSelector? true
print("respondsToSelector? \(y.responds(to: #selector(Derived.dynamicMethod)))")
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.
Well, Derived
does not descend from NSObject
, which was intentional (remember when we had the resilient class hack in 4.2, and I only did it for @objc classes, and it broke categories).
However, using AnyObject dispatch also clearly demonstrates that its dynamically dispatched, so I'll do that instead.
And FWIW, we infer dynamic
on @objc
members of extensions, because you're supposed to be able to override them in subclasses.
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.
Yeah, I know it is actually testing all that, but I'd prefer it be a little more direct. AnyObject dispatch sounds good.
Build failed |
49ba088
to
17e514e
Compare
@swift-ci Please test |
Build failed |
Build failed |
@@ -3345,8 +3408,11 @@ class TargetTypeContextDescriptor | |||
const TargetForeignMetadataInitialization<Runtime> & | |||
getForeignMetadataInitialization() const; | |||
|
|||
const TargetInPlaceValueMetadataInitialization<Runtime> & | |||
getInPlaceMetadataInitialization() 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.
The reason I didn't have this here (and called it InPlace*Value*MetadataInitialization
in the first place) is that I was anticipating that resilient classes might need more members, like a reference to a pattern. So before you unify them, you might want to consider whether you're just making more work for yourself to split them again in a follow-up patch. (It'd be better if value metadata didn't have to pay for an always-null reference to a pattern.)
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 think we can put the pattern in the class context descriptor, and use a trailing object for it so it’s only there for resilient classes.
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.
... or just say that IncompleteMetadata is either a metadata or a pattern (instead of metadata or function). That was sort of my plan here. I wasn’t going to add an always-null field.
include/swift/ABI/MetadataValues.h
Outdated
|
||
/// If true, don't initialize the vtable, assuming it has been statically | ||
/// emitted already. | ||
HasStaticVTable = 0x100, |
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 comment should be more descriptive than imperative. I assume this means that the v-table is guaranteed to have been initialized in the statically-emitted metadata?
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.
Done.
fnType = llvm::FunctionType::get(TypeMetadataPtrTy, argTys, | ||
/*isVarArg*/ false); | ||
} | ||
|
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.
Maybe these should be commented with the name of the corresponding typedef in the runtime.
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.
Done.
@@ -1226,6 +1206,19 @@ namespace { | |||
addVTable(); | |||
} | |||
|
|||
void addIncompleteMetadataOrRelocationFunction() { |
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.
In a world in which InPlaceClassMetadataInitialization
requires more fields than InPlaceValueMetadataInitialization
, I think a cleaner solution might be to have ClassMetadataBuilder
provide its own, independent definition of addInPlaceMetadataInitialization
which contains this logic.
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.
Should we actually add more fields to it, or have it point to a separate pattern structure in place of the incomplete metadata?
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.
Good question. The only reason we wouldn't want to just replace the IncompleteMetadata with a pattern reference is if there's a situation where we'd want an allocation function but not a pattern, and that just seems really unlikely to me. The reverse scenario — where we want a patten but not an allocation function, e.g. as a load-time optimization — is more plausible but doesn't need to be specially optimized because it's easy to fit a metadata pointer into the pattern — we can just union the allocation function in the pattern with a constant metadata, discriminated by some flag in the pattern (which needs flags anyway).
That does make me wonder, though, whether having a resilient superclass is the right condition to flag the presence of a pattern. Is there any other reasonable way to store this?
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.
We could just add another flag to the type context descriptor flags.
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.
Yeah. Or maybe make IncompleteMetadata a pointer union, since they're both pointers to structures now instead of one side being a pointer to code.
auto type = Type->getDeclaredTypeInContext()->getCanonicalType(); | ||
auto metadata = IGM.getAddrOfTypeMetadata(type); | ||
B.addRelativeAddress(metadata); | ||
} |
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 feel like this function ought to be called addIncompleteMetadata
. You can still have an addIncompleteMetadataOrRelocationFunction
that just calls it if that's the right design. But calling this the right thing will make the super-call to it much clearer.
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.
Sounds good
fullPattern -= pattern->getClassAddressPoint(); | ||
memcpy(bytes, fullPattern, patternSize); | ||
memset(bytes + patternSize, 0, | ||
metadataSize - patternSize); |
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.
If we're calling swift_initClassMetadata
properly on a relocated class now, I'm not sure how necessary this copy is. But I guess you were planning on reworking all this in a follow-up?
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.
Yes, I’m going to rework it. There’s actually very little useful data that gets copied here.
…two predicates - doesClassMetadataRequireRelocation() -- returns true if we must allocate new metadata at runtime and fill it in, because the class has multiple instantiations (generic case) or because the total size of the metadata is not known at compile time (resilient ancestry). - doesClassMetadataRequireInitialization() -- weaker condition than the above. It's true if the metadata must be relocated, but it is also true if the metadata has otherwise fixed size but must be filled in dynamically. This occurs if the class has generic ancestry but is itself not generic, or if the class has resiliently-sized fields, or missing members. For now, we don't actually care about the distinciton anywhere, because we cannot do in-place initialization of class metadata yet.
17e514e
to
2f80c78
Compare
…resiliently-sized fields If a class has generic ancestry or resiliently-sized fields, but is itself not generic and does not have resilient ancestry, we must perform runtime metadata initialization, but we can initialize the metadata in-place. As with generic classes or classes with resilient ancestry, we copy generic requirements and field offset vectors from the superclass. We also calculate the layout of the class's direct fields at runtime. Unlike the fully resilient case, we don't copy vtable entries from the superclass, or install the direct class's vtable entries from the type context descriptor. Instead, we statically emit the vtable as with fixed-size class metadata. Both the in-place and resilient case call the same runtime entry point to initialize class metadata; the new HasStaticVTable flag in ClassLayoutFlags is used to select between the two behaviors concerning the vtable.
…generic classes In-place initialization means the class has a symbol we can reference from the category, so there's nothing to do on the IRGen side. For JIT mode, we just need to realize the class metadata by calling an accessor instead of directly referencing the symbol though.
…tent" class initialization This is the simplest case, and not very interesting.
…asses Note that this patch also consolidates the recursive metadata tests into one place while adding an execution test.
…m the class descriptor Using the superclass metadata here no longer makes sense with two-phase init, in case the superclass metadata depends on the class being instantiated. It would also be nice to rework the resilient class metadata 'pattern' to be its own data structure that's true const, instead of just the prefix of a real class metadata, but for now let's keep the existing crappy design.
Now that we don't need the superclass before calling swift_relocateClassMetadata(), it seems simpler to set it here instead of doing it in various places in IRGen.
Similar to the non-resilient case, except we also emit a 'relocation function'. The class descriptor now contains this relocation function if the class has resilient ancestry, and the relocation function calls the runtime's swift_relocateClassMetadata() entry point. The metadata completion function calls swift_initClassMetadata() and does layout, just like the non-resilient case. Fixes <rdar://problem/40810002>.
2f80c78
to
1eed99d
Compare
@swift-ci Please test |
@swift-ci Please test source compatibility |
Up until Swift 4.2, classes with generic ancestry and classes with resilient fields used a metadata initialization scheme where the metadata was emitted statically and initialized at runtime with the right fields (a dynamic superclass in the generic case, dynamic field offsets in the resilient case). I removed this form of initialization while implementing the fully general case of resilient ancestry, where the runtime size of metadata is not known at compile time.
This PR brings back the intermediate in-place initialization style. The main benefit is that we can now define Objective-C categories on classes that use it. It will also allow subclasses of NSObject with resilient-sized fields to be exposed to Clang.
The latter half of this PR mostly restructures metadata initialization for non-generic classes -- both the in-place and resilient case -- to use the new two-phase initialization model, allowing recursive metadata cycles. Note that here, the metadata initialization code uses "in-place" to mean something different -- it means the type descriptor is globally unique, and there is only ever one instance of metadata for that type, but not necessarily that this metadata itself is emitted statically. This is confusing and I plan on renaming this to "singleton metadata" and similar throughout.