Although I don't plan to bring over new assertions wholesale
into the current qualification branch, it's entirely possible
that various minor changes in main will use the new assertions;
having this basic support in the release branch will simplify that.
(This is why I'm adding the includes as a separate pass from
rewriting the individual assertions)
In the C++ sources it is slightly more convenient to dump to stderr than
to print to stdout, but it is rather more unsightly to print to stderr
from the Swift sources. Switch to stdout. Also allows the dump
functions to be marked debug only.
Although by analogy with def instructions as barrier instructions one
could understand how a block where the def appears as a phi could be
regarded as a barrier block, the analogy is nonobvious.
Reachability knows the difference between an initial block and a barrier
block. Although most current clients don't care about this distinction,
one does. Here, Reachability calls back with visitInitialBlock for the
former and visitBarrierBlock for the latter.
Most clients are updated to have the same implementation in both
visitBarrierBlock and visitInitialBlock. The findBarriersBackward
client is updated to retain the distinction and pass it on to its
clients. Its one client, CanonicalizeOSSALifetime is updated to have a
simpler handling for barrier edges and to ignore the initial blocks.
When shrinking a borrow scope like
%borrow = begin_borrow %value
barrier
%copy = copy_value %borrow
end_borrow %borrow
the copy will be rewritten to be a copy of the borrowee:
%borrow = begin_borrow %value
barrier
%copy = copy_value %value
^^^^^^
end_borrow %borrow
If, as here, shrinking next encounters a barrier, then the insertion
point will be that rewritten copy_value instruction.
In such a case, when rather than creating a new end_borrow there and
deleting the old, just reuse the old one. The lifetime of the value
being copied will be canonicalized by CopyPropagation regardless.
Pass a BasicCalleeAnalysis instance to isDeinitBarrier. This will allow
ShrinkBorrowScope to hoist end_borrows over applies of functions which
are not deinit barriers.
Added new C++-to-Swift callback for isDeinitBarrier.
And pass it CalleeAnalysis so it can depend on function effects. For
now, the argument is ignored. And, all callers just pass nullptr.
Promoted to API the mayAccessPointer component predicate of
isDeinitBarrier which needs to remain in C++. That predicate will also
depends on function effects. For that reason, it too is now passed a
BasicCalleeAnalysis and is moved into SILOptimizer.
Also, added more conservative versions of isDeinitBarrier and
maySynchronize which will never consider side-effects.
IterableBackwardReachability just requires an iterable list of gens.
ShrinkBorrowScope, LexicalDestroyHoisting, and SSADestroyHoisting all
need to be able to check whether a given instruction is scope ending
quickly. Use a SmallSetVector rather than a SmallVector for the gens in
all three.
Adopt IterativeBackwardReachability. Enables hoisting end_borrow
instructions over loops. That in turn enables hoisting destroy_values
over those same loops.
rdar://93186505
While it is sometimes valid to hoist over begin_borrows of copies of the
borrowee, it is not always valid, as the test case committed here
illustrates. As a future optimization, we can reenable this hoisting
with the appropriate condition.
Previously, it was checked whether a barrier terminator was actually a
control flow terminator before adding end_borrows at the beginnings of
its successors. But it can't actually happen that a terminator is
classified as a barrier without the beginnings of all of its
predecessors having been reached because the BackwardReachability data
flow is pessimistic and only visits the end of a block (i.e. its
terminator) if it reached the beginnings of all of that block's
successors (see BackwardReachability::meetOverSuccessors and where the
data flow calls it).
Replaced ShrinkBorrowScope's own data flow with the general
BackwardReachability.
Took this opportunity to refactor and document the utility.
Taken together these changes make ShrinkBorrowScope serve as a template
for a future LexicalDestroyHoisting which will operate on owned lexical
values (rather than guaranteed as here) and hoist destroy_values (rather
than end_borrows as here) but should otherwise be quite similar.
In 0325e296fe, the implementation of
deinit barrier predicate was copied out of ShrinkBorrowScope into
MemAccessUtils. Delete the source of that copy from ShrinkBorrowScope
and just call the now common utility.
Previously, hoisting was done over applies that were users of the
borrowed value. Here, that is no longer done.
That hoisting was done on the theory that multiple distinct lexical
scopes were equivalent to a single enclosing lexical scope. Dead borrow
elimination, however, means that they are not in fact the same, however:
If a callee takes an object as an argument, and in that callee that
argument is dead, FSO and inliniing (even with approaches to maintain
lexical borrow scopes when the value that is borrowed comes from an
owned argument) can result in a dead borrow scope in the caller which
could then be eliminated which would then enable the destroy_value
to be hoisted over the inlined body, including over barriers.
Previously, the body--of the loop that iterated the instructions in the
block--contained the check that the instruction over which hoisting
might be done is the introducer. Here, that check is moved into
tryHoistOverInstruction. Now that function and its other caller,
canHoistOverInstruction, will return the right value (false) if they are
ever called with the introducer (though that could not happen without
this change).
Previously, it was asserted that hoisting over a terminator succeeded
(because it is previously checked that it can be done). That was
erroneously done by putting the call to hoist within an invocation of
the assert macro. The result was that in release builds, the call was
not made. Here, that is fixed by just calling the hoist function and,
in debug builds, asserting its return.
rdar://86809882
Previously, after determining that a block was dead, all its
predecessors were added to the worklist. That is a problem, with the
current algorithm, if any of the predecessors have a terminator over
which the borrow scope cannot be shrunk. Here, before adding a dead
block's predecessors to the worklist of blocks through which to shrink,
ensure that all those predecessors have terminators over which the scope
can be shrunk. Finally, when beginning to process a block, if it
doesn't have a starting instruction, first hoist over thet terminator.
The blocksWithReachedTops variable is only ever used while finding
barriers and in a small helper function called at that time. Make it
local to that function and make that helper a lambda in that function.
So that CopyPropagation and other clients can react accordingly, pass
back a list of copy_value instructions that were rewritten by
ShrinkBorrowScope. In CopyPropagation, add each modified copy to the
copy worklist.
Replaced findInnerTransitiveGuaranteeedUsesOfBorrowedValue with
findExtendedUsesOfSimpleBorrowedValue. Starting from a borrowed value,
it finds all extended (i.e., seeing through copies) uses of the borrow
and its projections within the simple (i.e. without considering
reborrowing) borrow scope.
During copy propagation (for which -enable-copy-propagation must still
be passed), also try to shrink borrow scopes by hoisting end_borrows
using the newly added ShrinkBorrowScope utility.
Allow end_borrow instructions to be hoisted over instructions that are
not deinit barriers for the value which is borrowed. Deinit barriers
include uses of the value, loads of memory, loads of weak references
that may be zeroed during deinit, and "synchronization points".
rdar://79149830