Skip to content

Commit ae2bef9

Browse files
authored
Merge pull request #30069 from gottesmm/pr-cdc5d32f1a5317b8716c016a0e72f9ee40eae4dd
[semantic-arc] Change StorageGuaranteesLoadVisitor::visitClassAccess to find borrow scope introducers using utilities from OwnershipUtils.h.
2 parents f142eed + 962b4ed commit ae2bef9

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)