Skip to content

SwiftCompilerSources support for OSSA and lifetime dependence #70460

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

Closed
wants to merge 14 commits into from

Conversation

atrick
Copy link
Contributor

@atrick atrick commented Dec 14, 2023

Fundamental APIs for working with OSSA in swift.

These are both simpler and more complete than the C++ counterpart. I'm adding support for the new on-stack closure representation and handling mark_dependence.

@atrick atrick marked this pull request as draft December 14, 2023 09:20
return .abortWalk
}
default:
fatalError("unknown borrow scope end")
Copy link
Contributor

Choose a reason for hiding this comment

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

Worth encoding these two possibilities as an enum which scopeEndingOperands is a sequence of?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I encoded EndBorrowOperand as an enum. But there are always unreachable cases here because borrow scopes can now counterintuitively be owned values in the case of partial_apply.

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.

nice! I didn't look into all details, yet, but basically it looks good to me.

/// terminated by end_borrow instructions that directly use this
/// value. Function arguments do not introduce a local scope because
/// the caller owns the scope.
struct BeginBorrowValue {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here: should this be an enum?
Bonus: the phi argument case could be a struct Phi instead of an Argument

@atrick
Copy link
Contributor Author

atrick commented Dec 22, 2023

@meg-gupta I'll move InteriorLiveness into its own PR now and work on test cases, so if you have already made fixes then I can cherry pick them into that PR.

And rename `uses.lifetimeEndingUses` to `uses.endingLifetime`
Also fix the memory binding within stack access.
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.

The first batch of comments: BorrowUtils.
Basically it looks good. Most comments are about documentation, API ergonomics and suggestions for coding style.

Something to consider for future work: My gut feeling tells me that it would be better to represent the borrow-introducer relationship of re-borrows with an explicit instruction in SIL. For example:

bb3(%outer_adjacent_phi : @owned, %inner_adjacent_phi : @guaranteed):
  %v = borrowed %inner_adjacent_phi from %outer_adjacent_phi, %some_dominating_value
  use %v // %v replaces %inner_adjacent_phi in the following SIL

This ensures the dominance relationship of borrow-introducers and makes it easier for passes to obtain this information. The gather-utilities would then be something like SSAUpdater, which can be run by a pass if it changes the SIL.

The borrowed-from instruction is the "moral" OSSA equivalent of a phi-term in plain SSA. Like a phi-term it makes dominance relation a structural property and avoids that passes need to recursively walk the predecessor blocks to gather defs/introducers.

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.

Some comments on utilities

/// Mark a stack location for future iteration.
///
/// TODO: Marker should be ~Escapable.
struct Marker {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is effectively an index. So let's call it Index

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok. but I was avoiding introducing the concept of an Index which would be totally unsafe. The point was that the marker has a scope.

Copy link
Contributor

Choose a reason for hiding this comment

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

The point was that the marker has a scope.

That makes sense. I still would rename it to Index. But make endIndex a private property (and add a comment why it's unsafe when used directly).

@atrick
Copy link
Contributor Author

atrick commented Jan 13, 2024

I kept this draft open for review comments. It is in sync with the current PR:
#70887

Format comments so they are readable on github and 80-col tools.
Key APIs necessary for using OSSA.

- BorrowingInstruction

- BeginBorrowValue

- scopeEndingOperands

- BorrowIntroducers

- EnclosingValues

- innerAdjacentPhis

These need to be complete to be correct.
This is what you need to correctly analyze OSSA.

- computeLinearLiveness

- computeInteriorLiveness

- InteriorUseVisitor

- OwnershipUseVisitor

- LivenessBoundary

Along with BorrowUtils.swift, all of our OSSA transformations are
built on top of these fundamentals. With these APIs, we can build
anything OSSA-related in SwiftCompilerSources. These utilities are
immediately needed for borrowed arguments and lifetime dependence. In
the near future, we can also use them to complete OSSA lifetimes and
*correctly* fixup OSSA after transformation without introducing lots
of copies and creating lots of incorrect corner cases.
@atrick
Copy link
Contributor Author

atrick commented Jan 13, 2024

Something to consider for future work: My gut feeling tells me that it would be better to represent the borrow-introducer relationship of re-borrows with an explicit instruction in SIL. For example:

bb3(%outer_adjacent_phi : @owned, %inner_adjacent_phi : @guaranteed):
  %v = borrowed %inner_adjacent_phi from %outer_adjacent_phi, %some_dominating_value
  use %v // %v replaces %inner_adjacent_phi in the following SIL

This ensures the dominance relationship of borrow-introducers and makes it easier for passes to obtain this information. The gather-utilities would then be something like SSAUpdater, which can be run by a pass if it changes the SIL.

The borrowed-from instruction is the "moral" OSSA equivalent of a phi-term in plain SSA. Like a phi-term it makes dominance relation a structural property and avoids that passes need to recursively walk the predecessor blocks to gather defs/introducers.

We've had ongoing discussions about doing this ever since OSSA was introduced. It would obviously simplify OSSA liveness and dead code elimination (although we've finally solved those problems). There are serious tradeoffs though. One is making all passes aware of the borrowed phi dependence. Another problem is figuring out how to guarantee that the dependence always stays valid. Keeping the borrowed phi dependence valid and verifying it requires that same logic we have here, so getting the utilities in this PR right is a good start.

I'm generally in favor of augmenting the SSAUpdater to do more things as an OSSAUpdater. Most importantly, we need to flag phis with lexical lifetimes and pointer escapes because without that, our OSSA utilities all have serious time-complexity problems. If we can also teach OSSAUpdater to update borrowed phi dependencies, that would be reasonable. Then the only question is whether other optimizations might invalidate the borrowed phis in a way the the OSSAUpdater does not catch.

    - LifetimeDependence

    - VariableIntroducerUseDefWalker

    - LifetimeDependenceUseDefWalker

    - LifetimeDependenceDefUseWalker
@eeckstein
Copy link
Contributor

eeckstein commented Jan 16, 2024

One is making all passes aware of the borrowed phi dependence.

That should be encapsulated in the BeginBorrowValue abstraction.

Another problem is figuring out how to guarantee that the dependence always stays valid.

That's what I mean with providing a utility for it (similar to SSAUpdated) which passes can use when they modify the SIL.
It would also make the compiler more efficient because you only have to use the utilities once you modify the SIL and not every time you analyze the SIL.

getting the utilities in this PR right is a good start.

Right 👍

@atrick
Copy link
Contributor Author

atrick commented Jan 17, 2024

Once #70887 is merged, the LifetimeDependence utils will be introduced in a separate PR.

@atrick atrick closed this Jan 17, 2024
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