-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[1/3][Distributed] Make distributed thunks the witnesses, fix calls on generic DAs #59711
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4439,15 +4439,22 @@ void SILGenFunction::emitProtocolWitness( | |
SmallVector<ManagedValue, 8> origParams; | ||
collectThunkParams(loc, origParams); | ||
|
||
// If the witness is isolated to a distributed actor, but the requirement is | ||
// not, go through the distributed thunk. | ||
if (witness.hasDecl() && | ||
getActorIsolation(witness.getDecl()).isDistributedActor() && | ||
requirement.hasDecl() && | ||
!getActorIsolation(requirement.getDecl()).isDistributedActor()) { | ||
witness = SILDeclRef( | ||
cast<AbstractFunctionDecl>(witness.getDecl())->getDistributedThunk()) | ||
.asDistributed(); | ||
getActorIsolation(witness.getDecl()).isDistributedActor()) { | ||
// We witness protocol requirements using the distributed thunk, when: | ||
// - the witness is isolated to a distributed actor, but the requirement is not | ||
// - the requirement is a distributed func, and therefore can only be witnessed | ||
// by a distributed func; we handle this by witnessing the requirement with the thunk | ||
// FIXME(distributed): this limits us to only allow distributed explicitly throwing async requirements... we need to fix this somehow. | ||
if (requirement.hasDecl()) { | ||
if ((!getActorIsolation(requirement.getDecl()).isDistributedActor()) || | ||
(isa<FuncDecl>(requirement.getDecl()) && | ||
witness.getFuncDecl()->isDistributed())) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the last line was meant to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes, thank you! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in #59722 |
||
auto thunk = cast<AbstractFunctionDecl>(witness.getDecl()) | ||
->getDistributedThunk(); | ||
witness = SILDeclRef(thunk).asDistributed(); | ||
} | ||
} | ||
} else if (enterIsolation) { | ||
// If we are supposed to enter the actor, do so now by hopping to the | ||
// actor. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -648,8 +648,13 @@ static FuncDecl *createDistributedThunkFunction(FuncDecl *func) { | |
auto &C = func->getASTContext(); | ||
auto DC = func->getDeclContext(); | ||
|
||
auto systemTy = getConcreteReplacementForProtocolActorSystemType(func); | ||
assert(systemTy && | ||
// NOTE: So we don't need a thunk in the protocol, we should call the underlying | ||
// thing instead, which MUST have a thunk, since it must be a distributed func as well... | ||
if (dyn_cast<ProtocolDecl>(DC)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
return nullptr; | ||
} | ||
|
||
assert(getConcreteReplacementForProtocolActorSystemType(func) && | ||
"Thunk synthesis must have concrete actor system type available"); | ||
|
||
DeclName thunkName = func->getName(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// 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-build-swift -module-name main -Xfrontend -disable-availability-checking -j2 -parse-as-library -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -o %t/a.out | ||
// RUN: %target-run %t/a.out | %FileCheck %s --color --dump-input=always | ||
|
||
// REQUIRES: executable_test | ||
// REQUIRES: concurrency | ||
// REQUIRES: distributed | ||
|
||
// rdar://76038845 | ||
// UNSUPPORTED: use_os_stdlib | ||
// UNSUPPORTED: back_deployment_runtime | ||
|
||
// FIXME(distributed): Distributed actors currently have some issues on windows, isRemote always returns false. rdar://82593574 | ||
// UNSUPPORTED: OS=windows-msvc | ||
|
||
import Distributed | ||
import FakeDistributedActorSystems | ||
|
||
|
||
typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem | ||
|
||
protocol DistributedWorker: DistributedActor where ActorSystem == DefaultDistributedActorSystem { | ||
associatedtype WorkItem: Sendable & Codable | ||
associatedtype WorkResult: Sendable & Codable | ||
|
||
// distributed requirement currently is forced to be `async throws`... | ||
// FIXME(distributed): requirements don't have to be async throws, | ||
// distributed makes them implicitly async throws anyway... | ||
distributed func submit(work: WorkItem) async throws -> WorkResult | ||
|
||
// non distributed requirements can be witnessed with _normal_ functions | ||
func sync(work: WorkItem) -> WorkResult | ||
func async(work: WorkItem) async -> WorkResult | ||
func syncThrows(work: WorkItem) throws -> WorkResult | ||
func asyncThrows(work: WorkItem) async throws -> WorkResult | ||
} | ||
|
||
distributed actor TheWorker: DistributedWorker { | ||
typealias ActorSystem = DefaultDistributedActorSystem | ||
typealias WorkItem = String | ||
typealias WorkResult = String | ||
|
||
distributed func submit(work: WorkItem) async throws -> WorkResult { | ||
"\(#function): \(work)" | ||
} | ||
|
||
func sync(work: WorkItem) -> WorkResult { | ||
return "\(#function): \(work)" | ||
} | ||
func async(work: WorkItem) async -> WorkResult { | ||
return "\(#function): \(work)" | ||
} | ||
func syncThrows(work: WorkItem) throws -> WorkResult { | ||
return "\(#function): \(work)" | ||
} | ||
func asyncThrows(work: WorkItem) async throws -> WorkResult { | ||
return "\(#function): \(work)" | ||
} | ||
} | ||
|
||
func test_generic(system: DefaultDistributedActorSystem) async throws { | ||
let localW = TheWorker(actorSystem: system) | ||
let remoteW = try! TheWorker.resolve(id: localW.id, using: system) | ||
precondition(__isRemoteActor(remoteW)) | ||
|
||
// direct calls work ok: | ||
let replyDirect = try await remoteW.submit(work: "Direct") | ||
print("reply direct: \(replyDirect)") | ||
// CHECK: >> remoteCall: on:main.TheWorker, target:main.TheWorker.submit(work:), invocation:FakeInvocationEncoder(genericSubs: [], arguments: ["Direct"], returnType: Optional(Swift.String), errorType: Optional(Swift.Error)), throwing:Swift.Error, returning:Swift.String | ||
// CHECK: reply direct: submit(work:): Direct | ||
|
||
func callWorker<W: DistributedWorker>(w: W) async throws -> String where W.WorkItem == String, W.WorkResult == String { | ||
try await w.submit(work: "Hello") | ||
} | ||
let reply = try await callWorker(w: remoteW) | ||
print("reply (remote): \(reply)") | ||
// CHECK: >> remoteCall: on:main.TheWorker, target:main.TheWorker.submit(work:), invocation:FakeInvocationEncoder(genericSubs: [], arguments: ["Hello"], returnType: Optional(Swift.String), errorType: Optional(Swift.Error)), throwing:Swift.Error, returning:Swift.String | ||
// CHECK: << remoteCall return: submit(work:): Hello | ||
// CHECK: reply (remote): submit(work:): Hello | ||
|
||
let replyLocal = try await callWorker(w: localW) | ||
print("reply (local): \(replyLocal)") | ||
// CHECK-NOT: >> remoteCall | ||
// CHECK: reply (local): submit(work:): Hello | ||
} | ||
|
||
func test_whenLocal(system: DefaultDistributedActorSystem) async throws { | ||
let localW = TheWorker(actorSystem: system) | ||
let remoteW = try! TheWorker.resolve(id: localW.id, using: system) | ||
precondition(__isRemoteActor(remoteW)) | ||
|
||
do { | ||
let replySync = await remoteW.whenLocal { __secretlyKnownToBeLocal in | ||
__secretlyKnownToBeLocal.sync(work: "test") | ||
} | ||
print("replySync (remote): \(replySync)") | ||
// CHECK: replySync (remote): nil | ||
|
||
let replySyncThrows = try await remoteW.whenLocal { __secretlyKnownToBeLocal in | ||
try __secretlyKnownToBeLocal.syncThrows(work: "test") | ||
} | ||
print("replySyncThrows (remote): \(replySyncThrows)") | ||
// CHECK: replySyncThrows (remote): nil | ||
|
||
let replyAsync = await remoteW.whenLocal { __secretlyKnownToBeLocal in | ||
await __secretlyKnownToBeLocal.async(work: "test") | ||
} | ||
print("replyAsync (remote): \(replyAsync)") | ||
// CHECK: replyAsync (remote): nil | ||
|
||
let replyAsyncThrows = try await remoteW.whenLocal { __secretlyKnownToBeLocal in | ||
try await __secretlyKnownToBeLocal.asyncThrows(work: "test") | ||
} | ||
print("replyAsyncThrows (remote): \(replyAsyncThrows)") | ||
// CHECK: replyAsyncThrows (remote): nil | ||
} | ||
// ==== ---------------------------------------------------------------------- | ||
|
||
do { | ||
let replyDistSubmit = try await localW.whenLocal { __secretlyKnownToBeLocal in | ||
try await __secretlyKnownToBeLocal.submit(work: "local-test") | ||
} | ||
print("replyDistSubmit (local): \(replyDistSubmit ?? "nil")") | ||
// CHECK-NOT: >> remoteCall | ||
// CHECK: replyDistSubmit (local): submit(work:): local-test | ||
|
||
let replySyncLocal = await localW.whenLocal { __secretlyKnownToBeLocal in | ||
__secretlyKnownToBeLocal.sync(work: "local-test") | ||
} | ||
print("replySyncLocal (local): \(replySyncLocal ?? "nil")") | ||
// CHECK-NOT: >> remoteCall | ||
// CHECK: replySyncLocal (local): sync(work:): local-test | ||
|
||
let replySyncThrowsLocal = try await localW.whenLocal { __secretlyKnownToBeLocal in | ||
try __secretlyKnownToBeLocal.syncThrows(work: "local-test") | ||
} | ||
print("replySyncThrowsLocal (local): \(replySyncThrowsLocal ?? "nil")") | ||
// CHECK-NOT: >> remoteCall | ||
// CHECK: replySyncThrowsLocal (local): syncThrows(work:): local-test | ||
|
||
let replyAsyncLocal = await localW.whenLocal { __secretlyKnownToBeLocal in | ||
await __secretlyKnownToBeLocal.async(work: "local-test") | ||
} | ||
print("replyAsyncLocal (local): \(replyAsyncLocal ?? "nil")") | ||
// CHECK-NOT: >> remoteCall | ||
// CHECK: replyAsyncLocal (local): async(work:): local-test | ||
|
||
let replyAsyncThrowsLocal = try await localW.whenLocal { __secretlyKnownToBeLocal in | ||
try await __secretlyKnownToBeLocal.asyncThrows(work: "local-test") | ||
} | ||
print("replyAsyncThrowsLocal (local): \(replyAsyncThrowsLocal ?? "nil")") | ||
// CHECK-NOT: >> remoteCall | ||
// CHECK: replyAsyncThrowsLocal (local): asyncThrows(work:): local-test | ||
} | ||
} | ||
|
||
@main struct Main { | ||
static func main() async { | ||
let system = DefaultDistributedActorSystem() | ||
try! await test_generic(system: system) | ||
print("==== ---------------------------------------------------") | ||
try! await test_whenLocal(system: system) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
marked all sites where we emit this with rdar://95949498