Skip to content

Commit 13e8eed

Browse files
committed
[rbi] Cleanup handling of how we send parameters to fix issues with inout sending.
We previously were not "unsending" inout sending parameters after sending them so they could not be used again in the caller and could not be forwarded into other 'inout sending' parameters. While looking at the code I realized it was pretty obtuse/confusing so I cleaned up the logic and fixed a few other issues in the process. Now we follow the following pattern in the non-isolation crossing case: 1. We first require the callee operand. 2. We then merge/require all of the non-explicitly sent parameters. 3. We then through all of the parameters and require/send all of the sending parameters. 4. At the end of processing, we unsend all of the sending parameters that were 'inout sending' parameters. In the case of isolation crossing applies we: 1. Require all parameters that are not explicitly marked as sending and then send them all at once. We are really just saving a little work by not merging them into one large region and then requiring/sending that region once. 2. Then for each sending parameter, we require/send them one by one interleaving the requires/sends. This ensures that if a value is passed to different explicitly sending parameters, we get an error. 3. Then once we have finished processing results, we perform an undo send on all of the 'inout sending' params. rdar://154440896
1 parent 7b109c6 commit 13e8eed

File tree

4 files changed

+175
-69
lines changed

4 files changed

+175
-69
lines changed

include/swift/SIL/ApplySite.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,14 +645,27 @@ class ApplySite {
645645
return getSubstCalleeConv().getParamInfoForSILArg(calleeArgIndex);
646646
}
647647

648+
/// Returns true if \p op is the callee operand of this apply site
649+
/// and not an argument operand.
650+
///
651+
/// If this instruction is not a full apply site, this always returns false.
652+
bool isCalleeOperand(const Operand &op) const;
653+
654+
/// Returns true if this is an 'out' parameter.
648655
bool isSending(const Operand &oper) const {
649-
if (isIndirectErrorResultOperand(oper))
656+
if (isIndirectErrorResultOperand(oper) || oper.isTypeDependent() ||
657+
isCalleeOperand(oper))
650658
return false;
651659
if (isIndirectResultOperand(oper))
652660
return getSubstCalleeType()->hasSendingResult();
653661
return getArgumentParameterInfo(oper).hasOption(SILParameterInfo::Sending);
654662
}
655663

664+
/// Returns true if this operand is an 'inout sending' parameter.
665+
bool isInOutSending(const Operand &oper) const {
666+
return isSending(oper) && getArgumentConvention(oper).isInoutConvention();
667+
}
668+
656669
/// Return true if 'operand' is addressable after type substitution in the
657670
/// caller's context.
658671
bool isAddressable(const Operand &operand) const;
@@ -1037,6 +1050,13 @@ inline bool ApplySite::isIndirectErrorResultOperand(const Operand &op) const {
10371050
return fas.isIndirectErrorResultOperand(op);
10381051
}
10391052

1053+
inline bool ApplySite::isCalleeOperand(const Operand &op) const {
1054+
auto fas = asFullApplySite();
1055+
if (!fas)
1056+
return false;
1057+
return fas.isCalleeOperand(op);
1058+
}
1059+
10401060
} // namespace swift
10411061

10421062
#endif

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 106 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,9 +1723,13 @@ struct PartitionOpBuilder {
17231723
PartitionOp::Send(lookupValueID(representative), op));
17241724
}
17251725

1726-
void addUndoSend(SILValue representative, SILInstruction *unsendingInst) {
1726+
void addUndoSend(TrackableValue value, SILInstruction *unsendingInst) {
1727+
if (value.isSendable())
1728+
return;
1729+
1730+
auto representative = value.getRepresentative().getValue();
17271731
assert(valueHasID(representative) &&
1728-
"value should already have been encountered");
1732+
"sent value should already have been encountered");
17291733

17301734
currentInstPartitionOps->emplace_back(
17311735
PartitionOp::UndoSend(lookupValueID(representative), unsendingInst));
@@ -2430,7 +2434,7 @@ class PartitionOpTranslator {
24302434
}))
24312435
continue;
24322436

2433-
builder.addUndoSend(trackedArgValue.getRepresentative().getValue(), ai);
2437+
builder.addUndoSend(trackedArgValue, ai);
24342438
}
24352439
}
24362440
}
@@ -2567,60 +2571,53 @@ class PartitionOpTranslator {
25672571
// gather our non-sending parameters.
25682572
SmallVector<Operand *, 8> nonSendingParameters;
25692573
SmallVector<Operand *, 8> sendingIndirectResults;
2570-
if (fas.getNumArguments()) {
2571-
// NOTE: We want to process indirect parameters as if they are
2572-
// parameters... so we process them in nonSendingParameters.
2573-
for (auto &op : fas.getOperandsWithoutSelf()) {
2574-
// If op is the callee operand, skip it.
2575-
if (fas.isCalleeOperand(op))
2576-
continue;
25772574

2578-
if (fas.isSending(op)) {
2579-
if (fas.isIndirectResultOperand(op)) {
2580-
sendingIndirectResults.push_back(&op);
2581-
continue;
2582-
}
2575+
// NOTE: We want to process indirect parameters as if they are
2576+
// parameters... so we process them in nonSendingParameters.
2577+
for (auto &op : fas->getAllOperands()) {
2578+
// If op is the callee operand or type dependent operand, skip it.
2579+
if (op.isTypeDependent())
2580+
continue;
25832581

2584-
// Attempt to lookup the value we are passing as sending. We want to
2585-
// require/send value if it is non-Sendable and require its base if it
2586-
// is non-Sendable as well.
2587-
if (auto lookupResult = tryToTrackValue(op.get())) {
2588-
builder.addRequire(*lookupResult);
2589-
builder.addSend(lookupResult->value, &op);
2590-
}
2591-
} else {
2592-
nonSendingParameters.push_back(&op);
2582+
if (fas.isCalleeOperand(op)) {
2583+
if (auto calleeResult = tryToTrackValue(op.get())) {
2584+
builder.addRequire(*calleeResult);
25932585
}
2586+
continue;
25942587
}
2595-
}
25962588

2597-
// If our self parameter was sending, send it. Otherwise, just
2598-
// stick it in the non self operand values array and run multiassign on
2599-
// it.
2600-
if (fas.hasSelfArgument()) {
2601-
auto &selfOperand = fas.getSelfArgumentOperand();
2602-
if (fas.getArgumentParameterInfo(selfOperand)
2603-
.hasOption(SILParameterInfo::Sending)) {
2604-
if (auto lookupResult = tryToTrackValue(selfOperand.get())) {
2605-
builder.addRequire(*lookupResult);
2606-
builder.addSend(lookupResult->value, &selfOperand);
2607-
}
2608-
} else {
2609-
nonSendingParameters.push_back(&selfOperand);
2589+
// If our parameter is not sending, just add it to the non-sending
2590+
// parameters array and continue.
2591+
if (!fas.isSending(op)) {
2592+
nonSendingParameters.push_back(&op);
2593+
continue;
26102594
}
2611-
}
26122595

2613-
// Require our callee operand if it is non-Sendable.
2614-
//
2615-
// DISCUSSION: Even though we do not include our callee operand in the same
2616-
// region as our operands/results, we still need to require that it is live
2617-
// at the point of application. Otherwise, we will not emit errors if the
2618-
// closure before this function application is already in the same region as
2619-
// a sent value. In such a case, the function application must error.
2620-
if (auto calleeResult = tryToTrackValue(fas.getCallee())) {
2621-
builder.addRequire(*calleeResult);
2596+
// Otherwise, first handle indirect result operands.
2597+
if (fas.isIndirectResultOperand(op)) {
2598+
sendingIndirectResults.push_back(&op);
2599+
continue;
2600+
}
2601+
2602+
// Attempt to lookup the value we are passing as sending. We want to
2603+
// require/send value if it is non-Sendable and require its base if it
2604+
// is non-Sendable as well.
2605+
if (auto lookupResult = tryToTrackValue(op.get())) {
2606+
builder.addRequire(*lookupResult);
2607+
builder.addSend(lookupResult->value, &op);
2608+
}
26222609
}
26232610

2611+
SWIFT_DEFER {
2612+
for (auto &op : fas->getAllOperands()) {
2613+
if (!fas.isInOutSending(op))
2614+
continue;
2615+
if (auto lookupResult = tryToTrackValue(op.get())) {
2616+
builder.addUndoSend(lookupResult->value, op.getUser());
2617+
}
2618+
}
2619+
};
2620+
26242621
SmallVector<SILValue, 8> applyResults;
26252622
getApplyResults(*fas, applyResults);
26262623

@@ -2688,36 +2685,77 @@ class PartitionOpTranslator {
26882685

26892686
/// Handles the semantics for SIL applies that cross isolation.
26902687
///
2691-
/// Semantically this causes all arguments of the applysite to be sent.
2688+
/// Semantically we are attempting to implement the following:
2689+
///
2690+
/// * Step 1: Require all non-sending parameters and then send those
2691+
/// parameters. We perform all of the requires first and the sends second
2692+
/// since all of the parameters are getting sent to the same isolation
2693+
/// domains and become part of the same region in our callee. So in a
2694+
/// certain sense, we are performing a require over the entire merge of the
2695+
/// parameter regions and then send each constituant part of the region
2696+
/// without requiring again so we do not emit use-after-send diagnostics.
2697+
///
2698+
/// * Step 2: Require/Send each of the sending parameters one by one. This
2699+
/// includes both 'sending' and 'inout sending' parameters. We purposely
2700+
/// interleave the require/send operations to ensure that if one passes a
2701+
/// value twice to different 'sending' or 'inout sending' parameters, we
2702+
/// will emit an error.
2703+
///
2704+
/// * Step 3: Unsend each of the unsending parameters. Since our caller
2705+
/// ensures that 'inout sending' parameters are disconnected on return and
2706+
/// are in different regions from all other parameters, we can just simply
2707+
/// unsend the parameter so we can use it again later.
26922708
void translateIsolationCrossingSILApply(FullApplySite applySite) {
2693-
// Require all operands first before we emit a send.
2694-
for (auto op : applySite.getArguments()) {
2695-
if (auto lookupResult = tryToTrackValue(op)) {
2709+
SmallVector<TrackableValue, 8> inoutSendingParams;
2710+
2711+
// First go through and require all of our operands that are not 'sending'
2712+
for (auto &op : applySite.getArgumentOperands()) {
2713+
if (applySite.isSending(op))
2714+
continue;
2715+
if (auto lookupResult = tryToTrackValue(op.get()))
26962716
builder.addRequire(*lookupResult);
2697-
}
26982717
}
26992718

2700-
auto handleSILOperands = [&](MutableArrayRef<Operand> operands) {
2701-
for (auto &op : operands) {
2702-
if (auto lookupResult = tryToTrackValue(op.get())) {
2703-
builder.addSend(lookupResult->value, &op);
2704-
}
2719+
// Then go through our operands again and send all of our non-sending
2720+
// parameters. We do not interleave these sends with our requires since we
2721+
// are considering these values to be merged into the same region. We could
2722+
// also merge them in the caller but there is no point in doing so
2723+
// semantically since the values cannot be used again locally.
2724+
for (auto &op : applySite.getOperandsWithoutIndirectResults()) {
2725+
if (applySite.isSending(op))
2726+
continue;
2727+
if (auto lookupResult = tryToTrackValue(op.get())) {
2728+
builder.addSend(lookupResult->value, &op);
27052729
}
27062730
};
27072731

2708-
auto handleSILSelf = [&](Operand *self) {
2709-
if (auto lookupResult = tryToTrackValue(self->get())) {
2710-
builder.addSend(lookupResult->value, self);
2732+
// Then go through our 'sending' params and require/send each in sequence.
2733+
//
2734+
// We do this interleaved so that if a value is passed to multiple 'sending'
2735+
// parameters, we emit errors.
2736+
for (auto &op : applySite.getOperandsWithoutIndirectResults()) {
2737+
if (!applySite.isSending(op))
2738+
continue;
2739+
auto lookupResult = tryToTrackValue(op.get());
2740+
if (!lookupResult)
2741+
continue;
2742+
builder.addRequire(*lookupResult);
2743+
builder.addSend(lookupResult->value, &op);
2744+
}
2745+
2746+
// Now use a SWIFT_DEFER so that when the function is done executing, we
2747+
// unsend 'inout sending' params.
2748+
SWIFT_DEFER {
2749+
for (auto &op : applySite.getOperandsWithoutIndirectResults()) {
2750+
if (!applySite.isInOutSending(op))
2751+
continue;
2752+
auto lookupResult = tryToTrackValue(op.get());
2753+
if (!lookupResult)
2754+
continue;
2755+
builder.addUndoSend(lookupResult->value, *applySite);
27112756
}
27122757
};
27132758

2714-
if (applySite.hasSelfArgument()) {
2715-
handleSILOperands(applySite.getOperandsWithoutIndirectResultsOrSelf());
2716-
handleSILSelf(&applySite.getSelfArgumentOperand());
2717-
} else {
2718-
handleSILOperands(applySite.getOperandsWithoutIndirectResults());
2719-
}
2720-
27212759
// Create a new assign fresh for each one of our values and unless our
27222760
// return value is sending, emit an extra error bit on the results that are
27232761
// non-Sendable.

test/Concurrency/transfernonsendable_inout_sending_params.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ struct MyError : Error {}
8080

8181
func takeClosure(_ x: sending () -> ()) {}
8282
func takeClosureAndParam(_ x: NonSendableKlass, _ y: sending () -> ()) {}
83+
func useInOutSending(_ x: inout sending NonSendableKlass) {}
84+
func useInOutSending(_ x: inout sending NonSendableKlass,
85+
_ y: inout sending NonSendableKlass) {}
8386

8487
///////////////////////////////
8588
// MARK: InOut Sending Tests //
@@ -162,6 +165,17 @@ func testWrongIsolationGlobalIsolation(_ x: inout sending NonSendableKlass) {
162165
} // expected-warning {{'inout sending' parameter 'x' cannot be main actor-isolated at end of function}}
163166
// expected-note @-1 {{main actor-isolated 'x' risks causing races in between main actor-isolated uses and caller uses since caller assumes value is not actor isolated}}
164167

168+
func passInOutSendingToInOutSending(_ x: inout sending NonSendableKlass) {
169+
useInOutSending(&x)
170+
}
171+
172+
func passInOutSendingMultipleTimes(_ x: inout NonSendableStruct) {
173+
useInOutSending(&x.first, &x.second) // expected-warning {{sending 'x.first' risks causing data races}}
174+
// expected-note @-1 {{task-isolated 'x.first' is passed as a 'sending' parameter}}
175+
// expected-warning @-2 {{sending 'x.second' risks causing data races}}
176+
// expected-note @-3 {{task-isolated 'x.second' is passed as a 'sending' parameter}}
177+
}
178+
165179
//////////////////////////////////////
166180
// MARK: Return Inout Sending Tests //
167181
//////////////////////////////////////

test/Concurrency/transfernonsendable_sending_params.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,22 @@ func transferArgAsync(_ x: sending NonSendableKlass) async {
6969
func transferArgWithOtherParam(_ x: sending NonSendableKlass, _ y: NonSendableKlass) {
7070
}
7171

72+
@MainActor
73+
func transferArgWithOtherParamIsolationCrossing(_ x: sending NonSendableKlass, _ y: NonSendableKlass) async {
74+
}
75+
7276
func transferArgWithOtherParam2(_ x: NonSendableKlass, _ y: sending NonSendableKlass) {
7377
}
7478

79+
@MainActor
80+
func transferArgWithOtherParam2IsolationCrossing(_ x: NonSendableKlass, _ y: sending NonSendableKlass) async {
81+
}
82+
7583
func twoTransferArg(_ x: sending NonSendableKlass, _ y: sending NonSendableKlass) {}
7684

85+
@MainActor
86+
func twoTransferArgIsolationCrossing(_ x: sending NonSendableKlass, _ y: sending NonSendableKlass) async {}
87+
7788
@MainActor var globalKlass = NonSendableKlass()
7889

7990
struct MyError : Error {}
@@ -114,6 +125,22 @@ func testSimpleTransferUseOfOtherParamNoError2() {
114125
useValue(k)
115126
}
116127

128+
func testSimpleTransferUseOfOtherParamError() async {
129+
let k = NonSendableKlass()
130+
await transferArgWithOtherParamIsolationCrossing(k, k) // expected-warning {{sending 'k' risks causing data races}}
131+
// expected-note @-1 {{sending 'k' to main actor-isolated global function 'transferArgWithOtherParamIsolationCrossing' risks causing data races between main actor-isolated and local nonisolated uses}}
132+
// expected-note @-2 {{access can happen concurrently}}
133+
}
134+
135+
// TODO: Improve this error message. We should say that we are emitting an
136+
// error. Also need to add SILLocation to ApplyInst.
137+
func testSimpleTransferUseOfOtherParam2Error() async {
138+
let k = NonSendableKlass()
139+
await transferArgWithOtherParam2IsolationCrossing(k, k) // expected-warning {{sending 'k' risks causing data races}}
140+
// expected-note @-1 {{sending 'k' to main actor-isolated global function 'transferArgWithOtherParam2IsolationCrossing' risks causing data races between main actor-isolated and local nonisolated uses}}
141+
// expected-note @-2 {{access can happen concurrently}}
142+
}
143+
117144
@MainActor func transferToMain2(_ x: sending NonSendableKlass, _ y: NonSendableKlass, _ z: NonSendableKlass) async {
118145
}
119146

@@ -358,6 +385,13 @@ func doubleArgument() async {
358385
// expected-note @-2 {{access can happen concurrently}}
359386
}
360387

388+
func doubleArgumentIsolationCrossing() async {
389+
let x = NonSendableKlass()
390+
await twoTransferArgIsolationCrossing(x, x) // expected-warning {{sending 'x' risks causing data races}}
391+
// expected-note @-1 {{'x' used after being passed as a 'sending' parameter}}
392+
// expected-note @-2 {{access can happen concurrently}}
393+
}
394+
361395
func testTransferSrc(_ x: sending NonSendableKlass) async {
362396
let y = NonSendableKlass()
363397
await transferToMain(y) // expected-warning {{sending 'y' risks causing data races}}

0 commit comments

Comments
 (0)