//===--- SILGenDistributed.cpp - SILGen for distributed -------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2020 - 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 // //===----------------------------------------------------------------------===// #include "ArgumentSource.h" #include "Conversion.h" #include "ExecutorBreadcrumb.h" #include "Initialization.h" #include "LValue.h" #include "RValue.h" #include "SILGenFunction.h" #include "SILGenFunctionBuilder.h" #include "Scope.h" #include "swift/AST/ASTMangler.h" #include "swift/AST/ForeignErrorConvention.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PropertyWrappers.h" #include "swift/Basic/Defer.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILUndef.h" #include "swift/SIL/TypeLowering.h" using namespace swift; using namespace Lowering; /******************************************************************************/ /****************** DISTRIBUTED ACTOR STORAGE INITIALIZATION ******************/ /******************************************************************************/ /// Get the `ActorTransport` parameter of the constructor. /// Sema should have guaranteed that there is exactly one of them for any /// designated initializer of a distributed actor. static SILArgument* getActorTransportArgument(ASTContext& C, SILFunction& F, ConstructorDecl *ctor) { auto *DC = cast(ctor); auto module = DC->getParentModule(); auto *transportProto = C.getProtocol(KnownProtocolKind::ActorTransport); Type transportTy = transportProto->getDeclaredInterfaceType(); for (auto arg : F.getArguments()) { // TODO(distributed): also be able to locate a generic transport Type argTy = arg->getType().getASTType(); auto argDecl = arg->getDecl(); auto conformsToTransport = module->lookupConformance( argDecl->getInterfaceType(), transportProto); // Is it a protocol that conforms to ActorTransport? if (argTy->isEqual(transportTy) || conformsToTransport) { return arg; } // Is it some specific ActorTransport? auto result = module->lookupConformance(argTy, transportProto); if (!result.isInvalid()) { return arg; } } // did not find argument of ActorTransport type! llvm_unreachable("Missing required ActorTransport argument!"); } static AbstractFunctionDecl* lookupAssignIdentityFunc(ASTContext& C) { auto transportDecl = C.getActorTransportDecl(); for (auto decl : transportDecl->lookupDirect(DeclName(C.Id_assignIdentity))) if (auto funcDecl = dyn_cast(decl)) return funcDecl; return nullptr; } /// Synthesize the actorTransport initialization: /// /// \verbatim /// self.actorTransport = <> /// \endverbatim static void emitDistributedActor_init_transportStore( SILGenFunction &SGF, ManagedValue borrowedSelfArg, VarDecl *selfDecl, ConstructorDecl *ctor, Pattern *pattern, VarDecl *var) { auto &C = selfDecl->getASTContext(); auto &B = SGF.B; auto &F = SGF.F; auto &SGM = SGF.SGM; SILGenFunctionBuilder builder(SGM); auto loc = SILLocation(ctor); loc.markAutoGenerated(); // ==== Prepare assignment: get the self.transport address SILValue transportArgValue = getActorTransportArgument(C, F, ctor); ManagedValue transportArgManaged = ManagedValue::forUnmanaged(transportArgValue); // ---- auto *selfTyDecl = ctor->getParent()->getSelfNominalTypeDecl(); auto transportFieldAddr = B.createRefElementAddr( loc, borrowedSelfArg.getValue(), var, SGF.getLoweredType(var->getInterfaceType())); // ==== Store the transport B.createCopyAddr(loc, /*src*/transportArgValue, /*dest*/transportFieldAddr, IsNotTake, IsInitialization); } /// Synthesize storing the passed in managed identity to the `id` property: /// /// \verbatim /// self.id = <> /// \endverbatim static void emitDistributedActorIdentityStore( ASTContext& C, SILGenFunction &SGF, SILValue actorSelf, FuncDecl *func, SILArgument *identityArg) { auto &B = SGF.B; auto &F = SGF.F; auto &SGM = SGF.SGM; SILGenFunctionBuilder builder(SGM); auto *dc = func->getDeclContext(); auto classDecl = dc->getSelfClassDecl(); assert(classDecl->isDistributedActor()); auto loc = SILLocation(func); loc.markAutoGenerated(); // ==== Prepare the property reference: self.id auto idVars = classDecl->lookupDirect(C.Id_id); assert(idVars.size() == 1); auto *idVar = dyn_cast(idVars.front()); // ==== Prepare assignment // SILValue identityArgValue = identityArg->getValue(); fprintf(stderr, "[%s:%d] (%s) THE ACTOR SELF\n", __FILE__, __LINE__, __FUNCTION__); actorSelf->dump(); SILValue identityArgValue = identityArg; auto idFieldAddr = B.createRefElementAddr( loc, actorSelf, idVar, SGF.getLoweredType(idVar->getInterfaceType())); // ==== Store the transport B.createCopyAddr(loc, /*src*/identityArgValue, /*dest*/idFieldAddr, IsNotTake, IsInitialization); // TODO(distributed): should it be take? } /// Synthesize the distributed actor's identity (`id`) initialization: /// /// \verbatim /// self.id = transport.assignIdentity(Self.self) /// \endverbatim static void emitDistributedActorIdentity_init_assignIdentity( SILGenFunction &SGF, ManagedValue borrowedSelfArg, VarDecl *selfVarDecl, ConstructorDecl *ctor, Pattern *pattern, VarDecl *var) { auto &C = selfVarDecl->getASTContext(); auto &B = SGF.B; auto &F = SGF.F; auto &SGM = SGF.SGM; SILGenFunctionBuilder builder(SGM); auto loc = SILLocation(ctor); loc.markAutoGenerated(); // === prepare the transport.assignIdentity(_:) function AbstractFunctionDecl *assignIdentityFuncDecl = lookupAssignIdentityFunc(C); assert(assignIdentityFuncDecl && "Cannot find ActorTransport.assignIdentity!"); auto assignIdentityFnRef = SILDeclRef(assignIdentityFuncDecl); // === Open the transport existential before call SILValue transportArgValue = getActorTransportArgument(C, F, ctor); SILValue selfArgValue = F.getSelfArgument(); ProtocolDecl *distributedActorProto = C.getProtocol(KnownProtocolKind::DistributedActor); ProtocolDecl *transportProto = C.getProtocol(KnownProtocolKind::ActorTransport); assert(distributedActorProto); assert(transportProto); // --- open the transport existential OpenedArchetypeType *Opened; auto transportASTType = transportArgValue->getType().getASTType(); auto openedTransportType = transportASTType->openAnyExistentialType(Opened)->getCanonicalType(); auto openedTransportSILType = F.getLoweredType(openedTransportType); auto transportArchetypeValue = B.createOpenExistentialAddr( loc, transportArgValue, openedTransportSILType, OpenedExistentialAccess::Immutable); // --- prepare `Self.self` metatype auto *selfTyDecl = ctor->getParent()->getSelfNominalTypeDecl(); // This would be bad: since not ok for generic // auto selfMetatype = SGF.getLoweredType(selfTyDecl->getInterfaceType()); auto selfMetatype = SGF.getLoweredType(F.mapTypeIntoContext(selfTyDecl->getInterfaceType())); // selfVarDecl.getType() // TODO: do this; then dont need the self type decl SILValue selfMetatypeValue = B.createMetatype(loc, selfMetatype); // === Make the transport.assignIdentity call // --- prepare the witness_method // Note: it does not matter on what module we perform the lookup, // it is currently ignored. So the Stdlib module is good enough. auto *module = SGF.getModule().getSwiftModule(); // the conformance here is just an abstract thing so we can simplify // auto transportConfRef = module->lookupConformance( // openedTransportType, transportProto); auto transportConfRef = ProtocolConformanceRef(transportProto); assert(!transportConfRef.isInvalid() && "Missing conformance to `ActorTransport`"); auto selfTy = F.mapTypeIntoContext(selfTyDecl->getDeclaredInterfaceType()); // TODO: thats just self var devl getType auto distributedActorConfRef = module->lookupConformance( selfTy, distributedActorProto); assert(!distributedActorConfRef.isInvalid() && "Missing conformance to `DistributedActor`"); auto assignIdentityMethod = cast(transportProto->getSingleRequirement(C.Id_assignIdentity)); auto assignIdentityRef = SILDeclRef(assignIdentityMethod, SILDeclRef::Kind::Func); auto assignIdentitySILTy = SGF.getConstantInfo(SGF.getTypeExpansionContext(), assignIdentityRef) .getSILType(); auto assignWitnessMethod = B.createWitnessMethod( loc, /*lookupTy*/openedTransportType, /*Conformance*/transportConfRef, /*member*/assignIdentityRef, /*methodTy*/assignIdentitySILTy); // --- prepare conformance subs auto genericSig = assignIdentityMethod->getGenericSignature(); SubstitutionMap subs = SubstitutionMap::get(genericSig, {openedTransportType, selfTy}, {transportConfRef, distributedActorConfRef}); // --- create a temporary storage for the result of the call // it will be deallocated automatically as we exit this scope auto resultTy = SGF.getLoweredType(var->getInterfaceType()); auto temp = SGF.emitTemporaryAllocation(loc, resultTy); // ---- actually call transport.assignIdentity(Self.self) B.createApply( loc, assignWitnessMethod, subs, { temp, selfMetatypeValue, transportArchetypeValue}); // ==== Assign the identity to stored property // --- Prepare address of self.id auto idFieldAddr = B.createRefElementAddr( loc, borrowedSelfArg.getValue(), var, SGF.getLoweredType(var->getInterfaceType())); // --- assign to the property B.createCopyAddr(loc, /*src*/temp, /*dest*/idFieldAddr, IsTake, IsInitialization); } void SILGenFunction::initializeDistributedActorImplicitStorageInit( ConstructorDecl *ctor, ManagedValue selfArg) { VarDecl *selfVarDecl = ctor->getImplicitSelfDecl(); auto *dc = ctor->getDeclContext(); auto classDecl = dc->getSelfClassDecl(); auto &C = classDecl->getASTContext(); // Only designated initializers get the lifecycle handling injected if (!ctor->isDesignatedInit()) return; SILLocation prologueLoc = RegularLocation(ctor); prologueLoc.markAsPrologue(); // TODO: no idea if this is necessary or makes sense auto transportTy = C.getActorTransportType(); auto identityProtoTy = C.getActorIdentityType(); auto anyIdentityTy = C.getAnyActorIdentityType(); // ==== Find the stored properties we will initialize VarDecl *transportMember = nullptr; VarDecl *idMember = nullptr; auto borrowedSelfArg = selfArg.borrow(*this, prologueLoc); // TODO(distributed): getStoredProperties might be better here, avoid the `break;` for (auto member : classDecl->getMembers()) { PatternBindingDecl *pbd = dyn_cast(member); if (!pbd) continue; if (pbd->isStatic()) continue; Pattern *pattern = pbd->getPattern(0); VarDecl *var = pbd->getSingleVar(); if (!var) continue; if (var->getName() == C.Id_actorTransport && var->getInterfaceType()->isEqual(transportTy)) { transportMember = var; emitDistributedActor_init_transportStore( *this, borrowedSelfArg, selfVarDecl, ctor, pattern, var); } else if (var->getName() == C.Id_id && (var->getInterfaceType()->isEqual(identityProtoTy) || var->getInterfaceType()->isEqual(anyIdentityTy))) { // TODO(distributed): stick one way to store, but today we can't yet store the existential idMember = var; emitDistributedActorIdentity_init_assignIdentity( *this, borrowedSelfArg, selfVarDecl, ctor, pattern, var); } if (transportMember && idMember) { break; // we found all properties we care about, break out of the loop early } } assert(transportMember && "Missing DistributedActor.actorTransport member"); assert(idMember && "Missing DistributedActor.id member"); } void SILGenFunction::emitDistributedActorReady( ConstructorDecl *ctor, ManagedValue selfArg) { // TODO(distributed): implement actorReady call } /******************************************************************************/ /******************* DISTRIBUTED ACTOR RESOLVE FUNCTION ***********************/ /******************************************************************************/ /// Function body of: /// \verbatim /// DistributedActor.resolve( /// _ identity: Identity, /// using transport: ActorTransport /// ) throws -> Self where Identity: ActorIdentity /// \endverbatim void SILGenFunction::emitDistributedActorFactory(FuncDecl *fd) { /// NOTE: this will only be reached if the resolve function is actually /// demanded. For example, by declaring the actor as `public` or /// having at least one call to the resolve function. auto &C = getASTContext(); SILLocation loc = fd; fd->dump(); F.dump(); // ==== Prepare argument references // --- Parameter: identity SILArgument *identityArg = F.getArgument(0); assert(identityArg->getType().getASTType()->isEqual(C.getAnyActorIdentityType())); // --- Parameter: transport SILArgument *transportArg = F.getArgument(1); // existential assert( transportArg->getType().getASTType()->isEqual(C.getActorTransportType())); // --- Parameter: self // ClassDecl *selfDecl = cast(fd->getDeclContext()->getAsDecl()); auto *selfTyDecl = fd->getParent()->getSelfNominalTypeDecl(); assert(selfTyDecl->isDistributedActor()); auto selfTy = F.mapTypeIntoContext(selfTyDecl->getDeclaredInterfaceType()); // TODO: thats just self var devl getType // ManagedValue selfArg = B.createInputFunctionArgument(selfTy, selfDecl); VarDecl *selfVarDecl = fd->getImplicitSelfDecl(); SILValue selfArgValue = F.getSelfArgument(); ManagedValue selfArg = ManagedValue::forUnmanaged(selfArgValue); // // ==== Prepare all the basic blocks // auto returnBB = createBasicBlock(); // auto errorBB = createBasicBlock(); SILFunctionConventions fnConv = F.getConventions(); // TODO: no idea? // TODO(distributed): call the transport 'transport.resolve(identity)' // TODO(distributed): switch over result to determine if local or remote // NOTES: to make that call on the transport we need to open tne existential // but we already do such things in in the initializer synthesis (in this file) // so we can steal that code, it's fairly easy once we know how. // // That call is throwing, so we may need to cover it with some try? // ==== Case 'local') return the resolved instance // { // auto local = ... // // TODO(distributed): 'case .resolved(let instance): return instance' // // ==== Return the fully initialized 'remote' instance // B.createReturn(loc, local); // } // ==== Case 'remote') Create the remote instance { // ==== Create 'remote' distributed actor instance // --- Prepare arguments to remote actor initialization builtin // auto borrowedSelfArg = selfArg.borrow(*this, loc); // --- Prepare param: Self.self // type: SpecificDistributedActor auto returnTy = getLoweredType( F.mapTypeIntoContext(selfTyDecl->getDeclaredInterfaceType())); fprintf(stderr, "[%s:%d] (%s) auto returnTy = selfArg.getType().getDeclaredInterfaceType();\n", __FILE__, __LINE__, __FUNCTION__); returnTy.dump(); // type: SpecificDistributedActor.Type auto selfMetatype = getLoweredType(F.mapTypeIntoContext(selfArg.getType().getASTType())); SILValue selfMetatypeValue = B.createMetatype(loc, selfMetatype); // --- get the uninitialized allocation from the runtime system. FullExpr scope(Cleanups, CleanupLocation(fd)); // --- Call: _distributedActorRemoteInitialize(Self.self) auto builtinName = C.getIdentifier( getBuiltinName(BuiltinValueKind::InitializeDistributedRemoteActor)); auto *remote = B.createBuiltin( loc, builtinName, /*returnTy*/returnTy, /*subs*/ {}, {selfMetatypeValue}); // ==== Initialize identity and transport // --- Store the identity: self.id = identity emitDistributedActorIdentityStore( C, *this, /*actorSelf*/remote, fd, identityArg); // --- Store the transport: self.transport = transport // FIXME(distributed): IMPLEMENT: // emitDistributedActorTransportStore( // *this, borrowedSelfArg, selfVarDecl, fd, transportArg); // ==== Return the fully initialized remote instance B.createReturn(loc, remote); // // ==== Branch to return the fully initialized remote instance // B.createBranch(loc, returnBB, {remote}); // // // --- Emit return logic // // return // { // B.emitBlock(returnBB); // SILValue result = returnBB->createPhiArgument( // returnTy, OwnershipKind::Owned); // // B.createReturn(loc, result); // } // // // --- Emit rethrow logic // // throw error // { // B.emitBlock(errorBB); // // SILValue error = errorBB->createPhiArgument( // fnConv.getSILErrorType(getTypeExpansionContext()), // OwnershipKind::Owned); // // Cleanups.emitCleanupsForReturn(CleanupLocation(loc), IsForUnwind); // B.createThrow(loc, error); // } } fprintf(stderr, "[%s:%d] (%s) DONE HERE\n", __FILE__, __LINE__, __FUNCTION__); F.dump(); } /******************************************************************************/ /******************* DISTRIBUTED DEINIT: resignAddress ************************/ /******************************************************************************/ // TODO: implement calling resignAddress via a defer in deinit /******************************************************************************/ /***************************** DISTRIBUTED THUNKS *****************************/ /******************************************************************************/ void SILGenFunction::emitDistributedThunk(SILDeclRef thunk) { // Check if actor is local or remote and call respective function // // func X_distributedThunk(...) async throws -> T { // if __isRemoteActor(self) { // return try await self._remote_X(...) // } else { // return try await self.X(...) // } // } // assert(thunk.isDistributed); SILDeclRef native = thunk.asDistributed(false); auto fd = cast(thunk.getDecl()); ASTContext &ctx = getASTContext(); // Use the same generic environment as the native entry point. F.setGenericEnvironment(SGM.Types.getConstantGenericEnvironment(native)); auto loc = thunk.getAsRegularLocation(); loc.markAutoGenerated(); Scope scope(Cleanups, CleanupLocation(loc)); auto isRemoteBB = createBasicBlock(); auto isLocalBB = createBasicBlock(); auto localErrorBB = createBasicBlock(); auto remoteErrorBB = createBasicBlock(); auto localReturnBB = createBasicBlock(); auto remoteReturnBB = createBasicBlock(); auto errorBB = createBasicBlock(); auto returnBB = createBasicBlock(); auto methodTy = SGM.Types.getConstantOverrideType(getTypeExpansionContext(), thunk); auto derivativeFnSILTy = SILType::getPrimitiveObjectType(methodTy); auto silFnType = derivativeFnSILTy.castTo(); SILFunctionConventions fnConv(silFnType, SGM.M); auto resultType = fnConv.getSILResultType(getTypeExpansionContext()); auto *selfVarDecl = fd->getImplicitSelfDecl(); SmallVector params; bindParametersForForwarding(fd->getParameters(), params); bindParameterForForwarding(selfVarDecl, params); auto selfValue = ManagedValue::forUnmanaged(params[params.size() - 1]); // TODO(distributed): getSelfArgument instead auto selfType = selfVarDecl->getType(); // if __isRemoteActor(self) { // ... // } else { // ... // } { FuncDecl* isRemoteFn = ctx.getIsRemoteDistributedActor(); assert(isRemoteFn && "Could not find 'is remote' function, is the '_Distributed' module available?"); ManagedValue selfAnyObject = B.createInitExistentialRef(loc, getLoweredType(ctx.getAnyObjectType()), CanType(selfType), selfValue, {}); auto result = emitApplyOfLibraryIntrinsic(loc, isRemoteFn, SubstitutionMap(), {selfAnyObject}, SGFContext()); SILValue isRemoteResult = std::move(result).forwardAsSingleValue(*this, loc); SILValue isRemoteResultUnwrapped = emitUnwrapIntegerResult(loc, isRemoteResult); B.createCondBranch(loc, isRemoteResultUnwrapped, isRemoteBB, isLocalBB); } // // if __isRemoteActor(self) // { // return try await self._remote_X(...) // } { B.emitBlock(isRemoteBB); auto *selfTyDecl = FunctionDC->getParent()->getSelfNominalTypeDecl(); assert(selfTyDecl && "distributed function declared outside of actor"); auto remoteFnDecl = selfTyDecl->lookupDirectRemoteFunc(fd); assert(remoteFnDecl && "Could not find _remote_ function"); auto remoteFnRef = SILDeclRef(remoteFnDecl); SILGenFunctionBuilder builder(SGM); auto remoteFnSIL = builder.getOrCreateFunction(loc, remoteFnRef, ForDefinition); SILValue remoteFn = B.createFunctionRefFor(loc, remoteFnSIL); auto subs = F.getForwardingSubstitutionMap(); SmallVector remoteParams(params); B.createTryApply(loc, remoteFn, subs, remoteParams, remoteReturnBB, remoteErrorBB); } // // else // { // return (try)? (await)? self.X(...) // } { B.emitBlock(isLocalBB); auto nativeMethodTy = SGM.Types.getConstantOverrideType(getTypeExpansionContext(), native); auto nativeFnSILTy = SILType::getPrimitiveObjectType(nativeMethodTy); auto nativeSilFnType = nativeFnSILTy.castTo(); SILValue nativeFn = emitClassMethodRef( loc, params[params.size() - 1], native, nativeMethodTy); auto subs = F.getForwardingSubstitutionMap(); if (nativeSilFnType->hasErrorResult()) { B.createTryApply(loc, nativeFn, subs, params, localReturnBB, localErrorBB); } else { auto result = B.createApply(loc, nativeFn, subs, params); B.createBranch(loc, returnBB, {result}); } } { B.emitBlock(remoteErrorBB); SILValue error = remoteErrorBB->createPhiArgument( fnConv.getSILErrorType(getTypeExpansionContext()), OwnershipKind::Owned); B.createBranch(loc, errorBB, {error}); } { B.emitBlock(localErrorBB); SILValue error = localErrorBB->createPhiArgument( fnConv.getSILErrorType(getTypeExpansionContext()), OwnershipKind::Owned); B.createBranch(loc, errorBB, {error}); } { B.emitBlock(remoteReturnBB); SILValue result = remoteReturnBB->createPhiArgument( resultType, OwnershipKind::Owned); B.createBranch(loc, returnBB, {result}); } { B.emitBlock(localReturnBB); SILValue result = localReturnBB->createPhiArgument( resultType, OwnershipKind::Owned); B.createBranch(loc, returnBB, {result}); } // Emit return logic { B.emitBlock(returnBB); SILValue resArg = returnBB->createPhiArgument( resultType, OwnershipKind::Owned); B.createReturn(loc, resArg); } // Emit the rethrow logic. { B.emitBlock(errorBB); SILValue error = errorBB->createPhiArgument( fnConv.getSILErrorType(getTypeExpansionContext()), OwnershipKind::Owned); Cleanups.emitCleanupsForReturn(CleanupLocation(loc), IsForUnwind); B.createThrow(loc, error); } }