void SILGenFunction::emitDistributedThunk()

in lib/SILGen/SILGenDistributed.cpp [666:1553]


void SILGenFunction::emitDistributedThunk(SILDeclRef thunk) {
  // Check if actor is local or remote and call respective logic
  //
  // func X_distributedThunk(...) async throws -> T {
  //   if __isRemoteActor(self) {
  //     // ... prepare args ...
  //     return try await actorSystem.remoteCall(<args>)
  //   } else {
  //     return try await self.X(...)
  //   }
  // }
  //

  assert(thunk.isDistributed);
  SILDeclRef native = thunk.asDistributed(false);
  auto fd = cast<AbstractFunctionDecl>(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 methodTy = SGM.Types.getConstantOverrideType(getTypeExpansionContext(),
                                                    thunk);
  auto derivativeFnSILTy = SILType::getPrimitiveObjectType(methodTy);
  auto silFnType = derivativeFnSILTy.castTo<SILFunctionType>();
  SILFunctionConventions fnConv(silFnType, SGM.M);
  auto resultType = fnConv.getSILResultType(getTypeExpansionContext());

  auto shouldRecordGenericSubstitutions = false;
  auto shouldRecordArguments = fd->getParameters()->size() > 0;
  auto shouldRecordErrorType = fd->hasThrows();
  auto shouldRecordReturnType = !resultType.isVoid();

  auto errorBB = createBasicBlock();
  auto returnBB = createBasicBlock();

  auto *selfVarDecl = fd->getImplicitSelfDecl();

  SmallVector<SILValue, 8> paramsForForwarding;
  bindParametersForForwarding(fd->getParameters(), paramsForForwarding);
  bindParameterForForwarding(selfVarDecl, paramsForForwarding);

  // === `Self` types
  auto selfValue = ManagedValue::forUnmanaged(F.getSelfArgument());
  auto selfTy = selfVarDecl->getType();
  auto selfSILTy = getLoweredType(selfTy);
  auto *selfTyDecl = FunctionDC->getParent()->getSelfNominalTypeDecl();
  assert(selfTyDecl && "distributed instance method declared outside of actor");

  // === Thrown 'Err' type
  auto errorTy = F.mapTypeIntoContext(ctx.getErrorDecl()->getInterfaceType());
  auto neverTy = F.mapTypeIntoContext(ctx.getNeverType());
  auto errTy = fd->hasThrows() ? errorTy : neverTy;

  // === `InvocationEncoder` types
  AbstractFunctionDecl *makeInvocationEncoderFnDecl =
      ctx.getMakeInvocationEncoderOnDistributedActorSystem(selfTyDecl);
  assert(makeInvocationEncoderFnDecl && "no 'makeInvocationEncoder' func found!");
  auto makeInvocationEncoderFnRef = SILDeclRef(makeInvocationEncoderFnDecl);

  ProtocolDecl *invocationEncoderProto =
      ctx.getProtocol(KnownProtocolKind::DistributedTargetInvocationEncoder);
  ProtocolDecl *distributedActorProto =
      ctx.getProtocol(KnownProtocolKind::DistributedActor);

  auto makeInvocationEncoderMethodTy = SGM.Types.getConstantOverrideType(
      getTypeExpansionContext(), makeInvocationEncoderFnRef);
  auto makeInvocationEncoderDerivativeFnSILTy = SILType::getPrimitiveObjectType(makeInvocationEncoderMethodTy);
  auto makeInvocationEncoderSilFnType = makeInvocationEncoderDerivativeFnSILTy.castTo<SILFunctionType>();

  auto invocationEncoderResultInfo =
      makeInvocationEncoderSilFnType->getResults().begin();
  auto invocationEncoderCanTy = invocationEncoderResultInfo->getInterfaceType();
  auto invocationEncoderTy = getLoweredType(invocationEncoderCanTy);

  NominalTypeDecl *invocationEncoderNominal =
      invocationEncoderTy.getNominalOrBoundGenericNominal();

  // ==== ----------------------------------------------------------------------

  // if __isRemoteActor(self) {
  //   ...
  // } else {
  //   ...
  // }
  auto isLocalBB = createBasicBlock();
  auto isRemoteBB = createBasicBlock();
  {
    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(selfTy), 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);
  }

  // === Local Call ------------------------------------------------------------
  // {
  //   return (try)? (await)? self.X(...)
  // }
  SILBasicBlock *localReturnBB;
  SILBasicBlock *localCallErrorBB;
  {
    B.emitBlock(isLocalBB);

    auto nativeMethodTy = SGM.Types.getConstantOverrideType(getTypeExpansionContext(),
                                                            native);
    auto nativeFnSILTy = SILType::getPrimitiveObjectType(nativeMethodTy);
    auto nativeSilFnType = nativeFnSILTy.castTo<SILFunctionType>();

    localReturnBB = createBasicBlock();
    localCallErrorBB = nativeSilFnType->hasErrorResult() ? createBasicBlock() : nullptr;

    bool isClassMethod = false;
    if (auto classDecl = dyn_cast<ClassDecl>(fd->getDeclContext())) {
      if (!classDecl->isFinal() && !fd->isFinal() &&
          !fd->hasForcedStaticDispatch())
        isClassMethod = true;
    }

    SILValue nativeFn;
    if (isClassMethod) {
      nativeFn = emitClassMethodRef(
          loc, selfValue.getValue(), native, nativeMethodTy);
    } else {
      nativeFn = emitGlobalFunctionRef(loc, native);
    }
    auto subs = F.getForwardingSubstitutionMap();

    if (localCallErrorBB) {
      B.createTryApply(loc, nativeFn, subs, paramsForForwarding, localReturnBB, localCallErrorBB);
    } else {
      auto result = B.createApply(loc, nativeFn, subs, paramsForForwarding);
      B.createBranch(loc, localReturnBB, {result});
    }
  }
  {
    B.emitBlock(localReturnBB);

    SILValue result = localReturnBB->createPhiArgument(
        resultType, OwnershipKind::Owned);
    B.createBranch(loc, returnBB, {result});
  }
  { // local error
    emitThrowWithCleanupBasicBlock(*this, loc, thunk, localCallErrorBB, errorBB);
  }


  // === Remote Call -----------------------------------------------------------
  SILGenFunctionBuilder builder(SGM);
  // {
  //   var invocation = try self.actorSystem.makeInvocationEncoder()
  //   // ...
  // }
  {
    B.emitBlock(isRemoteBB);

    // We need to maintain a "next normal basic block" pointer because
    // we cannot just emit a bunch of tryApply right after one another
    // but each subsequent call must be in its own basic block on the
    // 'normal' path.
    SILBasicBlock *nextNormalBB = nullptr;

    // === -------------------------------------------------------------------
    // var encoder = actorSystem.makeInvocationEncoder()
    SILValue invocationEncoderBuf;
    ManagedValue invocationEncoder;

    SILValue actorSystemBuf;
    ManagedValue actorSystem;
    {
      invocationEncoderBuf = emitTemporaryAllocation(loc, invocationEncoderTy);
      invocationEncoder = emitManagedBufferWithCleanup(invocationEncoderBuf);

      // === get the actorSystem property
      // %16 = ref_element_addr %2 : $MyDistActor, #MyDistActor.actorSystem // user: %17
      auto systemRef = emitActorPropertyReference(
          *this, loc, selfValue.getValue(),
          lookupProperty(selfTyDecl, ctx.Id_actorSystem));

      auto actorSystemTy = systemRef->getType();

      // FIXME: this is wrong for struct with values, and classes?
      // %17 = load %16 : $*FakeActorSystem // users: %21, %20, %18
      SILValue systemLoaded;
      if (actorSystemTy.isAddressOnly(F)) {
        assert(false && "isAddressOnly");
      } else {
        if (actorSystemTy.isAddress()) {
          systemLoaded = B.createTrivialLoadOr(
              loc, systemRef, LoadOwnershipQualifier::Copy);
        } else {
          assert(false);
        }
      }

//            if (!actorSystemTy.isAddressOnly(F) &&
//                !actorSystemTy.isTrivial(F)) {
//              // retain_value %17 : $FakeActorSystem // id: %18
//              B.createRetainValue(loc, systemLoaded,
//                                  RefCountingInst::Atomicity::Atomic);
//            }

      // function_ref FakeActorSystem.makeInvocationEncoder()
      // %19 = function_ref @$s27FakeDistributedActorSystems0aC6SystemV21makeInvocationEncoderAA0aG0VyF : $@convention(method) (@guaranteed FakeActorSystem) -> FakeInvocation // user: %20
      SILFunction *makeInvocationEncoderFnSIL =
          builder.getOrCreateFunction(loc, makeInvocationEncoderFnRef, NotForDefinition);
      SILValue makeInvocationEncoderFn =
          B.createFunctionRefFor(loc, makeInvocationEncoderFnSIL);

      //  %20 = apply %19(%17) : $@convention(method) (@guaranteed FakeActorSystem) -> FakeInvocation // user: %22
      ApplyInst *invocationEncoderValue = B.createApply(
          loc, makeInvocationEncoderFn,
          /*subs=*/SubstitutionMap(),
          /*args=*/{systemLoaded});

      if (!systemLoaded->getType().isTrivial(F))
        B.createDestroyValue(loc, systemLoaded);
        // B.createEndLifetime(loc, systemLoaded);

      // FIXME(distributed): cannot deal with class yet
      // TODO(distributed): make into "emit apropriate store"
      if (invocationEncoderTy.isTrivial(F)) {
        B.createTrivialStoreOr(loc,
                               /*src=*/invocationEncoderValue,
                               /*dest=*/invocationEncoder.getValue(),
                               StoreOwnershipQualifier::Init);
      } else {
        B.createStore(loc,
                      /*src=*/invocationEncoderValue,
                      /*dest=*/invocationEncoder.getValue(),
                      StoreOwnershipQualifier::Init);
      }
    }

    // === -------------------------------------------------------------------
    // populate the invocation:

    // The graph of basic blocks depends
    // test()
    // [...] -> [doneRecording]
    //           \-...ErrorBB
    //
    // test() throws
    // [...] -> [recordErrorType] -> [doneRecordingBB]
    //           \-...ErrorBB         \-...ErrorBB
    //
    // test() -> T
    // [...] -> [recordReturnTypeBB] -> [doneRecordingBB]
    //           \-...ErrorBB            \-...ErrorBB
    //
    // test() throws -> T
    // [...] -> [recordErrorType] -> [recordReturnTypeBB] -> [doneRecordingBB]
    //           \-...ErrorBB         \-...ErrorBB          \-...ErrorBB
    //
    // test(p: P1)
    // [...] -> [recordArgument] -> [doneRecordingBB]
    //           \-...ErrorBB        \-...ErrorBB
    //
    // test(p: P1) throws
    // [...] -> [recordArgument] -> [recordErrorTypeBB] -> [doneRecordingBB]
    //           \-...ErrorBB        \-...ErrorBB           \-...ErrorBB
    //
    // test(p: P1) throws -> P1
    // [...] -> [recordArgument] -> [recordErrorTypeBB] -> [recordReturnTypeBB] -> [doneRecordingBB]
    //           \-...ErrorBB        \-...ErrorBB           \-...ErrorBB            \-...ErrorBB
    //
    // test(p: P1, p: P2, ...)
    // [...] -> [recordArgument] (-> [recordArgumentNBB])* -> [doneRecordingBB]
    //           \-...ErrorBB         \-...ErrorBB             \-...ErrorBB
    //
    // test(p: P1, p: P2, ...) throws
    // [...] -> [recordArgument] (-> [recordArgumentNBB])* -> [recordErrorType] -> [doneRecording]
    //           \-...ErrorBB         \-...ErrorBB             \-...ErrorBB         \-...ErrorBB
    //
    // test(p: P1, p: P2, ...) throws -> T
    // [...] -> [recordArgument] (-> [recordArgumentNBB])* -> [recordErrorType] -> [recordReturnType] -> [doneRecording]
    //           \-...ErrorBB         \-...ErrorBB             \-...ErrorBB         \-...ErrorBB          \-...ErrorBB
    auto firstOfThrowingApplyBBs = true;
    auto anyRecordBlocks = false;

    SILBasicBlock *recordGenericSubstitutionsBB = nullptr;
    SILBasicBlock *recordErrorGenericSubstitutionsBB = nullptr;
    if (shouldRecordGenericSubstitutions) {
      anyRecordBlocks = true;
      assert(false);
      if (!firstOfThrowingApplyBBs) {
        recordGenericSubstitutionsBB = createBasicBlock();
        firstOfThrowingApplyBBs = false;
      }
      recordErrorGenericSubstitutionsBB = createBasicBlock();
    }

    if (shouldRecordArguments) {
      anyRecordBlocks = true;
      firstOfThrowingApplyBBs = false;
    }

    SILBasicBlock *recordErrorTypeBB = nullptr;
    SILBasicBlock *recordErrorTypeErrorBB = nullptr;
    if (shouldRecordErrorType) {
      anyRecordBlocks = true;
      if (!firstOfThrowingApplyBBs) {
        recordErrorTypeBB = createBasicBlock();
      }
      firstOfThrowingApplyBBs = false;
      recordErrorTypeErrorBB = createBasicBlock();
    }

    SILBasicBlock *recordReturnTypeBB = nullptr;
    SILBasicBlock *recordReturnTypeErrorBB = nullptr;
    if (shouldRecordReturnType) {
      anyRecordBlocks = true;
      if (!firstOfThrowingApplyBBs) {
        recordReturnTypeBB = createBasicBlock();
      }
      firstOfThrowingApplyBBs = false;
      recordReturnTypeErrorBB = createBasicBlock();
    }

    firstOfThrowingApplyBBs = false;
    SILBasicBlock *recordingDoneBB = nullptr;
    SILBasicBlock *recordingDoneErrorBB = createBasicBlock();
    if (anyRecordBlocks) {
      // If any previous record* calls have been made, we need a BB to jump to
      // on the normal path to the recordingDone call:
      recordingDoneBB = createBasicBlock();
    }

    // === All calls on invocationEncoder need Access:
    SILValue invocationEncoderAccess;
    {
      invocationEncoderAccess = B.createBeginAccess(
          loc, invocationEncoder.getValue(), SILAccessKind::Modify,
          SILAccessEnforcement::Static, false, false);
    }

    // === encoder.recordGenericSubstitution() ---------------------------------
    SILBasicBlock *recordGenericSubstitutionsErrorBB = nullptr;
    if (shouldRecordGenericSubstitutions) {
      // TODO(distributed): record substitutions
      assert(false);
    }

    // === encoder.recordArgument() --------------------------------------------
    if (shouldRecordArguments) {
      if (nextNormalBB) {
        B.emitBlock(nextNormalBB);
      }
      nextNormalBB = nullptr;

      assert(invocationEncoderNominal);
      FuncDecl *recordArgumentFnDecl =
          ctx.getRecordArgumentOnDistributedInvocationEncoder(
              invocationEncoderNominal);
      auto recordArgumentFnRef = SILDeclRef(recordArgumentFnDecl);
      assert(recordArgumentFnRef && "no 'recordArgument' func found!");

      auto recordArgumentFnSIL =
          builder.getOrCreateFunction(loc, recordArgumentFnRef, NotForDefinition);
      SILValue recordArgumentFn =
          B.createFunctionRefFor(loc, recordArgumentFnSIL);

      // --- invoke recordArgument for every parameter
      auto funcDeclParamsNum = fd->getParameters()->size();
      assert(funcDeclParamsNum > 0 &&
             "attempted recording arguments but no actual parameters declared "
             "on distributed method");

      assert(paramsForForwarding.size() == funcDeclParamsNum + 1);
      assert(paramsForForwarding.back()->getType().getNominalOrBoundGenericNominal()->isDistributedActor());
      // the parameters for forwarding include `self`, but here we should not
      // copy that self, so we just drop it.
      paramsForForwarding.pop_back();

      unsigned long paramIdx = 0;
      Optional<SILValue> previousArgumentToDestroy;
      for (SILValue paramValue : paramsForForwarding) {
        auto isLastParam = ++paramIdx == funcDeclParamsNum;

        auto paramTy = paramValue->getType().getASTType();
        if (nextNormalBB) {
          // this will be `nextRecordArgumentNormalBB` from the previous
          // iteration i.e. if we're the first parameter, we just emit directly;
          // if we're the second (or later) parameter, we need to emit a basic
          // block here.
          B.emitBlock(nextNormalBB);
          createVoidPhiArgument(*this, ctx, nextNormalBB);
        }

        if (previousArgumentToDestroy.hasValue()) {
          B.createDestroyAddr(loc, previousArgumentToDestroy.getValue());
        }

        // Prepare the next normalBB we need to jump to on successful recordArgument call;
        // If this is the last parameter we need to record though, then we always
        // go to the following record* function type, which need to be the
        // 'nextNormalBB'.
        SILBasicBlock *nextRecordArgumentNormalBB;
        if (!isLastParam) {
          nextRecordArgumentNormalBB = createBasicBlock();
        } else if (shouldRecordErrorType) {
          nextRecordArgumentNormalBB = recordErrorTypeBB;
        } else if (shouldRecordReturnType) {
          nextRecordArgumentNormalBB = recordReturnTypeBB;
        } else {
          nextRecordArgumentNormalBB = recordingDoneBB;
        }
        nextNormalBB = nextRecordArgumentNormalBB;
        auto recordArgumentErrorBB = createBasicBlock();

        // === Prepare the argument
        SILType argType = paramValue->getType();
        if (paramValue->getType().hasArchetype()) {
          argType = paramValue->getType().mapTypeOutOfContext();
        }

        // FIXME: something is off here
        llvm::Optional<ManagedValue> argValue;
        {
          auto argTemp = emitTemporaryAllocation(loc, paramValue->getType());
          argValue = emitManagedBufferWithCleanup(argTemp);

          if (paramValue->getType().isAddressOnly(F)) {
            B.createCopyAddr(loc, paramValue, argTemp, IsNotTake, IsInitialization);
          } else {
            if (paramValue->getType().isAddress()) {
              paramValue = B.createTrivialLoadOr(loc, paramValue,
                                                 LoadOwnershipQualifier::Take);
            } else {
              paramValue = B.emitCopyValueOperation(loc, paramValue);
            }

            B.emitStoreValueOperation(loc, paramValue, argTemp,
                                      StoreOwnershipQualifier::Init);
          }
        }

        // === Prepare generic signature
        auto recordArgumentGenericSig = recordArgumentFnDecl->getGenericSignature();
        SmallVector<Type, 1> subTypes;
        SmallVector<ProtocolConformanceRef, 2> subConformances;
        {
          auto module = B.getModule().getSwiftModule();
          subTypes.push_back(paramTy);

          // --- Codable: Decodable
          auto decodableRequirementTy =
              ctx.getProtocol(KnownProtocolKind::Decodable); // FIXME: actually use SerializatioNRequirement
          auto paramDecodableTypeConfRef = module->lookupConformance(
              paramTy, decodableRequirementTy);
          subConformances.push_back(paramDecodableTypeConfRef);

          // --- Codable: Encodable
          auto encodableRequirementTy = ctx.getProtocol(
              KnownProtocolKind::Encodable); // FIXME: actually use SerializatioNRequirement
          auto paramEncodableTypeConfRef = module->lookupConformance(
              paramTy, encodableRequirementTy);
          subConformances.push_back(paramEncodableTypeConfRef);

        }
        auto subs = SubstitutionMap::get(
            recordArgumentGenericSig,
            subTypes, subConformances);

        B.createTryApply(
            loc, recordArgumentFn,
            subs,
            {
                argValue.hasValue() ? argValue->getValue() : paramValue,
                invocationEncoderAccess
            },
            /*normalBB=*/nextNormalBB,
            /*errorBB=*/recordArgumentErrorBB);
        {
          emitThrowWithCleanupBasicBlock(
              *this, loc, thunk, recordArgumentErrorBB, errorBB,
              /*endAccesses=*/{invocationEncoderAccess});
        }
      }
    }

    // === encoder.recordErrorType() -------------------------------------------
    if (shouldRecordErrorType) {
      if (recordErrorTypeBB) {
        B.emitBlock(recordErrorTypeBB);
        createVoidPhiArgument(*this, ctx, recordErrorTypeBB);
      }

      if (recordReturnTypeBB) {
        nextNormalBB = recordReturnTypeBB;
      } else {
        nextNormalBB = recordingDoneBB;
      }

      // Get the error type.
      // If we ever did typed-throws we'd get the error type from fd here...
      auto errorMetatype = getLoweredType(MetatypeType::get(errorTy, MetatypeRepresentation::Thick));
      auto errorMetatypeValue = B.createMetatype(loc, errorMetatype);

      // Get the function
      FuncDecl *recordErrorTypeFnDecl =
          ctx.getRecordErrorTypeOnDistributedInvocationEncoder(
              invocationEncoderNominal);
      assert(recordErrorTypeFnDecl);
      auto recordErrorTyFnRef = SILDeclRef(recordErrorTypeFnDecl);
      auto recordErrorTypeFnSIL =
          builder.getOrCreateFunction(loc, recordErrorTyFnRef, NotForDefinition);
      SILValue recordErrorTyFn = B.createFunctionRefFor(loc, recordErrorTypeFnSIL);

      // Prepare the <E: Error> substitution,
      // but just fill it with Error anyway.
      auto recordErrorTypeGenericSig = recordErrorTypeFnDecl->getGenericSignature();
      SmallVector<Type, 1> subTypes;
      SmallVector<ProtocolConformanceRef, 1> subConformances;
      {
        // <Err: Error>
        pushErrorConformance(*this, ctx, subTypes, subConformances);
      }
      auto errorSubs = SubstitutionMap::get(
          recordErrorTypeGenericSig,
          subTypes, subConformances);

      B.createTryApply(
          loc, recordErrorTyFn,
          /*subs*/errorSubs,
          /*args*/{ errorMetatypeValue, invocationEncoder.getValue() },
          /*normalBB*/nextNormalBB,
          /*errorBB*/recordErrorTypeErrorBB);
    }
    if (shouldRecordErrorType) {
      emitThrowWithCleanupBasicBlock(
          *this, loc, thunk, recordErrorTypeErrorBB, errorBB,
          /*endAccesses=*/{invocationEncoderAccess});
    }

    // === encoder.recordReturnType() ------------------------------------------
    if (shouldRecordReturnType) {
      if (recordReturnTypeBB) {
        B.emitBlock(recordReturnTypeBB);
        createVoidPhiArgument(*this, ctx, recordReturnTypeBB);
      }

      // Get the return meta type.
      // If we ever did typed-throws we'd get the error type from fd here...
      auto returnMetatype = getLoweredType(MetatypeType::get(resultType.getASTType(), MetatypeRepresentation::Thick));
      auto returnMetatypeValue = B.createMetatype(loc, returnMetatype);

      // Get the function
      FuncDecl *recordReturnTypeFnDecl =
          ctx.getRecordReturnTypeOnDistributedInvocationEncoder(
              invocationEncoderNominal);
      assert(recordReturnTypeFnDecl);
      auto recordErrorTyFnRef = SILDeclRef(recordReturnTypeFnDecl);
      auto recordReturnTypeFnSIL =
          builder.getOrCreateFunction(loc, recordErrorTyFnRef, NotForDefinition);
      SILValue recordErrorTyFn = B.createFunctionRefFor(loc, recordReturnTypeFnSIL);

      // Prepare the <Res: SerializationRequirement> substitution,
      // but just fill it with Error anyway.
      auto recordReturnTypeGenericSig = recordReturnTypeFnDecl->getGenericSignature();
      SmallVector<Type, 1> subTypes;
      SmallVector<ProtocolConformanceRef, 2> subConformances;
      {
        auto module = B.getModule().getSwiftModule();

        // <Res: SerializationRequirement>
        subTypes.push_back(resultType.getASTType());

        // pushSerializationRequirementConformance(*this, ctx, resultType, subTypes, subConformances); // FIXME(distributed): use this

        // FIXME: actually use SerializationRequirement
        subConformances.push_back(module->lookupConformance(
            resultType.getASTType(),
            ctx.getProtocol(KnownProtocolKind::Decodable)));

        // FIXME: actually use SerializationRequirement
        subConformances.push_back(module->lookupConformance(
            resultType.getASTType(),
            ctx.getProtocol(KnownProtocolKind::Encodable)));
      }
      auto errorSubs = SubstitutionMap::get(
          recordReturnTypeGenericSig,
          subTypes, subConformances);

      B.createTryApply(
          loc, recordErrorTyFn,
          /*subs*/errorSubs,
          /*args*/{ returnMetatypeValue, invocationEncoder.getValue() },
          /*normalBB*/recordingDoneBB,
          /*errorBB*/recordReturnTypeErrorBB);
    }
    if (shouldRecordReturnType) {
      emitThrowWithCleanupBasicBlock(
          *this, loc, thunk, recordReturnTypeErrorBB, errorBB,
          /*endAccesses=*/{invocationEncoderAccess});
    }

    // === encoder.doneRecording() ---------------------------------------------
    SILBasicBlock *makeRemoteCallTargetBB = createBasicBlock();
    {
      if (recordingDoneBB) {
        B.emitBlock(recordingDoneBB);

        createVoidPhiArgument(*this, ctx, recordingDoneBB);
      }

      assert(invocationEncoderNominal);
      FuncDecl *doneRecordingFnDecl =
          ctx.getDoneRecordingOnDistributedInvocationEncoder(
              invocationEncoderNominal);
      assert(doneRecordingFnDecl);
      auto doneRecordingFnRef = SILDeclRef(doneRecordingFnDecl);

      auto doneRecordingFnSIL =
          builder.getOrCreateFunction(loc, doneRecordingFnRef, NotForDefinition);
      SILValue doneRecordingFn = B.createFunctionRefFor(loc, doneRecordingFnSIL);

      B.createTryApply(
          loc, doneRecordingFn,
          /*subs=*/SubstitutionMap(),
          /*args=*/{invocationEncoderAccess},
          /*normalBB=*/makeRemoteCallTargetBB,
          /*errorBB*/recordingDoneErrorBB);
    }
    {
      emitThrowWithCleanupBasicBlock(*this, loc, thunk, recordingDoneErrorBB,
                                     errorBB, /*endAccesses=*/{invocationEncoderAccess});
    }

    // === create the RemoteCallTarget -----------------------------------------
    auto remoteCallTargetDecl = ctx.getRemoteCallTargetDecl();
    auto remoteCallTargetTy = F.mapTypeIntoContext(remoteCallTargetDecl->getDeclaredInterfaceType());
    ManagedValue remoteCallTargetValue;
    LoadInst *remoteCallSystemSelf = nullptr;
    SILBasicBlock *remoteCallReturnBB = createBasicBlock();
    SILBasicBlock *remoteCallErrorBB = createBasicBlock();
    ManagedValue remoteCallReturnValue;
    {
      B.emitBlock(makeRemoteCallTargetBB);
      createVoidPhiArgument(*this, ctx, makeRemoteCallTargetBB);

      // --- Get the `RemoteCallTarget` type

      // %28 = alloc_stack $RemoteCallTarget, let, name "target" // users: %58, %57, %50, %77, %76, %37
      auto remoteCallTargetBuf = emitTemporaryAllocation(loc, getLoweredType(remoteCallTargetTy));
      remoteCallTargetValue = emitManagedBufferWithCleanup(remoteCallTargetBuf);

      // %29 = metatype $@thin RemoteCallTarget.Type // user: %37
      auto remoteCallTargetMetatype = getLoweredType(MetatypeType::get(remoteCallTargetTy));
      auto remoteCallTargetMetatypeValue = B.createMetatype(loc, remoteCallTargetMetatype);

      auto mangledName = thunk.mangle(SILDeclRef::ManglingKind::Default);
      auto mangledNameRef = llvm::StringRef(mangledName.c_str(), mangledName.size()); // FIXME(distributed): can just pass the mangledName?

      auto mangledNameString = emitStringLiteral(loc, mangledNameRef); // FIXME(distributed): trouble with the cleanups running in error BB too...

      // --- Create the RemoteCallTarget instance, passing the mangledNameString
      // function_ref RemoteCallTarget.init(_mangledName:)
      // %36 = function_ref @$s12_Distributed16RemoteCallTargetV12_mangledNameACSS_tcfC : $@convention(method) (@owned String, @thin RemoteCallTarget.Type) -> @out RemoteCallTarget // user: %37
      auto remoteCallTargetInitDecl = remoteCallTargetDecl->getDistributedRemoteCallTargetInitFunction();
      assert(remoteCallTargetInitDecl && "no 'RemoteCallTarget.init' found!");
      auto remoteCallTargetInitRef = SILDeclRef(remoteCallTargetInitDecl);
      auto remoteCallTargetInitFnSIL =
          builder.getOrCreateFunction(loc, remoteCallTargetInitRef, NotForDefinition);
      SILValue remoteCallTargetInitFn = B.createFunctionRefFor(loc, remoteCallTargetInitFnSIL);

      // %37 = apply %36(%28, %35, %29) : $@convention(method) (@owned String, @thin RemoteCallTarget.Type) -> @out RemoteCallTarget
      B.createApply(
          loc, remoteCallTargetInitFn, {},
          {/*out*/ remoteCallTargetValue.getValue(), mangledNameString.forward(*this),
           remoteCallTargetMetatypeValue});

      // === Prepare `actorSystem.remoteCall()` --------------------------------
      // --- Prepare storage for the return value
      // %38 = alloc_stack $String // users: %54, %56, %50, %75
      auto remoteCallReturnBuf = emitTemporaryAllocation(loc, resultType);
      remoteCallReturnValue = emitManagedBufferWithCleanup(remoteCallReturnBuf);

      auto systemRef = emitActorPropertyReference(
          *this, loc, selfValue.getValue(),
          lookupProperty(selfTyDecl, ctx.Id_actorSystem));
      remoteCallSystemSelf = B.createTrivialLoadOr(loc, systemRef, LoadOwnershipQualifier::Copy);

      // --- Prepare 'throwing' type, Error or Never depending on throws of the target
      SILValue thrownErrorMetatypeValue;
      if (fd->hasThrows()) {
        auto errorMetatype = getLoweredType(MetatypeType::get(errorTy, MetatypeRepresentation::Thick));
        thrownErrorMetatypeValue = B.createMetatype(loc, errorMetatype);
      } else {
        auto neverMetatype = getLoweredType(MetatypeType::get(neverTy, MetatypeRepresentation::Thick));
        thrownErrorMetatypeValue = B.createMetatype(loc, neverMetatype);
      }
      assert(thrownErrorMetatypeValue);

      // --- Prepare 'returning' type, can be 'Void' or specific type
      SILValue returnMetatypeValue;
      switch (methodTy->getNumResults()) {
      case 0: {
        auto voidTy = ctx.getVoidType();
        auto voidMetatype =
            getLoweredType(MetatypeType::get(voidTy, MetatypeRepresentation::Thick));
        // %42 = metatype $@thin Never.Type
        // %43 = metatype $@thick Never.Type /// we just have this one
        returnMetatypeValue = B.createMetatype(loc, voidMetatype);
        break;
      }
      case 1: {
        CanType returnType = methodTy->getSingleResult().getInterfaceType();
        auto returnMetatype = getLoweredType(MetatypeType::get(returnType, MetatypeRepresentation::Thick));
        returnMetatypeValue = B.createMetatype(loc, returnMetatype);
        break;
      }
      default:
        llvm_unreachable("Can't support more results than one.");
      }
      assert(returnMetatypeValue);

      // function_ref FakeActorSystem.remoteCall<A, B, C>(on:target:invocation:throwing:returning:)
      // %49 = function_ref @$s27FakeDistributedActorSystems0aC6SystemV10remoteCall2on6target17invocationDecoder8throwing9returningq0_x_01_B006RemoteG6TargetVAA0A10InvocationVzq_mq0_mSgtYaKAJ0bC0RzSeR0_SER0_AA0C7AddressV2IDRtzr1_lF : $@convention(method) @async <τ_0_0, τ_0_1, τ_0_2 where τ_0_0 : DistributedActor, τ_0_2 : Decodable, τ_0_2 : Encodable, τ_0_0.ID == ActorAddress> (@guaranteed τ_0_0, @in_guaranteed RemoteCallTarget, @inout FakeInvocation, @thick τ_0_1.Type, Optional<@thick τ_0_2.Type>, @guaranteed FakeActorSystem) -> (@out τ_0_2, @error Error) // user: %50
      auto remoteCallFnDecl =
          ctx.getRemoteCallOnDistributedActorSystem(selfTyDecl, /*isVoid=*/resultType.isVoid());
      assert(remoteCallFnDecl && "no remoteCall func found!");
      auto remoteCallFnRef = SILDeclRef(remoteCallFnDecl);
      auto remoteCallFnSIL =
          builder.getOrCreateFunction(loc, remoteCallFnRef, NotForDefinition);
      SILValue remoteCallFn = B.createFunctionRefFor(loc, remoteCallFnSIL);

      // --- prepare subs for the 'remoteCall'
      // <MyDistActor, ErrorType, ReturnType>
      auto remoteCallGenericSig = remoteCallFnDecl->getGenericSignature();
      SmallVector<Type, 3> subTypes;
      SmallVector<ProtocolConformanceRef, 3> subConformances;
      // <τ_0_0,
      //  τ_0_1,
      //  τ_0_2 // only if resultTy != Void
      //  where
      //  τ_0_0 : DistributedActor,
      //  τ_0_2 : Decodable, // only if resultTy != Void
      //  τ_0_2 : Encodable, // only if resultTy != Void
      //  τ_0_0.ID == ActorAddress
      // >
      // (
      //  @guaranteed τ_0_0,
      //  @in_guaranteed RemoteCallTarget,
      //  @inout FakeInvocation,
      //  @thick τ_0_1.Type,
      // @thick τ_0_2.Type, // only if resultTy != Void
      //  @guaranteed FakeActorSystem)
      {
        auto module = B.getModule().getSwiftModule();

        // <Self: DistributedActor>
        pushDistributedActorConformance(*this, ctx, selfSILTy, subTypes, subConformances);

        // <Err: Error>
        if (fd->hasThrows()) {
          pushErrorConformance(*this, ctx, subTypes, subConformances);
        } else {
          pushNeverErrorConformance(*this, ctx, subTypes, subConformances);
        }

        if (!resultType.isVoid()) {
          // <Res: SerializationRequirement>
          //        pushSerializationRequirementConformance(*this, ctx,
          //                                                resultType,
          //                                                subTypes, subConformances);
          subTypes.push_back(resultType.getASTType());

          // FIXME(distributed): get the types from SerializationRequirement
          subConformances.push_back(module->lookupConformance(
              resultType.getASTType(),
              ctx.getProtocol(KnownProtocolKind::Decodable)));

          subConformances.push_back(module->lookupConformance(
              resultType.getASTType(),
              ctx.getProtocol(KnownProtocolKind::Encodable)));
        }
      }

      SubstitutionMap remoteCallSubs =
          SubstitutionMap::get(remoteCallGenericSig,
                               subTypes, subConformances);

      SmallVector<SILValue, 7> remoteCallArgs;
      // 'out' arguments:
      if (!resultType.isVoid())
        remoteCallArgs.push_back(remoteCallReturnValue.forward(*this)); // return value buffer
      // function arguments:
      remoteCallArgs.push_back(selfValue.getValue()); // on actor
      remoteCallArgs.push_back(remoteCallTargetValue.getValue()); // target
      remoteCallArgs.push_back(invocationEncoderAccess); // invocation encoder
      remoteCallArgs.push_back(thrownErrorMetatypeValue); // throwing type
      if (!resultType.isVoid())
        remoteCallArgs.push_back(returnMetatypeValue); // returning type, only if non-void
      // self:
      remoteCallArgs.push_back(remoteCallSystemSelf); // ActorSystem

      // try_apply %49<MyDistActor, Never, String>(%38, %2, %28, %48, %43, %46, %40) : $@convention(method) @async <τ_0_0, τ_0_1, τ_0_2 where τ_0_0 : DistributedActor, τ_0_2 : Decodable, τ_0_2 : Encodable, τ_0_0.ID == ActorAddress> (@guaranteed τ_0_0, @in_guaranteed RemoteCallTarget, @inout FakeInvocation, @thick τ_0_1.Type, Optional<@thick τ_0_2.Type>, @guaranteed FakeActorSystem) -> (@out τ_0_2, @error Error), normal bb5, error bb10 // id: %50
      B.createTryApply(loc, remoteCallFn,
                       remoteCallSubs,
                       remoteCallArgs,
                       /*normalBB=*/remoteCallReturnBB,
                       /*errorBB=*/remoteCallErrorBB);
    }

    // === return <result of remote call> --------------------------------------
    {
      B.emitBlock(remoteCallReturnBB);
      createVoidPhiArgument(*this, ctx, remoteCallReturnBB);

      auto result = remoteCallReturnValue.getValue();
      auto resultLoaded = B.createTrivialLoadOr(loc, result, LoadOwnershipQualifier::Copy, true);

      // FIXME(distributed): manual since I could not figure out how to NOT destroy_addr in the error path, where the memory is not initialized, so the destroy would fail SIL verification
      B.createDestroyAddr(loc, result);
//      B.createDeallocStack(loc, result);

      // FIXME: these are very hacky, how to do properly?
      if (!remoteCallSystemSelf->getType().isTrivial(F))
        B.createDestroyValue(loc, remoteCallSystemSelf);
      if (remoteCallSystemSelf->getType().isAddress())
        B.createEndLifetime(loc, remoteCallSystemSelf);

      B.createEndAccess(loc, invocationEncoderAccess, /*aborted=*/false);
      Cleanups.emitCleanupsForReturn(CleanupLocation(loc), NotForUnwind);
      B.createBranch(loc, returnBB, {resultLoaded});
    }
    {
      // FIXME(distributed): manual since I could not figure out how to NOT destroy_addr in the error path, where the memory is not initialized, so the destroy would fail SIL verification
//      emitThrowWithCleanupBasicBlock(*this, loc, thunk, remoteCallErrorBB, errorBB,
//                                     /*endAccesses*/{invocationEncoderAccess},
//                                     /*endLifetimes*/{remoteCallSystemSelf});
      B.emitBlock(remoteCallErrorBB);
      SILValue error = remoteCallErrorBB->createPhiArgument(
          fnConv.getSILErrorType(getTypeExpansionContext()),
          OwnershipKind::Owned);

      auto result = remoteCallReturnValue.getValue();

      // TODO(distributed): make those into cleanups
      B.createEndAccess(loc, invocationEncoderAccess, /*aborted=*/false);

      // FIXME: these are very hacky, how to do properly?
      if (!remoteCallSystemSelf->getType().isTrivial(F))
        B.createDestroyValue(loc, remoteCallSystemSelf);
      if (remoteCallSystemSelf->getType().isAddress())
        B.createEndLifetime(loc, remoteCallSystemSelf);

      Cleanups.emitCleanupsForReturn(CleanupLocation(loc), IsForUnwind);

      B.createBranch(loc, errorBB, {error});
    }
  } // end of `if isRemote { ... }`

  // Emit return logic
  {
    B.emitBlock(returnBB);
    SILValue result =
        returnBB->createPhiArgument(resultType, OwnershipKind::Owned);

    B.createReturn(loc, result);
  }

  // Emit the rethrow logic.
  {
    B.emitBlock(errorBB);
    SILValue error = errorBB->createPhiArgument(
        fnConv.getSILErrorType(getTypeExpansionContext()),
        OwnershipKind::Owned);

    B.createThrow(loc, error);
  }
}