Skip to content

Fix @isolated(any) for distributed actors #72035

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
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
68 changes: 1 addition & 67 deletions lib/SILGen/SILGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1919,54 +1919,6 @@ static ManagedValue emitBuiltinInjectEnumTag(SILGenFunction &SGF, SILLocation lo
return ManagedValue::forObjectRValueWithoutOwnership(bi);
}

/// Find the extension on DistributedActor that defines __actorUnownedExecutor.
static ExtensionDecl *findDistributedActorAsActorExtension(
ProtocolDecl *distributedActorProto, ModuleDecl *module) {
ASTContext &ctx = distributedActorProto->getASTContext();
auto name = ctx.getIdentifier("__actorUnownedExecutor");
auto results = distributedActorProto->lookupDirect(
name, SourceLoc(),
NominalTypeDecl::LookupDirectFlags::IncludeAttrImplements);
for (auto result : results) {
if (auto var = dyn_cast<VarDecl>(result)) {
return dyn_cast<ExtensionDecl>(var->getDeclContext());
}
}

return nullptr;
}

ProtocolConformanceRef
SILGenModule::getDistributedActorAsActorConformance(SubstitutionMap subs) {
ASTContext &ctx = M.getASTContext();
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
Type distributedActorType = subs.getReplacementTypes()[0];

if (!distributedActorAsActorConformance) {
auto distributedActorProto = ctx.getProtocol(KnownProtocolKind::DistributedActor);
if (!distributedActorProto)
return ProtocolConformanceRef();

auto ext = findDistributedActorAsActorExtension(
distributedActorProto, M.getSwiftModule());
if (!ext)
return ProtocolConformanceRef();

// Conformance of DistributedActor to Actor.
auto genericParam = subs.getGenericSignature().getGenericParams()[0];
distributedActorAsActorConformance = ctx.getNormalConformance(
Type(genericParam), actorProto, SourceLoc(), ext,
ProtocolConformanceState::Incomplete, /*isUnchecked=*/false,
/*isPreconcurrency=*/false);
}

return ProtocolConformanceRef(
actorProto,
ctx.getSpecializedConformance(distributedActorType,
distributedActorAsActorConformance,
subs));
}

void SILGenModule::noteMemberRefExpr(MemberRefExpr *e) {
VarDecl *var = cast<VarDecl>(e->getMember().getDecl());

Expand All @@ -1989,25 +1941,7 @@ void SILGenModule::noteMemberRefExpr(MemberRefExpr *e) {
static ManagedValue emitBuiltinDistributedActorAsAnyActor(
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
ArrayRef<ManagedValue> args, SGFContext C) {
auto &ctx = SGF.getASTContext();
auto distributedActor = args[0];
ProtocolConformanceRef conformances[1] = {
SGF.SGM.getDistributedActorAsActorConformance(subs)
};

// Erase the distributed actor instance into an `any Actor` existential with
// the special conformance.
CanType distributedActorType =
subs.getReplacementTypes()[0]->getCanonicalType();
auto &distributedActorTL = SGF.getTypeLowering(distributedActorType);
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
auto &anyActorTL = SGF.getTypeLowering(actorProto->getDeclaredExistentialType());
return SGF.emitExistentialErasure(
loc, distributedActorType, distributedActorTL, anyActorTL,
ctx.AllocateCopy(conformances),
C, [&distributedActor](SGFContext) {
return distributedActor;
});
return SGF.emitDistributedActorAsAnyActor(loc, subs, args[0]);
}

std::optional<SpecializedEmitter>
Expand Down
133 changes: 125 additions & 8 deletions lib/SILGen/SILGenConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "RValue.h"
#include "Scope.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/Basic/Range.h"

using namespace swift;
Expand Down Expand Up @@ -242,11 +243,12 @@ ManagedValue SILGenFunction::emitNonIsolatedIsolation(SILLocation loc) {

SILValue SILGenFunction::emitLoadGlobalActorExecutor(Type globalActor) {
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
auto actor = emitLoadOfGlobalActorShared(loc, globalActor->getCanonicalType());
return emitLoadActorExecutor(loc, actor);
auto actorAndFormalType =
emitLoadOfGlobalActorShared(loc, globalActor->getCanonicalType());
return emitLoadActorExecutor(loc, actorAndFormalType.first);
}

ManagedValue
std::pair<ManagedValue, CanType>
SILGenFunction::emitLoadOfGlobalActorShared(SILLocation loc, CanType actorType) {
NominalTypeDecl *nominal = actorType->getNominalOrBoundGenericNominal();
VarDecl *sharedInstanceDecl = nominal->getGlobalActorInstance();
Expand All @@ -270,18 +272,53 @@ SILGenFunction::emitLoadOfGlobalActorShared(SILLocation loc, CanType actorType)
actorMetaType, /*isSuper*/ false, sharedInstanceDecl, PreparedArguments(),
subs, AccessSemantics::Ordinary, instanceType, SGFContext());
ManagedValue actorInstance = std::move(actorInstanceRV).getScalarValue();
return actorInstance;
return {actorInstance, instanceType->getCanonicalType()};
}

ManagedValue
SILGenFunction::emitGlobalActorIsolation(SILLocation loc,
CanType globalActorType) {
// GlobalActor.shared returns Self, so this should be a value of
// GlobalActor type.
auto actor = emitLoadOfGlobalActorShared(loc, globalActorType);
// Load the .shared property. Note that this isn't necessarily a value
// of the global actor type.
auto actorAndFormalType = emitLoadOfGlobalActorShared(loc, globalActorType);

// Since it's just a normal actor instance, we can use the normal path.
return emitActorInstanceIsolation(loc, actor, globalActorType);
return emitActorInstanceIsolation(loc, actorAndFormalType.first,
actorAndFormalType.second);
}

/// Given a value of some non-optional distributed actor type, convert it
/// to the non-optional `any Actor` type.
static ManagedValue
emitDistributedActorIsolation(SILGenFunction &SGF, SILLocation loc,
ManagedValue actor, CanType actorType) {
// First, open the actor type if it's an existential type.
if (actorType->isExistentialType()) {
CanType openedType = OpenedArchetypeType::getAny(actorType,
SGF.F.getGenericSignature());
SILType loweredOpenedType = SGF.getLoweredType(openedType);

actor = SGF.emitOpenExistential(loc, actor, loweredOpenedType,
AccessKind::Read);
actorType = openedType;
}

auto &ctx = SGF.getASTContext();
auto distributedActorProto =
ctx.getProtocol(KnownProtocolKind::DistributedActor);

// Build <T: DistributedActor> and its substitutions for actorType.
// Doing this manually is ill-advised in general, but this is such a
// simple case that it's okay.
auto sig = distributedActorProto->getGenericSignature();
auto distributedActorConf =
SGF.SGM.SwiftModule->lookupConformance(actorType, distributedActorProto);
auto distributedActorSubs = SubstitutionMap::get(sig, {actorType},
{distributedActorConf});

// Use that to build the magical conformance to Actor for the distributed
// actor type.
return SGF.emitDistributedActorAsAnyActor(loc, distributedActorSubs, actor);
}

/// Given a value of some non-optional actor type, convert it to
Expand All @@ -295,6 +332,13 @@ emitNonOptionalActorInstanceIsolation(SILGenFunction &SGF, SILLocation loc,
return actor;

CanType anyActorType = anyActorTy.getASTType();

// If the actor is a distributed actor, (1) it had better be local
// and (2) we need to use the special conformance.
if (actorType->isDistributedActor()) {
return emitDistributedActorIsolation(SGF, loc, actor, actorType);
}

return SGF.emitTransformExistential(loc, actor, actorType, anyActorType);
}

Expand Down Expand Up @@ -587,3 +631,76 @@ SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {
assert(ExpectedExecutor && "prolog failed to set up expected executor?");
return ExpectedExecutor;
}

/// Find the extension on DistributedActor that defines __actorUnownedExecutor.
static ExtensionDecl *findDistributedActorAsActorExtension(
ProtocolDecl *distributedActorProto, ModuleDecl *module) {
ASTContext &ctx = distributedActorProto->getASTContext();
auto name = ctx.getIdentifier("__actorUnownedExecutor");
auto results = distributedActorProto->lookupDirect(
name, SourceLoc(),
NominalTypeDecl::LookupDirectFlags::IncludeAttrImplements);
for (auto result : results) {
if (auto var = dyn_cast<VarDecl>(result)) {
return dyn_cast<ExtensionDecl>(var->getDeclContext());
}
}

return nullptr;
}

ProtocolConformanceRef
SILGenModule::getDistributedActorAsActorConformance(SubstitutionMap subs) {
ASTContext &ctx = M.getASTContext();
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
Type distributedActorType = subs.getReplacementTypes()[0];

if (!distributedActorAsActorConformance) {
auto distributedActorProto = ctx.getProtocol(KnownProtocolKind::DistributedActor);
if (!distributedActorProto)
return ProtocolConformanceRef();

auto ext = findDistributedActorAsActorExtension(
distributedActorProto, M.getSwiftModule());
if (!ext)
return ProtocolConformanceRef();

// Conformance of DistributedActor to Actor.
auto genericParam = subs.getGenericSignature().getGenericParams()[0];
distributedActorAsActorConformance = ctx.getNormalConformance(
Type(genericParam), actorProto, SourceLoc(), ext,
ProtocolConformanceState::Incomplete, /*isUnchecked=*/false,
/*isPreconcurrency=*/false);
}

return ProtocolConformanceRef(
actorProto,
ctx.getSpecializedConformance(distributedActorType,
distributedActorAsActorConformance,
subs));
}

ManagedValue
SILGenFunction::emitDistributedActorAsAnyActor(SILLocation loc,
SubstitutionMap distributedActorSubs,
ManagedValue actorValue) {
ProtocolConformanceRef conformances[1] = {
SGM.getDistributedActorAsActorConformance(distributedActorSubs)
};

// Erase the distributed actor instance into an `any Actor` existential with
// the special conformance.
auto &ctx = SGM.getASTContext();
CanType distributedActorType =
distributedActorSubs.getReplacementTypes()[0]->getCanonicalType();
auto &distributedActorTL = getTypeLowering(distributedActorType);
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
auto &anyActorTL = getTypeLowering(actorProto->getDeclaredExistentialType());
return emitExistentialErasure(loc, distributedActorType,
distributedActorTL, anyActorTL,
ctx.AllocateCopy(conformances),
SGFContext(),
[actorValue](SGFContext) {
return actorValue;
});
}
10 changes: 8 additions & 2 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
ArgumentSource &&fnSource,
SGFContext C);

ManagedValue emitDistributedActorAsAnyActor(SILLocation loc,
SubstitutionMap distributedActorSubs,
ManagedValue actor);

/// Generate a nullary function that returns the given value.
/// If \p emitProfilerIncrement is set, emit a profiler increment for
/// \p value.
Expand Down Expand Up @@ -1182,8 +1186,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
SILValue emitLoadGlobalActorExecutor(Type globalActor);

/// Call `.shared` on the given global actor type.
ManagedValue emitLoadOfGlobalActorShared(SILLocation loc,
CanType globalActorType);
///
/// Returns the value of the property and the formal instance type.
std::pair<ManagedValue, CanType>
emitLoadOfGlobalActorShared(SILLocation loc, CanType globalActorType);

/// Emit a reference to the given global actor as an opaque isolation.
ManagedValue emitGlobalActorIsolation(SILLocation loc,
Expand Down
38 changes: 38 additions & 0 deletions test/Distributed/distributed_actor_isolated_any.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/Inputs/FakeDistributedActorSystems.swift

// RUN: %target-swift-frontend -emit-silgen -enable-experimental-feature IsolatedAny %s -module-name test -swift-version 5 -disable-availability-checking -I %t | %FileCheck %s

// REQUIRES: concurrency
// REQUIRES: asserts
// REQUIRES: distributed

import Distributed
import FakeDistributedActorSystems

typealias DefaultDistributedActorSystem = FakeActorSystem

func takeInheritingAsyncIsolatedAny(@_inheritActorContext fn: @escaping @isolated(any) () async -> ()) {}

// CHECK-LABEL: sil hidden [distributed] [ossa] @$s4test2DAC0A20DistributedIsolationyyF
// CHECK: // function_ref closure #1
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test2DAC0A20DistributedIsolationyyFyyYacfU_ : $@convention(thin) @async (@guaranteed Optional<any Actor>, @sil_isolated @guaranteed DA) -> ()
// CHECK-NEXT: [[CAPTURE:%.*]] = copy_value %0 : $DA
// CHECK-NEXT: [[CAPTURE_FOR_ISOLATION:%.*]] = copy_value [[CAPTURE]] : $DA
// The conformance here is special, but we don't record that in the printed SIL.
// CHECK-NEXT: [[ISOLATION_OBJECT:%.*]] = init_existential_ref [[CAPTURE_FOR_ISOLATION]] : $DA : $DA, $any Actor
// CHECK-NEXT: [[ISOLATION:%.*]] = enum $Optional<any Actor>, #Optional.some!enumelt, [[ISOLATION_OBJECT]] : $any Actor
// CHECK-NEXT: [[CLOSURE:%.*]] = partial_apply [callee_guaranteed] [isolated_any] [[CLOSURE_FN]]([[ISOLATION]], [[CAPTURE]])
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[TAKE_FN:%.*]] = function_ref @$s4test30takeInheritingAsyncIsolatedAny2fnyyyYaYAc_tF
// CHECK-NEXT: apply [[TAKE_FN]]([[CLOSURE]])
// CHECK-NEXT: destroy_value [[CLOSURE]]
distributed actor DA {
distributed func testDistributedIsolation() {
takeInheritingAsyncIsolatedAny {
await self.asyncAction()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}

func asyncAction() async {}
}
36 changes: 35 additions & 1 deletion test/SILGen/isolated_any.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,41 @@ func testEraseInheritingAsyncNonIsolatedClosure() {
// CHECK-NEXT: return
@MainActor
func testEraseInheritingAsyncMainActorClosure() {
takeInheritingAsyncIsolatedAny { @MainActor in
takeInheritingAsyncIsolatedAny {
await asyncAction()
}
}

// Define a global actor that doesn't use Self as its instance type
actor MyGlobalActorInstance {}
@globalActor struct MyGlobalActor {
// Make sure this doesn't confuse things.
let shared = 0

static let shared = MyGlobalActorInstance()
}

// CHECK-LABEL: sil hidden [ossa] @$s4test0A38EraseInheritingAsyncGlobalActorClosureyyF
// CHECK: // function_ref closure #1
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test0A38EraseInheritingAsyncGlobalActorClosureyyFyyYacfU_ :
// CHECK-NEXT: [[GLOBAL_ACTOR_METATYPE:%.*]] = metatype $@thin MyGlobalActor.Type
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[GLOBAL_ACTOR_SHARED_FN:%.]] = function_ref @$s4test13MyGlobalActorV6sharedAA0bcD8InstanceCvau :
// CHECK-NEXT: [[GLOBAL_ACTOR_PTR:%.*]] = apply [[GLOBAL_ACTOR_SHARED_FN]]()
// CHECK-NEXT: [[GLOBAL_ACTOR_ADDR:%.*]] = pointer_to_address [[GLOBAL_ACTOR_PTR]] : $Builtin.RawPointer to [strict] $*MyGlobalActorInstance
// CHECK-NEXT: [[GLOBAL_ACTOR:%.*]] = load [copy] [[GLOBAL_ACTOR_ADDR]] : $*MyGlobalActorInstance
// CHECK-NEXT: [[ERASED_GLOBAL_ACTOR:%.*]] = init_existential_ref [[GLOBAL_ACTOR]] :
// CHECK-NEXT: [[ISOLATION:%.*]] = enum $Optional<any Actor>, #Optional.some!enumelt, [[ERASED_GLOBAL_ACTOR]] : $any Actor
// CHECK-NEXT: [[CLOSURE:%.*]] = partial_apply [callee_guaranteed] [isolated_any] [[CLOSURE_FN]]([[ISOLATION]])
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[TAKE_FN:%.*]] = function_ref @$s4test30takeInheritingAsyncIsolatedAny2fnyyyYaYAc_tF
// CHECK-NEXT: apply [[TAKE_FN]]([[CLOSURE]])
// CHECK-NEXT: destroy_value [[CLOSURE]]
// CHECK-NEXT: tuple ()
// CHECK-NEXT: return
@MyGlobalActor
func testEraseInheritingAsyncGlobalActorClosure() {
takeInheritingAsyncIsolatedAny {
await asyncAction()
}
}
Expand Down