Skip to content

Commit 198685c

Browse files
committed
[region-isolation] Make store_borrow a store operation that does not require.
TLDR: The reason why I am doing this is it ensures that temporary store_borrow that we create when materializing a value before were treated as uses. So we would error on this: ```swift @mainactor func transferToMain<T>(_ t: T) async {} func test() async { let x = NonSendableKlass() await transferToMain(x) await transferToMain(x) } ``` ---- store_borrow is an instruction intended to be used to initialize temporary alloc_stack with borrows. Since it is a temporary, we do not want to error on the temporaries initialization... instead, we want to error on the use of the temporary parameter. This is achieved by making it so that store_borrow still performs an assign/merge, but does not require that src/dest be alive. So the regions still merge (yielding diagnostics for later uses). It also required me to make it so that PartitionOp::{Assign,Merge} do not require by default. Instead, we want the individual operations to always emit a PartitionOp::Require explicitly (which they already did). One thing to be aware of is that when it comes to diagnostics, we already know how to find a temporaries original value and how to handle that. So this is the last part of making store_borrow behave nicely. rdar://129237675 (cherry-picked from commit bd472b1)
1 parent e58b7a3 commit 198685c

File tree

4 files changed

+85
-46
lines changed

4 files changed

+85
-46
lines changed

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,14 +1057,6 @@ struct PartitionOpEvaluator {
10571057
"Assign PartitionOp should be passed 2 arguments");
10581058
assert(p.isTrackingElement(op.getOpArgs()[1]) &&
10591059
"Assign PartitionOp's source argument should be already tracked");
1060-
// If we are using a region that was transferred as our assignment source
1061-
// value... emit an error.
1062-
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[1])) {
1063-
for (auto transferredOperand : transferredOperandSet->data()) {
1064-
handleLocalUseAfterTransferHelper(op, op.getOpArgs()[1],
1065-
transferredOperand);
1066-
}
1067-
}
10681060
p.assignElement(op.getOpArgs()[0], op.getOpArgs()[1]);
10691061
return;
10701062
case PartitionOpKind::AssignFresh:
@@ -1154,20 +1146,6 @@ struct PartitionOpEvaluator {
11541146
p.isTrackingElement(op.getOpArgs()[1]) &&
11551147
"Merge PartitionOp's arguments should already be tracked");
11561148

1157-
// if attempting to merge a transferred region, handle the failure
1158-
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[0])) {
1159-
for (auto transferredOperand : transferredOperandSet->data()) {
1160-
handleLocalUseAfterTransferHelper(op, op.getOpArgs()[0],
1161-
transferredOperand);
1162-
}
1163-
}
1164-
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[1])) {
1165-
for (auto transferredOperand : transferredOperandSet->data()) {
1166-
handleLocalUseAfterTransferHelper(op, op.getOpArgs()[1],
1167-
transferredOperand);
1168-
}
1169-
}
1170-
11711149
p.merge(op.getOpArgs()[0], op.getOpArgs()[1]);
11721150
return;
11731151
case PartitionOpKind::Require:

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,7 +1606,8 @@ class PartitionOpTranslator {
16061606
void
16071607
translateSILMultiAssign(const TargetRange &resultValues,
16081608
const SourceRange &sourceValues,
1609-
SILIsolationInfo resultIsolationInfoOverride = {}) {
1609+
SILIsolationInfo resultIsolationInfoOverride = {},
1610+
bool requireSrcValues = true) {
16101611
SmallVector<SILValue, 8> assignOperands;
16111612
SmallVector<SILValue, 8> assignResults;
16121613

@@ -1672,9 +1673,17 @@ class PartitionOpTranslator {
16721673
}
16731674
}
16741675

1675-
// Require all srcs.
1676-
for (auto src : assignOperands)
1677-
builder.addRequire(src);
1676+
// Require all srcs if we are supposed to. (By default we do).
1677+
//
1678+
// DISCUSSION: The reason that this may be useful is for special
1679+
// instructions like store_borrow. On the one hand, we want store_borrow to
1680+
// act like a store in the sense that we want to combine the regions of its
1681+
// src and dest... but at the same time, we do not want to treat the store
1682+
// itself as a use of its parent value. We want that to be any subsequent
1683+
// uses of the store_borrow.
1684+
if (requireSrcValues)
1685+
for (auto src : assignOperands)
1686+
builder.addRequire(src);
16781687

16791688
// Merge all srcs.
16801689
for (unsigned i = 1; i < assignOperands.size(); i++) {
@@ -2136,21 +2145,28 @@ class PartitionOpTranslator {
21362145
}
21372146

21382147
template <typename Collection>
2139-
void translateSILMerge(SILValue dest, Collection collection) {
2148+
void translateSILMerge(SILValue dest, Collection collection,
2149+
bool requireOperands = true) {
21402150
auto trackableDest = tryToTrackValue(dest);
21412151
if (!trackableDest)
21422152
return;
21432153
for (SILValue elt : collection) {
21442154
if (auto trackableSrc = tryToTrackValue(elt)) {
2155+
if (requireOperands) {
2156+
builder.addRequire(trackableSrc->getRepresentative().getValue());
2157+
builder.addRequire(trackableDest->getRepresentative().getValue());
2158+
}
21452159
builder.addMerge(trackableDest->getRepresentative().getValue(),
21462160
trackableSrc->getRepresentative().getValue());
21472161
}
21482162
}
21492163
}
21502164

21512165
template <>
2152-
void translateSILMerge<SILValue>(SILValue dest, SILValue src) {
2153-
return translateSILMerge(dest, TinyPtrVector<SILValue>(src));
2166+
void translateSILMerge<SILValue>(SILValue dest, SILValue src,
2167+
bool requireOperands) {
2168+
return translateSILMerge(dest, TinyPtrVector<SILValue>(src),
2169+
requireOperands);
21542170
}
21552171

21562172
void translateSILAssignmentToTransferringParameter(TrackableValue destRoot,
@@ -2641,7 +2657,6 @@ CONSTANT_TRANSLATION(InitExistentialValueInst, LookThrough)
26412657
CONSTANT_TRANSLATION(CopyAddrInst, Store)
26422658
CONSTANT_TRANSLATION(ExplicitCopyAddrInst, Store)
26432659
CONSTANT_TRANSLATION(StoreInst, Store)
2644-
CONSTANT_TRANSLATION(StoreBorrowInst, Store)
26452660
CONSTANT_TRANSLATION(StoreWeakInst, Store)
26462661
CONSTANT_TRANSLATION(MarkUnresolvedMoveAddrInst, Store)
26472662
CONSTANT_TRANSLATION(UncheckedRefCastAddrInst, Store)
@@ -2895,6 +2910,44 @@ LOOKTHROUGH_IF_NONSENDABLE_RESULT_AND_OPERAND(UncheckedTakeEnumDataAddrInst)
28952910
// Custom Handling
28962911
//
28972912

2913+
TranslationSemantics
2914+
PartitionOpTranslator::visitStoreBorrowInst(StoreBorrowInst *sbi) {
2915+
// A store_borrow is an interesting instruction since we are essentially
2916+
// temporarily binding an object value to an address... so really any uses of
2917+
// the address, we want to consider to be uses of the parent object. So we
2918+
// basically put source/dest into the same region, but do not consider the
2919+
// store_borrow itself to be a require use. This prevents the store_borrow
2920+
// from causing incorrect diagnostics.
2921+
SILValue destValue = sbi->getDest();
2922+
SILValue srcValue = sbi->getSrc();
2923+
2924+
auto nonSendableDest = tryToTrackValue(destValue);
2925+
if (!nonSendableDest)
2926+
return TranslationSemantics::Ignored;
2927+
2928+
// In the following situations, we can perform an assign:
2929+
//
2930+
// 1. A store to unaliased storage.
2931+
// 2. A store that is to an entire value.
2932+
//
2933+
// DISCUSSION: If we have case 2, we need to merge the regions since we
2934+
// are not overwriting the entire region of the value. This does mean that
2935+
// we artificially include the previous region that was stored
2936+
// specifically in this projection... but that is better than
2937+
// miscompiling. For memory like this, we probably need to track it on a
2938+
// per field basis to allow for us to assign.
2939+
if (nonSendableDest.value().isNoAlias() &&
2940+
!isProjectedFromAggregate(destValue)) {
2941+
translateSILMultiAssign(sbi->getResults(), sbi->getOperandValues(),
2942+
SILIsolationInfo(), false /*require src*/);
2943+
} else {
2944+
// Stores to possibly aliased storage must be treated as merges.
2945+
translateSILMerge(destValue, srcValue, false /*require src*/);
2946+
}
2947+
2948+
return TranslationSemantics::Special;
2949+
}
2950+
28982951
TranslationSemantics
28992952
PartitionOpTranslator::visitAllocStackInst(AllocStackInst *asi) {
29002953
// Before we do anything, see if asi is Sendable or if it is non-Sendable,

test/Concurrency/transfernonsendable.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
////////////////////////
1515

1616
/// Classes are always non-sendable, so this is non-sendable
17-
class NonSendableKlass { // expected-complete-note 51{{}}
17+
class NonSendableKlass { // expected-complete-note 53{{}}
1818
// expected-typechecker-only-note @-1 3{{}}
1919
// expected-tns-note @-2 {{}}
2020
var field: NonSendableKlass? = nil
@@ -1825,3 +1825,13 @@ func testBooleanCapture(_ x: inout NonSendableKlass) {
18251825
print(z)
18261826
}
18271827
}
1828+
1829+
// We do not get an error here since we are transferring x both times to a main
1830+
// actor isolated thing function. We used to emit an error when using region
1831+
// isolation since we would trip on the store_borrow we used to materialize the
1832+
// value.
1833+
func testIndirectParameterSameIsolationNoError() async {
1834+
let x = NonSendableKlass()
1835+
await transferToMain(x) // expected-complete-warning {{passing argument of non-sendable type 'NonSendableKlass' into main actor-isolated context may introduce data races}}
1836+
await transferToMain(x) // expected-complete-warning {{passing argument of non-sendable type 'NonSendableKlass' into main actor-isolated context may introduce data races}}
1837+
}

test/Concurrency/transfernonsendable_asynclet.swift

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ final actor FinalMyActor {
3737
func useKlass(_ x: NonSendableKlass) {}
3838
}
3939

40+
actor CustomActorInstance {}
41+
42+
@globalActor
43+
struct CustomActor {
44+
static let shared = CustomActorInstance()
45+
}
46+
4047
func useInOut<T>(_ x: inout T) {}
4148
@discardableResult
4249
func useValue<T>(_ x: T) -> T { x }
@@ -52,6 +59,7 @@ func useMainActorValueNoReturn<T>(_ x: T) -> () { fatalError() }
5259
@MainActor func returnValueFromMain<T>() async -> T { fatalError() }
5360
@MainActor func transferToMain<T>(_ t: T) async {}
5461
@MainActor func transferToMainInt<T>(_ t: T) async -> Int { 5 }
62+
@CustomActor func transferToCustomInt<T>(_ t: T) async -> Int { 5 }
5563
@MainActor func transferToMainIntOpt<T>(_ t: T) async -> Int? { 5 }
5664

5765
func transferToNonIsolated<T>(_ t: T) async {}
@@ -69,15 +77,6 @@ struct TwoFieldKlassBox {
6977
var k2 = NonSendableKlass()
7078
}
7179

72-
actor CustomActorInstance {}
73-
74-
@globalActor
75-
struct CustomActor {
76-
static let shared = CustomActorInstance()
77-
}
78-
79-
@CustomActor func transferToCustomInt<T>(_ t: T) async -> Int { 5 }
80-
8180
/////////////////////////////////////
8281
// MARK: Async Let Let Actor Tests //
8382
/////////////////////////////////////
@@ -294,14 +293,13 @@ func asyncLet_Let_ActorIsolated_CallBuriedInOtherExpr3() async {
294293
let _ = await y
295294
}
296295

297-
// Make sure that we emit an error for transferToMainInt in the async val
298-
// function itself.
296+
// Make sure that we do not emit an error for transferToMainInt in the async val
297+
// function itself since we are sending the value to the same main actor
298+
// isolated use and transferring it into one async let variable.
299299
func asyncLet_Let_ActorIsolated_CallBuriedInOtherExpr4() async {
300300
let x = NonSendableKlass()
301301

302-
async let y = useValue(transferToMainInt(x) + transferToMainInt(x)) // expected-warning {{sending 'x' risks causing data races}}
303-
// expected-note @-1 {{sending 'x' to main actor-isolated global function 'transferToMainInt' risks causing data races between main actor-isolated and local nonisolated uses}}
304-
// expected-note @-2:67 {{access can happen concurrently}}
302+
async let y = useValue(transferToMainInt(x) + transferToMainInt(x))
305303

306304
let _ = await y
307305
}
@@ -314,7 +312,7 @@ func asyncLet_Let_ActorIsolated_CallBuriedInOtherExpr5() async {
314312
async let y = useValue(transferToMainInt(x) + transferToCustomInt(x))
315313
// expected-warning @-1 {{sending 'x' risks causing data races}}
316314
// expected-note @-2 {{sending 'x' to main actor-isolated global function 'transferToMainInt' risks causing data races between main actor-isolated and local nonisolated uses}}
317-
// expected-note @-3:69 {{access can happen concurrently}}
315+
// expected-note @-3:49 {{access can happen concurrently}}
318316

319317
let _ = await y
320318
}

0 commit comments

Comments
 (0)