Skip to content

Commit 82fbde0

Browse files
committed
[send-non-sendable] Ensure that we properly warn if a field of a final actor is transferred.
Previously, we were not recognizing that a ref_element_addr from an actor object is equivalent to the actor object and we shouldn't allow for it to be consumed. rdar://115132118
1 parent 3382352 commit 82fbde0

File tree

3 files changed

+126
-13
lines changed

3 files changed

+126
-13
lines changed

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ class Partition {
394394
llvm::function_ref<void(const PartitionOp &, Element)> handleFailure =
395395
[](const PartitionOp &, Element) {},
396396

397-
std::vector<Element> nonconsumables = {},
397+
ArrayRef<Element> nonconsumables = {},
398398

399399
llvm::function_ref<void(const PartitionOp &, Element)>
400400
handleConsumeNonConsumable = [](const PartitionOp &, Element) {}) {

lib/SILOptimizer/Mandatory/SendNonSendable.cpp

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
using namespace swift;
3232

33+
#pragma clang optimize off
34+
3335
//===----------------------------------------------------------------------===//
3436
// MARK: Utilities
3537
//===----------------------------------------------------------------------===//
@@ -240,6 +242,12 @@ class PartitionOpTranslator {
240242
// utilities would make it easier than handrolling
241243
llvm::DenseSet<SILValue> capturedUIValues;
242244

245+
/// A list of values that can never be consumed.
246+
///
247+
/// This includes all function arguments as well as ref_element_addr from
248+
/// actors.
249+
std::vector<TrackableValueID> neverConsumedValueIDs;
250+
243251
TrackableValue getTrackableValue(SILValue value) const {
244252
value = getUnderlyingTrackedValue(value);
245253

@@ -280,6 +288,21 @@ class PartitionOpTranslator {
280288
if (!isNonSendableType(value->getType()))
281289
iter.first->getSecond().addFlag(TrackableValueFlag::isSendable);
282290

291+
// Check if our base is a ref_element_addr from an actor value. In such a
292+
// case, add this id to the neverConsumedValueIDs.
293+
if (isa<LoadInst, LoadBorrowInst>(iter.first->first)) {
294+
auto *svi = cast<SingleValueInstruction>(iter.first->first);
295+
auto storage = AccessStorageWithBase::compute(svi->getOperand(0));
296+
if (storage.storage && isa<RefElementAddrInst>(storage.base)) {
297+
if (storage.storage.getRoot()->getType().getASTType()->isActorType()) {
298+
self->neverConsumedValueIDs.push_back(iter.first->second.getID());
299+
}
300+
}
301+
}
302+
303+
// If our access storage is from a class, then see if we have an actor. In
304+
// such a case, we need to add this id to the neverConsumed set.
305+
283306
return {iter.first->first, iter.first->second};
284307
}
285308

@@ -417,12 +440,9 @@ class PartitionOpTranslator {
417440
std::vector<TrackableValueID> getArgIDs() {
418441
std::vector<TrackableValueID> argIDs;
419442

420-
for (SILArgument *arg : function->getArguments())
421-
if (auto state = trackIfNonSendable(arg))
422-
argIDs.push_back(state->getID());
423-
424-
if (auto *selfArg = function->maybeGetSelfArgument()) {
425-
if (auto state = trackIfNonSendable(selfArg)) {
443+
for (SILArgument *arg : function->getArguments()) {
444+
if (auto state = trackIfNonSendable(arg)) {
445+
neverConsumedValueIDs.push_back(state->getID());
426446
argIDs.push_back(state->getID());
427447
}
428448
}
@@ -442,11 +462,8 @@ class PartitionOpTranslator {
442462
// Get the vector of IDs that cannot be legally consumed at any point in
443463
// this function. Since we place all args and self in a single region right
444464
// now, it is only necessary to choose a single representative of the set.
445-
std::vector<TrackableValueID> getNonConsumables() {
446-
if (const auto &argIDs = getArgIDs(); !argIDs.empty()) {
447-
return {argIDs.front()};
448-
}
449-
return {};
465+
ArrayRef<TrackableValueID> getNeverConsumedValues() {
466+
return llvm::makeArrayRef(neverConsumedValueIDs);
450467
}
451468

452469
// get the results of an apply instruction. This is the single result value
@@ -1049,7 +1066,7 @@ class BlockPartitionState {
10491066
Partition workingPartition = entryPartition;
10501067
for (auto &partitionOp : blockPartitionOps) {
10511068
workingPartition.apply(partitionOp, handleFailure,
1052-
translator.getNonConsumables(),
1069+
translator.getNeverConsumedValues(),
10531070
handleConsumeNonConsumable);
10541071
}
10551072
}

test/Concurrency/sendnonsendable_basic.swift

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,48 @@ class NonSendableKlass {
1515
actor Actor {
1616
var klass = NonSendableKlass()
1717
final var finalKlass = NonSendableKlass()
18+
19+
func useKlass(_ x: NonSendableKlass) {}
1820
}
1921

2022
final actor FinalActor {
2123
var klass = NonSendableKlass()
24+
func useKlass(_ x: NonSendableKlass) {}
25+
}
26+
27+
func useInOut<T>(_ x: inout T) {}
28+
func useValue<T>(_ x: T) {}
29+
30+
////////////////////////////
31+
// MARK: Actor Self Tests //
32+
////////////////////////////
33+
34+
extension Actor {
35+
func noWarningIfCallingGetter() async {
36+
// We do not emit a warning in this case since we are calling transferring
37+
// the result of a getter of an actor. The result of this getter could not
38+
// have been returned from the actor without us emitting a warning as shown
39+
// by the test klassGetter below.
40+
await self.klass.asyncCall()
41+
}
42+
43+
func warningIfCallingAsyncOnFinalField() async {
44+
// Since we are calling finalKlass directly, we emit a warning here.
45+
await self.finalKlass.asyncCall() // expected-warning {{}}
46+
}
47+
48+
// We warn on this since we are escaping an actor field out of its isolation
49+
// domain.
50+
var klassGetter: NonSendableKlass {
51+
self.finalKlass
52+
}
53+
}
54+
55+
extension FinalActor {
56+
func warningIfCallingAsyncOnFinalField() async {
57+
// Since our whole class is final, we emit the error directly here.
58+
await self.klass.asyncCall() // expected-warning {{}}
59+
}
2260
}
2361

2462
/////////////////////////////
@@ -30,3 +68,61 @@ func formClosureWithoutCrashing() {
3068
var c = NonSendableKlass() // expected-warning {{variable 'c' was never mutated; consider changing to 'let' constant}}
3169
let _ = { print(c) }
3270
}
71+
72+
// In this test, closure is not promoted into its box form. As a result, we
73+
// treat assignments into contents as a merge operation rather than an assign.
74+
func closureInOut(_ a: Actor) async {
75+
var contents = NonSendableKlass()
76+
let ns0 = NonSendableKlass()
77+
let ns1 = NonSendableKlass()
78+
79+
contents = ns0
80+
contents = ns1
81+
82+
var closure = {}
83+
closure = { useInOut(&contents) }
84+
85+
await a.useKlass(ns0) // expected-warning {{passing argument of non-sendable type 'NonSendableKlass' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function}}
86+
await a.useKlass(ns1) // expected-note {{access here could race}}
87+
88+
closure() // expected-note {{access here could race}}
89+
}
90+
91+
func closureInOut2(_ a: Actor) async {
92+
var contents = NonSendableKlass()
93+
let ns0 = NonSendableKlass()
94+
let ns1 = NonSendableKlass()
95+
96+
contents = ns0
97+
contents = ns1
98+
99+
var closure = {}
100+
101+
await a.useKlass(ns0) // expected-warning {{passing argument of non-sendable type 'NonSendableKlass' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function}}
102+
103+
closure = { useInOut(&contents) } // expected-note {{access here could race}}
104+
105+
await a.useKlass(ns1) // expected-note {{access here could race}}
106+
107+
closure()
108+
}
109+
110+
func closureNonInOut(_ a: Actor) async {
111+
var contents = NonSendableKlass()
112+
let ns0 = NonSendableKlass()
113+
let ns1 = NonSendableKlass()
114+
115+
contents = ns0
116+
contents = ns1
117+
118+
var closure = {}
119+
120+
await a.useKlass(ns0)
121+
122+
closure = { useValue(contents) }
123+
124+
await a.useKlass(ns1) // expected-warning {{passing argument of non-sendable type 'NonSendableKlass' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function}}
125+
126+
closure() // expected-note {{access here could race}}
127+
}
128+

0 commit comments

Comments
 (0)