Skip to content

[NFC] Add SSADestroyHoisting prototype (originally in CopyForwarding) #40392

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 9 commits into from
Dec 22, 2021

Conversation

atrick
Copy link
Contributor

@atrick atrick commented Dec 3, 2021

Extract and rewrite the destroy hoisting algorithm originally from
CopyForwarding (in 2014).

This is now a light-weight utility for hoisting destroy_addr instructions. Shrinking an object's memory lifetime can allow removal of copy_addr and other optimization.

This algorithm is:

  • Incremental
  • SSA-based
  • Canonical
  • Free from alias analysis

See file-level comments.

The immediate purpose is to specify and test the constraints introduced by adding lexical variable lifetimes to SIL semantics. It can be used as a template for end_borrow hoisting.

Ultimately, this utility can be invoked within any pass that needs to optimize a particular uniquely identified address. It will be used to remove much of the complexity from CopyForwarding.

@atrick
Copy link
Contributor Author

atrick commented Dec 3, 2021

@nate-chandler I'm posting this hastily as an example to guide review of ShrinkBorrowScopes.

I haven't taken the time to write unit tests so I'm sure I missed something.

Copy link
Contributor Author

@atrick atrick left a comment

Choose a reason for hiding this comment

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

comment was cross posted from a different PR


// MARK: Analysis of the address SSA uses

bool hasPointerEscape = false;
Copy link
Contributor

@eeckstein eeckstein Dec 5, 2021

Choose a reason for hiding this comment

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

Using member variables in this way is as bad as using global variables.
Functions, like findKnownUses, should return their results (or return via a reference parameter) and get their inputs as parameters. Otherwise it's a nightmare to figure out the flow of information in the algorithm.

I know that this is (or was) common practice, but we need to change this habit to make our code more maintainable.

Copy link
Contributor Author

@atrick atrick Dec 7, 2021

Choose a reason for hiding this comment

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

@eeckstein I think the code is very clear that these members are the result of the SSA-use analysis. hasPointerEscape should be declared with the rest of the analysis result, including addressUsers and originalDestroys.

These are private to this small utility, initialized once by the analysis and subsequently immutable. That's nothing like a global variable!

That said, this utility performs two analysis steps, each with separate results. I usually separate each stage of results into separate classes. In fact, that was also my intention here. The first step, visitAddressUses, should be defined in MemAccessUtils. (This is the only code in this utility with substantial logic, so the utility truly becomes trivial at that point). I didn't do this yet because the existing AccessPath API is massive overkill for this case, and I didn't have time to refactor MemAccessUtils such that we can have a simpler API for discovering SSA address uses when we don't care about the access path.

Copy link
Contributor

Choose a reason for hiding this comment

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

For sure, those member variables are like global variables within the algorithm. It's not clear what variables are set and used, e.g. by looking at HoistDestroyAddrs::perform().
Why not let the functions in HoistDestroyAddrs::perform() return the results and pass inputs as parameters? That would make the flow of information clear.

nate-chandler added a commit to nate-chandler/swift that referenced this pull request Dec 9, 2021
Until swiftlang#40392 lands, run destroy
hoisting with lexical lifetimes.
@atrick atrick force-pushed the destroy-hoist branch 2 times, most recently from 4d9b3e0 to c5cdf14 Compare December 13, 2021 16:30
nate-chandler added a commit to nate-chandler/swift that referenced this pull request Dec 20, 2021
Until swiftlang#40392 lands, run destroy
hoisting with lexical lifetimes.
@atrick atrick force-pushed the destroy-hoist branch 2 times, most recently from 7de9d1b to ecf9d83 Compare December 21, 2021 10:38
@atrick
Copy link
Contributor Author

atrick commented Dec 21, 2021

@swift-ci test

@atrick atrick changed the title Add a HoistDestroyAddr prototype (originally in CopyForwarding) Add SSADestroyHoisting prototype (originally in CopyForwarding) Dec 21, 2021
@atrick atrick changed the title Add SSADestroyHoisting prototype (originally in CopyForwarding) [NFC] Add SSADestroyHoisting prototype (originally in CopyForwarding) Dec 21, 2021
@atrick atrick marked this pull request as ready for review December 21, 2021 10:45
@atrick atrick requested a review from eeckstein December 21, 2021 16:29
@atrick
Copy link
Contributor Author

atrick commented Dec 21, 2021

@nate-chandler @eeckstein ShrinkBorrowScopes and SSADestroyHoisting should share as much logic and structure as we can.

I extracted a few small shared SIL utilities from ShrinkBorrowScopes

  • getNextInstruction()
  • isDeinitBarrier()

I created a completely generic BackwardReachability utility as a more recognizable template for the kind of simple, efficient worklist data flow that we perform in many places. I could easily add an "optimistic" variation that iterates to a meet-over-paths fix point, but this "pessimistic" variation is more urgently needed for utilities that are called frequently to canonicalize the SIL.

I extracted a UniqueStorageVisitor API that analyzes and classifies the leaf uses of unique storage (generally alloc_stack or indirect function arguments). Essentially just a switch over recognized opcode patterns. This is a pattern that we use in many analyses where we query the known uses of unique address storage. The sort of thing we do in CopyForwarding, TempRValue, and several more specific optimizations. This is urgently needed so we can add recognition of temporary copies. That will allow adding logic to SSADestroyHoisting to skip deinit barriers the same way that ShrinkBorrowScopes currently does. It could also be used to cleanup lots of other passes.

The SSADestroyHoisting pass itself now only has logic for rewriting the destroys. It strings together two utilities to provide information to the rewrite step:

UniqueStorageUseVisitor -> KnownUses -> BackwardReachability -> DeinitBarriers -> RewriteDestroys

Needs to be common across ShrinkBorrowScopes and SSADestroyHoisting.
Analyze and classify the leaf uses of unique storage.

Storage that has a unique set of roots within this function includes
alloc_stack, alloc_box, exclusive argument, and global variables. All access
to the storage within this function is derived from these roots.

Gather the kinds of uses that are typically relevant to algorithms:
- loads	      (including copies out of, not including inout args)
- stores      (including copies into and inout args)
- destroys    (of the entire aggregate)
- debugUses   (only populated when preserveDebugInfo == false)
Set the single-pass pipeline's isMandatory flag based on the opt-mode.
@atrick
Copy link
Contributor Author

atrick commented Dec 22, 2021

@swift-ci smoke test

Pessimistic, non-iterative data flow for analyzing backward reachability
from a set of last uses to their dominating def or nearest barrier.

Meet: ReachableEnd(predecessor) = intersection(ReachableBegin, successors)

Intended for frequently called utilities where minimizing the cost of
data flow is more important than analyzing reachability across
loops. Expected to visit very few blocks because barriers often occur
close to a last use.

Note: this does not require initializing bitsets for all blocks in the
function for each SSA value being analyzed.
Extract and rewrite the destroy hoisting algorithm originally from
CopyForwarding (in 2014).

This is now a light-weight utility for hoisting destroy_addr
instructions. Shrinking an object's memory lifetime can allow removal
of copy_addr and other optimization.

This is extremely low-overhead and can run at any optimization level
without dependency on any analysis.

This algorithm is:
- Incremental
- SSA-based
- Canonical
- Free from alias analysis

See file-level comments.

The immediate purpose is to specify and test the constraints
introduced by adding lexical variable lifetimes to SIL semantics. It
can be used as a template for end_borrow hoisting.

Ultimately, this utility can be invoked within any pass that needs to
optimize a particular uniquely identified address. It will be used to
remove much of the complexity from CopyForwarding.
@atrick
Copy link
Contributor Author

atrick commented Dec 22, 2021

@swift-ci smoke test and merge

@swift-ci swift-ci merged commit e32105e into swiftlang:main Dec 22, 2021
@atrick atrick deleted the destroy-hoist branch December 22, 2021 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants