Skip to content

Commit 962b4ed

Browse files
committed
[semantic-arc] Change StorageGuaranteesLoadVisitor::visitClassAccess to find borrow scope introducers using utilities from OwnershipUtils.h.
I added a new API into OwnershipUtils called getSingleBorrowIntroducingValue. This API returns a single BorrowScopeIntroducingValue for a passed in guaranteed value. If we can not find such a BorrowScopeIntroducingValue or we find multiple such, we return None. Using that, I refactored StorageGuaranteesLoadVisitor::visitClassAccess(...) to use this new API which should make it significantly more robust since the routine uses the definitions of "guaranteed forwarding" that the ownership verifier uses when it verifies meaning that we can rely on the routine to be exhaustive and correct. This means that we now promote load [copy] -> load_borrow even if our borrow scope introducer feeds through a switch_enum or checked_cast_br result (the main reason I looked into this change). To create getSingleBorrowIntroducingValue, I refactored getUnderlyingBorrowIntroucingValues to use a generator to find all of its underlying values. Then in getSingleBorrowIntroducingValue, I just made it so that we call the generator 1-2 times (as appropriate) to implement this query.
1 parent b58ea4b commit 962b4ed

File tree

3 files changed

+199
-29
lines changed

3 files changed

+199
-29
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,14 @@ bool getAllBorrowIntroducingValues(
564564
Optional<BorrowScopeIntroducingValue>
565565
getSingleBorrowIntroducingValue(SILValue value);
566566

567+
/// Look up through the def-use chain of \p inputValue, looking for an initial
568+
/// "borrow" introducing value. If at any point, we find two introducers or we
569+
/// find a point in the chain we do not understand, we bail and return false. If
570+
/// we are able to understand all of the def-use graph and only find a single
571+
/// introducer, then we return a .some(BorrowScopeIntroducingValue).
572+
Optional<BorrowScopeIntroducingValue>
573+
getSingleBorrowIntroducingValue(SILValue inputValue);
574+
567575
} // namespace swift
568576

569577
#endif

lib/SILOptimizer/Transforms/SemanticARCOpts.cpp

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,45 +1091,50 @@ class StorageGuaranteesLoadVisitor
10911091
if (!field->getField()->isLet()) {
10921092
return answer(true);
10931093
}
1094-
1094+
10951095
// The lifetime of the `let` is guaranteed if it's dominated by the
1096-
// guarantee on the base. Check for a borrow.
1096+
// guarantee on the base. See if we can find a single borrow introducer for
1097+
// this object. If we could not find a single such borrow introducer, assume
1098+
// that our property is conservatively written to.
10971099
SILValue baseObject = field->getOperand();
1098-
auto beginBorrow = dyn_cast<BeginBorrowInst>(baseObject);
1099-
if (beginBorrow)
1100-
baseObject = beginBorrow->getOperand();
1101-
baseObject = stripCasts(baseObject);
1102-
1103-
// A guaranteed argument trivially keeps the base alive for the duration of
1104-
// the projection.
1105-
if (auto *arg = dyn_cast<SILFunctionArgument>(baseObject)) {
1106-
if (arg->getArgumentConvention().isGuaranteedConvention()) {
1100+
auto value = getSingleBorrowIntroducingValue(baseObject);
1101+
if (!value) {
1102+
return answer(true);
1103+
}
1104+
1105+
// Ok, we have a single borrow introducing value. First do a quick check if
1106+
// we have a non-local scope that is a function argument. In such a case, we
1107+
// know statically that our let can not be written to in the current
1108+
// function. To be conservative, assume that all other non-local scopes
1109+
// write to memory.
1110+
if (!value->isLocalScope()) {
1111+
if (value->kind == BorrowScopeIntroducingValueKind::SILFunctionArgument) {
11071112
return answer(false);
11081113
}
1114+
1115+
// TODO: Once we model Coroutine results as non-local scopes, we should be
1116+
// able to return false here for them as well.
1117+
return answer(true);
11091118
}
1110-
1111-
// See if there's a borrow of the base object our load is based on.
1112-
SILValue borrowInst;
1113-
if (isa<LoadBorrowInst>(baseObject)) {
1114-
borrowInst = baseObject;
1115-
} else {
1116-
borrowInst = beginBorrow;
1117-
}
1118-
// TODO: We could also look at a guaranteed phi argument and see whether
1119-
// the loaded copy is dominated by it.
1120-
if (!borrowInst)
1119+
1120+
// TODO: This is disabled temporarily for guaranteed phi args just for
1121+
// staging purposes. Thus be conservative and assume true in these cases.
1122+
if (value->kind == BorrowScopeIntroducingValueKind::Phi) {
11211123
return answer(true);
1124+
}
11221125

1123-
// Use the linear lifetime checker to check whether the copied
1124-
// value is dominated by the lifetime of the borrow it's based on.
1125-
SmallVector<SILInstruction *, 4> baseEndBorrows;
1126-
llvm::copy(borrowInst->getUsersOfType<EndBorrowInst>(),
1127-
std::back_inserter(baseEndBorrows));
1126+
// Ok, we now know that we have a local scope whose lifetime we need to
1127+
// analyze. With that in mind, gather up the lifetime ending uses of our
1128+
// borrow scope introducing value and then use the linear lifetime checker
1129+
// to check whether the copied value is dominated by the lifetime of the
1130+
// borrow it's based on.
1131+
SmallVector<SILInstruction *, 4> endScopeInsts;
1132+
value->getLocalScopeEndingInstructions(endScopeInsts);
11281133

11291134
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
11301135
LinearLifetimeChecker checker(visitedBlocks, ARCOpt.getDeadEndBlocks());
11311136
// Returns true on success. So we invert.
1132-
bool foundError = !checker.validateLifetime(baseObject, baseEndBorrows,
1137+
bool foundError = !checker.validateLifetime(baseObject, endScopeInsts,
11331138
liveRange.getDestroys());
11341139
return answer(foundError);
11351140
}

test/SILOptimizer/semantic-arc-opts.sil

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ sil @get_nativeobject_pair : $@convention(thin) () -> @owned NativeObjectPair
2929
class Klass {}
3030
sil @guaranteed_klass_user : $@convention(thin) (@guaranteed Klass) -> ()
3131
sil @guaranteed_fakeoptional_klass_user : $@convention(thin) (@guaranteed FakeOptional<Klass>) -> ()
32+
sil @guaranteed_fakeoptional_classlet_user : $@convention(thin) (@guaranteed FakeOptional<ClassLet>) -> ()
3233

3334
struct MyInt {
3435
var value: Builtin.Int32
@@ -1499,4 +1500,160 @@ bb2:
14991500
bb3:
15001501
%9999 = tuple()
15011502
return %9999 : $()
1502-
}
1503+
}
1504+
1505+
// CHECK-LABEL: sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_functionarg : $@convention(thin) (@guaranteed FakeOptional<ClassLet>) -> () {
1506+
// CHECK-NOT: load [copy]
1507+
// CHECK: load_borrow
1508+
// CHECK-NOT: load [copy]
1509+
// CHECK: } // end sil function 'convert_load_copy_to_load_borrow_despite_switch_enum_functionarg'
1510+
sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_functionarg : $@convention(thin) (@guaranteed FakeOptional<ClassLet>) -> () {
1511+
bb0(%0 : @guaranteed $FakeOptional<ClassLet>):
1512+
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()
1513+
switch_enum %0 : $FakeOptional<ClassLet>, case #FakeOptional.some!enumelt.1: bb1, case #FakeOptional.none!enumelt: bb2
1514+
1515+
bb1(%1 : @guaranteed $ClassLet):
1516+
%2 = ref_element_addr %1 : $ClassLet, #ClassLet.aLet
1517+
%3 = load [copy] %2 : $*Klass
1518+
apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> ()
1519+
destroy_value %3 : $Klass
1520+
br bb3
1521+
1522+
bb2:
1523+
br bb3
1524+
1525+
bb3:
1526+
%9999 = tuple()
1527+
return %9999 : $()
1528+
}
1529+
1530+
// CHECK-LABEL: sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_beginborrow : $@convention(thin) (@owned FakeOptional<ClassLet>) -> () {
1531+
// CHECK-NOT: load [copy]
1532+
// CHECK: load_borrow
1533+
// CHECK-NOT: load [copy]
1534+
// CHECK: } // end sil function 'convert_load_copy_to_load_borrow_despite_switch_enum_beginborrow'
1535+
sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_beginborrow : $@convention(thin) (@owned FakeOptional<ClassLet>) -> () {
1536+
bb0(%0 : @owned $FakeOptional<ClassLet>):
1537+
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()
1538+
%0a = begin_borrow %0 : $FakeOptional<ClassLet>
1539+
switch_enum %0a : $FakeOptional<ClassLet>, case #FakeOptional.some!enumelt.1: bb1, case #FakeOptional.none!enumelt: bb2
1540+
1541+
bb1(%1 : @guaranteed $ClassLet):
1542+
%2 = ref_element_addr %1 : $ClassLet, #ClassLet.aLet
1543+
%3 = load [copy] %2 : $*Klass
1544+
apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> ()
1545+
destroy_value %3 : $Klass
1546+
br bb3
1547+
1548+
bb2:
1549+
br bb3
1550+
1551+
bb3:
1552+
end_borrow %0a : $FakeOptional<ClassLet>
1553+
destroy_value %0 : $FakeOptional<ClassLet>
1554+
%9999 = tuple()
1555+
return %9999 : $()
1556+
}
1557+
1558+
// CHECK-LABEL: sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_loadborrow : $@convention(thin) (@in_guaranteed FakeOptional<ClassLet>) -> () {
1559+
// CHECK-NOT: load [copy]
1560+
// CHECK: load_borrow
1561+
// CHECK-NOT: load [copy]
1562+
// CHECK: load_borrow
1563+
// CHECK-NOT: load [copy]
1564+
// CHECK: } // end sil function 'convert_load_copy_to_load_borrow_despite_switch_enum_loadborrow'
1565+
sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_loadborrow : $@convention(thin) (@in_guaranteed FakeOptional<ClassLet>) -> () {
1566+
bb0(%0 : $*FakeOptional<ClassLet>):
1567+
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()
1568+
%0a = load_borrow %0 : $*FakeOptional<ClassLet>
1569+
switch_enum %0a : $FakeOptional<ClassLet>, case #FakeOptional.some!enumelt.1: bb1, case #FakeOptional.none!enumelt: bb2
1570+
1571+
bb1(%1 : @guaranteed $ClassLet):
1572+
%2 = ref_element_addr %1 : $ClassLet, #ClassLet.aLet
1573+
%3 = load [copy] %2 : $*Klass
1574+
apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> ()
1575+
destroy_value %3 : $Klass
1576+
br bb3
1577+
1578+
bb2:
1579+
br bb3
1580+
1581+
bb3:
1582+
end_borrow %0a : $FakeOptional<ClassLet>
1583+
%9999 = tuple()
1584+
return %9999 : $()
1585+
}
1586+
1587+
// TODO: We can support this in a little bit once the rest of SemanticARCOpts is
1588+
// guaranteed to be safe with guaranteed phis.
1589+
//
1590+
// CHECK-LABEL: sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_guaranteedphi_1 : $@convention(thin) (@owned FakeOptional<ClassLet>) -> () {
1591+
// CHECK: load [copy]
1592+
// CHECK: } // end sil function 'convert_load_copy_to_load_borrow_despite_switch_enum_guaranteedphi_1'
1593+
sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_guaranteedphi_1 : $@convention(thin) (@owned FakeOptional<ClassLet>) -> () {
1594+
bb0(%0 : @owned $FakeOptional<ClassLet>):
1595+
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()
1596+
cond_br undef, bb0a, bb0b
1597+
1598+
bb0a:
1599+
%0a = begin_borrow %0 : $FakeOptional<ClassLet>
1600+
br bb0c(%0a : $FakeOptional<ClassLet>)
1601+
1602+
bb0b:
1603+
%0b = begin_borrow %0 : $FakeOptional<ClassLet>
1604+
br bb0c(%0b : $FakeOptional<ClassLet>)
1605+
1606+
bb0c(%0c : @guaranteed $FakeOptional<ClassLet>):
1607+
switch_enum %0c : $FakeOptional<ClassLet>, case #FakeOptional.some!enumelt.1: bb1, case #FakeOptional.none!enumelt: bb2
1608+
1609+
bb1(%1 : @guaranteed $ClassLet):
1610+
%2 = ref_element_addr %1 : $ClassLet, #ClassLet.aLet
1611+
%3 = load [copy] %2 : $*Klass
1612+
apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> ()
1613+
destroy_value %3 : $Klass
1614+
br bb3
1615+
1616+
bb2:
1617+
br bb3
1618+
1619+
bb3:
1620+
end_borrow %0c : $FakeOptional<ClassLet>
1621+
destroy_value %0 : $FakeOptional<ClassLet>
1622+
%9999 = tuple()
1623+
return %9999 : $()
1624+
}
1625+
1626+
// Make sure that if begin_borrow has a consuming end scope use, we can still
1627+
// eliminate load [copy].
1628+
//
1629+
// CHECK-LABEL: sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_guaranteedphi_2 : $@convention(thin) (@owned FakeOptional<ClassLet>) -> () {
1630+
// CHECK-NOT: load [copy]
1631+
// CHECK: load_borrow
1632+
// CHECK-NOT: load [copy]
1633+
// CHECK: } // end sil function 'convert_load_copy_to_load_borrow_despite_switch_enum_guaranteedphi_2'
1634+
sil [ossa] @convert_load_copy_to_load_borrow_despite_switch_enum_guaranteedphi_2 : $@convention(thin) (@owned FakeOptional<ClassLet>) -> () {
1635+
bb0(%0 : @owned $FakeOptional<ClassLet>):
1636+
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()
1637+
cond_br undef, bb1, bb2
1638+
1639+
bb1:
1640+
%0a = begin_borrow %0 : $FakeOptional<ClassLet>
1641+
br bb3(%0a : $FakeOptional<ClassLet>)
1642+
1643+
bb2:
1644+
%0b = begin_borrow %0 : $FakeOptional<ClassLet>
1645+
%0b2 = unchecked_enum_data %0b : $FakeOptional<ClassLet>, #FakeOptional.some!enumelt.1
1646+
%2 = ref_element_addr %0b2 : $ClassLet, #ClassLet.aLet
1647+
%3 = load [copy] %2 : $*Klass
1648+
apply %f(%3) : $@convention(thin) (@guaranteed Klass) -> ()
1649+
destroy_value %3 : $Klass
1650+
br bb3(%0b : $FakeOptional<ClassLet>)
1651+
1652+
bb3(%0c : @guaranteed $FakeOptional<ClassLet>):
1653+
%f2 = function_ref @guaranteed_fakeoptional_classlet_user : $@convention(thin) (@guaranteed FakeOptional<ClassLet>) -> ()
1654+
apply %f2(%0c) : $@convention(thin) (@guaranteed FakeOptional<ClassLet>) -> ()
1655+
end_borrow %0c : $FakeOptional<ClassLet>
1656+
destroy_value %0 : $FakeOptional<ClassLet>
1657+
%9999 = tuple()
1658+
return %9999 : $()
1659+
}

0 commit comments

Comments
 (0)