Skip to content

[ownership] Enable SILMem2Reg for OSSA #34276

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 5 commits into from
Oct 21, 2020
Merged

Conversation

meg-gupta
Copy link
Contributor

@meg-gupta meg-gupta commented Oct 12, 2020

SILMem2Reg is currently not enabled for OSSA. This PR enables it for OSSA, and adds support for composite loads and stores with ownership qualifiers [copy], [assign] and [take] that are new to OSSA.

This PR currently does not add support for load_borrow/store_borrow and access instructions.

A dealloc_stack ends the lifetime of an alloc_stack on a path. We don't
have to pass RunningVal beyond the dealloc_stack as phi argument to the
post dominating block.
@meg-gupta meg-gupta requested a review from gottesmm October 12, 2020 17:02
@meg-gupta
Copy link
Contributor Author

@swift-ci test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 6b3258ab08db7b4fb79ef24595d7a5e123c6a56f

@meg-gupta
Copy link
Contributor Author

@swift-ci test

Copy link
Contributor

@zoecarver zoecarver left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! I know first hand how complicated this pass can be (esspecially with ownership).

I think part of supporting ownership in this pass is adding support for access instructions, especially if you're adding those instructions in the pass. Although, that could probably be its own patch. When adding support for access instructions, please update isAddressForLoad, isLoadFromStack, collectLoads, isDeadAddrProjection, removeSingleBlockAllocation, and isCaptured. I think after these instructions are supported, you will get much more ossa test coverage (for example, while building the stdlib).

Add a case for DestroyAddrInst to isWriteOnlyAllocation.

Also, if isWriteOnlyAllocation is true, line 883 will remove all uses of alloc. This includes lifetime ending stores which may cause problems.

Last, when I created a (much worse) version of this patch (which I ended up closing: #34209), I added quite a few tests which might be helpful. Some of the tests are just OSSA versions of the existing tests (which it looks like you've already added) but most of them are new (lines 465 and beyond).


Edit: #30812 is a patch for supporting begin_access in mem2reg. If you'd like to land it (as its own patch or part of this one) that's fine with me. I'm also happy to land it myself (but I'll need someone to review it 😉).

@@ -348,21 +350,52 @@ static void collectLoads(SILInstruction *I, SmallVectorImpl<LoadInst *> &Loads)
static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) {
ProjectionPath projections(val->getType());
SILValue op = LI->getOperand();
SILBuilder builder(LI);

while (op != ASI) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should look through access instructions too. Maybe you can use @atrick's AccessPath utility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Thanks for pointing out.

borrowedVals.push_back(opVal);
}
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: no newline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Will fix.

// If we don't know the content of the AllocStack then the loaded
// value *is* the new value;
// Don't use result of load [copy] as a RunningVal, it necessitates
// additional logic for cleanup of consuming instructions of the result.
Copy link
Contributor

Choose a reason for hiding this comment

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

One day we could support this. Can you please add a TODO?

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'll update the comment, needs more clarification. This doesn't in any way weaken the optimization. It just adds more work for StackAllocationPromoter::fixBranchesAndUses because we prune less in StackAllocationPromoter::promoteAllocationInBlock now.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, fair enough.

@@ -433,16 +470,51 @@ StackAllocationPromoter::promoteAllocationInBlock(SILBasicBlock *BB) {
if (SI->getDest() != ASI)
continue;

// The stored value is the new running value.
RunningVal = SI->getSrc();
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 we want to keep the old behavior in non-ossa.

if (BB->isEntry()) {
if (SI->getOwnershipQualifier() == StoreOwnershipQualifier::Assign) {
assert(RunningVal);
SILBuilder builder(SI);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this can be SILBuilder(SI).create.... (There are a few other places like this.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Will fix.

Copy link
Contributor Author

@meg-gupta meg-gupta left a comment

Choose a reason for hiding this comment

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

Thanks for your comments @zoecarver. I'll try to incorporate the new tests you've added if they are not already covered here.
I'll probably make another PR for access instructions or am happy to review your PR : ) thanks for pointing it out!

@@ -348,21 +350,52 @@ static void collectLoads(SILInstruction *I, SmallVectorImpl<LoadInst *> &Loads)
static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) {
ProjectionPath projections(val->getType());
SILValue op = LI->getOperand();
SILBuilder builder(LI);

while (op != ASI) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Thanks for pointing out.

// If we don't know the content of the AllocStack then the loaded
// value *is* the new value;
// Don't use result of load [copy] as a RunningVal, it necessitates
// additional logic for cleanup of consuming instructions of the result.
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'll update the comment, needs more clarification. This doesn't in any way weaken the optimization. It just adds more work for StackAllocationPromoter::fixBranchesAndUses because we prune less in StackAllocationPromoter::promoteAllocationInBlock now.

if (BB->isEntry()) {
if (SI->getOwnershipQualifier() == StoreOwnershipQualifier::Assign) {
assert(RunningVal);
SILBuilder builder(SI);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Will fix.

borrowedVals.push_back(opVal);
}
}
else {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Will fix.

@meg-gupta
Copy link
Contributor Author

@zoecarver I am going to test #30812 with this branch and see how it goes.

@zoecarver
Copy link
Contributor

@zoecarver I am going to test #30812 with this branch and see how it goes.

Sounds good! You should make sure you can also build the stdlib with both patches applied.

@meg-gupta
Copy link
Contributor Author

@zoecarver I am going to test #30812 with this branch and see how it goes.

Sounds good! You should make sure you can also build the stdlib with both patches applied.

No new bugs with access instructions. I think its best to handle it separately from this PR. #30812 will need some OSSA tests.

@zoecarver
Copy link
Contributor

zoecarver commented Oct 13, 2020

No new bugs with access instructions.

Great.

I think its best to handle it separately from this PR.

SGTM.

#30812 will need some OSSA tests.

Happy to add them. But they're useless until this patch lands (I can rebase that patch on this one until then).

@meg-gupta
Copy link
Contributor Author

meg-gupta commented Oct 13, 2020

Add a case for DestroyAddrInst to isWriteOnlyAllocation.

Looked into this. This is not okay because we just erase instructions in the case of isWriteOnlyAllocation. But a destroy_addr will need to be transformed to the destroy_val of the RunningVal

@zoecarver
Copy link
Contributor

Looked into this. This is not okay because we just erase instructions in the case of isWriteOnlyAllocation. But a destroy_addr will need to be transformed to the destroy_val of the RunningVal

Fair enough.

@meg-gupta meg-gupta force-pushed the mem2regossa branch 2 times, most recently from 7dcf44b to 8e42060 Compare October 13, 2020 23:20
@meg-gupta
Copy link
Contributor Author

@gottesmm cleaned up history and added dead phi arg removal in the pass.

@@ -348,21 +350,50 @@ static void collectLoads(SILInstruction *I, SmallVectorImpl<LoadInst *> &Loads)
static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) {
ProjectionPath projections(val->getType());
SILValue op = LI->getOperand();
SILBuilder builder(LI);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be SILBuilderWithScope.

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

@gottesmm
Copy link
Contributor

@meg-gupta one thing that you should look into is changing all SILBuilder -> SILBuilderWithScope with this change.

@meg-gupta meg-gupta force-pushed the mem2regossa branch 2 times, most recently from 5bd11af to 5800a0a Compare October 14, 2020 08:10

// If we met a store before this one, delete it.
// If the LastStore was a store with [assign], delete it only if we know
// the RunningValue to destroy. If not, it will be deleted in
// StackAllocationPromoter::fixBranchesAndUses.
Copy link
Contributor

Choose a reason for hiding this comment

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

Great comment!

Copy link
Contributor

@gottesmm gottesmm left a comment

Choose a reason for hiding this comment

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

LGTM with my comments fixed.

@@ -613,3 +613,20 @@ bb0(%0 : @owned $(Builtin.BridgeObject, Builtin.Int32)):
%7 = tuple ()
return %7 : $()
}

// CHECK-LABEL: sil [ossa] @multiple_debug_value :
// CHECK-NOT: alloc_stack
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice fix! We have seen this bug before I think where someone had a test case that hit this somehow!

Block->getArgument(Block->getNumArguments() - 1));
if (phiArg->getOwnershipKind() == ValueOwnershipKind::Owned &&
phiArg->use_empty()) {
assert(hasOnlyUndefIncomingValues(phiArg));
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice defensive assert!

@@ -679,6 +806,16 @@ void StackAllocationPromoter::fixBranchesAndUses(BlockSet &PhiBlocks) {
// on.
SILBasicBlock *BB = Inst->getParent();

if (!BB->isEntry()) {
if (auto *SI = dyn_cast<StoreInst>(Inst)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If I am understanding this correctly, this is here b/c of the code https://github.com/apple/swift/pull/34276/files#diff-a0cdc552f97b0b66a7c8f63788292539566ab2a53affa110bc00f904df39ecf9R504. Can you put a comment here explaining that?

// guaranteed ownership. For replacing load's users, we need an owned value.
// We will need a new copy and destroy of the running val placed after the
// last use. This is not implemented currently.
if (hasGuaranteedOwnership && cast<LoadInst>(I)->getOwnershipQualifier() ==
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you give an example of this in SIL that shows this problem?

@@ -433,16 +491,51 @@ StackAllocationPromoter::promoteAllocationInBlock(SILBasicBlock *BB) {
if (SI->getDest() != ASI)
continue;

// The stored value is the new running value.
RunningVal = SI->getSrc();
// Special handling of entry block
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you make this pop more? Maybe put a newline? This line is a bit of a title.

@meg-gupta
Copy link
Contributor Author

@swift-ci test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 84f17c0a1b929e99525887e17b557a8987230d4e

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 84f17c0a1b929e99525887e17b557a8987230d4e

SILMem2Reg has roughly 2 central algorithms, removal of alloc_stack with uses in a
single block vs multiple blocks. While replacing loads and stores to the
stack location in each of the 2 algorithms, new handling of qualifiers
like [assign], [copy] and [take] which are new to OSSA are needed.

Also Disable SILMem2Reg when we see this pattern:
load [take] (struct_element_addr/tuple_element_addr %ASI)

Convert SILMem2Reg tests into ossa
And add new SILMem2Reg tests for non-trivial values.
Thanks to zoecarver for additional tests.
… OSSA

unchecked_ref_cast is a forwarding cast while unchecked_bitwise_cast is
not. We cannot just convert one to other in OSSA. Disable it now.
@meg-gupta
Copy link
Contributor Author

meg-gupta commented Oct 21, 2020

Had to force push to fix an aggressive assert. Only difference after the approval :

--- a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp
+++ b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp
@@ -407,7 +407,8 @@ static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) {
   if (LI->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) {
     LI->replaceAllUsesWith(builder.createCopyValue(LI->getLoc(), val));
   } else {
-    assert(val.getOwnershipKind() != ValueOwnershipKind::Guaranteed);
+    assert(!ASI->getFunction()->hasOwnership() ||
+           val.getOwnershipKind() != ValueOwnershipKind::Guaranteed);
     LI->replaceAllUsesWith(val);
   }

@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

I'll fix the remaining comments in a follow up PR as suggested.

@meg-gupta meg-gupta merged commit a790439 into swiftlang:main Oct 21, 2020
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.

4 participants