Skip to content

[5.9] Fix SIL function side effects to handle unapplied escaping closures #68203

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 1 commit into from
Sep 1, 2023

Conversation

atrick
Copy link
Contributor

@atrick atrick commented Aug 30, 2023

Fixes rdar://113339972 DeadStoreElimination causes uninitialized closure context

Before this fix, the recently enabled function side effect implementation would return no side effects for a partial apply that is not applied in the same function. This resulted in DeadStoreElimination incorrectly eliminating the initialization of the closure context.

The fix is to model the effects of capturing the arguments for the closure context. The effects of running the closure body will be considered later, at the point that the closure is applied. Running the closure does, however, depend on the captured values to be valid. If the value being captured is addressible, then we need to model the effect of reading from that memory. In this case, the capture reads from a local stack slot:

%stack = alloc_stack $Klass
store %ref to %stack : $*Klass
%closure = partial_apply [callee_guaranteed] %f(%stack)
  : $@convention(thin) (@in_guaranteed Klass) -> ()

Later, when the closure is applied, we won't have any reference back to the original stack slot. The application may not even happen in a caller.

Note that, even if the closure will be applied in the current function, the side effects of the application are insufficient to cover the side effects of the capture. For example, the closure body itself may not read from an argument, but the context must still be valid in case it is copied or if the capture itself was not a bitwise-move.

As an optimization, we ignore the effect of captures for on-stack partial applies. Such captures are always either a bitwise-move or, more commonly, capture the source value by address. In these cases, the side effects of applying the closure are sufficient to cover the effects of the captures. And, if an on-stack closure is not invoked in the current function (or passed to a callee) then it will never be invoked, so the captures never have effects.

(cherry picked from commit 4ce6fcf) (cherry picked from commit 8a71844)

main PR: #67955

Fixes rdar://113339972 DeadStoreElimination causes uninitialized closure context

Before this fix, the recently enabled function side effect implementation
would return no side effects for a partial apply that is not applied
in the same function. This resulted in DeadStoreElimination
incorrectly eliminating the initialization of the closure context.

The fix is to model the effects of capturing the arguments for the
closure context. The effects of running the closure body will be
considered later, at the point that the closure is
applied. Running the closure does, however, depend on the captured
values to be valid. If the value being captured is addressible,
then we need to model the effect of reading from that memory. In
this case, the capture reads from a local stack slot:

    %stack = alloc_stack $Klass
    store %ref to %stack : $*Klass
    %closure = partial_apply [callee_guaranteed] %f(%stack)
      : $@convention(thin) (@in_guaranteed Klass) -> ()

Later, when the closure is applied, we won't have any reference back
to the original stack slot. The application may not even happen in a caller.

Note that, even if the closure will be applied in the current
function, the side effects of the application are insufficient to
cover the side effects of the capture. For example, the closure
body itself may not read from an argument, but the context must
still be valid in case it is copied or if the capture itself was
not a bitwise-move.

As an optimization, we ignore the effect of captures for on-stack
partial applies. Such captures are always either a bitwise-move
or, more commonly, capture the source value by address. In these
cases, the side effects of applying the closure are sufficient to
cover the effects of the captures. And, if an on-stack closure is
not invoked in the current function (or passed to a callee) then
it will never be invoked, so the captures never have effects.

(cherry picked from commit 4ce6fcf)
(cherry picked from commit 8a71844)
@atrick atrick requested review from eeckstein and tbkka August 30, 2023 00:50
@atrick atrick requested a review from a team as a code owner August 30, 2023 00:50
@atrick
Copy link
Contributor Author

atrick commented Aug 30, 2023

@swift-ci test

Copy link
Contributor

@eeckstein eeckstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@atrick
Copy link
Contributor Author

atrick commented Aug 30, 2023

@tbkka 5.9 candidate - potential miscompilation, although we only observed it on main

@atrick atrick changed the title [5.9] Fix SIL function side effects to handle unapplied escaping [5.9] Fix SIL function side effects to handle unapplied escaping closures Aug 31, 2023
@atrick atrick merged commit 46a3b41 into swiftlang:release/5.9 Sep 1, 2023
@atrick atrick deleted the 5.9-fix-dse-capture-effects branch September 1, 2023 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants