///===--- DistributedActor.cpp - Distributed actor implementation ----------===/// /// /// This source file is part of the Swift.org open source project /// /// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors /// Licensed under Apache License v2.0 with Runtime Library Exception /// /// See https:///swift.org/LICENSE.txt for license information /// See https:///swift.org/CONTRIBUTORS.txt for the list of Swift project authors /// ///===----------------------------------------------------------------------===/// /// /// The implementation of Swift distributed actors. /// ///===----------------------------------------------------------------------===/// #include "swift/ABI/Task.h" #include "swift/ABI/Actor.h" #include "swift/ABI/Metadata.h" #include "swift/Runtime/AccessibleFunction.h" #include "swift/Runtime/Concurrency.h" using namespace swift; static const AccessibleFunctionRecord * findDistributedAccessor(const char *targetNameStart, size_t targetNameLength) { if (auto *func = runtime::swift_findAccessibleFunction(targetNameStart, targetNameLength)) { assert(func->Flags.isDistributed()); return func; } return nullptr; } /// Find an accessible function record given a concrete actor type we're /// attempting to make the call on, and the remote call method identifier. static const AccessibleFunctionRecord *findDistributedProtocolMethodAccessor( bool findConcreteWitness, const char *targetActorTypeNameStart, size_t targetActorTypeNameLength, const char *targetNameStart, size_t targetNameLength) { // Find using just the method identifier; // This will work if the call is a concrete method identifier if (auto *func = runtime::swift_findAccessibleFunctionForConcreteType( findConcreteWitness, targetActorTypeNameStart, targetActorTypeNameLength, targetNameStart, targetNameLength)) { assert(func->Flags.isDistributed()); return func; } return nullptr; } /// Given the presence of protocol witness distributed invocation targets, /// obtain a concrete target name. /// /// A distributed target can be identified by a protocol method name: /// ``` /// protocol WorkerProtocol { /// associatedtype Ret /// distributed func func test() -> Ret /// } /// ``` /// /// So the remote call identifier may be "WorkerProtocol.test", however in order /// to perform the invocation on a concrete target actor, we need to obtain /// the concrete function we are about to invoke -- not least because of /// its generic context details. /// /// A concrete type on the server may be: /// /// ``` /// distributed actor WorkerImpl: WorkerProtocol { /// distributed func func test() -> String { "Hello" } /// } /// ``` /// /// Thus this method allows mapping the "protocol method identifier" and /// concrete actor name, into the target witness name. /// /// This way the generic context and other concrete information may be obtained /// in order to perform the call on the concrete `WorkerImpl` type. SWIFT_CC(swift) SWIFT_EXPORT_FROM(swiftDistributed) TypeNamePair swift_distributed_getConcreteAccessibleWitnessName( DefaultActor *actor, const char *targetNameStart, size_t targetNameLength) { // TODO(distributed): Avoid mangling twice; we do it here and in `execute_` auto actorTy = swift_getObjectType(actor); auto actorTyNamePair = swift_getMangledTypeName(actorTy); std::string actorTyName = actorTyNamePair.data; auto accessor = findDistributedProtocolMethodAccessor( /*findConcreteWitness=*/true, actorTyNamePair.data, actorTyNamePair.length, targetNameStart, targetNameLength); if (!accessor) { return {"", 0}; } auto concreteNameData = accessor->Name.get(); auto concreteNameLength = strlen(accessor->Name.get()); return {concreteNameData, concreteNameLength}; } SWIFT_CC(swift) SWIFT_EXPORT_FROM(swiftDistributed) void *swift_distributed_getGenericEnvironmentForConcreteActor( DefaultActor *actor, const char *targetNameStart, size_t targetNameLength) { // TODO(distributed): Avoid mangling twice; we do it here and in `execute_` auto actorTy = swift_getObjectType(actor); auto actorTyNamePair = swift_getMangledTypeName(actorTy); auto *accessor = findDistributedProtocolMethodAccessor( /*findConcreteWitness=*/true, actorTyNamePair.data, actorTyNamePair.length, targetNameStart, targetNameLength); if (!accessor) { return nullptr; } return accessor->GenericEnvironment.get(); } SWIFT_CC(swift) SWIFT_EXPORT_FROM(swiftDistributed) void *swift_distributed_getGenericEnvironment(const char *targetNameStart, size_t targetNameLength) { auto *accessor = findDistributedAccessor(targetNameStart, targetNameLength); return accessor ? accessor->GenericEnvironment.get() : nullptr; } /// func _executeDistributedTarget( /// on: AnyObject, /// _ targetName: UnsafePointer, /// _ targetNameLength: UInt, /// argumentDecoder: inout D, /// argumentTypes: UnsafeBufferPointer, /// resultBuffer: Builtin.RawPointer, /// substitutions: UnsafeRawPointer?, /// witnessTables: UnsafeRawPointer?, /// numWitnessTables: UInt /// ) async throws using TargetExecutorSignature = AsyncSignature; SWIFT_CC(swiftasync) SWIFT_EXPORT_FROM(swiftDistributed) TargetExecutorSignature::FunctionType swift_distributed_execute_target; /// Accessor takes: /// - an async context /// - an argument decoder as an instance of type conforming to `InvocationDecoder` /// - a list of all argument types (with substitutions applied) /// - a result buffer as a raw pointer /// - a list of substitutions /// - a list of witness tables /// - a number of witness tables in the buffer /// - a reference to an actor to execute method on. /// - a type of the argument decoder /// - a witness table associated with argument decoder value using DistributedAccessorSignature = AsyncSignature; SWIFT_CC(swiftasync) static DistributedAccessorSignature::ContinuationType swift_distributed_execute_target_resume; SWIFT_CC(swiftasync) static void swift_distributed_execute_target_resume( SWIFT_ASYNC_CONTEXT AsyncContext *context, SWIFT_CONTEXT SwiftError *error) { auto parentCtx = context->Parent; auto resumeInParent = reinterpret_cast( parentCtx->ResumeParent); swift_task_dealloc(context); // See `swift_distributed_execute_target` - `parentCtx` in this case // is `callContext` which should be completely transparent on resume. return resumeInParent(parentCtx, error); } SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL SwiftError* swift_distributed_makeDistributedTargetAccessorNotFoundError(); SWIFT_CC(swiftasync) void swift_distributed_execute_target( SWIFT_ASYNC_CONTEXT AsyncContext *callerContext, DefaultActor *actor, const char *targetNameStart, size_t targetNameLength, HeapObject *argumentDecoder, const Metadata *const *argumentTypes, void *resultBuffer, void *substitutions, void **witnessTables, size_t numWitnessTables, Metadata *decoderType, Metadata *actorType, void **decoderWitnessTable, void **actorWitnessTable) { std::string targetName(targetNameStart, targetNameLength); auto actorTy = swift_getObjectType(actor); auto actorTyNamePair = swift_getMangledTypeName(actorTy); auto *accessor = findDistributedProtocolMethodAccessor( /*findConcreteWitness=*/false, actorTyNamePair.data, actorTyNamePair.length, targetNameStart, targetNameLength); if (!accessor) { SwiftError *error = swift_distributed_makeDistributedTargetAccessorNotFoundError(); auto resumeInParent = reinterpret_cast( callerContext->ResumeParent); resumeInParent(callerContext, error); return; } auto *asyncFnPtr = reinterpret_cast< const AsyncFunctionPointer *>( accessor->Function.get()); assert(asyncFnPtr && "no function pointer for distributed_execute_target"); DistributedAccessorSignature::FunctionType *accessorEntry = asyncFnPtr->Function.get(); AsyncContext *calleeContext = reinterpret_cast( swift_task_alloc(asyncFnPtr->ExpectedContextSize)); calleeContext->Parent = callerContext; calleeContext->ResumeParent = reinterpret_cast( swift_distributed_execute_target_resume); accessorEntry(calleeContext, argumentDecoder, argumentTypes, resultBuffer, substitutions, witnessTables, numWitnessTables, actor, decoderType, actorType, decoderWitnessTable, actorWitnessTable); }