Commit Graph

20 Commits

Author SHA1 Message Date
Andrew Trick
c86d112891 Update #include for InstructionDeleter.h 2021-11-18 11:38:08 -08:00
Michael Gottesman
e5d0f386e4 [moveOnly] Add a new experimental move only checker that checks noImplicitCopy variables.
NOTE: This is only available when the flag -enable-experimental-move-only. There
are no effects when the flag is disabled.

The way that this works is that it takes advantage of the following changes to
SILGen emission:

* When SILGen initializes a let with NoImplicitCopyAttribute, SILGen now emits
a begin_borrow [lexical] + copy + move_only. This is a pattern that we can check
and know that we are processing a move only value. When performing move
checking, we check move_only as a move only value and that it isn't consumed
multiple times.

* The first point works well for emitting all diagnostics except for
initializing an additional let var. To work around that I changed let
initialization to always bind to an owned value to a move of that owned
value. There is no semantic difference since that value is going to be consumed
by the binding operation anyways so we effectively just move the cleanup from
the original value we wanted to bind to the move. We still then actually borrow
the new let value with a begin_borrow [lexical] for the new let value. This
ensures that an initialization of a let value appears to be a consuming use to
the move only value checker while ensuring that the value has a proper
begin_borrow [lexical].

Some notes on functionality:

1. This attribute can only be applied to local 'let'.

2. "print" due to how we call it today with a vararg array is treated as a
   consuming use (unfortunately).

3. I have not added the builtin copy operator yet, but I recently added a _move
   skeleton attribute so one can end the lifetimes of these values early.

4. This supports all types that are not address only types (similar to
   _move). To support full on address only types we need opaque values.

rdar://83957088
2021-10-28 11:32:22 -07:00
Nate Chandler
42f318d9ef [Basic] Renamed GraphNodeWorklist.
Addressed the TODO saying that DAGNodeWorklist should be renamed
GraphNodeWorklist.
2021-10-18 08:55:13 -07:00
Andrew Trick
6a38f579d8 CopyPropagation: Avoid regenerating destroys.
CanonicalizeOSSA is now used iteratively in SILCombine. To avoid
endless worklist iteration based on whether InstructionDeleter's
callbacks fired, ensure that destroys are only deleted and recreated
when necessary.
2021-10-09 15:29:22 -07:00
Andrew Trick
add3406cfe Move PrunedLiveness so it can be used as a lightweight OSSA helper.
For use in OwnershipUtils.
2021-10-06 09:23:02 -07:00
Min-Yih Hsu
343d842394 [SIL][DebugInfo] PATCH 3/3: Deprecate debug_value_addr SIL instruciton
This patch removes all references to DebugValueAddrInst class and
debug_value_addr instruction in textual SIL files.
2021-08-31 12:01:04 -07:00
Andrew Trick
2c6d5afd28 Copy propagation redesign
This rewrites functionality that was mostly disabled but is now ready
to be enabled.

Allow lifetime canonicalization of owned values and function arguments
as a simple stand-alone utility. This is now being called from within
SILCombine, so we should only do the kind of canonicalization that
makes sense in that context.

Canonicalizing other borrow scopes should *not* be invoked as a
single-value cleanup because it affects other lifetimes outside the
borrow scope boundary. It is a somewhat complicated process that
hoists and sinks forwarding instructions and can generate surrounding
compensation code. The copy propagation pass knows how to post-process
the related lifetimes in just the right order. So borrow scope
rewriting should only be done in the copy propagation pass.

Similarly, only do simple canonicalization of owned values and
function arguments at -Onone.

The feature to canoncalize borrow scopes is now ready to be
enabled (-canonical-ossa-rewrite-borrows), but flipping the switch
should be a separate commit. So most of the functionality that was
affected is not exposed by this PR.

Changes:

Split canonicalization of owned lifetimes vs. borrowed lifetimes into
separate utilities. The owned lifetime utility is now back to being
the simple utility that I originally envisioned. So not much happened
to it other than removing complexity.

We now have a separate entry point for finding the starting point for
rewriting borrow scopes:
CanonicalizeBorrowScope::getCanonicalBorrowedDef.

We now have a utility that defines forwarding instructions that we can
treat consistently as part of a guaranteed lifetime,
CanonicalizeBorrowScope::isRewritableOSSAForward.

We now have a utility that defines the uses of a borrowed value that
are considered part of its lifetime,
CanonicalizeBorrowScope::visitBorrowScopeUses. This single utility is
used to implement three different parts of the alogrithm:

1. Find any uses of the borrowed value that need to be propagated
outside the borrow scope

2. RewriteInnerBorrowUses for SILFunction arguments and borrow scopes
with no outer uses.

3. RewriteOuterBorrowUses for borrow scopes with outer uses. Handling
these involves creating new copies outside the borrow scope and
hoisting forwarding instructions.

The end result is that a lot of borrow scopes can be eliminated and
owned values can be forwarded to destructures, reducing copies and
destroys.

If we stop generating borrow scopes for all interior pointers, then
we'll need to design a comparable optimization that works on
"implicit" borrow scopes:

  %ownedDef = ...
  %element struct_extract %ownedDef
  %copy = copy_value %element
  apply(@guaranteed %element)
  apply(@owned %copy)
  destroy %ownedDef

Should be:

  %ownedDef = ...
  %borrowedElement = destructure_struct @guaranteed %ownedDef
  apply(@guaranteed %borrowedElement)
  %ownedElement = destructure_struct %ownedDef
  apply(@owned %copy)
2021-07-01 21:17:27 -07:00
Andrew Trick
825185de84 Move PtrWorklist into DAGNodeWorklist.h for use across files. 2021-07-01 20:32:06 -07:00
Michael Gottesman
387806b9c6 [ownership] Add a simple helper wrapper called canonicalizeOSSALifetimes around CanonicalizeOSSALifetime that clean up extra copies inserted when RAUWing or promoting loads.
Based on code in CopyPropagation. It is assumed that the passed in set of defs
is unique and that all such defs were found by using
CanonicalizeOSSALifetime::getCanonicalCopiedDef(copy).
2021-05-01 16:01:45 -07:00
Michael Gottesman
c3c2b84e48 [ownership] Change CanonicalOSSALifetime to use an InstModCallback.
This enables passes to use this as a utility that properly composes with how the
pass maintains its state. If an InstModCallback isn't passed in, we use the
default InstModCallback which should be cheap (always succeeding check for
nullptr + call inline default callback).
2021-04-21 22:09:55 -07:00
Andrew Trick
340feb41df CanonicalOSSALifetime: rewrite consolidateBorrowScope
It now handles looking through forwarding consumes such as
destructures.

Expected to be NFC since borrow consolidation is still disabled by
default.

TODO: Write unit tests for various forwarding consumes in addition to
destructure.
2021-04-21 14:39:48 -07:00
Andrew Trick
3c045a85d5 CanonicalOSSALifetime: Add convertExtractToDestructure utility
struct_extract and tuple_extract do not belong in OSSA (except to
workaround certain extreme cases). They completely defeat
simplification that OSSA provides for optimizing owned lifetimes.

Copy propagation uses this utility to canonicalize owned values before
canonicalizing their lifetime.
2021-04-19 23:26:16 -07:00
Andrew Trick
97ec127d25 Lower and emit debug_value [poison].
OwnershipEliminator lowers destroy_value [poison] to debug_value
[poison].

IRGen overwrites all references in shadow copies with a sentinel value
in place of debug_value [poison].

Part 2/2: rdar://75012368 (-Onone compiler support for early object
deinitialization with sentinel dead references)
2021-03-12 19:33:23 -08:00
Andrew Trick
0b3027a37e Add destroy_value [poison] support to mandatory copy propagation.
Ensure that any object lifetime that will be shortened ends in
destroy_value [poison], indicating that the reference should not be
dereferenced (e.g. by the debugger) beyond this point.

This has no way of knowing whether the object will actually be
deallocated at this point. It conservatively avoids showing garbage to
debuggers.

Part 1/2: rdar://75012368 (-Onone compiler support for early object
deinitialization with sentinel dead references)
2021-03-12 19:32:55 -08:00
Andrew Trick
cb1ed89c9a [NFC] Add support for a mandatory-copy-propagation pass.
It is currently disabled so this commit is NFC.

MandatoryCopyPropagation canonicalizes all all OSSA lifetimes with
either CopyValue or DestroyValue operations. While regular
CopyPropagation only canonicalizes lifetimes with copies. This ensures
that more lifetime program bugs are found in debug builds. Eventually,
regular CopyPropagation will also canonicalize all lifetimes, but for
now, we don't want to expose optimized code to more behavior change
than necessary.

Add frontend flags for developers to easily control copy propagation:

-enable-copy-propagation: enables whatever form of copy propagation
 the current pipeline runs (mandatory-copy-propagation at -Onone,
 regular copy-propation at -O).

-disable-copy-propagation: similarly disables any form of copy
 propagation in the current pipelien.

To control a specific variant of the passes, use
   -Xllvm -disable-pass=mandatory-copy-propagation
or -Xllvm -disable-pass=copy-propagation instead.

The meaning of these flags will stay the same as we adjust the
defaults. Soon mandatory-copy-propagation will be enabled by
default. There are two reasons to do this, both related to predictable
behavior across Debug and Release builds.

1. Shortening object lifetimes can cause observable changes in program
   behavior in the presense of weak/unowned reference and
   deinitializer side effects.

2. Programmers need to know reliably whether a given code pattern will
   copy the storage for copy-on-write types (Array, Set). Eliminating
   the "unexpected" copies the same way at -Onone and -O both makes
   debugging tractable and provides assurance that the code isn't
   relying on the luck of the optimizer in a particular compiler
   release.
2021-03-02 22:19:47 -08:00
Andrew Trick
f509d67e7c Fix -canonical-ossa-rewrite-borrows but leave it disabled.
Add support for interleaved borrow scopes:

%b1 = begin_borrow %a
%c = copy
%b2 = begin_borrow %b1
end_borrow %b1
use %c
end_borrow %b2

Will be transformed to:

%c = copy %a
%b1 = begin_borrow %a
%b2 = begin_borrow %b1
end_borrow %b1
use %c
end_borrow %b2

This was the original intention but the implementation was incomplete.

This option can be enabled as soon as we need it for performance.

The intention is also to handle multi-block borrows, but that hasn't
been implemented.
2021-01-19 19:07:54 -08:00
Nate Chandler
d2e5fa7f42 Tweaked comment.
Matched the comment on the definition of NonLocalAccessBlocks::compute()
to that on the declaration.
2021-01-19 09:34:15 -08:00
Andrew Trick
1ba3066950 CanonicalOSSALifetime: Add support for overlapping access scopes.
Access scopes for enforcing exclusivity are currently the only
exception to our ability to canonicalize OSSA lifetime purely based on
the SSA value's known uses. This is because access scopes have
semantics relative to object deinitializers.

In general, deinitializers are asynchronous with respect to code that
is unrelated to the object's uses. Ignoring exclusivity, the optimizer
may always destroy objects as early as it wants, as long as the object
won't be used again. The optimizer may also extend the lifetime
(although in the future this lifetime extension should be limited by
"synchronization points").

The optimizer's freedom is however limited by exclusivity
enforcement. Optimization may never introduce new exclusivity
violations. Destroying an object within an access scope is an
exclusivity violation if the deinitializer accesses the same variable.

To handle this, OSSA canonicalization must detect access scopes that
overlap with the end of the pruned extended lifetime. Essentially:

    %def
    begin_access // access scope unrelated to def
    use %def     // pruned liveness ends here
    end_access
    destroy %def

Support for access scopes composes cleanly with the existing algorithm
without adding significant cost in the usual case. Overlapping access
scopes are unusual. A single CFG walk within the original extended
lifetime is normally sufficient. Only the blocks that are not already
LiveOut in the pruned liveness need to be visited. During this walk,
local overlapping access are detected by scanning for end_access
instructions after the last use point. Global overlapping accesses are
detected by checking NonLocalAccessBlockAnalysis. This avoids scanning
instructions in the common case. NonLocalAccessBlockAnalysis is a
trivial analysis that caches the rare occurence of nonlocal access
scopes. The analysis itself is a single linear scan over the
instruction stream. This analysis can be preserved across most
transformations and I expect it to be used to speed up other
optimizations related to access marker.

When an overlapping access is detected, pruned liveness is simply
extended to include the end_access as a new use point. Extending the
lifetime is iterative, but with each iteration, blocks that are now
marked LiveOut no longer need to be visited. Furthermore, interleaved
accessed scopes are not expected to happen in practice.
2021-01-15 19:48:33 -08:00
Andrew Trick
f2ccd171aa CanonicalOSSALifetime: canonicalize guaranteed values.
If a guaranteed value is not a recognized and handled borrow
introducer, then treat the copy as a separate owned live range.
2021-01-05 22:20:33 -08:00
Andrew Trick
f22d0855df Add a CanonicalOSSALifetime utility.
Canonicalizing OSSA provably minimizes the number of retains and
releases within the boundaries of that lifetime. This eliminates the
need for ad-hoc optimization of OSSA copies.

This initial implementation only canonicalizes owned values, but
canonicalizing guaranteed values is a simple extension.

This was originally part of the CopyPropagation prototype years
ago. Now OSSA is specified completely enough that it can be turned
into a simple utility instead.

CanonicalOSSALifetime uses PrunedLiveness to find the extended live
range and identify the consumes on the boundary. All other consumes
need their own copy. No other copies are needed.

By running this after other transformations that affect OSSA
lifetimes, we can avoid the need to run pattern-matching optimization
to SemanticARC to recover from suboptimal patterns, which is not
robust, maintainable, or efficient.
2021-01-05 20:38:30 -08:00