Files
swift-mirror/lib/SILGen/SILGenLazyConformance.cpp
Doug Gregor 97ea19d191 Introduce a builtin and API for getting the local actor from a distributed one
When an actual instance of a distributed actor is on the local node, it is
has the capabilities of `Actor`. This isn't expressible directly in the type
system, because not all `DistributedActor`s are `Actor`s, nor is the
opposite true.

Instead, provide an API `DistributedActor.asLocalActor` that can only
be executed when the distributed actor is known to be local (because
this API is not itself `distributed`), and produces an existential
`any Actor` referencing that actor. The resulting existential value
carries with it a special witness table that adapts any type
conforming to the DistributedActor protocol into a type that conforms
to the Actor protocol. It is "as if" one had written something like this:

    extension DistributedActor: Actor { }

which, of course, is not permitted in the language. Nonetheless, we
lovingly craft such a witness table:

* The "type" being extended is represented as an extension context,
rather than as a type context. This hasn't been done before, all Swift
runtimes support it uniformly.

* A special witness is provided in the Distributed library to implement
the `Actor.unownedExecutor` operation. This witness back-deploys to the
Swift version were distributed actors were introduced (5.7). On Swift
5.9 runtimes (and newer), it will use
`DistributedActor.unownedExecutor` to support custom executors.

* The conformance of `Self: DistributedActor` is represented as a
conditional requirement, which gets satisfied by the witness table
that makes the type a `DistributedActor`. This makes the special
witness work.

* The witness table is *not* visible via any of the normal runtime
lookup tables, because doing so would allow any
`DistributedActor`-conforming type to conform to `Actor`, which would
break the safety model.

* The witness table is emitted on demand in any client that needs it.
In back-deployment configurations, there may be several witness tables
for the same concrete distributed actor conforming to `Actor`.
However, this duplication can only be observed under fairly extreme
circumstances (where one is opening the returned existential and
instantiating generic types with the distributed actor type as an
`Actor`, then performing dynamic type equivalence checks), and will
not be present with a new Swift runtime.

All of these tricks together mean that we need no runtime changes, and
`asLocalActor` back-deploys as far as distributed actors, allowing it's
use in `#isolation` and the async for...in loop.
2024-01-22 17:27:31 -08:00

372 lines
13 KiB
C++

//===--- SILGenLazyConformance.cpp ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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 "SILGen.h"
#include "swift/AST/Decl.h"
#include "swift/AST/PackConformance.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/ProtocolConformanceRef.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/SIL/FormalLinkage.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILVisitor.h"
using namespace swift;
using namespace swift::Lowering;
void SILGenModule::useConformance(ProtocolConformanceRef conformanceRef) {
// If the conformance is invalid, crash deterministically even in noassert
// builds.
if (conformanceRef.isInvalid()) {
llvm::report_fatal_error("Invalid conformance in type-checked AST");
}
// We don't need to emit dependent conformances.
if (conformanceRef.isAbstract())
return;
// Recursively visit pack conformances.
if (conformanceRef.isPack()) {
auto *packConformance = conformanceRef.getPack();
for (auto patternConformanceRef : packConformance->getPatternConformances())
useConformance(patternConformanceRef);
return;
}
auto conformance = conformanceRef.getConcrete();
// Always look through inherited conformances.
if (auto *inherited = dyn_cast<InheritedProtocolConformance>(conformance))
conformance = inherited->getInheritedConformance();
// Emit any conformances implied by conditional requirements.
if (auto *specialized = dyn_cast<SpecializedProtocolConformance>(conformance)) {
useConformancesFromSubstitutions(specialized->getSubstitutionMap());
conformance = specialized->getGenericConformance();
}
// Get the normal conformance. If we don't have one, this is a self
// conformance, which we can ignore.
auto normal = dyn_cast<NormalProtocolConformance>(conformance);
if (normal == nullptr)
return;
// If this conformance was not synthesized, we're not going to be emitting
// it lazily either, so we can avoid doing anything below.
if (!normal->isSynthesized())
return;
// If we already emitted this witness table, we don't need to track the fact
// we need it.
if (emittedWitnessTables.count(normal))
return;
// Check if we already forced this witness table but haven't emitted it yet.
if (!forcedConformances.insert(normal).second)
return;
pendingConformances.push_back(normal);
}
void SILGenModule::useConformancesFromSubstitutions(
const SubstitutionMap subs) {
for (auto conf : subs.getConformances())
useConformance(conf);
}
void SILGenModule::useConformancesFromType(CanType type) {
if (!usedConformancesFromTypes.insert(type.getPointer()).second)
return;
type.visit([&](Type t) {
auto *decl = t->getAnyNominal();
if (!decl)
return;
if (isa<ProtocolDecl>(decl))
return;
auto genericSig = decl->getGenericSignature();
if (!genericSig)
return;
auto subMap = t->getContextSubstitutionMap(SwiftModule, decl);
useConformancesFromSubstitutions(subMap);
return;
});
}
void SILGenModule::useConformancesFromObjectiveCType(CanType type) {
if (!usedConformancesFromObjectiveCTypes.insert(type.getPointer()).second)
return;
auto &ctx = getASTContext();
auto objectiveCBridgeable = ctx.getProtocol(
KnownProtocolKind::ObjectiveCBridgeable);
auto bridgedStoredNSError = ctx.getProtocol(
KnownProtocolKind::BridgedStoredNSError);
if (!objectiveCBridgeable && !bridgedStoredNSError)
return;
type.visit([&](Type t) {
auto *decl = t->getAnyNominal();
if (!decl)
return;
if (!isa<ClangModuleUnit>(decl->getModuleScopeContext()))
return;
if (objectiveCBridgeable) {
if (auto subConformance =
SwiftModule->lookupConformance(t, objectiveCBridgeable))
useConformance(subConformance);
}
if (bridgedStoredNSError) {
if (auto subConformance =
SwiftModule->lookupConformance(t, bridgedStoredNSError))
useConformance(subConformance);
}
});
}
/// A visitor class that tries to guess which SIL instructions can cause
/// IRGen to emit references to witness tables. This is used to emit
/// ClangImporter-synthesized conformances lazily.
///
/// In the long run, we'll instead have IRGen directly ask SILGen to
/// generate a witness table when needed, so that we don't have to do
/// any "guessing" here.
class LazyConformanceEmitter : public SILInstructionVisitor<LazyConformanceEmitter> {
SILGenModule &SGM;
public:
LazyConformanceEmitter(SILGenModule &SGM) : SGM(SGM) {}
void visitAllocExistentialBoxInst(AllocExistentialBoxInst *AEBI) {
SGM.useConformancesFromType(AEBI->getFormalConcreteType());
SGM.useConformancesFromObjectiveCType(AEBI->getFormalConcreteType());
for (auto conformance : AEBI->getConformances())
SGM.useConformance(conformance);
}
void visitAllocGlobalInst(AllocGlobalInst *AGI) {
SGM.useConformancesFromType(
AGI->getReferencedGlobal()->getLoweredType().getASTType());
}
void visitAllocRefInst(AllocRefInst *ARI) {
SGM.useConformancesFromType(ARI->getType().getASTType());
}
void visitAllocStackInst(AllocStackInst *ASI) {
SGM.useConformancesFromType(ASI->getType().getASTType());
}
void visitApplyInst(ApplyInst *AI) {
SGM.useConformancesFromObjectiveCType(AI->getSubstCalleeType());
SGM.useConformancesFromSubstitutions(AI->getSubstitutionMap());
}
void visitBeginApplyInst(BeginApplyInst *BAI) {
SGM.useConformancesFromObjectiveCType(BAI->getSubstCalleeType());
SGM.useConformancesFromSubstitutions(BAI->getSubstitutionMap());
}
void visitBuiltinInst(BuiltinInst *BI) {
SGM.useConformancesFromSubstitutions(BI->getSubstitutions());
}
void visitCheckedCastBranchInst(CheckedCastBranchInst *CCBI) {
SGM.useConformancesFromType(CCBI->getSourceFormalType());
SGM.useConformancesFromType(CCBI->getTargetFormalType());
SGM.useConformancesFromObjectiveCType(CCBI->getSourceFormalType());
SGM.useConformancesFromObjectiveCType(CCBI->getTargetFormalType());
}
void visitCheckedCastAddrBranchInst(CheckedCastAddrBranchInst *CCABI) {
SGM.useConformancesFromType(CCABI->getSourceFormalType());
SGM.useConformancesFromType(CCABI->getTargetFormalType());
SGM.useConformancesFromObjectiveCType(CCABI->getSourceFormalType());
SGM.useConformancesFromObjectiveCType(CCABI->getTargetFormalType());
}
void visitCopyAddrInst(CopyAddrInst *CAI) {
SGM.useConformancesFromType(CAI->getSrc()->getType().getASTType());
SGM.useConformancesFromType(CAI->getDest()->getType().getASTType());
}
void visitMarkUnresolvedMoveAddrInst(MarkUnresolvedMoveAddrInst *MAI) {
SGM.useConformancesFromType(MAI->getSrc()->getType().getASTType());
SGM.useConformancesFromType(MAI->getDest()->getType().getASTType());
}
void visitCopyValueInst(CopyValueInst *CVI) {
SGM.useConformancesFromType(CVI->getOperand()->getType().getASTType());
}
void visitDestroyAddrInst(DestroyAddrInst *DAI) {
SGM.useConformancesFromType(DAI->getOperand()->getType().getASTType());
}
void visitDestroyValueInst(DestroyValueInst *DVI) {
SGM.useConformancesFromType(DVI->getOperand()->getType().getASTType());
}
void visitGlobalAddrInst(GlobalAddrInst *GAI) {
SGM.useConformancesFromType(
GAI->getReferencedGlobal()->getLoweredType().getASTType());
}
void visitGlobalValueInst(GlobalValueInst *GVI) {
SGM.useConformancesFromType(
GVI->getReferencedGlobal()->getLoweredType().getASTType());
}
void visitKeyPathInst(KeyPathInst *KPI) {
SGM.useConformancesFromSubstitutions(KPI->getSubstitutions());
}
void visitInitEnumDataAddrInst(InitEnumDataAddrInst *IEDAI) {
SGM.useConformancesFromType(
IEDAI->getOperand()->getType().getASTType());
}
void visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI) {
SGM.useConformancesFromType(IEAI->getOperand()->getType().getASTType());
}
void visitInitExistentialAddrInst(InitExistentialAddrInst *IEAI) {
SGM.useConformancesFromType(IEAI->getFormalConcreteType());
SGM.useConformancesFromObjectiveCType(IEAI->getFormalConcreteType());
for (auto conformance : IEAI->getConformances())
SGM.useConformance(conformance);
}
void visitInitExistentialMetatypeInst(InitExistentialMetatypeInst *IEMI) {
SGM.useConformancesFromType(IEMI->getOperand()->getType().getASTType());
for (auto conformance : IEMI->getConformances())
SGM.useConformance(conformance);
}
void visitInitExistentialRefInst(InitExistentialRefInst *IERI) {
SGM.useConformancesFromType(IERI->getFormalConcreteType());
SGM.useConformancesFromObjectiveCType(IERI->getFormalConcreteType());
for (auto conformance : IERI->getConformances())
SGM.useConformance(conformance);
}
void visitInitExistentialValueInst(InitExistentialValueInst *IEVI) {
SGM.useConformancesFromType(IEVI->getFormalConcreteType());
SGM.useConformancesFromObjectiveCType(IEVI->getFormalConcreteType());
for (auto conformance : IEVI->getConformances())
SGM.useConformance(conformance);
}
void visitMetatypeInst(MetatypeInst *MI) {
SGM.useConformancesFromType(MI->getType().getASTType());
}
void visitPartialApplyInst(PartialApplyInst *PAI) {
SGM.useConformancesFromObjectiveCType(PAI->getSubstCalleeType());
SGM.useConformancesFromSubstitutions(PAI->getSubstitutionMap());
}
void visitSelectEnumAddrInst(SelectEnumAddrInst *SEAI) {
SGM.useConformancesFromType(
SEAI->getEnumOperand()->getType().getASTType());
}
void visitStructElementAddrInst(StructElementAddrInst *SEAI) {
SGM.useConformancesFromType(SEAI->getOperand()->getType().getASTType());
}
void visitTryApplyInst(TryApplyInst *TAI) {
SGM.useConformancesFromObjectiveCType(TAI->getSubstCalleeType());
SGM.useConformancesFromSubstitutions(TAI->getSubstitutionMap());
}
void visitTupleElementAddrInst(TupleElementAddrInst *TEAI) {
SGM.useConformancesFromType(TEAI->getOperand()->getType().getASTType());
}
void visitUnconditionalCheckedCastInst(UnconditionalCheckedCastInst *UCCI) {
SGM.useConformancesFromType(UCCI->getSourceFormalType());
SGM.useConformancesFromType(UCCI->getTargetFormalType());
SGM.useConformancesFromObjectiveCType(UCCI->getSourceFormalType());
SGM.useConformancesFromObjectiveCType(UCCI->getTargetFormalType());
}
void visitUnconditionalCheckedCastAddrInst(UnconditionalCheckedCastAddrInst *UCCAI) {
SGM.useConformancesFromType(UCCAI->getSourceFormalType());
SGM.useConformancesFromType(UCCAI->getTargetFormalType());
SGM.useConformancesFromObjectiveCType(UCCAI->getSourceFormalType());
SGM.useConformancesFromObjectiveCType(UCCAI->getTargetFormalType());
}
void visitUncheckedTakeEnumDataAddrInst(UncheckedTakeEnumDataAddrInst *UTEDAI) {
SGM.useConformancesFromType(UTEDAI->getOperand()->getType().getASTType());
}
void visitWitnessMethodInst(WitnessMethodInst *WMI) {
SGM.useConformance(WMI->getConformance());
}
void visitSILInstruction(SILInstruction *I) {}
};
void SILGenModule::emitLazyConformancesForFunction(SILFunction *F) {
LazyConformanceEmitter emitter(*this);
for (auto &BB : *F)
for (auto &I : BB)
emitter.visit(&I);
}
void SILGenModule::emitLazyConformancesForType(NominalTypeDecl *NTD) {
auto genericSig = NTD->getGenericSignature();
for (auto reqt : genericSig.getRequirements()) {
if (reqt.getKind() != RequirementKind::Layout)
useConformancesFromType(reqt.getSecondType()->getCanonicalType());
}
if (auto *ED = dyn_cast<EnumDecl>(NTD)) {
for (auto *EED : ED->getAllElements()) {
if (EED->hasAssociatedValues()) {
useConformancesFromType(EED->getArgumentInterfaceType()
->getReducedType(genericSig));
}
}
}
if (isa<StructDecl>(NTD) || isa<ClassDecl>(NTD)) {
for (auto *VD : NTD->getStoredProperties()) {
useConformancesFromType(VD->getValueInterfaceType()
->getReducedType(genericSig));
}
}
if (auto *CD = dyn_cast<ClassDecl>(NTD))
if (auto superclass = CD->getSuperclass())
useConformancesFromType(superclass->getReducedType(genericSig));
if (auto *PD = dyn_cast<ProtocolDecl>(NTD)) {
for (auto reqt : PD->getRequirementSignature().getRequirements()) {
if (reqt.getKind() != RequirementKind::Layout)
useConformancesFromType(reqt.getSecondType()->getCanonicalType());
}
}
}