Skip to content

Commit 4324432

Browse files
authored
Merge pull request #35676 from atrick/fix-copyprop-reborrow
Fix CopyPropagation's reborrow handling.
2 parents 854c258 + 17bcf54 commit 4324432

File tree

3 files changed

+254
-21
lines changed

3 files changed

+254
-21
lines changed

lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ bool CanonicalizeOSSALifetime::computeBorrowLiveness() {
142142
if (!EnableRewriteBorrows) {
143143
return false;
144144
}
145+
// Note that there is no need to look through any reborrows. The reborrowed
146+
// value is considered a separate lifetime for canonicalization. Any copies of
147+
// the reborrowed value will not be rewritten when canonicalizing the current
148+
// borrow scope because they are "hidden" behind the reborrow.
145149
borrowedVal.visitLocalScopeEndingUses([this](Operand *use) {
146150
liveness.updateForUse(use->getUser(), /*lifetimeEnding*/ true);
147151
return true;
@@ -174,6 +178,10 @@ static CopyValueInst *createOuterCopy(BeginBorrowInst *beginBorrow) {
174178
return copy;
175179
}
176180

181+
// If this succeeds, then all uses of the borrowed value outside the borrow
182+
// scope will be rewritten to use an outer copy, and all remaining uses of the
183+
// borrowed value will be confined to the borrow scope.
184+
//
177185
// TODO: Canonicalize multi-block borrow scopes, load_borrow scope, and phi
178186
// borrow scopes by adding one copy per block to persistentCopies for
179187
// each block that dominates an outer use.
@@ -200,7 +208,16 @@ bool CanonicalizeOSSALifetime::consolidateBorrowScope() {
200208
outerUses.push_back(use);
201209
outerUseInsts.insert(use->getUser());
202210
};
203-
211+
// getCanonicalCopiedDef ensures that if currentDef is a guaranteed value,
212+
// then it is a borrow scope introducer.
213+
assert(BorrowedValue(currentDef).isLocalScope());
214+
215+
// This def-use traversal is similar to
216+
// findExtendedTransitiveGuaranteedUses(), however, to cover the canonical
217+
// lifetime, it looks through copies. It also considered uses within the
218+
// introduced borrow scope itself (instead of simply visiting the scope-ending
219+
// uses). It does not, however, look into nested borrow scopes uses, since
220+
// nested scopes are canonicalized independently.
204221
defUseWorklist.clear();
205222
defUseWorklist.insert(currentDef);
206223
while (!defUseWorklist.empty()) {
@@ -227,11 +244,18 @@ bool CanonicalizeOSSALifetime::consolidateBorrowScope() {
227244
case OperandOwnership::EndBorrow:
228245
case OperandOwnership::Reborrow:
229246
// Ignore uses that must be within the borrow scope.
247+
// Rewriting does not look through reborrowed values--it considers them
248+
// part of a separate lifetime.
230249
break;
231250

232251
case OperandOwnership::ForwardingUnowned:
233252
case OperandOwnership::PointerEscape:
234-
return false;
253+
// Pointer escapes are only allowed if they use the guaranteed value,
254+
// which means that the escaped value must be confied to the current
255+
// borrow scope.
256+
if (use->get().getOwnershipKind() != OwnershipKind::Guaranteed)
257+
return false;
258+
break;
235259

236260
case OperandOwnership::InstantaneousUse:
237261
case OperandOwnership::UnownedInstantaneousUse:
@@ -242,21 +266,20 @@ bool CanonicalizeOSSALifetime::consolidateBorrowScope() {
242266
break;
243267
case OperandOwnership::Borrow:
244268
BorrowingOperand borrowOper(use);
245-
if (borrowOper.kind == BorrowingOperandKind::Invalid) {
246-
return false;
247-
}
269+
assert(borrowOper && "BorrowingOperand must handle OperandOwnership");
248270
recordOuterUse(use);
249271
// For borrows, record the scope-ending instructions in addition to the
250-
// borrow instruction as an outer use point.
251-
borrowOper.visitScopeEndingUses([&](Operand *endBorrow) {
272+
// borrow instruction outer use points. The logic below to check whether
273+
// a borrow scope is an outer use must visit the same set of uses.
274+
borrowOper.visitExtendedScopeEndingUses([&](Operand *endBorrow) {
252275
if (!isUserInLiveOutBlock(endBorrow->getUser())) {
253276
outerUseInsts.insert(endBorrow->getUser());
254277
}
255278
return true;
256279
});
257280
break;
258281
}
259-
}
282+
} // end switch OperandOwnership
260283
} // end def-use traversal
261284

262285
auto *beginBorrow = cast<BeginBorrowInst>(currentDef);
@@ -286,7 +309,7 @@ bool CanonicalizeOSSALifetime::consolidateBorrowScope() {
286309
}
287310
// For sub-borrows also check that the scope-ending instructions are
288311
// within the scope.
289-
if (borrowOper.visitScopeEndingUses([&](Operand *endBorrow) {
312+
if (borrowOper.visitExtendedScopeEndingUses([&](Operand *endBorrow) {
290313
return !outerUseInsts.count(endBorrow->getUser());
291314
})) {
292315
continue;
@@ -364,7 +387,7 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
364387
llvm_unreachable("this operand cannot handle ownership");
365388

366389
// Conservatively treat a conversion to an unowned value as a pointer
367-
// escape. Is it legal to canonicalize these?
390+
// escape. Is it legal to canonicalize ForwardingUnowned?
368391
case OperandOwnership::ForwardingUnowned:
369392
case OperandOwnership::PointerEscape:
370393
return false;
@@ -385,12 +408,13 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
385408
recordConsumingUse(use);
386409
break;
387410
case OperandOwnership::Borrow:
388-
// An entire borrow scope is considered a single use that occurs at the
389-
// point of the end_borrow.
390-
BorrowingOperand(use).visitScopeEndingUses([this](Operand *end) {
391-
liveness.updateForUse(end->getUser(), /*lifetimeEnding*/ false);
392-
return true;
393-
});
411+
// The liveness of an entire extended borrow scope is modeled as use
412+
// points and the scope-ending uses.
413+
BorrowingOperand(use).visitExtendedScopeEndingUses(
414+
[this](Operand *end) {
415+
liveness.updateForUse(end->getUser(), /*lifetimeEnding*/ false);
416+
return true;
417+
});
394418
break;
395419
case OperandOwnership::InteriorPointer:
396420
case OperandOwnership::ForwardingBorrow:

test/SIL/ownership-verifier/borrow_validate.sil

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,31 @@ bb3(%borrow : @guaranteed $FakeOptional<Klass>, %mKlass : @owned $FakeOptional<K
740740
%9999 = tuple()
741741
return %9999 : $()
742742
}
743+
744+
// Test copying a borrowed value after it has been reborrowed in a different block.
745+
//
746+
// CHECK-LABEL: Error#: 0. Begin Error in Function: 'test_copy_borrow_after_reborrow'
747+
// CHECK: Found outside of lifetime use!
748+
// CHECK: Value: %1 = begin_borrow %0 : $Klass
749+
// CHECK: User: %6 = copy_value %1 : $Klass
750+
// CHECK: Block: bb3
751+
// CHECK-LABEL: End Error in Function: 'test_copy_borrow_after_reborrow'
752+
sil [ossa] @test_copy_borrow_after_reborrow : $@convention(thin) () -> () {
753+
bb0:
754+
%alloc = alloc_ref $Klass
755+
%borrow = begin_borrow %alloc : $Klass
756+
cond_br undef, bb1, bb2
757+
bb1:
758+
br bb3(%borrow: $Klass)
759+
bb2:
760+
br bb3(%borrow: $Klass)
761+
bb3(%borrowphi : @guaranteed $Klass):
762+
%innercopy = copy_value %borrow : $Klass
763+
end_borrow %borrowphi : $Klass
764+
%outercopy = copy_value %innercopy : $Klass
765+
destroy_value %innercopy : $Klass
766+
destroy_value %outercopy : $Klass
767+
destroy_value %alloc : $Klass
768+
%99 = tuple ()
769+
return %99 : $()
770+
}

test/SILOptimizer/copy_propagation.sil

Lines changed: 186 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,19 +1345,21 @@ bb3:
13451345
return %result : $()
13461346
}
13471347

1348-
// Do not consolidate this local borrowscope because it has a PointerEscape.
1348+
// Consolidate this local borrowscope even though it has a
1349+
// PointerEscape. The escaping value can be assumed not to be used
1350+
// outside the borrow scope.
13491351
//
13501352
// CHECK-LABEL: sil [ossa] @testBorrowEscape : $@convention(thin) (@guaranteed C) -> () {
13511353
// CHECK: [[BORROW:%.*]] = begin_borrow %0 : $C
1352-
// CHECK: [[COPY:%.*]] = copy_value [[BORROW]] : $C
1353-
// CHECK-NEXT: end_borrow %1 : $C
1354+
// CHECK-NOT: copy_value
1355+
// CHECK: end_borrow [[BORROW]] : $C
13541356
// CHECK-NEXT: cond_br undef, bb1, bb2
13551357
// CHECK: bb1:
1358+
// CHECK: [[COPY:%.*]] = copy_value %0 : $C
13561359
// CHECK: apply %{{.*}}([[COPY]]) : $@convention(thin) (@owned C) -> ()
13571360
// CHECK: br bb3
13581361
// CHECK: bb2:
1359-
// CHECK-NEXT: destroy_value [[COPY]] : $C
1360-
// CHECK: br bb3
1362+
// CHECK-NEXT: br bb3
13611363
// CHECK: bb3:
13621364
// CHECK-NOT: destroy
13631365
// CHECK-LABEL: } // end sil function 'testBorrowEscape'
@@ -1447,3 +1449,182 @@ bb3:
14471449
destroy_value %2 : $C
14481450
return %0 : $C
14491451
}
1452+
1453+
// =============================================================================
1454+
// Test reborrows
1455+
// =============================================================================
1456+
1457+
// Extend the lifetime of an owned value through a nested borrow scope
1458+
// with cross-block reborrows. Its lifetime must be extended past the
1459+
// end_borrow of the phi.
1460+
//
1461+
// CHECK-LABEL: sil [ossa] @testSubReborrowExtension : $@convention(thin) () -> () {
1462+
// CHECK: bb0:
1463+
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
1464+
// CHECK: [[BORROW:%.*]] = begin_borrow %0 : $C
1465+
// CHECK: cond_br undef, bb1, bb2
1466+
// CHECK: bb1:
1467+
// CHECK: [[CP1:%.*]] = copy_value %0 : $C
1468+
// CHECK: br bb3([[CP1]] : $C, [[BORROW]] : $C)
1469+
// CHECK: bb2:
1470+
// CHECK: [[CP2:%.*]] = copy_value %0 : $C
1471+
// CHECK: br bb3([[CP2]] : $C, [[BORROW]] : $C)
1472+
// CHECK: bb3([[OWNEDPHI:%.*]] : @owned $C, [[BORROWPHI:%.*]] @guaranteed $C
1473+
// CHECK: end_borrow [[BORROWPHI]]
1474+
// CHECK: destroy_value [[OWNEDPHI]] : $C
1475+
// CHECK: destroy_value %0 : $C
1476+
// CHECK-LABEL: } // end sil function 'testSubReborrowExtension'
1477+
sil [ossa] @testSubReborrowExtension : $@convention(thin) () -> () {
1478+
bb0:
1479+
%alloc = alloc_ref $C
1480+
%copy = copy_value %alloc : $C
1481+
%borrow = begin_borrow %alloc : $C
1482+
cond_br undef, bb1, bb2
1483+
bb1:
1484+
br bb3(%copy : $C, %borrow: $C)
1485+
bb2:
1486+
br bb3(%copy : $C, %borrow: $C)
1487+
bb3(%phi : @owned $C, %borrowphi : @guaranteed $C):
1488+
end_borrow %borrowphi : $C
1489+
destroy_value %phi : $C
1490+
destroy_value %alloc : $C
1491+
%99 = tuple ()
1492+
return %99 : $()
1493+
}
1494+
1495+
// Test a cross-block reborrow and a live nested copy of the reborrow:
1496+
// def: borrow
1497+
// use: phi reborrow (in the same block as the borrow)
1498+
// copy reborrowed phi
1499+
// end reborrow
1500+
// copy outside borrow scope.
1501+
//
1502+
// consolidateBorrow only processes the SSA borrow scope, not the
1503+
// extended borrow scope, and it does not process the borrow scope
1504+
// introduced by the phi. Therefore, it should leave the in-scope copy
1505+
// alone-- it will be treated like the definition of a separate OSSA
1506+
// lifetime. The inner copy's lifetime will be canonicalized, removing
1507+
// outercopy.
1508+
//
1509+
// CHECK-LABEL: sil [ossa] @testLiveCopyAfterReborrow : $@convention(thin) () -> () {
1510+
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
1511+
// CHECK: bb3([[BORROWPHI:%.*]] : @guaranteed $C):
1512+
// CHECK: [[COPY:%.*]] = copy_value [[BORROWPHI]]
1513+
// CHECK: end_borrow [[BORROWPHI]] : $C
1514+
// CHECK-NOT: copy_value
1515+
// CHECK-NOT: destroy_value
1516+
// CHECK: apply
1517+
// CHECK: destroy_value [[COPY]]
1518+
// CHECK: destroy_value [[ALLOC]] : $C
1519+
// CHECK-LABEL: } // end sil function 'testLiveCopyAfterReborrow'
1520+
sil [ossa] @testLiveCopyAfterReborrow : $@convention(thin) () -> () {
1521+
bb0:
1522+
%alloc = alloc_ref $C
1523+
cond_br undef, bb1, bb2
1524+
bb1:
1525+
%borrow1 = begin_borrow %alloc : $C
1526+
br bb3(%borrow1: $C)
1527+
bb2:
1528+
%borrow2 = begin_borrow %alloc : $C
1529+
br bb3(%borrow2: $C)
1530+
bb3(%borrowphi : @guaranteed $C):
1531+
%innercopy = copy_value %borrowphi : $C
1532+
end_borrow %borrowphi : $C
1533+
%outercopy = copy_value %innercopy : $C
1534+
destroy_value %innercopy : $C
1535+
%f = function_ref @takeGuaranteedC : $@convention(thin) (@guaranteed C) -> ()
1536+
apply %f(%outercopy) : $@convention(thin) (@guaranteed C) -> ()
1537+
destroy_value %outercopy : $C
1538+
destroy_value %alloc : $C
1539+
%99 = tuple ()
1540+
return %99 : $()
1541+
}
1542+
1543+
// Test a cross-block reborrow and a dead nested copy of the reborrow:
1544+
// def: borrow
1545+
// use: phi reborrow (in the same block as the borrow)
1546+
// copy reborrowed phi
1547+
// end reborrow
1548+
// use of copied reborrow outside borrow scope.
1549+
//
1550+
// consolidateBorrow only processes the SSA borrow scope, not the
1551+
// extended borrow scope, and it does not process the borrow scope
1552+
// introduced by the phi. Therefore, it should leave the in-scope copy alone--
1553+
// it will be treated like the definition of a separate OSSA lifetime.
1554+
// The inner copy's lifetime will be canonicalized, removing
1555+
// outercopy.
1556+
//
1557+
// CHECK-LABEL: sil [ossa] @testDeadCopyAfterReborrow : $@convention(thin) () -> () {
1558+
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
1559+
// CHECK: bb3([[BORROWPHI:%.*]] : @guaranteed $C):
1560+
// CHECK-NOT: copy_value
1561+
// CHECK: end_borrow [[BORROWPHI]] : $C
1562+
// CHECK-NOT: copy_value
1563+
// CHECK: destroy_value [[ALLOC]] : $C
1564+
// CHECK-LABEL: } // end sil function 'testDeadCopyAfterReborrow'
1565+
sil [ossa] @testDeadCopyAfterReborrow : $@convention(thin) () -> () {
1566+
bb0:
1567+
%alloc = alloc_ref $C
1568+
cond_br undef, bb1, bb2
1569+
bb1:
1570+
%borrow1 = begin_borrow %alloc : $C
1571+
br bb3(%borrow1: $C)
1572+
bb2:
1573+
%borrow2 = begin_borrow %alloc : $C
1574+
br bb3(%borrow2: $C)
1575+
bb3(%borrowphi : @guaranteed $C):
1576+
%innercopy = copy_value %borrowphi : $C
1577+
end_borrow %borrowphi : $C
1578+
%outercopy = copy_value %innercopy : $C
1579+
destroy_value %innercopy : $C
1580+
destroy_value %outercopy : $C
1581+
destroy_value %alloc : $C
1582+
%99 = tuple ()
1583+
return %99 : $()
1584+
}
1585+
1586+
// Test a reborrow with a nested borrow with an outside use
1587+
// def: borrowphi
1588+
// nested borrow -- consolidated borrow scope
1589+
// inner copy -- removed when rewriting nested borrow
1590+
// end nested borrow
1591+
// middle copy -- when consolidating borrow scope,
1592+
// its operand is replace with a copy of borrowphi
1593+
// then removeed when borrowphi's copy is canonicalized
1594+
// end borrowphi
1595+
// outer copy -- removeed when borrowphi's copy is canonicalized
1596+
//
1597+
// CHECK-LABEL: sil [ossa] @testNestedReborrowOutsideUse : $@convention(thin) () -> () {
1598+
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
1599+
// CHECK: bb3([[BORROWPHI:%.*]] : @guaranteed $C):
1600+
// CHECK-NEXT: [[COPY:%.*]] = copy_value [[BORROWPHI]] : $C
1601+
// CHECK-NEXT: begin_borrow [[BORROWPHI]] : $C
1602+
// CHECK-NEXT: end_borrow
1603+
// CHECK-NEXT: end_borrow
1604+
// CHECK-NEXT: destroy_value [[COPY]] : $C
1605+
// CHECK-NEXT: destroy_value [[ALLOC]] : $C
1606+
// CHECK-LABEL: } // end sil function 'testNestedReborrowOutsideUse'
1607+
sil [ossa] @testNestedReborrowOutsideUse : $@convention(thin) () -> () {
1608+
bb0:
1609+
%alloc = alloc_ref $C
1610+
cond_br undef, bb1, bb2
1611+
bb1:
1612+
%borrow1 = begin_borrow %alloc : $C
1613+
br bb3(%borrow1: $C)
1614+
bb2:
1615+
%borrow2 = begin_borrow %alloc : $C
1616+
br bb3(%borrow2: $C)
1617+
bb3(%borrowphi : @guaranteed $C):
1618+
%nestedborrow = begin_borrow %borrowphi : $C
1619+
%innercopy = copy_value %nestedborrow : $C
1620+
end_borrow %nestedborrow : $C
1621+
%middlecopy = copy_value %innercopy : $C
1622+
end_borrow %borrowphi : $C
1623+
%outercopy = copy_value %middlecopy : $C
1624+
destroy_value %innercopy : $C
1625+
destroy_value %middlecopy : $C
1626+
destroy_value %outercopy : $C
1627+
destroy_value %alloc : $C
1628+
%99 = tuple ()
1629+
return %99 : $()
1630+
}

0 commit comments

Comments
 (0)