Skip to content

Support @guaranteed forwarding phis #61505

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 11 commits into from
Oct 20, 2022
Merged

Conversation

meg-gupta
Copy link
Contributor

@meg-gupta meg-gupta commented Oct 8, 2022

We required borrow scopes to pass @guaranteed forwarding values as phi operands. With this change we eliminate unnecessary borrow scopes making it easier for optimizations to work with @guaranteed forwarding values.

// This SIL:

%outer = begin_borrow %_ : $T
  cond_br _, bb1, bb2
bb1:
  %cast1 = unchecked_ref_cast %outer : $T to U
  %inner1 = begin_borrow %cast1 : $U
  br bb3(%inner1 : $U)
bb2:
  %cast2 = unchecked_ref_cast %outer : $T to U
  %inner2 = begin_borrow %cast2 : $U
  br bb3(%inner1 : $U)
bb3(%phi : $U):
  //...
  end_borrow %phi : $U
  end_borrow %outer : $T

// Can now be written as:

  %outer = begin_borrow %_ : $T
  cond_br _, bb1, bb2
bb1:
  %cast1 = unchecked_ref_cast %outer : $T to U
w  br bb3(%cast1 : $U)
bb2:
  %cast2 = unchecked_ref_cast %outer : $T to U
  br bb3(%cast2 : $U)
bb3(%phi : $U):
  //...
  end_borrow %outer : $T

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test source compatibility

@meg-gupta
Copy link
Contributor Author

@swift-ci benchmark

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test Linux platform

@meg-gupta meg-gupta marked this pull request as ready for review October 11, 2022 17:24
@meg-gupta
Copy link
Contributor Author

@swift-ci test macOS platform

@meg-gupta
Copy link
Contributor Author

@swift-ci benchmark

@meg-gupta
Copy link
Contributor Author

@swift-ci test source compatibility

@meg-gupta meg-gupta changed the title [WIP] Support @guaranteed forwarding phis Support @guaranteed forwarding phis Oct 11, 2022
if (destBlockArgOwnershipKind == OwnershipKind::Guaranteed) {
return OperandOwnership::Reborrow;
auto phiOp = getValue();
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder why you can't just call isGuaranteedForwardingPhi here instead of duplicating all the logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -339,6 +345,17 @@ bool swift::findExtendedUsesOfSimpleBorrowedValue(
recordUse(use);
break;

case OperandOwnership::GuaranteedForwardingPhi: {
Copy link
Contributor

Choose a reason for hiding this comment

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

findExtendedUsesOfSimpleBorrowedValue is finding "copy-extended" uses within the "simple" borrow scope. It doesn't look past reborrows. I think it expects all uses to be dominated by borrowedValue.

So I think it should only follow the uses of a guaranteed phi if that phi is dominated by borrowedValue

Copy link
Contributor Author

@meg-gupta meg-gupta Oct 12, 2022

Choose a reason for hiding this comment

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

findExtendedUsesOfSimpleBorrowedValue is just recording the use of Reborrow, and the clients are bailing out if there is a Reborrow before calling this api. I changed findExtendedUsesOfSimpleBorrowedValue to return false when there is a Reborrow (similar to findUsesOfSimpleValue). With that, all collected GuranteedForwardingPhi ops will have a dominated borrow root.

Copy link
Contributor

Choose a reason for hiding this comment

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

In OperandOwnership::GuaranteedForwardingPhi: { please comment "borrowedValue is known to dominate this phi because if any reborrow exists then this utility returns false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -842,6 +859,85 @@ bool BorrowedValue::visitTransitiveLifetimeEndingUses(
return true;
}

bool BorrowedValue::visitTransitiveGuaranteedForwardingUses(
Copy link
Contributor

Choose a reason for hiding this comment

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

findTransitiveGuaranteed may reach the same instruction via multiple paths. We need to make sure we don't revisit the same instruction and have exponential path explosion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

return true;
}

bool BorrowedValue::visitTransitiveGuaranteedForwardingPhiUses(
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make sure that these utilities are only called on borrow-introducing values.

How can we make it clear that this utility is intentionally skipping over the first level of phi uses (that wasn't clear to me).

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 wasn't accurate when I said visitTransitiveGuaranteedForwardingPhis these will only be called on borrow value introducers. I have moved it out of BorrowedValue and added assert on values it can accept.

Copy link
Contributor

@atrick atrick Oct 12, 2022

Choose a reason for hiding this comment

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

Naming: visitTransitiveGuaranteedForwardingPhis maybe something like
visitGuaranteedForwardingPhisForSSAValue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

}

for (unsigned i = 0; i < guaranteedForwardingOps.size(); i++) {
for (auto val : guaranteedForwardingOps[i]->getUser()->getResults()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we make the structural SIL assumption that all GuaranteedForwarding results are borrowed values then we should check that somewhere

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 added a TODO. There are other places in SILOwnershipVerifier where we make the same assumption. I'll take it up as a follow on task.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a simple assert can be added to verifyOperandOwnership? Such as canOpcodeForwardGuaranteedValues must be true for the user instruction...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

visitGuaranteedForwardingPhiBaseValuePair(phiValue, newBaseValue);

collectGuaranteedForwardingPhis(phiValue, newBaseValue);
collectGuaranteedForwardingPhis(newBaseValue, newBaseValue);
Copy link
Contributor

Choose a reason for hiding this comment

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

Explain: if an adjacent reborrow is found in the same block as the guaranteed phi, then set newBaseValue to the reborrow.

If we didn't find a reborrow, then don't call this
collectGuaranteedForwardingPhis(newBaseValue, newBaseValue);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

borrow.kind == BorrowedValueKind::LoadBorrow);
// worklist stores (guaranteed forwarding phi, base value) pairs.
// We need a SetVector to make sure we don't revisit the same pair again.
SmallSetVector<std::tuple<PhiOperand, SILValue>, 4> worklist;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really want to visit the same phi multiple times with a different base value each time?
I worry that could cause an infinite worklist.
Can we assert that a guaranteed phi always has the same borrow base?
Why is the worklist tracking operands? A phi should be identified by the its value.
Instead of std::tuple<PhiOperand, SILValue> worklist
Maybe it should be
ValueSet visitedPhis
`SmallVector<std::tuple<SILValue, SILValue> worklist // (guaranteedPhi, borrowBase)

Copy link
Contributor Author

@meg-gupta meg-gupta Oct 12, 2022

Choose a reason for hiding this comment

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

We can have different base values on different paths:

sil [ossa] @test_forwarded_separate_base_values : $@convention(thin) (@owned Wrapper1) -> () {
bb0(%0 : @owned $Wrapper1):
  %outer = begin_borrow %0 : $Wrapper1
  cond_br undef, bb1, bb2 

bb1:
  %unrelated = begin_borrow %0 : $Wrapper1
  %ex1 = struct_extract %outer : $Wrapper1, #Wrapper1.val1
  br bb3(%ex1 : $Wrapper2, %unrelated : $Wrapper1)

bb2:
  %inner = begin_borrow %0 : $Wrapper1
  %ex2 = struct_extract %inner : $Wrapper1, #Wrapper1.val1
  br bb3(%ex2 : $Wrapper2, %inner : $Wrapper1)

bb3(%phi1 : @guaranteed $Wrapper2, %phi2 : @guaranteed $Wrapper1):
  %f = function_ref @use_wrapper2 : $@convention(thin) (@guaranteed Wrapper2) -> ()
  apply %f(%phi1) : $@convention(thin) (@guaranteed Wrapper2) -> ()
  end_borrow %phi2 : $Wrapper1
  end_borrow %outer : $Wrapper1
  destroy_value %0 : $Wrapper1
  %9999 = tuple()
  return %9999 : $() 
} 

Here %phi1 has %phi2 as base value on one path and %outer as base value in another.

Copy link
Contributor

Choose a reason for hiding this comment

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

Comment long term, would like a "single-base" invariant!

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of saying we want a "single-base" invariant. Please add the .sil above as a basic OSSA verification test case. Also please add the same test where the base is an owned value. That you can refer to the fact that a guaranteed phi can have multiple bases in a comment here, along with a stripped down version of the SIL. This comment and example should also appear at the top of the reborrow verifier!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return true;
}

bool BorrowedValue::visitExtendedTransitiveGuaranteedForwardingPhiUses(
Copy link
Contributor

Choose a reason for hiding this comment

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

The combination of BorrowedValue::visitTransitiveGuaranteedForwardingPhiUses and BorrowedValue::visitExtendedTransitiveGuaranteedForwardingPhiUses only makes sense as a single utility:
BorrowedValue::visitExtendedTransitiveGuaranteedForwardingPhis
that "finds all reachable guaranteed phis within a simple borrow scope"

We shouldn't need two levels of loops. We can have one loop that uses the same visited set and worklist.
We can assert that we can't have a guaranteed phi that is a direct use of borrowedValue.

Copy link
Contributor Author

@meg-gupta meg-gupta Oct 12, 2022

Choose a reason for hiding this comment

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

I just have one visitor now: visitTransitiveGuaranteedForwardingPhis which finds all reachable guaranteed forwarding phis within a simple borrow scope

Copy link
Contributor

Choose a reason for hiding this comment

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

One more thing... the first level of traversal can find more guaranteed forwarding phis (if we start from a guaranteed forwarding phi)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -295,6 +296,13 @@ SimpleLiveRangeSummary PrunedLiveRange<LivenessWithDefs>::updateForDef(SILValue
});
break;
}
case OperandOwnership::GuaranteedForwardingPhi: {
SILArgument *phi = cast<BranchInst>(use->getUser())
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the PhiOperand abstraction handles/hides all the BranchInst/DestBB stuff

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta meg-gupta force-pushed the guaranteedphis branch 2 times, most recently from 1eac4cf to cfa3bd5 Compare October 13, 2022 21:24
@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test macOS Platform

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@meg-gupta
Copy link
Contributor Author

@swift-ci test windows platform

// Iterate guaranteedForwardingOps and call the visitor on
// GuaranteedForwardingPhi uses.
for (unsigned i = 0; i < guaranteedForwardingOps.size(); i++) {
for (auto val : guaranteedForwardingOps[i]->getUser()->getResults()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this loop necessary? All guaranteed forwarding op results were handled in the loop above. Why not call visitor directly on any phi uses in the above loop?

Copy link
Contributor Author

@meg-gupta meg-gupta Oct 15, 2022

Choose a reason for hiding this comment

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

@atrick
We need to process GuaranteedForwarding op results transitively, to find all GuaranteedForwardingPhi uses.

%b = begin_borrow ...
%ex1 = struct_extract %b ...
%ex2 = struct_extract %ex1 ...
....
br bbdest(%ex2)

We need the second loop to find %ex2

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for clarifying. I have fixed this.

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@@ -919,7 +922,8 @@ inline OwnershipConstraint OperandOwnership::getOwnershipConstraint() {
case OperandOwnership::ForwardingConsume:
return {OwnershipKind::Owned, UseLifetimeConstraint::LifetimeEnding};
case OperandOwnership::InteriorPointer:
case OperandOwnership::ForwardingBorrow:
case OperandOwnership::GuaranteedForwarding:
case OperandOwnership::GuaranteedForwardingPhi:
return {OwnershipKind::Guaranteed,
UseLifetimeConstraint::NonLifetimeEnding};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@atrick I fixed GuaranteedForwardingPhi to NonLifetimeEnding!

Copy link
Contributor

@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.

LGTM

@meg-gupta
Copy link
Contributor Author

@swift-ci test macOS Platform

@meg-gupta
Copy link
Contributor Author

Resolved merge conflict...

@meg-gupta
Copy link
Contributor Author

@swift-ci smoke test and merge

@swift-ci swift-ci merged commit 24ec0ed into swiftlang:main Oct 20, 2022
@meg-gupta meg-gupta mentioned this pull request Oct 21, 2022
meg-gupta added a commit that referenced this pull request Oct 23, 2022
compnerd added a commit that referenced this pull request Oct 25, 2022
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