-
Notifications
You must be signed in to change notification settings - Fork 10.5k
When emitting no-escape closures, make sure that we copy the closure … #23478
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
When emitting no-escape closures, make sure that we copy the closure … #23478
Conversation
…before we pass it into the partial apply to eliminate an ownership violation. The problem here is that without this patch we emit code like this: bb0(%0 : @owned $T): %1 = partial_apply %foo(%0) %2 = mark_dependence %1 on %0 Since a partial_apply consumes the object, the mark_dependence is a use after free (especially if one has any further uses of %0 after the mark_dependence). So what I did was I copied the value before creating the partial_apply. So now we get this: bb0(%0 : @owned $T): %1 = copy_value %0 %2 = partial_apply %foo(%1) %3 = mark_dependence %2 on %0 ... destroy_value %0 This ensures that one can actually have uses after the mark_dependence of both operands. This enables ownership verification to be enabled on Interpreter/enforce_exclusive_access. rdar://48521061
@swift-ci smoke 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.
lg
Uh, what? A noescape closure shouldn't need copying; that defeats the purpose. Why do we need a |
This is inside the code we generate for withoutActuallyEscaping expressions. We need to make sure that the lifetime of the noescape closure context is not shortened beyond the use of the closure we insert to verify withoutActuallyEscaping. The code looks like the following (before this patch). We need to make sure that the lifetime of the context of %0 is not shortened to before %3's uses e.g after inlining and other optimizations. Hence the mark_dependence. @gottesmm I assume there are situations where we end up in this code and the operand to withoutActuallyEscaping is not a trivial value (@NoEscape closure)?
|
Sounds to me like a |
A regular partial_apply does capture its arguments. |
|
@jrose-apple this "footgun" is why we have wanted to split the capturing box from the partial apply itself. The capturing box in general takes values at +1 no matter the convention. This is easily confused if one sees a signature with a guaranteed argument or sees the partial_apply marked callee_guaranteed. |
@aschwaighofer I will add that as a SILGen test case to document this. |
@aschwaighofer I am going to do that in a subsequent commit. |
@gottesmm This test case already exists: test/SILGen/without_actually_escaping.swift |
@aschwaighofer ok. I will add to that one. |
What does it mean to copy a noescape closure? |
This is the SILGen output when I put the bad SIL into withoutActuallyEscaping.swift. The copy_value that is added by this PR is
|
So the reason this happens is the thunk we introduce. |
It doesn't semantically make sense to copy a value, consume it to produce a new value, and then mark dependence on the thing it was copied from. What does that mean? |
I don't understand your issue. With %17 = mark_dependence %16 : we communicate to the optimizer that all uses of %17 depend on %13 (being alive) - the optimizer is not allowed to move the ultimate destroy of %13 before any of %17's uses. This is a precise semantic meaning? |
That's true! But it doesn't make sense to copy %13 and use the copy to generate %16 (and then have %16 rely on %13 instead of the copy). That means that %16 isn't actually consuming the copy, since if it were, it wouldn't need %13 to be kept alive. |
You are right that the constraint is not needed in this case. It is an overly conservative constraint in this case (the argument to withoutAcutallyEscaping being an escaping closure) since the partial_apply captures the reference. It is not redundant information if the input was a noescape closure (only the trivial value is captured by the partial_apply of a noescape closure type): in which case it means %17 depends on the closure context of %13 (via transitivity of the mark_dependence chain). %17 = mark_dependence %16 : If we inline s11TestClosure9letEscape1fyycyyXE_tF into s22closure_lifetime_fixup10testSimple1cyAA1CC_tF the optimizer sees that %4 is ultimately dependent on [ARG] and therefore must not move the ultimate release of [ARG] accross any of %4s uses.
|
Forget the implementation. I'm saying that the semantics of "copy a value X to X2, consume X2 to produce Y, and mark that Y is dependent on the original X" are wrong. Either Y now owns X2 (and therefore doesn't need X), or X2 secretly depends on X (and therefore the copy didn't make an independent value). In this case it's the latter. |
Sorry, I misinterpreted this part. The previous comment still applies. |
If |
…before we pass it into the partial apply to eliminate an ownership violation.
The problem here is that without this patch we emit code like this:
Since a partial_apply consumes the object, the mark_dependence is a use after
free (especially if one has any further uses of %0 after the mark_dependence).
So what I did was I copied the value before creating the partial_apply. So
now we get this:
This ensures that one can actually have uses after the mark_dependence of both
operands.
This enables ownership verification to be enabled on
Interpreter/enforce_exclusive_access.
rdar://48521061