Skip to content

[region-isolation] Add support for global actors. #70192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions lib/SILOptimizer/Mandatory/TransferNonSendable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "swift/SIL/NodeDatastructures.h"
#include "swift/SIL/OperandDatastructures.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/PatternMatch.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
Expand All @@ -36,6 +37,7 @@

using namespace swift;
using namespace swift::PartitionPrimitives;
using namespace swift::PatternMatch;

//===----------------------------------------------------------------------===//
// MARK: Utilities
Expand Down Expand Up @@ -292,6 +294,26 @@ static bool isAsyncLetBeginPartialApply(PartialApplyInst *pai) {
return *kind == BuiltinValueKind::StartAsyncLetWithLocalBuffer;
}

static bool isGlobalActorInit(SILFunction *fn) {
auto block = fn->begin();

// Make sure our function has a single block. We should always have a single
// block today. Return nullptr otherwise.
if (block == fn->end() || std::next(block) != fn->end())
return false;

GlobalAddrInst *gai = nullptr;
if (!match(cast<SILInstruction>(block->getTerminator()),
m_ReturnInst(m_AddressToPointerInst(m_GlobalAddrInst(gai)))))
return false;

auto *globalDecl = gai->getReferencedGlobal()->getDecl();
if (!globalDecl)
return false;

return globalDecl->getGlobalActorAttr() != std::nullopt;
}

//===----------------------------------------------------------------------===//
// MARK: Diagnostics
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -809,6 +831,17 @@ class PartitionOpTranslator {
}
}

// Check if we have an unsafeMutableAddressor from a global actor, mark the
// returned value as being actor derived.
if (auto applySite = FullApplySite::isa(iter.first->first)) {
if (auto *calleeFunction = applySite.getCalleeFunction()) {
if (calleeFunction->isGlobalInit() &&
isGlobalActorInit(calleeFunction)) {
iter.first->getSecond().addFlag(TrackableValueFlag::isActorDerived);
}
}
}

// If our access storage is from a class, then see if we have an actor. In
// such a case, we need to add this id to the neverTransferred set.

Expand Down Expand Up @@ -1294,6 +1327,14 @@ class PartitionOpTranslator {
"srcID and dstID are different?!");
}

void translateSILLookThrough(SingleValueInstruction *svi) {
assert(svi->getNumOperands() == 1);
auto srcID = tryToTrackValue(svi->getOperand(0));
auto destID = tryToTrackValue(svi);
assert(((!destID || !srcID) || destID->getID() == srcID->getID()) &&
"srcID and dstID are different?!");
}

template <typename Collection>
void translateSILAssign(SILValue dest, Collection collection) {
return translateSILMultiAssign(TinyPtrVector<SILValue>(dest), collection);
Expand Down Expand Up @@ -1518,6 +1559,21 @@ class PartitionOpTranslator {
return translateSILLookThrough(inst->getResult(0), inst->getOperand(0));
return translateSILAssign(inst);

case SILInstructionKind::PointerToAddressInst: {
auto *atpi = cast<PointerToAddressInst>(inst);

// A raw pointer is considered to be a non-Sendable type. If we cast it to
// a Sendable type, treat it as a require. We can assume that if the user
// casted it to a Sendable type, if the type were not actually sendable,
// it would be undefined behavior.
if (!isNonSendableType(atpi->getType())) {
return translateSILRequire(atpi->getOperand());
}

// Otherwise, if we have a non-Sendable type, look through it.
return translateSILAssign(atpi);
}

// Just make the result part of the operand's region without requiring.
//
// This is appropriate for things like object casts and object
Expand All @@ -1534,8 +1590,6 @@ class PartitionOpTranslator {
case SILInstructionKind::InitExistentialRefInst:
case SILInstructionKind::OpenExistentialBoxInst:
case SILInstructionKind::OpenExistentialRefInst:
case SILInstructionKind::PointerToAddressInst:
case SILInstructionKind::ProjectBlockStorageInst:
case SILInstructionKind::RefToUnmanagedInst:
case SILInstructionKind::TailAddrInst:
case SILInstructionKind::ThickToObjCMetatypeInst:
Expand Down
144 changes: 144 additions & 0 deletions test/Concurrency/sendnonsendable_global_actor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// RUN: %target-swift-frontend -emit-sil -strict-concurrency=complete -disable-availability-checking -verify -verify-additional-prefix complete- %s -o /dev/null -parse-as-library
// RUN: %target-swift-frontend -emit-sil -strict-concurrency=complete -enable-experimental-feature RegionBasedIsolation -disable-availability-checking -verify -verify-additional-prefix tns- %s -o /dev/null -parse-as-library

// REQUIRES: concurrency
// REQUIRES: asserts

////////////////////////
// MARK: Declarations //
////////////////////////

class NonSendableKlass {}
final class SendableKlass : Sendable {}

actor GlobalActorInstance {}

@globalActor
struct GlobalActor {
static let shared = GlobalActorInstance()
}

func transferToNonIsolated<T>(_ t: T) async {}
@MainActor func transferToMainActor<T>(_ t: T) async {}
@GlobalActor func transferToGlobalActor<T>(_ t: T) async {}
func useValue<T>(_ t: T) {}

var booleanFlag: Bool { false }

/////////////////
// MARK: Tests //
/////////////////

private class NonSendableLinkedList<T> { // expected-complete-note 5{{}}
var listHead: NonSendableLinkedListNode<T>?

init() { listHead = nil }
}

private class NonSendableLinkedListNode<T> { // expected-complete-note 3{{}}
var next: NonSendableLinkedListNode?
var data: T?

init() { next = nil }
}

@GlobalActor private var firstList = NonSendableLinkedList<Int>()
@GlobalActor private var secondList = NonSendableLinkedList<Int>()

@GlobalActor func useGlobalActor1() async {
let x = firstList

await transferToMainActor(x) // 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}}
// expected-complete-warning @-1 {{passing argument of non-sendable type 'NonSendableLinkedList<Int>' into main actor-isolated context may introduce data races}}

let y = secondList.listHead!.next!

await transferToMainActor(y) // 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}}
// expected-complete-warning @-1 {{passing argument of non-sendable type 'NonSendableLinkedListNode<Int>' into main actor-isolated context may introduce data races}}
}

@GlobalActor func useGlobalActor2() async {
var x = NonSendableLinkedListNode<Int>()

if booleanFlag {
x = secondList.listHead!.next!
}

await transferToMainActor(x) // 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}}
// expected-complete-warning @-1 {{passing argument of non-sendable type 'NonSendableLinkedListNode<Int>' into main actor-isolated context may introduce data races}}
}

@GlobalActor func useGlobalActor3() async {
var x = NonSendableLinkedListNode<Int>()

if booleanFlag {
x = secondList.listHead!.next!
}

await transferToGlobalActor(x)
}

@GlobalActor func useGlobalActor4() async {
let x = NonSendableLinkedListNode<Int>()

await transferToGlobalActor(x)

useValue(x)
}

@GlobalActor func useGlobalActor5() async {
let x = NonSendableLinkedListNode<Int>()

await transferToNonIsolated(x) // expected-tns-warning {{passing argument of non-sendable type 'NonSendableLinkedListNode<Int>' from global actor 'GlobalActor'-isolated context to nonisolated context at this call site could yield a race with accesses later in this function}}
// expected-complete-warning @-1 {{passing argument of non-sendable type 'NonSendableLinkedListNode<Int>' outside of global actor 'GlobalActor'-isolated context may introduce data races}}

useValue(x) // expected-tns-note {{access here could race}}
}

private struct StructContainingValue { // expected-complete-note 2{{}}
var x = NonSendableLinkedList<Int>()
var y = SendableKlass()
}

@GlobalActor func useGlobalActor6() async {
var x = StructContainingValue()
x = StructContainingValue()

await transferToNonIsolated(x) // expected-tns-warning {{passing argument of non-sendable type 'StructContainingValue' from global actor 'GlobalActor'-isolated context to nonisolated context at this call site could yield a race with accesses later in this function}}
// expected-complete-warning @-1 {{passing argument of non-sendable type 'StructContainingValue' outside of global actor 'GlobalActor'-isolated context may introduce data races}}

useValue(x) // expected-tns-note {{access here could race}}
}

@GlobalActor func useGlobalActor7() async {
var x = StructContainingValue()
x.x = firstList

await transferToNonIsolated(x) // 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}}
// expected-complete-warning @-1 {{passing argument of non-sendable type 'StructContainingValue' outside of global actor 'GlobalActor'-isolated context may introduce data races}}

useValue(x)
}

@GlobalActor func useGlobalActor8() async {
var x = (NonSendableLinkedList<Int>(), NonSendableLinkedList<Int>())
x = (NonSendableLinkedList<Int>(), NonSendableLinkedList<Int>())

await transferToNonIsolated(x) // expected-tns-warning {{passing argument of non-sendable type '(NonSendableLinkedList<Int>, NonSendableLinkedList<Int>)' from global actor 'GlobalActor'-isolated context to nonisolated context at this call site could yield a race with accesses later in this function}}
// expected-complete-warning @-1 {{passing argument of non-sendable type '(NonSendableLinkedList<Int>, NonSendableLinkedList<Int>)' outside of global actor 'GlobalActor'-isolated context may introduce data races}}
// expected-complete-warning @-2 {{passing argument of non-sendable type '(NonSendableLinkedList<Int>, NonSendableLinkedList<Int>)' outside of global actor 'GlobalActor'-isolated context may introduce data races}}

useValue(x) // expected-tns-note {{access here could race}}
}

@GlobalActor func useGlobalActor9() async {
var x = (NonSendableLinkedList<Int>(), NonSendableLinkedList<Int>())

x.1 = firstList

await transferToNonIsolated(x) // 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}}
// expected-complete-warning @-1 {{passing argument of non-sendable type '(NonSendableLinkedList<Int>, NonSendableLinkedList<Int>)' outside of global actor 'GlobalActor'-isolated context may introduce data races}}
// expected-complete-warning @-2 {{passing argument of non-sendable type '(NonSendableLinkedList<Int>, NonSendableLinkedList<Int>)' outside of global actor 'GlobalActor'-isolated context may introduce data races}}

useValue(x)
}