Skip to content

Metadata and runtime support for checking isolated conformances at runtime #79785

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
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
58 changes: 55 additions & 3 deletions include/swift/ABI/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,21 @@ struct TargetResilientWitnessesHeader {
};
using ResilientWitnessesHeader = TargetResilientWitnessesHeader<InProcess>;

/// Describes a reference to a global actor type and its conformance to the
/// global actor protocol.
template<typename Runtime>
struct TargetGlobalActorReference {
/// The type of the global actor.
RelativeDirectPointer<const char, /*nullable*/ false> type;

/// The conformance of the global actor to the GlobalActor protocol.
TargetRelativeProtocolConformanceDescriptorPointer<Runtime> conformance;
};

/// Describes the context of a protocol conformance that is relevant when
/// the conformance is used, such as global actor isolation.
struct ConformanceExecutionContext;

/// The structure of a protocol conformance.
///
/// This contains enough static information to recover the witness table for a
Expand All @@ -2747,7 +2762,8 @@ struct TargetProtocolConformanceDescriptor final
GenericPackShapeDescriptor,
TargetResilientWitnessesHeader<Runtime>,
TargetResilientWitness<Runtime>,
TargetGenericWitnessTable<Runtime>> {
TargetGenericWitnessTable<Runtime>,
TargetGlobalActorReference<Runtime>> {

using TrailingObjects = swift::ABI::TrailingObjects<
TargetProtocolConformanceDescriptor<Runtime>,
Expand All @@ -2756,7 +2772,8 @@ struct TargetProtocolConformanceDescriptor final
GenericPackShapeDescriptor,
TargetResilientWitnessesHeader<Runtime>,
TargetResilientWitness<Runtime>,
TargetGenericWitnessTable<Runtime>>;
TargetGenericWitnessTable<Runtime>,
TargetGlobalActorReference<Runtime>>;
friend TrailingObjects;

template<typename T>
Expand Down Expand Up @@ -2871,8 +2888,13 @@ struct TargetProtocolConformanceDescriptor final
/// Get the witness table for the specified type, realizing it if
/// necessary, or return null if the conformance does not apply to the
/// type.
///
/// The context will be populated with any information that needs to be
/// checked before this witness table can be used within a given execution
/// context.
const swift::TargetWitnessTable<Runtime> *
getWitnessTable(const TargetMetadata<Runtime> *type) const;
getWitnessTable(const TargetMetadata<Runtime> *type,
ConformanceExecutionContext &context) const;

/// Retrieve the resilient witnesses.
llvm::ArrayRef<ResilientWitness> getResilientWitnesses() const {
Expand All @@ -2892,6 +2914,32 @@ struct TargetProtocolConformanceDescriptor final
return this->template getTrailingObjects<GenericWitnessTable>();
}

/// Whether this conformance has any conditional requirements that need to
/// be evaluated.
bool hasGlobalActorIsolation() const {
return Flags.hasGlobalActorIsolation();
}

/// Retrieve the global actor type to which this conformance is isolated, if
/// any.
llvm::StringRef
getGlobalActorType() const {
if (!Flags.hasGlobalActorIsolation())
return llvm::StringRef();

return Demangle::makeSymbolicMangledNameStringRef(this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->type);
}

/// Retrieve the protocol conformance of the global actor type to the
/// GlobalActor protocol.
const TargetProtocolConformanceDescriptor<Runtime> *
getGlobalActorConformance() const {
if (!Flags.hasGlobalActorIsolation())
return nullptr;

return this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->conformance;
}

#if !defined(NDEBUG) && SWIFT_OBJC_INTEROP
void dump() const;
#endif
Expand Down Expand Up @@ -2934,6 +2982,10 @@ struct TargetProtocolConformanceDescriptor final
size_t numTrailingObjects(OverloadToken<GenericWitnessTable>) const {
return Flags.hasGenericWitnessTable() ? 1 : 0;
}

size_t numTrailingObjects(OverloadToken<RelativeDirectPointer<const char, /*nullable*/ true>>) const {
return Flags.hasGlobalActorIsolation() ? 1 : 0;
}
};
using ProtocolConformanceDescriptor
= TargetProtocolConformanceDescriptor<InProcess>;
Expand Down
20 changes: 19 additions & 1 deletion include/swift/ABI/MetadataValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ class ConformanceFlags {
HasResilientWitnessesMask = 0x01u << 16,
HasGenericWitnessTableMask = 0x01u << 17,
IsConformanceOfProtocolMask = 0x01u << 18,
HasGlobalActorIsolation = 0x01u << 19,

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

ConformanceFlags withHasGlobalActorIsolation(
bool hasGlobalActorIsolation) const {
return ConformanceFlags((Value & ~HasGlobalActorIsolation)
| (hasGlobalActorIsolation
? HasGlobalActorIsolation
: 0));
}

/// Retrieve the type reference kind kind.
TypeReferenceKind getTypeReferenceKind() const {
return TypeReferenceKind(
Expand Down Expand Up @@ -843,7 +852,12 @@ class ConformanceFlags {
bool isConformanceOfProtocol() const {
return Value & IsConformanceOfProtocolMask;
}


/// Does this conformance have a global actor to which it is isolated?
bool hasGlobalActorIsolation() const {
return Value & HasGlobalActorIsolation;
}

/// Retrieve the # of conditional requirements.
unsigned getNumConditionalRequirements() const {
return (Value & NumConditionalRequirementsMask)
Expand Down Expand Up @@ -1755,6 +1769,10 @@ namespace SpecialPointerAuthDiscriminators {

/// Isolated deinit body function pointer
const uint16_t DeinitWorkFunction = 0x8438; // = 33848

/// IsCurrentGlobalActor function used between the Swift runtime and
/// concurrency runtime.
const uint16_t IsCurrentGlobalActorFunction = 0xd1b8; // = 53688
}

/// The number of arguments that will be passed directly to a generic
Expand Down
26 changes: 26 additions & 0 deletions include/swift/Runtime/Casting.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,32 @@ const WitnessTable *
swift_conformsToProtocolCommon(const Metadata *type,
const ProtocolDescriptor *protocol);

/// The size of the ConformanceExecutionContext structure.
SWIFT_RUNTIME_EXPORT
size_t swift_ConformanceExecutionContextSize;

/// Check whether a type conforms to a given native Swift protocol. This
/// is similar to swift_conformsToProtocolCommon, but allows the caller to
/// either capture the execution context (in *context).
SWIFT_RUNTIME_EXPORT
const WitnessTable *
swift_conformsToProtocolWithExecutionContext(
const Metadata *type,
const ProtocolDescriptor *protocol,
ConformanceExecutionContext *context);

/// Determine whether this function is being executed within the execution
/// context for a conformance. For example, if the conformance is
/// isolated to a given global actor, checks whether this code is running on
/// that global actor's executor.
///
/// The context should have been filled in by
/// swift_conformsToProtocolWithExecutionContext.
SWIFT_RUNTIME_EXPORT
bool swift_isInConformanceExecutionContext(
const Metadata *type,
const ConformanceExecutionContext *context);

} // end namespace swift

#endif // SWIFT_RUNTIME_CASTING_H
4 changes: 4 additions & 0 deletions include/swift/Runtime/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ extern uintptr_t __COMPATIBILITY_LIBRARIES_CANNOT_CHECK_THE_IS_SWIFT_BIT_DIRECTL
#define __ptrauth_swift_deinit_work_function \
__ptrauth(ptrauth_key_function_pointer, 1, \
SpecialPointerAuthDiscriminators::DeinitWorkFunction)
#define __ptrauth_swift_is_global_actor_function \
__ptrauth(ptrauth_key_function_pointer, 1, \
SpecialPointerAuthDiscriminators::IsCurrentGlobalActorFunction)

#if __has_attribute(ptrauth_struct)
#define swift_ptrauth_struct(key, discriminator) \
Expand Down Expand Up @@ -368,6 +371,7 @@ extern uintptr_t __COMPATIBILITY_LIBRARIES_CANNOT_CHECK_THE_IS_SWIFT_BIT_DIRECTL
#define swift_ptrauth_sign_opaque_modify_resume_function(__fn, __buffer) (__fn)
#define __ptrauth_swift_type_layout_string
#define __ptrauth_swift_deinit_work_function
#define __ptrauth_swift_is_global_actor_function
#define swift_ptrauth_struct(key, discriminator)
#define swift_ptrauth_struct_derived(from)
#endif
Expand Down
17 changes: 13 additions & 4 deletions include/swift/Runtime/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -1057,11 +1057,20 @@ struct ConcurrencyStandardTypeDescriptors {
#include "swift/Demangling/StandardTypesMangling.def"
};

/// Register the type descriptors with standard manglings from the Concurrency
/// runtime. The passed-in struct must be immortal.
/// Function that determines whether we are executing on the given global
/// actor. The metadata is for the global actor type, and the witness table
/// is the conformance of that type to the GlobalActor protocol.
typedef bool (* SWIFT_CC(swift) IsCurrentGlobalActor)(const Metadata *, const WitnessTable *);

/// Register various concurrency-related data and hooks needed in the Swift
/// standard library / runtime. This includes type descriptors with standard
/// manglings from the Concurrency runtime as well as a hook to check whether
/// we are running on a specific global actor. Any pointers passed in here must
/// be immortal.
SWIFT_RUNTIME_STDLIB_SPI
void _swift_registerConcurrencyStandardTypeDescriptors(
const ConcurrencyStandardTypeDescriptors *descriptors);
void _swift_registerConcurrencyRuntime(
const ConcurrencyStandardTypeDescriptors *descriptors,
IsCurrentGlobalActor isCurrentGlobalActor);

/// Initialize the value witness table for a struct using the provided like type
/// as the basis for the layout.
Expand Down
44 changes: 44 additions & 0 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "swift/AST/ASTContext.h"
#include "swift/AST/CanTypeVisitor.h"
#include "swift/AST/Types.h"
#include "swift/AST/ConformanceLookup.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsIRGen.h"
#include "swift/AST/GenericEnvironment.h"
Expand Down Expand Up @@ -93,6 +94,11 @@
using namespace swift;
using namespace irgen;

namespace swift {
// FIXME: Move this on to ProtocolConformance?
ActorIsolation getConformanceIsolation(ProtocolConformance *conformance);
}

namespace {

/// A class for computing how to pass arguments to a polymorphic
Expand Down Expand Up @@ -2180,6 +2186,7 @@ namespace {
addConditionalRequirements();
addResilientWitnesses();
addGenericWitnessTable();
addGlobalActorIsolation();

// We fill the flags last, since we continue filling them in
// after the call to addFlags() deposits the placeholder.
Expand Down Expand Up @@ -2216,6 +2223,7 @@ namespace {
Flags = Flags.withIsRetroactive(conf->isRetroactive());
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
Flags = Flags.withHasGlobalActorIsolation(conf->isIsolated());
} else {
Flags = Flags.withIsRetroactive(false)
.withIsSynthesizedNonUnique(false);
Expand Down Expand Up @@ -2403,6 +2411,42 @@ namespace {
B.addRelativeAddress(privateData);
}
}

void addGlobalActorIsolation() {
if (!Flags.hasGlobalActorIsolation())
return;

auto normal = cast<NormalProtocolConformance>(Conformance);
assert(normal->isIsolated());
auto nominal = normal->getDeclContext()->getSelfNominalTypeDecl();

// Add global actor type.
auto sig = nominal->getGenericSignatureOfContext();
auto isolation = getConformanceIsolation(
const_cast<RootProtocolConformance *>(Conformance));
assert(isolation.isGlobalActor());
Type globalActorType = isolation.getGlobalActor();
auto globalActorTypeName = IGM.getTypeRef(
globalActorType, sig, MangledTypeRefRole::Metadata).first;
B.addRelativeAddress(globalActorTypeName);

// Add conformance of the global actor type to the GlobalActor protocol.
SmallVector<ProtocolConformance *, 1> globalActorConformances;
auto globalActorProtocol =
IGM.Context.getProtocol(KnownProtocolKind::GlobalActor);
auto globalActorConformance = lookupConformance(
globalActorType, globalActorProtocol);

auto rootGlobalActorConformance = globalActorConformance.getConcrete()
->getRootConformance();
IGM.IRGen.addLazyWitnessTable(rootGlobalActorConformance);

auto globalActorConformanceDescriptor =
IGM.getAddrOfLLVMVariableOrGOTEquivalent(
LinkEntity::forProtocolConformanceDescriptor(
rootGlobalActorConformance));
B.addRelativeAddress(globalActorConformanceDescriptor);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolCommon, const WitnessTable *, , ,
const ProtocolDescriptor *protocol),
(type, protocol))

OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolWithExecutionContext,
const WitnessTable *, , , swift::,
(const Metadata * const type,
const ProtocolDescriptor *protocol,
ConformanceExecutionContext *context),
(type, protocol, context))

OVERRIDE_PROTOCOLCONFORMANCE(isInConformanceExecutionContext,
bool, , , swift::,
(const Metadata * const type,
const ConformanceExecutionContext *context),
(type, context))

OVERRIDE_KEYPATH(getKeyPath, const HeapObject *, , , swift::,
(const void *pattern, const void *arguments),
(pattern, arguments))
Expand Down
9 changes: 8 additions & 1 deletion stdlib/public/Concurrency/Setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
MANGLING, DESCRIPTOR_MANGLING_SUFFIX(KIND));
#include "swift/Demangling/StandardTypesMangling.def"

// Defined in Swift, redeclared here so we can register it with the runtime.
extern "C" SWIFT_CC(swift)
bool _swift_task_isCurrentGlobalActor(
const swift::Metadata *, const swift::WitnessTable *);

// Register our type descriptors with standard manglings when the concurrency
// runtime is loaded. This allows the runtime to quickly resolve those standard
// manglings.
Expand All @@ -43,5 +48,7 @@ __attribute__((constructor)) static void setupStandardConcurrencyDescriptors() {
&DESCRIPTOR_MANGLING(MANGLING, DESCRIPTOR_MANGLING_SUFFIX(KIND)),
#include "swift/Demangling/StandardTypesMangling.def"
};
_swift_registerConcurrencyStandardTypeDescriptors(&descriptors);
_swift_registerConcurrencyRuntime(
&descriptors,
&_swift_task_isCurrentGlobalActor);
}
8 changes: 8 additions & 0 deletions stdlib/public/Concurrency/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,14 @@ func _taskIsCurrentExecutor(_ executor: Builtin.Executor) -> Bool
internal func _taskIsCurrentExecutor(
executor: Builtin.Executor, flags: UInt64) -> Bool

extension GlobalActor {
@available(SwiftStdlib 6.2, *)
@_silgen_name("_swift_task_isCurrentGlobalActor")
internal static func _taskIsCurrentGlobalActor() -> Bool {
let executor = unsafe sharedUnownedExecutor
return unsafe _taskIsCurrentExecutor(executor: executor.executor, flags: 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok when the new isIsolatingCurrentContext I'm working on lands this will actually work then. Should be fine without changes I think (tho today it'll crash on not-isolated-to-the-expected-thing, which is expected). When run against an old runtime this must expect a potential of a crash basically I think, but in new runtimes it'll be good.

// ongoing discussions about how to roll out that new check with backwards compat etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the code that would call this entry point back-deploys, so I think it's safe from crash-if-not-expected. If we did try to back-deploy, yeah, it would be a problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's more to that though; Say there's someone's custom executor impl; they did implement checkIsolated, they did not implement the new is... API. We check _taskIsCurrentExecutor against it; we have no choice (well we can debate what we should do), but to call the only "check" implementation we have -- the crashing one that the developer implemented. So regardless of backdeployment or not, these may crash if people only implemented the check... APIs.

If that happens, we should be telling people to instead implement the new in 6.2 isIsolatingCurrentContext and it'll stop crashing, but it's something to be aware of.

There's a big flowchart of what can get called when in swiftlang/swift-evolution#2716 -- we can come up with some other more drastic policy that we'd ignore implemented check methods but that's pretty dramatic... Anyway, something to be aware of as we discuss semantics of swiftlang/swift-evolution#2716

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main executor has a bunch of special case sugar so it'll be fine; but this may come up for global actors on custom executors... there's probably pretty few of them so we may choose to not worry about this too much and just "if it blows up, update your impl" which is probably good enough, just highlighting that it's an issue that'll happen eventually

}
}
#endif

@available(SwiftStdlib 5.1, *)
Expand Down
Loading