Skip to content

Commit 40cd987

Browse files
committed
[region-isolation] Always require before transferring.
We purposely do not treat a PartitionOpKind::Transfer as a require since that would cause us to error when we weakly transfer the same parameter multiple times to the same function. This is safe since all of the function parameters will be in the same region. To ensure that this fixed the multiple strong transferring issue (which is exposed by requiring before transferring), I also had to muck around a little with how we emit errors to ensure that we emit errors if the transfer instruction is also the require.
1 parent f3edb57 commit 40cd987

File tree

5 files changed

+86
-33
lines changed

5 files changed

+86
-33
lines changed

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,7 @@ class PartitionOpTranslator {
16251625
if (auto value = tryToTrackValue(op.get())) {
16261626
// If we are tracking it, transfer it and if it is actor derived, mark
16271627
// our partial apply as actor derived.
1628+
builder.addRequire(value->getRepresentative().getValue());
16281629
builder.addTransfer(value->getRepresentative().getValue(), &op);
16291630
}
16301631
}
@@ -1731,6 +1732,7 @@ class PartitionOpTranslator {
17311732
fas.getArgumentParameterInfo(op).hasOption(
17321733
SILParameterInfo::Transferring)) {
17331734
if (auto value = tryToTrackValue(op.get())) {
1735+
builder.addRequire(value->getRepresentative().getValue());
17341736
builder.addTransfer(value->getRepresentative().getValue(), &op);
17351737
}
17361738
} else {
@@ -1747,6 +1749,7 @@ class PartitionOpTranslator {
17471749
if (fas.getArgumentParameterInfo(selfOperand)
17481750
.hasOption(SILParameterInfo::Transferring)) {
17491751
if (auto value = tryToTrackValue(selfOperand.get())) {
1752+
builder.addRequire(value->getRepresentative().getValue());
17501753
builder.addTransfer(value->getRepresentative().getValue(),
17511754
&selfOperand);
17521755
}
@@ -1816,7 +1819,7 @@ class PartitionOpTranslator {
18161819
assert((sourceApply || bool(applySite.getIsolationCrossing())) &&
18171820
"only ApplyExpr's should cross isolation domains");
18181821

1819-
// require all operands
1822+
// Require all operands first before we emit transferring.
18201823
for (auto op : applySite.getArguments())
18211824
if (auto value = tryToTrackValue(op))
18221825
builder.addRequire(value->getRepresentative().getValue());
@@ -1938,6 +1941,7 @@ class PartitionOpTranslator {
19381941
// Transfer src. This ensures that we cannot use src again locally in this
19391942
// function... which makes sense since its value is now in the transferring
19401943
// parameter.
1944+
builder.addRequire(srcRoot.getRepresentative().getValue());
19411945
builder.addTransfer(srcRoot.getRepresentative().getValue(), srcOperand);
19421946

19431947
// Then check if we are assigning into an aggregate projection. In such a

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,10 @@ void RequireLiveness::processDefBlock() {
214214

215215
// Then walk from our transferInst to the end of the block looking for the
216216
// first require inst. Once we find it... return.
217-
for (auto ii = std::next(transferInst->getIterator()),
217+
//
218+
// NOTE: We start walking at the transferInst since the transferInst could use
219+
// the requireInst as well.
220+
for (auto ii = transferInst->getIterator(),
218221
ie = transferInst->getParent()->end();
219222
ii != ie; ++ii) {
220223
if (!allRequires.contains(&*ii))
@@ -1083,11 +1086,6 @@ void TransferNonSendableImpl::emitUseAfterTransferDiagnostics() {
10831086
<< "Transfer Op. Number: " << transferOp->getOperandNumber()
10841087
<< ". User: " << *transferOp->getUser());
10851088

1086-
RequireLiveness liveness(blockLivenessInfoGeneration, transferOp,
1087-
blockLivenessInfo);
1088-
++blockLivenessInfoGeneration;
1089-
liveness.process(requireInsts);
1090-
10911089
UseAfterTransferDiagnosticInferrer diagnosticInferrer(
10921090
regionInfo->getValueMap());
10931091
diagnosticInferrer.init(transferOp);
@@ -1104,6 +1102,40 @@ void TransferNonSendableImpl::emitUseAfterTransferDiagnostics() {
11041102
continue;
11051103
}
11061104

1105+
// Then look for our requires before we emit any error. We want to emit a
1106+
// single we don't understand error if we do not find the require.
1107+
bool didEmitRequireNote = false;
1108+
InstructionSet requireInstsUnique(function);
1109+
RequireLiveness liveness(blockLivenessInfoGeneration, transferOp,
1110+
blockLivenessInfo);
1111+
++blockLivenessInfoGeneration;
1112+
liveness.process(requireInsts);
1113+
1114+
SmallVector<SILInstruction *, 8> requireInstsForError;
1115+
for (auto *require : requireInsts) {
1116+
// We can have multiple of the same require insts if we had a require
1117+
// and an assign from the same instruction. Our liveness checking
1118+
// above doesn't care about that, but we still need to make sure we do
1119+
// not emit twice.
1120+
if (!requireInstsUnique.insert(require))
1121+
continue;
1122+
1123+
// If this was not a last require, do not emit an error.
1124+
if (!liveness.finalRequires.contains(require))
1125+
continue;
1126+
1127+
requireInstsForError.push_back(require);
1128+
didEmitRequireNote = true;
1129+
}
1130+
1131+
// If we did not emit a require, emit an "unknown pattern" error that
1132+
// tells the user to file a bug. This importantly ensures that we can
1133+
// guarantee that we always find the require if we successfully compile.
1134+
if (!didEmitRequireNote) {
1135+
diagnoseError(transferOp, diag::regionbasedisolation_unknown_pattern);
1136+
continue;
1137+
}
1138+
11071139
using UseDiagnosticInfoKind =
11081140
UseAfterTransferDiagnosticInferrer::UseDiagnosticInfoKind;
11091141
for (auto &info : applyUses) {
@@ -1170,29 +1202,13 @@ void TransferNonSendableImpl::emitUseAfterTransferDiagnostics() {
11701202
.highlight(info.getLoc().getSourceRange());
11711203
break;
11721204
}
1205+
}
11731206

1174-
// Ok, we now have our requires... emit the errors.
1175-
bool didEmitRequire = false;
1176-
1177-
InstructionSet requireInstsUnique(function);
1178-
for (auto *require : requireInsts) {
1179-
// We can have multiple of the same require insts if we had a require
1180-
// and an assign from the same instruction. Our liveness checking
1181-
// above doesn't care about that, but we still need to make sure we do
1182-
// not emit twice.
1183-
if (!requireInstsUnique.insert(require))
1184-
continue;
1185-
1186-
// If this was not a last require, do not emit an error.
1187-
if (!liveness.finalRequires.contains(require))
1188-
continue;
1189-
1190-
diagnoseNote(require, diag::regionbasedisolation_maybe_race)
1191-
.highlight(require->getLoc().getSourceRange());
1192-
didEmitRequire = true;
1193-
}
1194-
1195-
assert(didEmitRequire && "Should have a require for all errors?!");
1207+
// Now actually emit the require notes.
1208+
while (!requireInstsForError.empty()) {
1209+
auto *require = requireInstsForError.pop_back_val();
1210+
diagnoseNote(require, diag::regionbasedisolation_maybe_race)
1211+
.highlight(require->getLoc().getSourceRange());
11961212
}
11971213
}
11981214
}

test/Concurrency/transfernonsendable.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,10 +1133,16 @@ func varNonSendableNonTrivialLetStructFieldClosureFlowSensitive4() async {
11331133
var cls = {}
11341134

11351135
for _ in 0..<1024 {
1136+
// TODO: The error here is b/c of the loop carry. We should probably store
1137+
// the operand instead of just the require inst, so we can better separate a
1138+
// loop carry use and an error due to multiple params. Then we could emit
1139+
// the error on test below. This is still correct though, just not as
1140+
// good... that is QoI though.
11361141
await transferToMain(test) // expected-tns-warning {{transferring non-Sendable value 'test' could yield races with later accesses}}
11371142
// expected-tns-note @-1 {{'test' is transferred from nonisolated caller to main actor-isolated callee. Later uses in caller could race with potential uses in callee}}
1138-
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
1139-
test = StructFieldTests() // expected-tns-note {{access here could race}}
1143+
// expected-tns-note @-2 {{access here could race}}
1144+
// expected-complete-warning @-3 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
1145+
test = StructFieldTests()
11401146
cls = {
11411147
useInOut(&test.varSendableNonTrivial)
11421148
}
@@ -1167,7 +1173,7 @@ func varNonSendableNonTrivialLetStructFieldClosureFlowSensitive5() async {
11671173
}
11681174

11691175
test.varSendableNonTrivial = SendableKlass()
1170-
useValue(test) // expected-tns-note {{access here could race}}
1176+
useValue(test)
11711177
}
11721178

11731179
func varNonSendableNonTrivialLetStructFieldClosureFlowSensitive6() async {
@@ -1321,7 +1327,7 @@ func controlFlowTest2() async {
13211327
x = NonSendableKlass()
13221328
}
13231329

1324-
useValue(x) // expected-tns-note {{access here could race}}
1330+
useValue(x)
13251331
}
13261332

13271333
////////////////////////

test/Concurrency/transfernonsendable_isolationcrossing_partialapply.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct CustomActor {
2525

2626
func useValue<T>(_ t: T) {}
2727
@MainActor func transferToMain<T>(_ t: T) {}
28+
@CustomActor func transferToCustom<T>(_ t: T) {}
2829

2930
/////////////////
3031
// MARK: Tests //
@@ -86,3 +87,15 @@ func normalFunc_testLocal_2() {
8687
}
8788
useValue(x) // expected-note {{access here could race}}
8889
}
90+
91+
// We error here since we are performing a double transfer.
92+
//
93+
// TODO: Add special transfer use so we can emit a double transfer error
94+
// diagnostic.
95+
func transferBeforeCaptureErrors() async {
96+
let x = NonSendableKlass()
97+
await transferToCustom(x) // expected-warning {{transferring value of non-Sendable type 'NonSendableKlass' from nonisolated context to global actor 'CustomActor'-isolated context}}
98+
let _ = { @MainActor in // expected-note {{access here could race}}
99+
useValue(x)
100+
}
101+
}

test/Concurrency/transfernonsendable_strong_transferring_params.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ func transferArgWithOtherParam(_ x: transferring Klass, _ y: Klass) {
3838
func transferArgWithOtherParam2(_ x: Klass, _ y: transferring Klass) {
3939
}
4040

41+
func twoTransferArg(_ x: transferring Klass, _ y: transferring Klass) {}
42+
4143
@MainActor var globalKlass = Klass()
4244

4345
/////////////////
@@ -282,3 +284,15 @@ func mergeDoesNotEliminateEarlierTransfer2(_ x: transferring NonSendableStruct)
282284

283285
useValue(y) // expected-note {{access here could race}}
284286
}
287+
288+
func doubleArgument() async {
289+
let x = Klass()
290+
twoTransferArg(x, x) // expected-warning {{binding of non-Sendable type 'Klass' accessed after being transferred}}
291+
// expected-note @-1 {{access here could race}}
292+
}
293+
294+
func testTransferSrc(_ x: transferring Klass) async {
295+
let y = Klass()
296+
await transferToMain(y) // expected-warning {{transferring value of non-Sendable type 'Klass' from nonisolated context to main actor-isolated context}}
297+
x = y // expected-note {{access here could race}}
298+
}

0 commit comments

Comments
 (0)