Skip to content

Commit dc8e479

Browse files
committed
[region-isolation] Make sure that we treat Sendable functions as Sendable.
The reason for this issue is that we were originally checking in our NonSendable type oracle just if a type conformed to Sendable. But function types do not conform to protocols, so this would fail for protocols. To fix this, I added some helper methods to call swift::isSendableType on SILType, called that in our oracle, and then I added support in swift::isSendableType for both SILFunctionType and AnyFunctionType so that we correctly handle them depending on their sendability. There was also a question if we also handled function conversions correctly. I added a test case that shows that we do not error in either of the cases. Another nice aspect of this change is that we no longer need to stash a pointer to a looked up Sendable protocol to perform the check since that just happens naturally inside SILType::isSendable() when it calls isSendableType. This is a better separation of concerns. rdar://116525224
1 parent e8761b8 commit dc8e479

File tree

8 files changed

+58
-20
lines changed

8 files changed

+58
-20
lines changed

include/swift/AST/Types.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,12 @@ class alignas(1 << TypeAlignInBits) TypeBase
901901
/// Determines whether this type is an actor type.
902902
bool isActorType();
903903

904+
/// Returns true if this type is a Sendable type.
905+
bool isSendableType(DeclContext *declContext);
906+
907+
/// Returns true if this type is a Sendable type.
908+
bool isSendableType(ModuleDecl *parentModule);
909+
904910
/// Determines whether this type conforms or inherits (if it's a protocol
905911
/// type) from `DistributedActor`.
906912
bool isDistributedActor();

include/swift/SIL/SILType.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,9 @@ class SILType {
880880

881881
bool isActor() const { return getASTType()->isActorType(); }
882882

883+
/// Returns true if this function conforms to the Sendable protocol.
884+
bool isSendable(SILFunction *fn) const;
885+
883886
ProtocolConformanceRef conformsToProtocol(SILFunction *fn,
884887
ProtocolDecl *protocol) const;
885888

lib/AST/Type.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,14 @@ bool TypeBase::isActorType() {
506506
return false;
507507
}
508508

509+
bool TypeBase::isSendableType(DeclContext *ctx) {
510+
return isSendableType(ctx->getParentModule());
511+
}
512+
513+
bool TypeBase::isSendableType(ModuleDecl *parentModule) {
514+
return ::isSendableType(parentModule, Type(this));
515+
}
516+
509517
bool TypeBase::isDistributedActor() {
510518
if (auto actor = getAnyActor())
511519
return actor->isDistributedActor();

lib/SIL/IR/SILType.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,3 +1224,7 @@ ProtocolConformanceRef
12241224
SILType::conformsToProtocol(SILFunction *fn, ProtocolDecl *protocol) const {
12251225
return fn->getParentModule()->conformsToProtocol(getASTType(), protocol);
12261226
}
1227+
1228+
bool SILType::isSendable(SILFunction *fn) const {
1229+
return getASTType()->isSendableType(fn->getParentModule());
1230+
}

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ class PartitionOpTranslator {
318318
friend PartitionOpBuilder;
319319

320320
SILFunction *function;
321-
ProtocolDecl *sendableProtocol;
322321

323322
/// A map from the representative of an equivalence class of values to their
324323
/// TrackableValueState. The state contains both the unique value id for the
@@ -460,13 +459,7 @@ class PartitionOpTranslator {
460459

461460
public:
462461
PartitionOpTranslator(SILFunction *function)
463-
: function(function),
464-
sendableProtocol(
465-
function->getASTContext().getProtocol(KnownProtocolKind::Sendable)),
466-
functionArgPartition(), builder() {
467-
assert(sendableProtocol && "PartitionOpTranslators should only be created "
468-
"in contexts in which the availability of the "
469-
"Sendable protocol has already been checked.");
462+
: function(function), functionArgPartition(), builder() {
470463
builder.translator = this;
471464
initCapturedUniquelyIdentifiedValues();
472465

@@ -483,7 +476,7 @@ class PartitionOpTranslator {
483476
if (auto state = tryToTrackValue(arg)) {
484477
// If we have an arg that is an actor, we allow for it to be
485478
// transfer... value ids derived from it though cannot be transferred.
486-
LLVM_DEBUG(llvm::dbgs() << " %%" << state->getID());
479+
LLVM_DEBUG(llvm::dbgs() << " %%" << state->getID() << ": ");
487480
neverTransferredValueIDs.push_back(state->getID());
488481
nonSendableIndices.push_back(state->getID());
489482
LLVM_DEBUG(llvm::dbgs() << *arg);
@@ -530,16 +523,14 @@ class PartitionOpTranslator {
530523
/// NOTE: We special case RawPointer and NativeObject to ensure they are
531524
/// treated as non-Sendable and strict checking is applied to it.
532525
bool isNonSendableType(SILType type) const {
533-
switch (type.getASTType()->getKind()) {
534-
case TypeKind::BuiltinNativeObject:
535-
case TypeKind::BuiltinRawPointer:
536-
// These are very unsafe... definitely not Sendable.
526+
// Treat Builtin.NativeObject and Builtin.RawPointer as non-Sendable.
527+
if (type.getASTType()->is<BuiltinNativeObjectType>() ||
528+
type.getASTType()->is<BuiltinRawPointerType>()) {
537529
return true;
538-
default:
539-
// Consider caching this if it's a performance bottleneck.
540-
return type.conformsToProtocol(function, sendableProtocol)
541-
.hasMissingConformance(function->getParentModule());
542530
}
531+
532+
// Otherwise, delegate to seeing if type conforms to the Sendable protocol.
533+
return !type.isSendable(function);
543534
}
544535

545536
// ===========================================================================

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,13 @@ bool swift::isSendableType(ModuleDecl *module, Type type) {
657657
if (!proto)
658658
return true;
659659

660+
// First check if we have a function type. If we do, check if it is
661+
// Sendable. We do this since functions cannot conform to protocols.
662+
if (auto *fas = type->getCanonicalType()->getAs<SILFunctionType>())
663+
return fas->isSendable();
664+
if (auto *fas = type->getCanonicalType()->getAs<AnyFunctionType>())
665+
return fas->isSendable();
666+
660667
auto conformance = TypeChecker::conformsToProtocol(type, proto, module);
661668
if (conformance.isInvalid())
662669
return false;

test/Concurrency/sendable_checking.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,7 @@ extension MyActor {
232232
@available(SwiftStdlib 5.1, *)
233233
func testConversionsAndSendable(a: MyActor, s: any Sendable, f: @Sendable () -> Void) async {
234234
await a.f(s)
235-
236-
// FIXME: 'f' is Sendable
237235
await a.g(f)
238-
// expected-tns-warning@-1 {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
239236
}
240237

241238
@available(SwiftStdlib 5.1, *)

test/Concurrency/sendnonsendable_basic.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ actor Actor {
2727
final var finalKlass = NonSendableKlass()
2828

2929
func useKlass(_ x: NonSendableKlass) {}
30+
31+
func useSendableFunction(_: @Sendable () -> Void) {}
32+
func useNonSendableFunction(_: () -> Void) {}
3033
}
3134

3235
final actor FinalActor {
@@ -469,3 +472,22 @@ extension Actor {
469472
closure() // expected-tns-note {{access here could race}}
470473
}
471474
}
475+
476+
/////////////////////////////
477+
// Sendable Function Tests //
478+
/////////////////////////////
479+
480+
// Make sure that we do not error on function values that are Sendable... even
481+
// if the function is converted to a non-Sendable form by a function conversion.
482+
func testConversionsAndSendable(a: Actor, f: @Sendable () -> Void, f2: () -> Void) async {
483+
// No function conversion.
484+
await a.useSendableFunction(f)
485+
486+
// Function conversion.
487+
await a.useNonSendableFunction(f)
488+
489+
// Show that we error if we are not sendable.
490+
await a.useNonSendableFunction(f2) // expected-tns-warning {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}}
491+
// expected-complete-warning @-1 {{passing argument of non-sendable type '() -> Void' into actor-isolated context may introduce data races}}
492+
// expected-complete-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
493+
}

0 commit comments

Comments
 (0)