-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Dispatch initializers by their allocating entry point #19151
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
Dispatch initializers by their allocating entry point #19151
Conversation
@swift-ci Please test source compatibility |
loc, selfMeta.getValue(), SGF.SGM.getLoweredType(objcMetaType))); | ||
SILValue convertedValue; | ||
switch (metatypeRepPair(givenMetatypeRep, destMetatypeRep)) { | ||
case metatypeRepPair(MetatypeRepresentation::Thick, |
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 cute, but I don't think its more readable than just an if/else if chain :)
lib/SILGen/SILGenBridging.cpp
Outdated
@@ -1377,6 +1377,20 @@ static SILFunctionType *emitObjCThunkArguments(SILGenFunction &SGF, | |||
void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) { | |||
assert(thunk.isForeign); | |||
SILDeclRef native = thunk.asForeign(false); | |||
|
|||
// If we're calling a native non-designated class initializer, we have to | |||
// throw the `self` object we were given away to make a new one, since |
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.
To avoid confusion, maybe say 'deallocate' instead of 'throw'
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 you'll still need some work for MissingMemberDecl (it will get simpler), but this is looking pretty reasonable to me. I'm not the SIL expert, though.
@@ -5246,6 +5246,18 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) { | |||
// Dynamic methods are always accessed by objc_msgSend(). | |||
if (decl->isFinal() || decl->isDynamic() || decl->hasClangNode()) | |||
return false; | |||
|
|||
// Initializers are not normally inherited, but required initializers can |
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 confused by this "inherited". Requiring a new vtable entry also happens when the type changes.
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.
A convenience initializer is always effectively final
, though, so even if it overrides a designated init while changing type, it'll override an existing vtable entry, but never need a new one of its own.
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.
Ah, sure, but this comment is about all initializers. I would have expected something like "Only designated initializers and required
convenience initializers appear in the vtable; bail out if this is an initializer that is neither designated nor required
."
lib/SILGen/SILGenApply.cpp
Outdated
assert(isa<ClassDecl>(nominal) | ||
&& "some new kind of init context we haven't implemented"); | ||
useAllocatingCtor = static_cast<bool>(SGF.AllocatorMetatype) && | ||
!ctorRef->getDecl()->isObjC(); | ||
useAllocatingCtor = ctorRef->getDecl()->isImportAsMember() |
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 a different test. What's special about isImportAsMember
that IRGen needs to know about it? What's wrong with isFactoryInit()
?
lib/SILGen/SILGenConstructor.cpp
Outdated
@@ -485,7 +481,7 @@ void SILGenFunction::emitClassConstructorAllocator(ConstructorDecl *ctor) { | |||
// Allocate the 'self' value. | |||
bool useObjCAllocation = usesObjCAllocator(selfClassDecl); | |||
|
|||
if (ctor->isConvenienceInit() || ctor->hasClangNode()) { | |||
if (ctor->hasClangNode()) { | |||
// For a convenience initializer or an initializer synthesized | |||
// for an Objective-C class, allocate using the metatype. |
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.
Comment needs updating here too, right?
They should never be dynamically dispatched (unless `required`), so this entry should never be used. We were accidentally dynamically dispatching to them in convenience-to-convenience `self.init` delegations; fix that.
60ea50e
to
73eef4d
Compare
@swift-ci Please test |
@jrose-apple @slavapestov mind taking another look over this now? It should be close to ready to commit. |
Build failed |
Build failed |
@swift-ci Please test |
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.
Implementation looks good, some concerns about the test updates (of which there were a lot!). Also, maybe I scrolled too fast, but is there a test for the new @objc
convenience initializer thunk, the one that does a dealloc_partial_ref
?
lib/Sema/TypeCheckDeclOverride.cpp
Outdated
: OverrideRequiresKeyword::Always; | ||
return !ctor->isDesignatedInit() || ctor->isRequired() | ||
? OverrideRequiresKeyword::Never | ||
: OverrideRequiresKeyword::Always; |
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.
Sorry, can you add parens around the condition here? I know ternary operator is low-precedence but I have to remind myself of that every time.
sil public_external @alloc_NonTrivialDerived : $@convention(method) (@thick NonTrivialDerived.Type) -> @owned NonTrivialDerived | ||
sil public_external @$S14ivar_destroyer17NonTrivialDerivedCfE : $@convention(method) (@guaranteed NonTrivialDerived) -> () | ||
|
||
sil_vtable NonTrivialDerived { | ||
#NonTrivialDerived.init!initializer.1: @init_NonTrivialDerived [override] | ||
#NonTrivialDerived.init!allocator.1: @alloc_NonTrivialDerived | ||
#TrivialBase.init!allocator.1: @alloc_NonTrivialDerived [override] |
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 changed a lot from what's there before, but correctly, AFAICT. What were we getting wrong that's now fixed?
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 didn't correctly follow override chains for initializing entry points, and by the previous rules, the allocating entry point wasn't established until the subclass made it required
.
// CHECK-NOT: @$S32convenience_init_peer_delegation1XCACycfC | ||
// CHECK: @$S32convenience_init_peer_delegation1XCACycfc | ||
// CHECK: @$S32convenience_init_peer_delegation1XCACycfC | ||
// CHECK-NOT: @$S32convenience_init_peer_delegation1XCACycfc |
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.
Can this be a two-pass FileCheck? That way we'll know it's not just an ordering problem if initializing entry points ever creep back in.
// CHECK: [[RESULT:%.*]] = load [copy] [[PB_BOX]] | ||
// CHECK: [[WRAPPED_RESULT:%.*]] = enum $Optional<FailableBaseClass>, #Optional.some!enumelt.1, [[RESULT]] : $FailableBaseClass | ||
// CHECK: destroy_value [[MARKED_SELF_BOX]] | ||
// CHECK: br [[EPILOG_BB:bb[0-9]+]]([[WRAPPED_RESULT]] | ||
// | ||
// CHECK: [[EPILOG_BB]]([[WRAPPED_RESULT:%.*]] : @owned $Optional<FailableBaseClass>): | ||
// CHECK: return [[WRAPPED_RESULT]] | ||
// CHECK: } // end sil function '$S21failable_initializers17FailableBaseClassC21failDuringDelegation2ACSgyt_tcfc' |
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.
Please update this instead of removing it; we've had problems with CHECK lines matching multiple successive function bodies.
_ = ivar // expected-error {{'self' used in property access 'ivar' before 'self.init' call}} | ||
ivar = x // expected-error {{'self' used in property access 'ivar' before 'self.init' call}} | ||
_ = ivar // expected-error {{'self' used before 'self.init' call}} | ||
ivar = x // expected-error* {{'self' used before 'self.init' call}} |
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 a regression, no?
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.
Definite initialization for convenience inits now uses the same code path as for delegating value type initializers, so some of the diagnostics specialized for partially-initialized class instances no longer fire. I can look into the QoI regressions after, but they're consistent with what you get for a value type delegating initializer.
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.
That seems wrong to me, then. A value type initializer is allowed to initialize a few fields and then throw those values away, but a convenience initializer isn't.
Either way, a test case should never use *
on an expected-error it's actually trying to test. Either the error is present or it's not.
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 agree; like I said, I'd like to look into the diagnostic issues once the codegen changes are in, because they also affect value type initializers today, and this branch is difficult to keep up with other changes to master.
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 happy with this, because this isn't just a diagnostic issue; it's a language change. Convenience initializers inherently can't do everything value initializers can. But I see the point about not having a long-lived branch that changes a massive number of tests.
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.
There's no language change, only the diagnostics changed.
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.
To be specific, "initialize a few fields and then throw it away" still does not work on convenience initializers because accessing any field of a class requires indirection through the instance pointer, and that pointer now does not exist until a self.init call happens.
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 error messages go away, that's a language change. In particular, a few lines down you're somehow permitted to call a method before calling self.init()
.
@@ -1247,8 +1247,8 @@ class SuperConvenienceSub : SuperConvenienceBase { | |||
super.init(i) | |||
} | |||
public init(_ i1: Int, _ i2: Int, _ i3: Int) { | |||
self.init(i1, i1) | |||
} | |||
self.init(i1, i1) // expected-error{{'self' used before 'super.init' call}} |
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.
Oops. I think this is meant to be written as a convenience init. I'm not sure why it was accepted before at all, honestly, but I wonder if that's a source compat issue now.
something(x) // expected-error {{'self' used inside 'catch' block reachable from self.init call}} | ||
something(self.x) // expected-error {{'self' used inside 'catch' block reachable from self.init call}} | ||
something(x) // expected-error* {{'self' used before 'self.init'}} | ||
something(self.x) // expected-error* {{'self' used before 'self.init'}} |
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.
What's up with these?
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.
There's special handling for catch
blocks trying to access partially-initialized class instances, but it looks irrelevant when there either is an object or isn't. This is again because convenience initializers now get checked exactly the same as other delegating initializers.
// 28 CHECK-VTABLE-NEXT: #User.init!initializer.1: | ||
// 29 CHECK-VTABLE-NEXT: #User.init!allocator.1: | ||
// 27 CHECK-VTABLE-NEXT: #User.init!allocator.1: | ||
// 28 CHECK-VTABLE-NEXT: #User.init!allocator.1: | ||
// 30 CHECK-VTABLE-NEXT: #User.lastMethod!1: |
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.
Please renumber too. It's a pain to check that the GEP index is correct otherwise.
|
||
/// This instruction is a load that's only used to answer a `type(of: self)` | ||
/// question. | ||
LoadForTypeOfSelf, |
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.
Is there a test case for this, or were tests previously failing and now they work again?
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 necessary for existing tests (as well as swiftpm and the compatibility suite) to keep working.
Build failed |
Build failed |
@swift-ci Please clean test |
@swift-ci Please test source compatibility |
Build failed |
Build failed |
@swift-ci Please test |
Build failed |
Build failed |
73eef4d
to
be4eabe
Compare
@swift-ci Please test |
Build failed |
Build failed |
@swift-ci Please test source compatibility |
And only dispatch designated inits by their allocating entry points. rdar://problem/29634243
… into the self argument. Before we changed convenience inits into allocating entry points, we allowed type(of: self) to be invoked on the uninitialized object, which was fine. This no longer Just Works when self doesn't even exist before `self.init` is called, but to maintain the old semantics and source compatibility, we can still just use the metatype value we were passed in.
36a2e2f
to
91bf80f
Compare
@swift-ci Please test |
@swift-ci Please test source compatibility |
1 similar comment
@swift-ci Please test source compatibility |
Build failed |
Build failed |
@jrose-apple I think I got all your suggested updates to the tests. I ended up ripping out the changes to convenience overriding checking in the AST, because they had some fallout in the compatibility suite, and it didn't really end up simplifying the vtable formation rules because there were a lot of other things like PrintAsObjC, selector collision checking, and QoI for attempted overrides, that depended on the overrides being present in the AST and would've ended up just needing different special case behavior. How's this look now? |
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.
Still a few comments but they're pretty small!
// CHECK: throw [[ERROR]] | ||
// CHECK: } // end sil function '$SSo4HiveC17objc_factory_initE15otherFlakyQueenABSo3BeeC_tKcfC' | ||
convenience init(otherFlakyQueen other: Bee) throws { | ||
try self.init(flakyQueen: other) |
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.
What happened to this test?
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 judged that it isn't testing anything unique at this point, since the convenience init behavior is consistent now with other tests for failable delegating init behavior, so I removed it.
if (isa<LoadInst>(User)) { | ||
auto UserVal = cast<SingleValueInstruction>(User); | ||
if (UserVal->hasOneUse() | ||
&& isa<ValueMetatypeInst>(UserVal->getSingleUse()->get())) { |
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.
Hm. What happens if someone uses type(of: self)
twice?
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.
Each one gets its own load, at least with the way SILGen is currently implemented.
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.
That seems worth having a test for, at least, in case SILGen ever figures out how peephole the load but not the ValueMetatypeInst.
} | ||
assert(valueMetatype); | ||
auto metatypeArgument = load->getFunction()->getEntryBlock()->getArguments() | ||
.back(); |
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.
Nitpick: load->getFunction()->getSelfMetadataArgument()
.
The override-in-AST thing is interesting and kind of worrisome but I totally agree that it shouldn't block your progress now. |
required
oroverride
-ing designated initializers)super.init
chains, and those can always be statically resolvedrdar://problem/29634243