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);
}
}