Skip to content

Fix BorrowedFromInst operand ownership. #79605

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 37 commits into from
Feb 27, 2025
Merged

Conversation

atrick
Copy link
Contributor

@atrick atrick commented Feb 25, 2025

This is a pile of interrelated liveness fixes. It is actually safer to combine them into one PR so no one is exposed to intermediate partly-broken states. I noticed these issues when reviewing fixes for OSSA lifetime bugs and auditing the code. Begining to fix the problems exposed other problems. 37 commits later I reached a steady state.

  1. Fix borrowed-from operand ownership

The borrowed-from instruction borrows its enclosing value. Liveness extends to the uses of the result. However, it was marked as an InstantaneousUse. Code that switches on OperandOwnership assumes that an InstantaneousUse does not propagate any information that may extend liveness. So we were not getting correct liveness information.

  1. Fix liveness utilities: handle inner borrow scopes and dependent values

Fix interior liveness to consistently handle inner borrows. Enforce rules for which borrowing operands create a new scope vs. those that forward guaranteed values. With both borrowed-from and mark_dependence, this depends on the context:

  • reborrows create a new scope, guaranteed forwarding phis do not
  • owned dependent values create a new scope, guaranteed dependent values do not

Handle borrowed-from and mark_dependence instructions of all flavors.

Follow dependent values to avoid unnecessary pointer escapes.

Conservatively report pointer escapes when they happen.

Consider the pointer escaping instruction also part of liveness.

  1. Fix liveness utilities: handle dead borrows

Conservatively report a begin_borrow, or other borrowing instruction without any uses as an unknown use themselves. This hack will go away with complete OSSA.

  1. Fix liveness utilities: handle visitInnerUses mode

Interior liveness is designed to be used with complete OSSA lifetimes. But we cannot rely on those yet. Meanwhile, passes like DestroyHoisting have started relying on complete liveness information. To workaround this problem, we need a special mode for interior liveness: visitInnerUses. This mode tries to find all the uses within inner borrow scopes. This mode is horribly complicated. The mode existed but was very incomplete and did not properly bail-out. Handle most of the important cases and add bail-outs where needed. Ensure the bail-outs are respected.

There are still plenty of FIXMEs and issues with this. But I fixed as much as I could.

Fix BorrowedValue::visitInteriorPointerOperandHelper to follow forwarding instructions.

Fix InteriorUseWalker.interiorPointerUse to follow address dependencies even when the addressable type is trivial.

  1. Synchronize the original C++ utilities with SwiftCompilerSources

These utilities need to behave the same way, or as close as possible, because passes that use the utilities are ported over without considering what's under the hood.

  1. Remove adjacent phi handling from liveness utilities

This made the internals of the utility very complicated with redundant logic. It was impossible to test support from borrowed-from.

  1. Centralize logic for switching on different kinds of borrows

Distinguish between scoped vs. unscoped borrowed-from instructions. They have different meaninings for ownership and liveness analysis.

--- C++
BorrowedFromInst::isReborrow
MarkDependenceInst::hasScopedLifetime
BorrowingOperand::getDependentUserResult()

--- Swift
MarkDependenceInst.hasScopedLifetime
BorrowingInstruction.scopedValue
BorrowingInstruction.dependentValue
BorrowingInstruction.innerValue

  1. Fix DestroyHoisting
  • visit all interior uses

  • don't ignore pointer escapes.

Known Effects

  • fixes DestroyHoisting use-after frees

  • improves some optimizations that benefit from borrowed-from, like SILCombine's read-only CoW buffer optimization

  • CopyPropagation becomes more conservative with dead-borrows. But we should avoid those rather than adding complexity to handle them.

@atrick
Copy link
Contributor Author

atrick commented Feb 25, 2025

@swift-ci test

@atrick
Copy link
Contributor Author

atrick commented Feb 25, 2025

@swift-ci benchmark

@atrick
Copy link
Contributor Author

atrick commented Feb 25, 2025

@swift-ci test source compatibility

@atrick
Copy link
Contributor Author

atrick commented Feb 25, 2025

No benchmark effect

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.

Swift source changes lgtm

It was too easy to confuse the "inner" vs. "outer" value.

Use less ambigous names: visitAllUses vs visitInnerScopeUses
Extracted from the functional changes for clarity.
This instruction obviously borrows its base value. Liveness extends to the uses
of the result.

Code that switches on OperandOwnership assumes that an Instantaneous use does
not propagate any information that may extend liveness.
All instructions with a "Borrow" operand ownership must be valid
BorrowingInstructions.
This encourages the client to check whether InteriorLivenessResult is valid or
escaping before using the instruction range.
If it is not a reborrow, then it produces a dependent value, just like
mark_dependence.
At least the Swift utilities should rely on borrowed-from.
Only return false if the visitor returns false. Clients were ignoring the
result.

If the BorrowingOperand does not create a borrow scope, call visitUnknownUse
instead.

Until we have complete lifetimes, to avoid breaking code that cannot handle dead
defs, consider a dead borrow scope to be an unknown use.
Check if the forwarded value is trivial, not the base value. Presumably, we
won't call this visitor at all if the base is trivial.

Record a dependent non-Escapable values as a pointer-escape. InteriorUseWalker
does not handle lifetime dependencies. That requires
LifetimeDependenceDefUseWalker.
Inner scopes must all be reported because the walker assumes they are
complete. It is up to the client to either complete them or recursively follow
them.
To handle borrowing operands that produce a dependence value but do not create a
nested borrow scope. This includes non-reborrow borrowed-from and guaranteed
mark_dependence [nonescaping].
It is buggy/misleading because of dead-def handling. The current operand may be
re-pushed if it is a dead borrow scope.
borrowed-from instructions have different ownership semantics if the are
attached to reborrows vs. guaranteed forwarding phis.
This API only makes sense for a scoped borrow-introducer such as:
- reborrow
- owned mark_dependence

Borrowing operands that forward guaranteed values do not have scope-ending uses.
Along with a few similar mark_dependence cases.
The extra complexity for traversing phis is not needed now that it handles
borrowed-from instructions. Remove the redundant logic because it complicates
the liveness algorithm and generates confusing results.
The extra complexity for traversing phis is not needed now that it handles
borrowed-from instructions. Remove the redundant logic because it complicates
the liveness algorithm and generates confusing results.
This consolidates the rules for borrow scopes so we can simplify interior liveness.
Add liveness support for dependent values: borrowed-from & mark_dependence so
they aren't reported as unknown uses.
Add liveness support for dependent values: borrowed-from & mark_dependence so
they aren't reported as unknown uses.

Centralize the logic in BorrowingOperand::getScopeIntroducingUserResult
and BorrowingOperand::getDependentUserResult().
to handle borrowed-from (as a ForwardingInstruction).
Remove complexity around reborrows. They no longer need special treatment.
Add support to all kinds of borrowing instructions so we visit all their inner uses.
and rename visitOwnedDependentUses
Set the visitInnerUses flag. This is only a quick, partial fix.

InteriorUseWalker does not generate complete liveness for two reasons

1. pointer escapes. The client must always check for escapes before assuming
complete liveness.

2. dead end blocks. Until we have complete OSSA lifetimes, the algorithm for
handling nested borrows is incorrect. The visitInnerUses flag works around this
problem, but it isn't well tested and I'm not sure it's properly records escapes yet.
For borrowed-from of a reborrow, the value is the block argument. So we need to
look through the borrowed-from user.
Always visit the use even if it is the beginning of an inner borrow. This
provides a "better" liveness result in the case of dead borrows and gives the
client a chance to see/intercept all uses.
A pointer escape means the liveness is incomplete. Don't hoist destroys with
incomplete liveness.
interiorPointerUse is dealing with addresses, so the memory still needs to be
alive regardless of the type.
DestroyHoisting is using this utility. This requires complete liveness. But we
can't rely on complete liveness because complete lifetimes is not
enabled. Instead, we use a visitInnerUses flag to try to ignore borrow
scopes. That flag was never properly implemented. Make an attempt here.
@atrick
Copy link
Contributor Author

atrick commented Feb 26, 2025

@eeckstein I added a few fixes to DestroyHoisting to unblock this PR and potentially unblock #79521. I haven't checked whether it fixes @nate-chandler's PR yet. Just speculating.

@atrick
Copy link
Contributor Author

atrick commented Feb 26, 2025

@swift-ci test

@atrick
Copy link
Contributor Author

atrick commented Feb 26, 2025

@swift-ci benchmark

@atrick
Copy link
Contributor Author

atrick commented Feb 26, 2025

@swift-ci test source compatibility

@atrick
Copy link
Contributor Author

atrick commented Feb 26, 2025

@swift-ci build toolchain

@atrick
Copy link
Contributor Author

atrick commented Feb 26, 2025

@swift-ci test source compatibility

@atrick atrick merged commit 1576ba4 into swiftlang:main Feb 27, 2025
9 of 11 checks passed
@atrick atrick deleted the fix-borrowedfrom branch February 27, 2025 06:50
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.

2 participants