You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[semantic-arc-opts] Fix small bug around eliminating copies from begin_borrow, load_borrow, and dead end blocks.
TLDR:
The bug occurs since a copy_value does not need to be balanced by destroy_values
along paths that end in dead end blocks. So our check that all "consuming" uses
of the copy_value are within the lifetime of the begin_borrow, load_borrow
trivially succeed since there are no consuming uses of the copy_value to check!
This then results in us creating a use of a borrowed value after the borrowed
values end borrow.
I go through the bug in detail (via an example) and provide a proof that this
bug can only occur if the copy_value if there exists a set of dead end blocks
that jointly post-dominate the copy.
----
Consider the following SIL:
```
%1 = begin_borrow %0 : $KlassPair (1)
%2 = struct_extract %1 : $KlassPair, #KlassPair.firstKlass
%3 = copy_value %2 : $Klass
...
end_borrow %1 : $LintCommand (2)
cond_br ..., bb1, bb2
...
bbN:
// Never return type implies dead end block.
apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> Never (3)
unreachable
```
For simplicity, note that if bbN post-dominates %3, given that when we compute
linear lifetime errors we ignore dead end blocks, we would not register that the
copy_values only use is outside of the begin_borrow region defined by (1), (2)
and thus would eliminate the copy. This would result in %2 being used by %f,
causing the linear lifetime checker to error.
Naively one may assume that the solution to this is to just check if %3 has
/any/ destroy_values at all and if it doesn't have any reachable destroy_values,
then we are in this case. But is this correct in general? We prove this below:
The only paths along which the copy_value can not be destroyed or consumed is
along paths to dead end blocks. Trivially, we know that such a dead end block,
can not be reachable from the end_borrow since by their definition dead end
blocks do not have any successor instructions or blocks.
So we know that we can only run into this bug if we have a dead end block
reachable from the end_borrow, meaning that the bug can not occur if we branch
before the end_borrow since in that case, the borrow scope would last over the
dead end block's no return meaning that we will not use the borrowed value after
its lifetime is ended by the end_borrow.
With that in hand, we note again that if we have exactly one consumed,
destroy_value /after/ the end_borrow we will not optimize here since the
optimization only attempts to eliminate copies that are not consumed by
non-destroy_value instructions. This means that this bug can only occur if the
copy_value is only post-dominated by dead end blocks that use the value in a
non-consuming way.
NOTE: This can only occur if the copy_value is from a "borrow introducer" that
is associated with an end_borrow. This today includes begin_borrow, load_borrow,
but importantly and notably not function arguments.
rdar://54788632
0 commit comments