-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[5.9][move-only] A series of batched commits. #65871
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
[5.9][move-only] A series of batched commits. #65871
Conversation
@swift-ci test |
8b41318
to
5af1ab0
Compare
@swift-ci test |
5af1ab0
to
907e506
Compare
@swift-ci test |
…consuming use instead of liveness use. rdar://109217216 (cherry picked from commit a4fcdd8)
… an address argument instead of an object. The signature was already correct, just the logic in the deinit code assumed that we would always have an object. rdar://109170069 (cherry picked from commit 12e1db4)
… resilient guaranteed arguments. The only difference from normal concrete guaranteed parameter emission is we have a load_borrow on the argument before the copy_value. That is instead of: ``` bb0(%0 : @guaranteed $Type): %1 = copy_value %0 %2 = mark_must_check [no_consume_or_assign] %1 ``` we have, ``` bb0(%0 : $*Type): // in_guaranteed %1 = load_borrow %0 %2 = copy_value %1 %3 = mark_must_check [no_consume_or_assign] %2 ``` So I just needed to update the checker to recognize that pattern. This is tested by just making sure that the checker can handle the borrowVal in the tests without emitting an error. rdar://109170906 (cherry picked from commit 7d7c929)
…ow if we call a resilient function that takes it in_guaranteed. This ensures that given a class that contains a noncopyable type that contains another noncopyable type: ``` @_moveOnly struct S2 {} @_moveOnly struct S { var s2: S2 } class C { var s: S } ``` if we call a resilient function that takes C.S.S2: ``` borrowVal(c.s.s2) ``` we properly spill s2 onto the stack using a store_borrow. Why Do This? ------------ Currently SILGenLValue treats ref_element_addr as a base that it needs to load from for both copyable and non-copyable types. We keep a separation of concerns and require emission of resilient functions to handle these loaded values. For copyable types this means copying the value and storing it into a temporary stack allocation. For noncopyable types, we never actually implemented this so we would hit an error in SILGenApply telling us that our resilient function expected an address argument, but we are passing an object. To work around this, I updated how we emit borrowed lvalue arguments to in this case to spill the value into a temporary allocation using a store_borrow. I also included a test that validates that we properly have a read exclusivity scope around the original loaded from memory for the entire call site so even though we are performing a load_borrow and then spilling it, we still have read exclusivity to the original memory for the entire region meaning that we still preserve the semantics. rdar://109171001 (cherry picked from commit 59fdfad)
I hit a weird bug where for some reason it wasn't working. I can't reproduce it now, so it makes sense to use the correct syntax. (cherry picked from commit e76f3f9)
…e_borrow if we are using lowered addresses. When opaque values are enabled, we do not need this store_borrow. This was caught by the following tests: Swift(macosx-x86_64) :: SILGen/opaque_values_silgen.swift Swift(macosx-x86_64) :: SILGen/opaque_values_silgen_resilient.swift (cherry picked from commit 81a09b3)
…guments as such even if the closure doesn't actually escape" This reverts commit 224674c. Originally, I made this change since we were going to follow the AST in a strict way in terms of what closures are considered escaping or not from a diagnostics perspective. Upon further investigation I found that we actually do something different for inout escaping semantics and by treating the AST as the one point of truth, we are being inconsistent with the rest of the compiler. As an example, the following code is considered by the compiler to not be an invalid escaping use of an inout implying that we do not consider the closure to be escaping: ``` func f(_ x: inout Int) { let g = { _ = x } } ``` in contrast, a var is always considered to be an escape: ``` func f(_ x: inout Int) { var g = { _ = x } } test2.swift:3:13: error: escaping closure captures 'inout' parameter 'x' var g = { ^ test2.swift:2:10: note: parameter 'x' is declared 'inout' func f(_ x: inout Int) { ^ test2.swift:4:11: note: captured here _ = x ^ ``` Of course, if we store the let into memory, we get the error one would expect: ``` var global: () -> () = {} func f(_ x: inout Int) { let g = { _ = x } global = g } test2.swift:4:11: error: escaping closure captures 'inout' parameter 'x' let g = { ^ test2.swift:3:10: note: parameter 'x' is declared 'inout' func f(_ x: inout Int) { ^ test2.swift:5:7: note: captured here _ = x ^ ``` By reverting to the old behavior where allocbox to stack ran early, noncopyable types now have the same sort of semantics: let closures that capture a noncopyable type that do not on the face of it escape are considered non-escaping, while if the closure is ever stored into memory (e.x.: store into a global, into a local var) or escapes, we get the appropriate escaping diagnostics. E.x.: ``` public struct E : ~Copyable {} public func borrowVal(_ e: borrowing E) {} public func consumeVal(_ e: consuming E) {} func f1() { var e = E() // Mutable borrowing use of e. We can consume e as long as we reinit at end // of function. We don't here, so we get an error. let c1: () -> () = { borrowVal(e) consumeVal(e) } // Mutable borrowing use of e. We can consume e as long as we reinit at end // of function. We do do that here, so no error. let c2: () -> () = { borrowVal(e) consumeVal(e) e = E() } } ``` (cherry picked from commit 3ccda34)
… consumable_and_assignable, not assignable_but_not_consumable. Otherwise, since the compiler views assignable_but_not_consumable in this position as a sign of an escaping closure, we emit escaping closure errors instead of nonescaping closure errors. (cherry picked from commit 9c9fe09)
…ping partial apply and use that to emit diagnostics. The previous commits in this series of changes reverted the allocbox to stack change. Even with that though, we were treating the forming of the partial apply and the destroys of the partial apply as the liveness requiring uses. This is not the correct semantics for the non-escaping let closures. Instead, we want to treat the passing off of the partial apply to another function or the invocation of the partial apply as the liveness requiring uses. This makes sense since when we capture a noncopyable type in the closure, we capture them as inout_aliasable, that is as a pointer, so we are not going to actually use the value until we call the partial_apply. (cherry picked from commit 7d86bf0)
I also extended the tests to handle more interesting cases. NOTE: There are a few cases where we introduced some new do not understand errors. I am going to fix that in the next commit. I just wanted to completely update the tests for the manner in which the allocbox to stack change affects them. (cherry picked from commit 9ff33c5)
…ey have a partial_apply that was identified by an earlier diagnostic as escaping. Previously, we would emit a "compiler doesn't understand error" since we would detect the escape and fail. That is the correct behavior here if the partial_apply is not already identified as escaping by an earlier pass. But in the case where we see that the partial_apply's callee was marked with semantics::NO_MOVEONLY_DIAGNOSTICS, then we: 1. Suppress the "compiler doesn't understand error" for this specific mark_must_check. 2. Suppress function wide the "copy of noncopyable type" error. Since we stopped processing the mark_must_check that was passed to the partial_apply, we may have left copies of noncopyable types on that mark_must_check value. This is ok since the user will get the error, will recompile, and if any further show up after they fix the inout escaping issue, they will be emitted appropriately. (cherry picked from commit 07979f0)
(cherry picked from commit 68884d6)
… stack. (cherry picked from commit 0b38bba)
Originally this was a method to determine if we had emitted a diagnostic when running our gather visitor. It was pretty footgunny since one could easily forget to set it. Instead of doing that, we now maintain a counter in the diagnostic emitter that counts how many diagnostics we have emitted and use that to determine if during the walk if we emitted any additional diagnostics. (cherry picked from commit d252902)
907e506
to
5023b93
Compare
@swift-ci test |
// the compiler doesn't understand error. | ||
if (fArg->getArgumentConvention().isInoutConvention() && | ||
pas->getCalleeFunction()->hasSemanticsAttr( | ||
semantics::NO_MOVEONLY_DIAGNOSTICS)) { |
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: This isn't actually a bug. I just by mistake left this in this specific commit. I filled in the if statement body here: de5737d#diff-9c8ec5474745801be2148dbcd9e1178e6aaead4739668d88d551b4137047cb8b
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.
Specifically in 052ecd1
This PR contains a few batched changes for 5.9. There is at least one change
that just updates tests and another that eliminates a dead field. I didn't create CCC for them.
Here are the CCC for the major changes:
Commit f634acf
• Description: [move-only] Fix a thinko where we are treating inout convention
as a consuming use instead of liveness use. The effect of this change is that
when we were capturing an inout in a closure, we were treating it as if it was
consuming it causing us to give the wrong diagnostic.
• Risk: Low risk. Only affects noncopyable types.
• Original PR: #65864
• Reviewed By: @jckarter
• Testing: I added tests/updated other tests to make sure this did not happen.
• Resolves: rdar://109217216
Commit a10a5db
• Description: [move-only] Ensure that resilient deinits take their self
argument as an address argument instead of an object. The function signature
was already correct, just the logic in SILGen around cerating the SIL argument
assumed that we would always have an object since it was setup originally just
to support classes which even when resilient are still objects.
• Risk: Low risk. Only affects noncopyable types.
• Original PR: #65842
• Reviewed By: @jckarter
• Testing: I added tests/updated other tests to make sure this did not happen.
• Resolves: rdar://109170069
Commit a10a5db
• Description: [move-only] Teach the move only object checker how to handle
concrete resilient guaranteed arguments.
The only difference from normal concrete guaranteed parameter emission is we
have a load_borrow on the argument before the copy_value. That is instead of:
we have,
So I just needed to update the checker to recognize that pattern.
This is tested by just making sure that the checker can handle the borrowVal in
the tests without emitting an error.
• Risk: Low risk. Only affects noncopyable types.
• Original PR: #65842
• Reviewed By: @jckarter
• Testing: I added tests/updated other tests to make sure this did not happen.
• Resolves: rdar://109170906
Commit 43fb28f && dddafe8
• Description: [move-only] Spill noncopyable types that are objects using
store_borrow if we call a resilient function that takes it in_guaranteed.
This is a typical update needed around library-evolution. Since we are inside
the module itself, the value is concrete and thus an object, but the API it
uses is resilient, so we have to pass it as an address.
The second commit is just a small update to the first that fixes an issue
where we were not properly handling opaque values with this code path. I just
didn't squash the two commits.
• Risk: Low risk. Only affects noncopyable types.
• Original PR: #65842
• Reviewed By: @jckarter
• Testing: I added tests/updated other tests to make sure this did not happen.
• Resolves: rdar://109171001
Commit d2b7e70
Commit cd3734c
Commit de5737d
Commit a6edf15
Commit 052ecd1
• Description: Originally, we were going to follow the AST in a strict way in
terms of what closures are considered escaping or not from a diagnostics
perspective. Upon further investigation I found that we actually do something
different for inout escaping semantics and by treating the AST as the one
point of truth, we are being inconsistent with the rest of the compiler. As an
example, the following code is considered by the compiler to not be an invalid
escaping use of an inout implying that we do not consider the closure to be
escaping:
in contrast, a var is always considered to be an escape:
Of course, if we store the let into memory, we get the error one would expect:
By reverting to the old behavior where allocbox to stack ran early,
noncopyable types now have the same sort of semantics: let closures that
capture a noncopyable type that do not on the face of it escape are considered
non-escaping, while if the closure is ever stored into memory (e.x.: store
into a global, into a local var) or escapes, we get the appropriate escaping
diagnostics. E.x.:
Additionally, I did follow on work to ensure that:
In these cases we treat applications of the partial_apply or passing the
partial_apply off to a function as the liveness requiring uses rather than the
partial_apply formation and the end lifetime of the partial_apply.
When we do have an escaping partial_apply where we emit an inout capture
error (and thus bail out of move checking early), we no longer attempt to emit
"copy of noncopyable type" errors.
• Risk: Low risk. Only affects noncopyable types.
• Original PR: #65911
• Reviewed By: @jckarter
• Testing: I added tests/updated other tests to make sure this did not happen.
• Resolves: rdar://109426644