//===--- CodeSynthesisDistributedActor.cpp --------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "TypeCheckDistributed.h" #include "CodeSynthesis.h" #include "DerivedConformances.h" #include "TypeChecker.h" #include "swift/AST/ASTMangler.h" #include "swift/AST/ASTPrinter.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/DistributedDecl.h" #include "swift/AST/ExistentialLayout.h" #include "swift/AST/Expr.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/Initializer.h" #include "swift/AST/NameLookupRequests.h" #include "swift/AST/ParameterList.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Defer.h" #include "swift/ClangImporter/ClangModule.h" #include "swift/Sema/ConstraintSystem.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" using namespace swift; /******************************************************************************/ /************************ PROPERTY SYNTHESIS **********************************/ /******************************************************************************/ static VarDecl* lookupDistributedActorProperty(NominalTypeDecl *decl, DeclName name) { assert(decl && "decl was null"); auto &C = decl->getASTContext(); auto clazz = dyn_cast(decl); if (!clazz) return nullptr; auto refs = decl->lookupDirect(name); if (refs.size() != 1) return nullptr; auto var = dyn_cast(refs.front()); if (!var) return nullptr; Type expectedType = Type(); if (name == C.Id_id) { expectedType = getDistributedActorIDType(decl); } else if (name == C.Id_actorSystem) { expectedType = getDistributedActorSystemType(decl); } else { llvm_unreachable("Unexpected distributed actor property lookup!"); } if (!expectedType) return nullptr; if (!var->getInterfaceType()->isEqual(expectedType)) return nullptr; return var; } // Note: This would be nice to implement in DerivedConformanceDistributedActor, // but we can't since those are lazily triggered and an implementation exists // for the 'id' property because 'Identifiable.id' has an extension that impls // it for ObjectIdentifier, and we have to instead emit this stored property. // // The "derived" mechanisms are not really geared towards emitting for // what already has a witness. static VarDecl *addImplicitDistributedActorIDProperty( ClassDecl *nominal) { if (!nominal || !nominal->isDistributedActor()) return nullptr; auto &C = nominal->getASTContext(); // ==== Synthesize and add 'id' property to the actor decl Type propertyType = getDistributedActorIDType(nominal); if (!propertyType || propertyType->hasError()) return nullptr; auto *propDecl = new (C) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let, SourceLoc(), C.Id_id, nominal); propDecl->setImplicit(); propDecl->setSynthesized(); propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); propDecl->setInterfaceType(propertyType); auto propContextTy = nominal->mapTypeIntoContext(propertyType); Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); propPat = TypedPattern::createImplicit(C, propPat, propContextTy); PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, nominal); // mark as nonisolated, allowing access to it from everywhere propDecl->getAttrs().add( new (C) NonisolatedAttr(/*unsafe=*/false, /*implicit=*/true)); // mark as @_compilerInitialized, since we synthesize the initializing // assignment during SILGen. propDecl->getAttrs().add( new (C) CompilerInitializedAttr(/*IsImplicit=*/true)); // IMPORTANT: The `id` MUST be the first field of any distributed actor, // because when we allocate remote proxy instances, we don't allocate memory // for anything except the first two fields: id and actorSystem, so they // MUST be those fields. // // Their specific order also matters, because it is enforced this way in IRGen // and how we emit them in AST MUST match what IRGen expects or cross-module // things could be using wrong offsets and manifest as reading trash memory on // id or system accesses. nominal->addMember(propDecl, /*hint=*/nullptr, /*insertAtHead=*/true); nominal->addMember(pbDecl, /*hint=*/nullptr, /*insertAtHead=*/true); return propDecl; } static VarDecl *addImplicitDistributedActorActorSystemProperty( ClassDecl *nominal) { if (!nominal) return nullptr; if (!nominal->isDistributedActor()) return nullptr; auto &C = nominal->getASTContext(); // ==== Synthesize and add 'actorSystem' property to the actor decl Type propertyType = getDistributedActorSystemType(nominal); auto *propDecl = new (C) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let, SourceLoc(), C.Id_actorSystem, nominal); propDecl->setImplicit(); propDecl->setSynthesized(); propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); propDecl->setInterfaceType(propertyType); auto propContextTy = nominal->mapTypeIntoContext(propertyType); Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); propPat = TypedPattern::createImplicit(C, propPat, propContextTy); PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, nominal); // mark as nonisolated, allowing access to it from everywhere propDecl->getAttrs().add( new (C) NonisolatedAttr(/*unsafe=*/false, /*implicit=*/true)); auto idProperty = nominal->getDistributedActorIDProperty(); // If the id was not yet synthesized, we need to ensure that eventually // the order of fields will be: id, actorSystem (because IRGen needs the // layouts to match with the AST we produce). We do this by inserting FIRST, // and then as the ID gets synthesized, it'll also force FIRST and therefore // the order will be okey -- ID and then system. auto insertAtHead = idProperty == nullptr; // IMPORTANT: The `id` MUST be the first field of any distributed actor. // So we find the property and add the system AFTER it using the hint. // // If the `id` was not synthesized yet, we'll end up inserting at head, // but the id synthesis will force itself to be FIRST anyway, so it works out. nominal->addMember(propDecl, /*hint=*/idProperty, /*insertAtHead=*/insertAtHead); nominal->addMember(pbDecl, /*hint=*/idProperty, /*insertAtHead=*/insertAtHead); return propDecl; } /******************************************************************************/ /*********************** DISTRIBUTED THUNK SYNTHESIS **************************/ /******************************************************************************/ static void forwardParameters(AbstractFunctionDecl *afd, SmallVectorImpl &forwardingParams) { auto &C = afd->getASTContext(); for (auto param : *afd->getParameters()) { forwardingParams.push_back(new (C) DeclRefExpr( ConcreteDeclRef(param), DeclNameLoc(), /*implicit=*/true, swift::AccessSemantics::Ordinary, afd->mapTypeIntoContext(param->getInterfaceType()))); } } /// Mangle the target thunk in a way that we can look up the appropriate record. static llvm::StringRef mangleDistributedThunkForAccessorRecordName( ASTContext &C, AbstractFunctionDecl *thunk) { Mangle::ASTMangler mangler(C); // default mangling auto mangled = C.AllocateCopy(mangler.mangleDistributedThunkRef(cast(thunk))); return mangled; } static std::pair deriveBodyDistributed_thunk(AbstractFunctionDecl *thunk, void *context) { auto implicit = true; ASTContext &C = thunk->getASTContext(); // mock locations, we're a thunk and don't really need detailed locations const SourceLoc sloc = SourceLoc(); const DeclNameLoc dloc = DeclNameLoc(); auto func = static_cast(context); auto funcDC = func->getDeclContext(); assert(funcDC->getSelfNominalTypeDecl() && funcDC->getSelfNominalTypeDecl()->isDistributedActor() && "Distributed function must be part of distributed actor"); auto selfDecl = thunk->getImplicitSelfDecl(); selfDecl->getAttrs().add(new (C) KnownToBeLocalAttr(implicit)); // === return type Type returnTy = func->getResultInterfaceType(); auto isVoidReturn = returnTy->isVoid(); // === Type: StructDecl *RCT = C.getRemoteCallTargetDecl(); assert(RCT && "Missing RemoteCalLTarget declaration"); Type remoteCallTargetTy = RCT->getDeclaredInterfaceType(); // === __isRemoteActor(self) ArgumentList *isRemoteArgs = ArgumentList::forImplicitSingle( C, /*label=*/Identifier(), new (C) DeclRefExpr(selfDecl, dloc, implicit)); FuncDecl *isRemoteFn = C.getIsRemoteDistributedActor(); assert(isRemoteFn && "Could not find 'is remote' function, is the " "'_Distributed' module available?"); auto isRemoteDeclRef = UnresolvedDeclRefExpr::createImplicit(C, isRemoteFn->getName()); auto isRemote = CallExpr::createImplicit(C, isRemoteDeclRef, isRemoteArgs); // === local branch ---------------------------------------------------------- BraceStmt *localBranchStmt; if (auto accessor = dyn_cast(func)) { auto selfRefExpr = new (C) DeclRefExpr(selfDecl, dloc, implicit); auto var = accessor->getStorage(); Expr *localPropertyAccess = new (C) MemberRefExpr( selfRefExpr, sloc, ConcreteDeclRef(var), dloc, implicit); localPropertyAccess = AwaitExpr::createImplicit(C, sloc, localPropertyAccess); if (accessor->hasThrows()) { localPropertyAccess = TryExpr::createImplicit(C, sloc, localPropertyAccess); } auto returnLocalPropertyAccess = ReturnStmt::createImplicit(C, sloc, localPropertyAccess); localBranchStmt = BraceStmt::create(C, sloc, {returnLocalPropertyAccess}, sloc, implicit); } else { // normal function auto selfRefExpr = new (C) DeclRefExpr(selfDecl, dloc, implicit); // -- forward arguments SmallVector forwardingParams; forwardParameters(thunk, forwardingParams); auto funcRef = UnresolvedDeclRefExpr::createImplicit(C, func->getName()); auto forwardingArgList = ArgumentList::forImplicitCallTo(funcRef->getName(), forwardingParams, C); auto funcDeclRef = UnresolvedDotExpr::createImplicit(C, selfRefExpr, func->getBaseName()); Expr *localFuncCall = CallExpr::createImplicit(C, funcDeclRef, forwardingArgList); localFuncCall = AwaitExpr::createImplicit(C, sloc, localFuncCall); if (func->hasThrows()) { localFuncCall = TryExpr::createImplicit(C, sloc, localFuncCall); } auto returnLocalFuncCall = ReturnStmt::createImplicit(C, sloc, localFuncCall); localBranchStmt = BraceStmt::create(C, sloc, {returnLocalFuncCall}, sloc, implicit); } // === remote branch -------------------------------------------------------- SmallVector remoteBranchStmts; // --- self.actorSystem auto systemRefExpr = UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(selfDecl, dloc, implicit), // TODO: make createImplicit C.Id_actorSystem); auto *systemVar = new (C) VarDecl( /*isStatic=*/false, VarDecl::Introducer::Let, sloc, C.Id_system, thunk); systemVar->setImplicit(); systemVar->setSynthesized(); Pattern *systemPattern = NamedPattern::createImplicit(C, systemVar); auto systemPB = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, systemPattern, systemRefExpr, thunk); remoteBranchStmts.push_back(systemPB); remoteBranchStmts.push_back(systemVar); // --- invocationEncoder = system.makeInvocationEncoder() auto *invocationVar = new (C) VarDecl(/*isStatic=*/false, VarDecl::Introducer::Var, sloc, C.Id_invocation, thunk); invocationVar->setImplicit(); invocationVar->setSynthesized(); { Pattern *invocationPattern = NamedPattern::createImplicit(C, invocationVar); auto makeInvocationExpr = UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(systemVar), dloc, implicit), DeclName(C.Id_makeInvocationEncoder)); auto *makeInvocationArgs = ArgumentList::createImplicit(C, {}); auto makeInvocationCallExpr = CallExpr::createImplicit(C, makeInvocationExpr, makeInvocationArgs); makeInvocationCallExpr->setThrows(nullptr); auto invocationEncoderPB = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, invocationPattern, makeInvocationCallExpr, thunk); remoteBranchStmts.push_back(invocationEncoderPB); remoteBranchStmts.push_back(invocationVar); } // --- Recording invocation details // -- recordGenericSubstitution(s) if (auto genEnv = thunk->getGenericEnvironment()) { auto recordGenericSubstitutionName = DeclName(C, C.Id_recordGenericSubstitution, /*labels=*/{Identifier()}); auto recordGenericSubstitutionDeclRef = UnresolvedDeclRefExpr::createImplicit(C, recordGenericSubstitutionName); for (auto genParamType : genEnv->getGenericParams()) { auto tyExpr = TypeExpr::createImplicit(genEnv->mapTypeIntoContext(genParamType), C); auto subTypeExpr = new (C) DotSelfExpr( tyExpr, sloc, sloc, tyExpr->getType()); auto recordGenericSubArgsList = ArgumentList::forImplicitCallTo( recordGenericSubstitutionDeclRef->getName(), {subTypeExpr}, C); Expr *recordGenericSub = CallExpr::createImplicit( C, UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(invocationVar), dloc, implicit, AccessSemantics::Ordinary), recordGenericSubstitutionName), recordGenericSubArgsList); recordGenericSub = TryExpr::createImplicit(C, sloc, recordGenericSub); remoteBranchStmts.push_back(recordGenericSub); } } // -- recordArgument(s) { auto recordArgumentName = DeclName(C, C.Id_recordArgument, /*labels=*/{Identifier()}); if (auto params = thunk->getParameters()) { if (params->begin()) for (auto param : *params) { auto argumentName = param->getArgumentName().str(); LiteralExpr *argumentLabelArg; if (argumentName.empty()) { argumentLabelArg = new (C) NilLiteralExpr(sloc, implicit); } else { argumentLabelArg = new (C) StringLiteralExpr(argumentName, SourceRange(), implicit); } auto parameterName = param->getParameterName().str(); // --- Prepare the RemoteCallArgument for the argument auto argumentVarName = C.getIdentifier("_" + parameterName.str()); StructDecl *RCA = C.getRemoteCallArgumentDecl(); VarDecl *callArgVar = new (C) VarDecl(/*isStatic=*/false, VarDecl::Introducer::Let, sloc, argumentVarName, thunk); callArgVar->setImplicit(); callArgVar->setSynthesized(); Pattern *callArgPattern = NamedPattern::createImplicit(C, callArgVar); auto remoteCallArgumentInitDecl = RCA->getDistributedRemoteCallArgumentInitFunction(); auto boundRCAType = BoundGenericType::get( RCA, Type(), {thunk->mapTypeIntoContext(param->getInterfaceType())}); auto remoteCallArgumentInitDeclRef = TypeExpr::createImplicit(boundRCAType, C); auto initCallArgArgs = ArgumentList::forImplicitCallTo( DeclNameRef(remoteCallArgumentInitDecl->getEffectiveFullName()), { // label: argumentLabelArg, // name: new (C) StringLiteralExpr(parameterName, SourceRange(), implicit), // _ argument: new (C) DeclRefExpr( ConcreteDeclRef(param), dloc, implicit, AccessSemantics::Ordinary, thunk->mapTypeIntoContext(param->getInterfaceType())) }, C); auto initCallArgCallExpr = CallExpr::createImplicit(C, remoteCallArgumentInitDeclRef, initCallArgArgs); auto callArgPB = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, callArgPattern, initCallArgCallExpr, thunk); remoteBranchStmts.push_back(callArgPB); remoteBranchStmts.push_back(callArgVar); /// --- Pass the argumentRepr to the recordArgument function auto recordArgArgsList = ArgumentList::forImplicitCallTo( DeclNameRef(recordArgumentName), {new (C) DeclRefExpr(ConcreteDeclRef(callArgVar), dloc, implicit, AccessSemantics::Ordinary)}, C); auto tryRecordArgExpr = TryExpr::createImplicit( C, sloc, CallExpr::createImplicit( C, UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(invocationVar), dloc, implicit, AccessSemantics::Ordinary), recordArgumentName), recordArgArgsList)); remoteBranchStmts.push_back(tryRecordArgExpr); } } } // -- recordErrorType if (func->hasThrows()) { auto recordErrorTypeName = DeclName(C, C.Id_recordErrorType, /*labels=*/{Identifier()}); // Error.self auto errorDecl = C.getErrorDecl(); auto *errorTypeExpr = new (C) DotSelfExpr( UnresolvedDeclRefExpr::createImplicit(C, errorDecl->getName()), sloc, sloc, errorDecl->getDeclaredInterfaceType()); auto recordArgsList = ArgumentList::forImplicitCallTo( DeclNameRef(recordErrorTypeName), {errorTypeExpr}, C); auto tryRecordErrorTyExpr = TryExpr::createImplicit( C, sloc, CallExpr::createImplicit( C, UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(invocationVar), dloc, implicit, AccessSemantics::Ordinary), recordErrorTypeName), recordArgsList)); remoteBranchStmts.push_back(tryRecordErrorTyExpr); } // -- recordReturnType if (!isVoidReturn) { auto recordReturnTypeName = DeclName(C, C.Id_recordReturnType, /*labels=*/{Identifier()}); // Result.self // Watch out and always map into thunk context auto resultType = thunk->mapTypeIntoContext(func->getResultInterfaceType()); auto *metaTypeRef = TypeExpr::createImplicit(resultType, C); auto *resultTypeExpr = new (C) DotSelfExpr(metaTypeRef, sloc, sloc, resultType); auto recordArgsList = ArgumentList::forImplicitCallTo( DeclNameRef(recordReturnTypeName), {resultTypeExpr}, C); auto tryRecordReturnTyExpr = TryExpr::createImplicit( C, sloc, CallExpr::createImplicit( C, UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(invocationVar), dloc, implicit, AccessSemantics::Ordinary), recordReturnTypeName), recordArgsList)); remoteBranchStmts.push_back(tryRecordReturnTyExpr); } // -- doneRecording { DeclName doneRecordingName(C.Id_doneRecording); auto argsList = ArgumentList::forImplicitCallTo(DeclNameRef(doneRecordingName), {}, C); auto tryDoneRecordingExpr = TryExpr::createImplicit( C, sloc, CallExpr::createImplicit( C, UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(invocationVar), dloc, implicit, AccessSemantics::Ordinary, invocationVar->getInterfaceType()), doneRecordingName), argsList)); remoteBranchStmts.push_back(tryDoneRecordingExpr); } // === Prepare the 'RemoteCallTarget' auto *targetVar = new (C) VarDecl( /*isStatic=*/false, VarDecl::Introducer::Let, sloc, C.Id_target, thunk); { // --- Mangle the thunk name auto mangledAccessorRecordName = mangleDistributedThunkForAccessorRecordName(C, thunk); StringLiteralExpr *mangledTargetStringLiteral = new (C) StringLiteralExpr(mangledAccessorRecordName, SourceRange(), implicit); // --- let target = RemoteCallTarget() targetVar->setInterfaceType(remoteCallTargetTy); targetVar->setImplicit(); targetVar->setSynthesized(); Pattern *targetPattern = NamedPattern::createImplicit(C, targetVar); auto remoteCallTargetInitDecl = RCT->getDistributedRemoteCallTargetInitFunction(); auto remoteCallTargetInitDeclRef = UnresolvedDeclRefExpr::createImplicit( C, remoteCallTargetInitDecl->getEffectiveFullName()); auto initTargetExpr = UnresolvedDeclRefExpr::createImplicit( C, RCT->getName()); auto initTargetArgs = ArgumentList::forImplicitCallTo( remoteCallTargetInitDeclRef->getName(), {mangledTargetStringLiteral}, C); auto initTargetCallExpr = CallExpr::createImplicit(C, initTargetExpr, initTargetArgs); auto targetPB = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, targetPattern, initTargetCallExpr, thunk); remoteBranchStmts.push_back(targetPB); remoteBranchStmts.push_back(targetVar); } // === Make the 'remoteCall(Void)(...)' { DeclName remoteCallName; if (isVoidReturn) { remoteCallName = DeclName(C, C.Id_remoteCallVoid, {C.Id_on, C.Id_target, C.Id_invocation, C.Id_throwing}); } else { remoteCallName = DeclName(C, C.Id_remoteCall, {C.Id_on, C.Id_target, C.Id_invocation, C.Id_throwing, C.Id_returning}); } auto systemRemoteCallRef = UnresolvedDotExpr::createImplicit( C, new (C) DeclRefExpr(ConcreteDeclRef(systemVar), dloc, implicit), remoteCallName); SmallVector args; // -- on actor: Act args.push_back(new (C) DeclRefExpr(selfDecl, dloc, implicit, swift::AccessSemantics::Ordinary, selfDecl->getInterfaceType())); // -- target: RemoteCallTarget args.push_back(new (C) DeclRefExpr(ConcreteDeclRef(targetVar), dloc, implicit, AccessSemantics::Ordinary, RCT->getDeclaredInterfaceType())); // -- invocation: inout InvocationEncoder args.push_back(new (C) InOutExpr( sloc, new (C) DeclRefExpr(ConcreteDeclRef(invocationVar), dloc, implicit, AccessSemantics::Ordinary), Type(), implicit)); // -- throwing: Err.Type if (func->hasThrows()) { // Error.self auto errorDecl = C.getErrorDecl(); auto *errorTypeExpr = new (C) DotSelfExpr( UnresolvedDeclRefExpr::createImplicit(C, errorDecl->getName()), sloc, sloc, errorDecl->getDeclaredInterfaceType()); args.push_back(errorTypeExpr); } else { // Never.self auto neverDecl = C.getNeverDecl(); auto *neverTypeExpr = new (C) DotSelfExpr( UnresolvedDeclRefExpr::createImplicit(C, neverDecl->getName()), sloc, sloc, neverDecl->getDeclaredInterfaceType()); args.push_back(neverTypeExpr); } // -- returning: Res.Type if (!isVoidReturn) { // Result.self auto resultType = func->mapTypeIntoContext(func->getResultInterfaceType()); auto *metaTypeRef = TypeExpr::createImplicit(resultType, C); auto *resultTypeExpr = new (C) DotSelfExpr(metaTypeRef, sloc, sloc, resultType); args.push_back(resultTypeExpr); } assert(args.size() == (isVoidReturn ? 4 : 5)); auto remoteCallArgs = ArgumentList::forImplicitCallTo( systemRemoteCallRef->getName(), args, C); Expr *remoteCallExpr = CallExpr::createImplicit(C, systemRemoteCallRef, remoteCallArgs); remoteCallExpr = AwaitExpr::createImplicit(C, sloc, remoteCallExpr); remoteCallExpr = TryExpr::createImplicit(C, sloc, remoteCallExpr); auto returnRemoteCall = ReturnStmt::createImplicit(C, sloc, remoteCallExpr); remoteBranchStmts.push_back(returnRemoteCall); } // --------------------------------------------------------------------------- auto remoteBranchStmt = BraceStmt::create(C, sloc, remoteBranchStmts, sloc, implicit); // --------------------------------------------------------------------------- // === if (isRemote(...) else auto ifStmt = new (C) IfStmt(sloc, /*condition=*/isRemote, /*then=*/remoteBranchStmt, sloc, /*else=*/localBranchStmt, implicit, C); auto body = BraceStmt::create(C, sloc, {ifStmt}, sloc, implicit); return {body, /*isTypeChecked=*/false}; } /// Create a new FuncDecl that has the same signature as the passed in func. /// This is used both to create stub witnesses as well as distributed thunks. /// /// \param DC The declaration context of the newly created function static FuncDecl *createSameSignatureDistributedThunkDecl(DeclContext *DC, FuncDecl *func) { auto &C = func->getASTContext(); // --- Prepare generic parameters GenericParamList *genericParamList = nullptr; if (auto genericParams = func->getGenericParams()) { genericParamList = genericParams->clone(DC); } GenericSignature baseSignature = func->getGenericSignature(); // --- Prepare parameters auto funcParams = func->getParameters(); SmallVector paramDecls; for (unsigned i : indices(*func->getParameters())) { auto funcParam = funcParams->get(i); auto paramName = funcParam->getParameterName(); // If internal name is empty it could only mean either // `_:` or `x _: ...`, so let's auto-generate a name // to be used in the body of a thunk. if (paramName.empty()) { paramName = C.getIdentifier("p" + llvm::utostr(i)); } auto paramDecl = new (C) ParamDecl(SourceLoc(), /*argumentNameLoc=*/SourceLoc(), funcParam->getArgumentName(), /*parameterNameLoc=*/SourceLoc(), paramName, DC); paramDecl->setImplicit(); paramDecl->setSending(); paramDecl->setSpecifier(funcParam->getSpecifier()); paramDecl->setInterfaceType(funcParam->getInterfaceType()); paramDecls.push_back(paramDecl); } ParameterList *params = ParameterList::create(C, paramDecls); FuncDecl *thunk; if (auto accessor = dyn_cast(func)) { auto accessorThunk = AccessorDecl::createImplicit( C, AccessorKind::DistributedGet, /*storage=*/accessor->getStorage(), /*async=*/true, /*throws=*/true, // since it's a distributed thunk /*thrownType=*/TypeLoc::withoutLoc(Type()), func->getResultInterfaceType(), DC); accessorThunk->setParameters(params); // An accessor does not have a name; the `var` does though, // and we'll be mangling the accessor based on the Storage name (the var) thunk = accessorThunk; } else { // Let's use the name of a 'distributed func' DeclName thunkName = func->getName(); thunk = FuncDecl::createImplicit( C, swift::StaticSpellingKind::None, thunkName, SourceLoc(), /*async=*/true, /*throws=*/true, // since it's a distributed thunk /*thrownType=*/Type(), genericParamList, params, func->getResultInterfaceType(), DC); } thunk->setSynthesized(true); if (isa(DC)) thunk->getAttrs().add(new (C) FinalAttr(/*isImplicit=*/true)); thunk->setGenericSignature(baseSignature); thunk->copyFormalAccessFrom(func, /*sourceIsParentContext=*/false); thunk->setSynthesized(true); thunk->setDistributedThunk(true); thunk->getAttrs().add( new (C) NonisolatedAttr(/*unsafe=*/false, /*implicit=*/true)); return thunk; } static FuncDecl *createDistributedThunkFunction(FuncDecl *func) { auto DC = func->getDeclContext(); FuncDecl *thunk = createSameSignatureDistributedThunkDecl(DC, func); assert(thunk && "couldn't create a distributed thunk"); // Protocol requirements don't have bodies. if (func->hasBody()) thunk->setBodySynthesizer(deriveBodyDistributed_thunk, func); return thunk; } /******************************************************************************/ /*********************** CODABLE CONFORMANCE **********************************/ /******************************************************************************/ static NormalProtocolConformance* addDistributedActorCodableConformance( ClassDecl *actor, ProtocolDecl *proto) { assert(proto->isSpecificProtocol(swift::KnownProtocolKind::Decodable) || proto->isSpecificProtocol(swift::KnownProtocolKind::Encodable)); auto &C = actor->getASTContext(); // === Only Distributed actors can gain this implicit conformance if (!actor->isDistributedActor()) { return nullptr; } // === Does the actor explicitly conform to the protocol already? auto explicitConformance = lookupConformance(actor->getInterfaceType(), proto); if (!explicitConformance.isInvalid()) { // ok, it was conformed explicitly -- let's not synthesize; return nullptr; } // Check whether we can infer conformance at all. if (auto *file = dyn_cast(actor->getModuleScopeContext())) { switch (file->getKind()) { case FileUnitKind::Source: // Check what kind of source file we have. if (auto sourceFile = actor->getParentSourceFile()) { switch (sourceFile->Kind) { case SourceFileKind::Interface: return nullptr; case SourceFileKind::Library: case SourceFileKind::Main: case SourceFileKind::MacroExpansion: case SourceFileKind::SIL: case SourceFileKind::DefaultArgument: break; } } break; case FileUnitKind::Builtin: case FileUnitKind::SerializedAST: case FileUnitKind::Synthesized: // Explicitly-handled modules don't infer Sendable conformances. return nullptr; case FileUnitKind::ClangModule: case FileUnitKind::DWARFModule: // Infer conformances for imported modules. break; } } else { return nullptr; } auto conformance = C.getNormalConformance( actor->getDeclaredInterfaceType(), proto, actor->getLoc(), /*dc=*/actor, ProtocolConformanceState::Incomplete, ProtocolConformanceOptions()); conformance->setSourceKindAndImplyingConformance( ConformanceEntryKind::Synthesized, nullptr); actor->registerProtocolConformance(conformance, /*synthesized=*/true); return conformance; } /******************************************************************************/ /******************************************************************************/ void swift::assertRequiredSynthesizedPropertyOrder(ASTContext &Context, NominalTypeDecl *nominal) { #ifndef NDEBUG if (auto id = nominal->getDistributedActorIDProperty()) { if (auto system = nominal->getDistributedActorSystemProperty()) { if (auto classDecl = dyn_cast(nominal)) { if (auto unownedExecutor = classDecl->getUnownedExecutorProperty()) { int idIdx, actorSystemIdx, unownedExecutorIdx = 0; int idx = 0; for (auto member : nominal->getMembers()) { if (auto binding = dyn_cast(member)) { if (binding->getSingleVar()->getName() == Context.Id_id) { idIdx = idx; } else if (binding->getSingleVar()->getName() == Context.Id_actorSystem) { actorSystemIdx = idx; } else if (binding->getSingleVar()->getName() == Context.Id_unownedExecutor) { unownedExecutorIdx = idx; } idx += 1; } } if (idIdx + actorSystemIdx + unownedExecutorIdx >= 0 + 1 + 2) { // we have found all the necessary fields, let's assert their order assert(idIdx < actorSystemIdx < unownedExecutorIdx && "order of fields MUST be exact."); } } } } } #endif } static bool canSynthesizeDistributedThunk(AbstractFunctionDecl *distributedTarget) { // `distributed` protocol requirements are allowed without additional checks. if (isa(distributedTarget->getDeclContext())) return true; if (getConcreteReplacementForProtocolActorSystemType(distributedTarget)) { return true; } auto serializationTy = getDistributedActorSerializationType(distributedTarget->getDeclContext()); return serializationTy && !serializationTy->hasDependentMember(); } /******************************************************************************/ /*********************** SYNTHESIS ENTRY POINTS *******************************/ /******************************************************************************/ FuncDecl *GetDistributedThunkRequest::evaluate(Evaluator &evaluator, Originator originator) const { AbstractFunctionDecl *distributedTarget = nullptr; if (auto *storage = originator.dyn_cast()) { if (!storage->isDistributed()) return nullptr; if (auto *var = dyn_cast(storage)) { if (checkDistributedActorProperty(var, /*diagnose=*/false)) return nullptr; distributedTarget = var->getAccessor(AccessorKind::Get); } else { llvm_unreachable("unsupported storage kind"); } } else { distributedTarget = originator.get(); if (!distributedTarget->isDistributed()) return nullptr; } assert(distributedTarget); // This evaluation type-check by now was already computed and cached; // We need to check in order to avoid emitting a THUNK for a distributed func // which had errors; as the thunk then may also cause un-addressable issues and confusion. if (swift::checkDistributedFunction(distributedTarget)) { return nullptr; } auto &C = distributedTarget->getASTContext(); if (!canSynthesizeDistributedThunk(distributedTarget)) { return nullptr; } // If the target function signature has errors, or if it is illegal in other // ways, such as e.g. parameters not conforming to SerializationRequirement, // we must avoid synthesis of the thunk because it'd also have errors, // giving an ugly user experience (errors in implicit code). if (distributedTarget->getInterfaceType()->hasError() || (!isa(distributedTarget) && checkDistributedFunction(distributedTarget))) { return nullptr; } if (auto func = dyn_cast(distributedTarget)) { // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. if (!C.getLoadedModule(C.Id_Distributed)) return nullptr; // --- Prepare the "distributed thunk" which does the "maybe remote" dance: return createDistributedThunkFunction(func); } llvm_unreachable("Unable to synthesize distributed thunk"); } VarDecl *GetDistributedActorIDPropertyRequest::evaluate( Evaluator &evaluator, NominalTypeDecl *actor) const { if (!actor->isDistributedActor()) return nullptr; auto &C = actor->getASTContext(); // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. if (!C.getLoadedModule(C.Id_Distributed)) return nullptr; auto classDecl = dyn_cast(actor); if (!classDecl) return nullptr; // We may enter this request multiple times, e.g. in multi-file projects, // so in order to avoid synthesizing a property many times, first perform // a lookup and return if it already exists. if (auto existingProp = lookupDistributedActorProperty(classDecl, C.Id_id)) { return existingProp; } return addImplicitDistributedActorIDProperty(classDecl); } VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( Evaluator &evaluator, NominalTypeDecl *nominal) const { auto &C = nominal->getASTContext(); auto DAS = C.getDistributedActorSystemDecl(); // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. if (!C.getLoadedModule(C.Id_Distributed)) return nullptr; if (!nominal->isDistributedActor()) return nullptr; if (auto proto = dyn_cast(nominal)) { auto DistributedActorProto = C.getDistributedActorDecl(); for (auto system : DistributedActorProto->lookupDirect(C.Id_actorSystem)) { if (auto var = dyn_cast(system)) { auto conformance = checkConformance( proto->mapTypeIntoContext(var->getInterfaceType()), DAS); if (conformance.isInvalid()) continue; return var; } } return nullptr; } auto classDecl = dyn_cast(nominal); if (!classDecl) return nullptr; // We may be triggered after synthesis was handled via `DerivedConformances`, // in which case we should locate the existing property, rather than add // another one. Generally derived conformances are triggered early and are right // but for some reason sometimes we get a request before synthesis was triggered // there... so this is to workaround that issue, and ensure we're always // synthesising correctly, regardless of entry-point. if (auto existingProp = lookupDistributedActorProperty(classDecl, C.Id_actorSystem)) { return existingProp; } return addImplicitDistributedActorActorSystemProperty(classDecl); } NormalProtocolConformance *GetDistributedActorImplicitCodableRequest::evaluate( Evaluator &evaluator, NominalTypeDecl *nominal, KnownProtocolKind protoKind) const { assert(nominal->isDistributedActor()); assert(protoKind == KnownProtocolKind::Encodable || protoKind == KnownProtocolKind::Decodable); auto &C = nominal->getASTContext(); // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. if (!C.getLoadedModule(C.Id_Distributed)) return nullptr; auto classDecl = dyn_cast(nominal); if (!classDecl) { // we only synthesize the conformance for concrete actors return nullptr; } return addDistributedActorCodableConformance(classDecl, C.getProtocol(protoKind)); } bool CanSynthesizeDistributedActorCodableConformanceRequest::evaluate( Evaluator &evaluator, NominalTypeDecl *actor) const { if (actor && !isa(actor)) return false; if (!actor->isDistributedActor()) return false; auto systemTy = getConcreteReplacementForProtocolActorSystemType(actor); if (!systemTy) return false; if (!systemTy->getAnyNominal()) return false; auto idTy = getDistributedActorSystemActorIDType(systemTy->getAnyNominal()); if (!idTy) return false; return TypeChecker::conformsToKnownProtocol( idTy, KnownProtocolKind::Decodable) && TypeChecker::conformsToKnownProtocol( idTy, KnownProtocolKind::Encodable); } NormalProtocolConformance * GetDistributedActorAsActorConformanceRequest::evaluate( Evaluator &evaluator, ProtocolDecl *distributedActorProto) const { auto &ctx = distributedActorProto->getASTContext(); auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor); auto ext = findDistributedActorAsActorExtension( distributedActorProto); if (!ext) return nullptr; auto genericParam = GenericTypeParamType::getType(/*depth=*/0, /*index=*/0, ctx); auto distributedActorAsActorConformance = ctx.getNormalConformance( Type(genericParam), actorProto, SourceLoc(), ext, ProtocolConformanceState::Incomplete, ProtocolConformanceOptions()); // NOTE: Normally we "register" a conformance, but here we don't // because we cannot (currently) register them in a protocol, // since they do not have conformance tables. return distributedActorAsActorConformance; }