Skip to content

Introduce a builtin and API for getting the local actor from a distributed one #71043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions include/swift/ABI/MetadataValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ class ConformanceFlags {

HasResilientWitnessesMask = 0x01u << 16,
HasGenericWitnessTableMask = 0x01u << 17,
IsConformanceOfProtocolMask = 0x01u << 18,

NumConditionalPackDescriptorsMask = 0xFFu << 24,
NumConditionalPackDescriptorsShift = 24
Expand Down Expand Up @@ -724,6 +725,14 @@ class ConformanceFlags {
: 0));
}

ConformanceFlags withIsConformanceOfProtocol(
bool isConformanceOfProtocol) const {
return ConformanceFlags((Value & ~IsConformanceOfProtocolMask)
| (isConformanceOfProtocol
? IsConformanceOfProtocolMask
: 0));
}

/// Retrieve the type reference kind kind.
TypeReferenceKind getTypeReferenceKind() const {
return TypeReferenceKind(
Expand All @@ -749,6 +758,20 @@ class ConformanceFlags {
return Value & IsSynthesizedNonUniqueMask;
}

/// Is this a conformance of a protocol to another protocol?
///
/// The Swift compiler can synthesize a conformance of one protocol to
/// another, meaning that every type that conforms to the first protocol
/// can also produce a witness table conforming to the second. Such
/// conformances cannot generally be written in the surface language, but
/// can be made available for specific tasks. The only such instance at the
/// time of this writing is that a (local) distributed actor can conform to
/// a local actor, but the witness table can only be used via a specific
/// builtin to form an existential.
bool isConformanceOfProtocol() const {
return Value & IsConformanceOfProtocolMask;
}

/// Retrieve the # of conditional requirements.
unsigned getNumConditionalRequirements() const {
return (Value & NumConditionalRequirementsMask)
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ SIMPLE_DECL_ATTR(GKInspectable, GKInspectable,
OnVar | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
66)
DECL_ATTR(_implements, Implements,
OnFunc | OnAccessor | OnVar | OnSubscript | OnTypeAlias | UserInaccessible | NotSerialized | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
OnFunc | OnAccessor | OnVar | OnSubscript | OnTypeAlias | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
67)
DECL_ATTR(_objcRuntimeName, ObjCRuntimeName,
OnClass | UserInaccessible | NotSerialized | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
Expand Down
6 changes: 6 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,12 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(GetEnumTag, "getEnumTag", "", Special)
/// tag's payload is already initialized with the given source.
BUILTIN_MISC_OPERATION_WITH_SILGEN(InjectEnumTag, "injectEnumTag", "", Special)

/// distributedActorAsAnyActor: <DA: DistributedActor>(_: DA) -> any Actor
///
/// For a given distributed actor that is known to be local, extract an
/// `any Actor` existential that refers to the local actor.
BUILTIN_MISC_OPERATION_WITH_SILGEN(DistributedActorAsAnyActor, "distributedActorAsAnyActor", "n", Special)

#undef BUILTIN_MISC_OPERATION_WITH_SILGEN

#undef BUILTIN_MISC_OPERATION
Expand Down
14 changes: 14 additions & 0 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,10 @@ class RootProtocolConformance : public ProtocolConformance {
/// Get the conformance substitution map.
SubstitutionMap getSubstitutionMap() const;

/// Whether this conformance was synthesized automatically and can have
/// multiple copies in a single program.
bool isSynthesized() const;

/// Apply the given function object to each value witness within this
/// protocol conformance.
///
Expand Down Expand Up @@ -679,6 +683,16 @@ class NormalProtocolConformance : public RootProtocolConformance,
/// modules, but in a manner that ensures that all copies are equivalent.
bool isSynthesizedNonUnique() const;

/// Whether this conformance represents the conformance of one protocol's
/// conforming types to another protocol.
///
/// Such conformances cannot generally be written in the surface language, but
/// can be made available for specific tasks. The only such instance at the
/// time of this writing is that a (local) distributed actor can conform to
/// a local actor, but the witness table can only be used via a specific
/// builtin to form an existential.
bool isConformanceOfProtocol() const;

/// Whether clients from outside the module can rely on the value witnesses
/// being consistent across versions of the framework.
bool isResilient() const;
Expand Down
5 changes: 3 additions & 2 deletions lib/AST/ASTMangler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1787,8 +1787,9 @@ static bool conformanceHasIdentity(const RootProtocolConformance *root) {
return true;
}

// Synthesized non-unique conformances all get collapsed together at run time.
if (conformance->isSynthesizedNonUnique())
// Synthesized conformances can have multiple copies, so they don't
// provide identity.
if (conformance->isSynthesized())
return false;

// Objective-C protocol conformances are checked by the ObjC runtime.
Expand Down
16 changes: 16 additions & 0 deletions lib/AST/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,19 @@ static ValueDecl *getHopToActor(ASTContext &ctx, Identifier id) {
return builder.build(id);
}

static ValueDecl *getDistributedActorAsAnyActor(ASTContext &ctx, Identifier id) {
BuiltinFunctionBuilder builder(ctx);
auto *distributedActorProto = ctx.getProtocol(KnownProtocolKind::DistributedActor);
auto *actorProto = ctx.getProtocol(KnownProtocolKind::Actor);

// Create type parameters and add conformance constraints.
auto actorParam = makeGenericParam();
builder.addParameter(actorParam);
builder.addConformanceRequirement(actorParam, distributedActorProto);
builder.setResult(makeConcrete(actorProto->getDeclaredExistentialType()));
return builder.build(id);
}

static ValueDecl *getPackLength(ASTContext &ctx, Identifier id) {
BuiltinFunctionBuilder builder(ctx, /* genericParamCount */ 1,
/* anyObject */ false,
Expand Down Expand Up @@ -3055,6 +3068,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {

case BuiltinValueKind::InjectEnumTag:
return getInjectEnumTag(Context, Id);

case BuiltinValueKind::DistributedActorAsAnyActor:
return getDistributedActorAsAnyActor(Context, Id);
}

llvm_unreachable("bad builtin value!");
Expand Down
13 changes: 13 additions & 0 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,13 @@ bool RootProtocolConformance::hasWitness(ValueDecl *requirement) const {
ROOT_CONFORMANCE_SUBCLASS_DISPATCH(hasWitness, (requirement))
}

bool RootProtocolConformance::isSynthesized() const {
if (auto normal = dyn_cast<NormalProtocolConformance>(this))
return normal->isSynthesizedNonUnique() || normal->isConformanceOfProtocol();

return false;
}

bool NormalProtocolConformance::isRetroactive() const {
auto module = getDeclContext()->getParentModule();

Expand Down Expand Up @@ -321,11 +328,17 @@ bool NormalProtocolConformance::isRetroactive() const {
}

bool NormalProtocolConformance::isSynthesizedNonUnique() const {
// Check if the conformance was synthesized by the ClangImporter.
if (auto *file = dyn_cast<FileUnit>(getDeclContext()->getModuleScopeContext()))
return file->getKind() == FileUnitKind::ClangModule;

return false;
}

bool NormalProtocolConformance::isConformanceOfProtocol() const {
return getDeclContext()->getSelfProtocolDecl() != nullptr;
}

bool NormalProtocolConformance::isResilient() const {
// If the type is non-resilient or the module we're in is non-resilient, the
// conformance is non-resilient.
Expand Down
58 changes: 49 additions & 9 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,7 @@ void IRGenerator::emitLazyDefinitions() {
assert(LazySpecializedTypeMetadataRecords.empty());
assert(LazyTypeContextDescriptors.empty());
assert(LazyOpaqueTypeDescriptors.empty());
assert(LazyExtensionDescriptors.empty());
assert(LazyFieldDescriptors.empty());
// LazyFunctionDefinitions are allowed, but they must not be generic
for (SILFunction *f : LazyFunctionDefinitions) {
Expand All @@ -1438,7 +1439,9 @@ void IRGenerator::emitLazyDefinitions() {
while (!LazyTypeMetadata.empty() ||
!LazySpecializedTypeMetadataRecords.empty() ||
!LazyTypeContextDescriptors.empty() ||
!LazyOpaqueTypeDescriptors.empty() || !LazyFieldDescriptors.empty() ||
!LazyOpaqueTypeDescriptors.empty() ||
!LazyExtensionDescriptors.empty() ||
!LazyFieldDescriptors.empty() ||
!LazyFunctionDefinitions.empty() || !LazyWitnessTables.empty() ||
!LazyCanonicalSpecializedMetadataAccessors.empty() ||
!LazyMetadataAccessors.empty() ||
Expand Down Expand Up @@ -1494,6 +1497,15 @@ void IRGenerator::emitLazyDefinitions() {
CurrentIGMPtr IGM = getGenModule(type->getDeclContext());
IGM->emitOpaqueTypeDecl(type);
}
while (!LazyExtensionDescriptors.empty()) {
ExtensionDecl *ext = LazyExtensionDescriptors.back();
LazyExtensionDescriptors.pop_back();
auto &entry = LazyExtensions.find(ext)->second;
assert(entry.IsDescriptorUsed && !entry.IsDescriptorEmitted);
entry.IsDescriptorEmitted = true;
CurrentIGMPtr IGM = getGenModule(ext->getDeclContext());
IGM->getAddrOfExtensionContextDescriptor(ext);
}
while (!LazyFieldDescriptors.empty()) {
NominalTypeDecl *type = LazyFieldDescriptors.pop_back_val();
CurrentIGMPtr IGM = getGenModule(type->getDeclContext());
Expand Down Expand Up @@ -1804,6 +1816,18 @@ void IRGenerator::noteUseOfOpaqueTypeDescriptor(OpaqueTypeDecl *opaque) {
}
}

void IRGenerator::noteUseOfExtensionDescriptor(ExtensionDecl *ext) {
auto insertResult = LazyExtensions.try_emplace(ext);
auto &entry = insertResult.first->second;

bool isNovelUseOfDescriptor = !entry.IsDescriptorUsed;
entry.IsDescriptorUsed = true;

if (isNovelUseOfDescriptor) {
LazyExtensionDescriptors.push_back(ext);
}
}

static std::string getDynamicReplacementSection(IRGenModule &IGM) {
std::string sectionName;
switch (IGM.TargetInfo.OutputObjectFormat) {
Expand Down Expand Up @@ -4065,11 +4089,11 @@ IRGenModule::getAddrOfLLVMVariableOrGOTEquivalent(LinkEntity entity) {
return indirect();
}

static TypeEntityReference
getContextDescriptorEntityReference(IRGenModule &IGM, const LinkEntity &entity){
TypeEntityReference
IRGenModule::getContextDescriptorEntityReference(const LinkEntity &entity) {
// TODO: consider using a symbolic reference (i.e. a symbol string
// to be looked up dynamically) for types defined outside the module.
auto ref = IGM.getAddrOfLLVMVariableOrGOTEquivalent(entity);
auto ref = getAddrOfLLVMVariableOrGOTEquivalent(entity);
auto kind = ref.isIndirect()
? TypeReferenceKind::IndirectTypeDescriptor
: TypeReferenceKind::DirectTypeDescriptor;
Expand All @@ -4081,15 +4105,15 @@ getTypeContextDescriptorEntityReference(IRGenModule &IGM,
NominalTypeDecl *decl) {
auto entity = LinkEntity::forNominalTypeDescriptor(decl);
IGM.IRGen.noteUseOfTypeContextDescriptor(decl, DontRequireMetadata);
return getContextDescriptorEntityReference(IGM, entity);
return IGM.getContextDescriptorEntityReference(entity);
}

static TypeEntityReference
getProtocolDescriptorEntityReference(IRGenModule &IGM, ProtocolDecl *protocol) {
assert(!protocol->hasClangNode() &&
"objc protocols don't have swift protocol descriptors");
auto entity = LinkEntity::forProtocolDescriptor(protocol);
return getContextDescriptorEntityReference(IGM, entity);
return IGM.getContextDescriptorEntityReference(entity);
}

static TypeEntityReference
Expand All @@ -4113,7 +4137,7 @@ IRGenModule::getTypeEntityReference(GenericTypeDecl *decl) {
if (auto opaque = dyn_cast<OpaqueTypeDecl>(decl)) {
auto entity = LinkEntity::forOpaqueTypeDescriptor(opaque);
IRGen.noteUseOfOpaqueTypeDescriptor(opaque);
return getContextDescriptorEntityReference(*this, entity);
return getContextDescriptorEntityReference(entity);
}

if (auto nominal = dyn_cast<NominalTypeDecl>(decl)) {
Expand Down Expand Up @@ -4360,12 +4384,28 @@ llvm::Constant *IRGenModule::emitSwiftProtocols(bool asContiguousArray) {
return nullptr;
}

/// Determine whether the given protocol conformance can be found via
/// metadata for dynamic lookup.
static bool conformanceIsVisibleViaMetadata(
RootProtocolConformance *conformance) {
auto normal = dyn_cast<NormalProtocolConformance>(conformance);
if (!normal)
return true;

// Conformances of a protocol to another protocol cannot be looked up
// dynamically.
return !normal->isConformanceOfProtocol();
}


void IRGenModule::addProtocolConformance(ConformanceDescription &&record) {

emitProtocolConformance(record);

// Add this conformance to the conformance list.
ProtocolConformances.push_back(std::move(record));
if (conformanceIsVisibleViaMetadata(record.conformance)) {
// Add this conformance to the conformance list.
ProtocolConformances.push_back(std::move(record));
}
}

void IRGenModule::addAccessibleFunction(SILFunction *func) {
Expand Down
7 changes: 5 additions & 2 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2719,8 +2719,11 @@ IRGenModule::getAddrOfSharedContextDescriptor(LinkEntity entity,
// at runtime.
auto mangledName = entity.mangleAsString();
if (auto otherDefinition = Module.getGlobalVariable(mangledName)) {
GlobalVars.insert({entity, otherDefinition});
return otherDefinition;
if (!otherDefinition->isDeclaration() ||
!entity.isAlwaysSharedLinkage()) {
GlobalVars.insert({entity, otherDefinition});
return otherDefinition;
}
}

// Otherwise, emit the descriptor.
Expand Down
39 changes: 35 additions & 4 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1933,6 +1933,24 @@ llvm::Function *FragileWitnessTableBuilder::buildInstantiationFunction() {
return fn;
}

/// Produce a reference for the conforming entity of a protocol conformance
/// descriptor, which is usually the conforming concrete type.
static TypeEntityReference getConformingEntityReference(
IRGenModule &IGM, const RootProtocolConformance *conformance) {
// This is a conformance of a protocol to another protocol. Module it as
// an extension.
if (isa<NormalProtocolConformance>(conformance) &&
cast<NormalProtocolConformance>(conformance)->isConformanceOfProtocol()) {
auto ext = cast<ExtensionDecl>(conformance->getDeclContext());
auto linkEntity = LinkEntity::forExtensionDescriptor(ext);
IGM.IRGen.noteUseOfExtensionDescriptor(ext);
return IGM.getContextDescriptorEntityReference(linkEntity);
}

return IGM.getTypeEntityReference(
conformance->getDeclContext()->getSelfNominalTypeDecl());
}

namespace {
/// Builds a protocol conformance descriptor.
class ProtocolConformanceDescriptorBuilder {
Expand Down Expand Up @@ -1984,8 +2002,7 @@ namespace {
void addConformingType() {
// Add a relative reference to the type, with the type reference
// kind stored in the flags.
auto ref = IGM.getTypeEntityReference(
Conformance->getDeclContext()->getSelfNominalTypeDecl());
auto ref = getConformingEntityReference(IGM, Conformance);
B.addRelativeAddress(ref.getValue());
Flags = Flags.withTypeReferenceKind(ref.getKind());
}
Expand All @@ -2000,6 +2017,7 @@ namespace {
if (auto conf = dyn_cast<NormalProtocolConformance>(Conformance)) {
Flags = Flags.withIsRetroactive(conf->isRetroactive());
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
} else {
Flags = Flags.withIsRetroactive(false)
.withIsSynthesizedNonUnique(false);
Expand Down Expand Up @@ -2027,9 +2045,22 @@ namespace {
if (!normal)
return;

llvm::Optional<Requirement> scratchRequirement;
auto condReqs = normal->getConditionalRequirements();
if (condReqs.empty())
return;
if (condReqs.empty()) {
// For a protocol P that conforms to another protocol, introduce a
// conditional requirement for that P's Self: P. This aligns with
// SILWitnessTable::enumerateWitnessTableConditionalConformances().
if (auto selfProto = normal->getDeclContext()->getSelfProtocolDecl()) {
auto selfType = selfProto->getSelfInterfaceType()->getCanonicalType();
scratchRequirement.emplace(RequirementKind::Conformance, selfType,
selfProto->getDeclaredInterfaceType());
condReqs = *scratchRequirement;
}

if (condReqs.empty())
return;
}

Flags = Flags.withNumConditionalRequirements(condReqs.size());

Expand Down
Loading