Skip to content

🍒[5.9][Concurrency] SerialExecutor.isSameExclusiveExecutionContext #64665

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
50 changes: 48 additions & 2 deletions include/swift/ABI/Executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,26 @@ class ExecutorRef {

// We future-proof the ABI here by masking the low bits off the
// implementation pointer before using it as a witness table.
//
// We have 3 bits for future use remaining here.
enum: uintptr_t {
WitnessTableMask = ~uintptr_t(alignof(void*) - 1)
};

/// The kind is stored in the free bits in the `Implementation` witness table reference.
enum class ExecutorKind : uintptr_t {
/// Ordinary executor.
///
/// Note that the "Generic" executor is also implicitly "Ordinary".
/// To check if an executor is Generic, explicitly check this by calling `isGeneric`.
Ordinary = 0b00,
/// Executor that may need to participate in complex "same context" checks,
/// by invoking `isSameExclusiveExecutionContext` when comparing execution contexts.
ComplexEquality = 0b01,
};

static_assert(static_cast<uintptr_t>(ExecutorKind::Ordinary) == 0);

constexpr ExecutorRef(HeapObject *identity, uintptr_t implementation)
: Identity(identity), Implementation(implementation) {}

Expand All @@ -89,7 +105,18 @@ class ExecutorRef {
const SerialExecutorWitnessTable *witnessTable) {
assert(identity);
assert(witnessTable);
return ExecutorRef(identity, reinterpret_cast<uintptr_t>(witnessTable));
auto wtable = reinterpret_cast<uintptr_t>(witnessTable) |
static_cast<uintptr_t>(ExecutorKind::Ordinary);
return ExecutorRef(identity, wtable);
}

static ExecutorRef forComplexEquality(HeapObject *identity,
const SerialExecutorWitnessTable *witnessTable) {
assert(identity);
assert(witnessTable);
auto wtable = reinterpret_cast<uintptr_t>(witnessTable) |
static_cast<uintptr_t>(ExecutorKind::ComplexEquality);
return ExecutorRef(identity, wtable);
}

HeapObject *getIdentity() const {
Expand All @@ -101,6 +128,23 @@ class ExecutorRef {
return Identity == 0;
}

ExecutorKind getExecutorKind() const {
return static_cast<ExecutorKind>(Implementation & ~WitnessTableMask);
}

/// Is this an ordinary executor reference?
/// These executor references are the default kind, and have no special treatment elsewhere in the system.
bool isOrdinary() const {
return getExecutorKind() == ExecutorKind::Ordinary;
}

/// Is this an `complex-equality` executor reference?
/// These executor references should implement `isSameExclusiveExecutionContext` which will be invoked
/// when two executors are compared for being the same exclusive execution context.
bool isComplexEquality() const {
return getExecutorKind() == ExecutorKind::ComplexEquality;
}

/// Is this a default-actor executor reference?
bool isDefaultActor() const {
return !isGeneric() && Implementation == 0;
Expand All @@ -126,7 +170,9 @@ class ExecutorRef {
bool isMainExecutor() const;

/// Get the raw value of the Implementation field, for tracing.
uintptr_t getRawImplementation() { return Implementation; }
uintptr_t getRawImplementation() const {
return Implementation & WitnessTableMask;
}

bool operator==(ExecutorRef other) const {
return Identity == other.Identity;
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,11 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(AutoDiffAllocateSubcontext, "autoDiffAllocate
BUILTIN_MISC_OPERATION_WITH_SILGEN(BuildOrdinarySerialExecutorRef,
"buildOrdinarySerialExecutorRef", "n", Special)

/// Build a Builtin.Executor value from an "complex equality" serial executor
/// reference.
BUILTIN_MISC_OPERATION_WITH_SILGEN(BuildComplexEqualitySerialExecutorRef,
"buildComplexEqualitySerialExecutorRef", "n", Special)

/// Build a Builtin.Executor value from a default actor reference.
BUILTIN_MISC_OPERATION_WITH_SILGEN(BuildDefaultActorExecutorRef,
"buildDefaultActorExecutorRef", "n", Special)
Expand Down
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ LANGUAGE_FEATURE(BuiltinTaskGroupWithArgument, 0, "TaskGroup builtins", true)
LANGUAGE_FEATURE(InheritActorContext, 0, "@_inheritActorContext attribute", true)
LANGUAGE_FEATURE(ImplicitSelfCapture, 0, "@_implicitSelfCapture attribute", true)
LANGUAGE_FEATURE(BuiltinBuildExecutor, 0, "Executor-building builtins", true)
LANGUAGE_FEATURE(BuiltinBuildComplexEqualityExecutor, 0, "Executor-building for 'complexEquality executor' builtins", true)
LANGUAGE_FEATURE(BuiltinBuildMainExecutor, 0, "MainActor executor building builtin", true)
LANGUAGE_FEATURE(BuiltinCreateAsyncTaskInGroup, 0, "MainActor executor building builtin", true)
LANGUAGE_FEATURE(BuiltinCopy, 0, "Builtin.copy()", true)
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Runtime/Concurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ bool swift_task_isOnExecutor(
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);

SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_executor_isComplexEquality(ExecutorRef ref);

/// Return the 64bit TaskID (if the job is an AsyncTask),
/// or the 32bits of the job Id otherwise.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
Expand Down
14 changes: 14 additions & 0 deletions include/swift/Runtime/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,20 @@ const RelativeWitnessTable *swift_getAssociatedConformanceWitnessRelative(
const ProtocolRequirement *reqBase,
const ProtocolRequirement *assocConformance);

/// Compare two witness tables, which may involving checking the
/// contents of their conformance descriptors.
///
/// Runtime availability: Swift 5.4
///
/// \param lhs The first protocol witness table to compare.
/// \param rhs The second protocol witness table to compare.
///
/// \returns true if both witness tables describe the same conformance, false otherwise.
SWIFT_RUNTIME_EXPORT
SWIFT_CC(swift)
bool swift_compareWitnessTables(const WitnessTable *lhs,
const WitnessTable *rhs);

/// Determine whether two protocol conformance descriptors describe the same
/// conformance of a type to a protocol.
///
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2979,6 +2979,10 @@ static bool usesFeatureBuiltinBuildExecutor(Decl *decl) {
return false;
}

static bool usesFeatureBuiltinBuildComplexEqualityExecutor(Decl *decl) {
return false;
}

static bool usesFeatureBuiltinBuildMainExecutor(Decl *decl) {
return false;
}
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,15 @@ static ValueDecl *getBuildOrdinarySerialExecutorRef(ASTContext &ctx,
_executor);
}

static ValueDecl *getBuildComplexEqualitySerialExecutorRef(ASTContext &ctx,
Identifier id) {
return getBuiltinFunction(ctx, id, _thin,
_generics(_unrestricted,
_conformsTo(_typeparam(0), _serialExecutor)),
_parameters(_typeparam(0)),
_executor);
}

static ValueDecl *getAutoDiffCreateLinearMapContext(ASTContext &ctx,
Identifier id) {
return getBuiltinFunction(
Expand Down Expand Up @@ -2887,6 +2896,8 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {

case BuiltinValueKind::BuildOrdinarySerialExecutorRef:
return getBuildOrdinarySerialExecutorRef(Context, Id);
case BuiltinValueKind::BuildComplexEqualitySerialExecutorRef:
return getBuildComplexEqualitySerialExecutorRef(Context, Id);

case BuiltinValueKind::PoundAssert:
return getPoundAssert(Context, Id);
Expand Down
7 changes: 7 additions & 0 deletions lib/IRGen/GenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
emitBuildOrdinarySerialExecutorRef(IGF, actor, type, conf, out);
return;
}
if (Builtin.ID == BuiltinValueKind::BuildComplexEqualitySerialExecutorRef) {
auto actor = args.claimNext();
auto type = substitutions.getReplacementTypes()[0]->getCanonicalType();
auto conf = substitutions.getConformances()[0];
emitBuildComplexEqualitySerialExecutorRef(IGF, actor, type, conf, out);
return;
}

if (Builtin.ID == BuiltinValueKind::InitializeDistributedRemoteActor) {
auto actorMetatype = args.claimNext();
Expand Down
27 changes: 26 additions & 1 deletion lib/IRGen/GenConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ void irgen::emitBuildDefaultActorExecutorRef(IRGenFunction &IGF,
void irgen::emitBuildOrdinarySerialExecutorRef(IRGenFunction &IGF,
llvm::Value *executor,
CanType executorType,
ProtocolConformanceRef executorConf,
ProtocolConformanceRef executorConf,
Explosion &out) {
// The implementation word of an "ordinary" serial executor is
// just the witness table pointer with no flags set.
Expand All @@ -173,6 +173,31 @@ void irgen::emitBuildOrdinarySerialExecutorRef(IRGenFunction &IGF,
out.add(impl);
}

void irgen::emitBuildComplexEqualitySerialExecutorRef(IRGenFunction &IGF,
llvm::Value *executor,
CanType executorType,
ProtocolConformanceRef executorConf,
Explosion &out) {
llvm::Value *identity =
IGF.Builder.CreatePtrToInt(executor, IGF.IGM.ExecutorFirstTy);

// The implementation word of an "complex equality" serial executor is
// the witness table pointer with the ExecutorKind::ComplexEquality flag set.
llvm::Value *impl =
emitWitnessTableRef(IGF, executorType, executorConf);
impl = IGF.Builder.CreatePtrToInt(impl, IGF.IGM.ExecutorSecondTy);

// NOTE: Refer to ExecutorRef::ExecutorKind for the flag values.
llvm::IntegerType *IntPtrTy = IGF.IGM.IntPtrTy;
auto complexEqualityExecutorKindFlag =
llvm::Constant::getIntegerValue(IntPtrTy, APInt(IntPtrTy->getBitWidth(),
0b01));
impl = IGF.Builder.CreateOr(impl, complexEqualityExecutorKindFlag);

out.add(identity);
out.add(impl);
}

void irgen::emitGetCurrentExecutor(IRGenFunction &IGF, Explosion &out) {
auto *call = IGF.Builder.CreateCall(
IGF.IGM.getTaskGetCurrentExecutorFunctionPointer(), {});
Expand Down
7 changes: 7 additions & 0 deletions lib/IRGen/GenConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ void emitBuildOrdinarySerialExecutorRef(IRGenFunction &IGF,
ProtocolConformanceRef executorConformance,
Explosion &out);

/// Emit the buildComplexEqualitySerialExecutorRef builtin.
void emitBuildComplexEqualitySerialExecutorRef(IRGenFunction &IGF,
llvm::Value *executor,
CanType executorType,
ProtocolConformanceRef executorConformance,
Explosion &out);

/// Emit the getCurrentExecutor builtin.
void emitGetCurrentExecutor(IRGenFunction &IGF, Explosion &out);

Expand Down
1 change: 1 addition & 0 deletions lib/SIL/IR/OperandOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ BUILTIN_OPERAND_OWNERSHIP(PointerEscape, AutoDiffProjectTopLevelSubcontext)
BUILTIN_OPERAND_OWNERSHIP(ForwardingConsume, ConvertTaskToJob)

BUILTIN_OPERAND_OWNERSHIP(BitwiseEscape, BuildOrdinarySerialExecutorRef)
BUILTIN_OPERAND_OWNERSHIP(BitwiseEscape, BuildComplexEqualitySerialExecutorRef)
BUILTIN_OPERAND_OWNERSHIP(BitwiseEscape, BuildDefaultActorExecutorRef)
BUILTIN_OPERAND_OWNERSHIP(BitwiseEscape, BuildMainActorExecutorRef)

Expand Down
1 change: 1 addition & 0 deletions lib/SIL/IR/ValueOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, ResumeNonThrowingContinuationReturning)
CONSTANT_OWNERSHIP_BUILTIN(None, ResumeThrowingContinuationReturning)
CONSTANT_OWNERSHIP_BUILTIN(None, ResumeThrowingContinuationThrowing)
CONSTANT_OWNERSHIP_BUILTIN(None, BuildOrdinarySerialExecutorRef)
CONSTANT_OWNERSHIP_BUILTIN(None, BuildComplexEqualitySerialExecutorRef)
CONSTANT_OWNERSHIP_BUILTIN(None, BuildDefaultActorExecutorRef)
CONSTANT_OWNERSHIP_BUILTIN(None, BuildMainActorExecutorRef)
CONSTANT_OWNERSHIP_BUILTIN(None, StartAsyncLet)
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/Utils/InstructionUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
case BuiltinValueKind::Swift3ImplicitObjCEntrypoint:
return RuntimeEffect::ObjectiveC | RuntimeEffect::Allocating;
case BuiltinValueKind::BuildOrdinarySerialExecutorRef:
case BuiltinValueKind::BuildComplexEqualitySerialExecutorRef:
case BuiltinValueKind::BuildDefaultActorExecutorRef:
case BuiltinValueKind::BuildMainActorExecutorRef:
case BuiltinValueKind::StartAsyncLet:
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
}

if (builtinKind == BuiltinValueKind::BuildOrdinarySerialExecutorRef ||
builtinKind == BuiltinValueKind::BuildComplexEqualitySerialExecutorRef ||
builtinKind == BuiltinValueKind::BuildDefaultActorExecutorRef) {
require(arguments.size() == 1,
"builtin expects one argument");
Expand Down
6 changes: 6 additions & 0 deletions lib/SILGen/SILGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,12 @@ static ManagedValue emitBuiltinBuildOrdinarySerialExecutorRef(
return emitBuildExecutorRef(SGF, loc, subs, args,
BuiltinValueKind::BuildOrdinarySerialExecutorRef);
}
static ManagedValue emitBuiltinBuildComplexEqualitySerialExecutorRef(
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
ArrayRef<ManagedValue> args, SGFContext C) {
return emitBuildExecutorRef(SGF, loc, subs, args,
BuiltinValueKind::BuildComplexEqualitySerialExecutorRef);
}
static ManagedValue emitBuiltinBuildDefaultActorExecutorRef(
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
ArrayRef<ManagedValue> args, SGFContext C) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ static bool isBarrier(SILInstruction *inst) {
case BuiltinValueKind::InitializeDistributedRemoteActor:
case BuiltinValueKind::InitializeNonDefaultDistributedActor:
case BuiltinValueKind::BuildOrdinarySerialExecutorRef:
case BuiltinValueKind::BuildComplexEqualitySerialExecutorRef:
case BuiltinValueKind::BuildDefaultActorExecutorRef:
case BuiltinValueKind::BuildMainActorExecutorRef:
case BuiltinValueKind::ResumeNonThrowingContinuationReturning:
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 755; // InternalOrBelow dependency
const uint16_t SWIFTMODULE_VERSION_MINOR = 756; // build complexEquality executor builtin

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down
43 changes: 39 additions & 4 deletions stdlib/public/Concurrency/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,49 @@ JobPriority swift::swift_task_getCurrentThreadPriority() {
#endif
}

// Implemented in Swift to avoid some annoying hard-coding about
// SerialExecutor's protocol witness table. We could inline this
// with effort, though.
extern "C" SWIFT_CC(swift)
bool _task_serialExecutor_isSameExclusiveExecutionContext(
HeapObject *currentExecutor, HeapObject *executor,
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);

SWIFT_CC(swift)
static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) {
if (auto currentTracking = ExecutorTrackingInfo::current()) {
return currentTracking->getActiveExecutor() == executor;
auto current = ExecutorTrackingInfo::current();

if (!current) {
// TODO(ktoso): checking the "is main thread" is not correct, main executor can be not main thread, relates to rdar://106188692
return executor.isMainExecutor() && isExecutingOnMainThread();
}

auto currentExecutor = current->getActiveExecutor();
if (currentExecutor == executor) {
return true;
}

// TODO(ktoso): checking the "is main thread" is not correct, main executor can be not main thread, relates to rdar://106188692
return executor.isMainExecutor() && isExecutingOnMainThread();
if (executor.isComplexEquality()) {
if (!swift_compareWitnessTables(
reinterpret_cast<const WitnessTable*>(currentExecutor.getSerialExecutorWitnessTable()),
reinterpret_cast<const WitnessTable*>(executor.getSerialExecutorWitnessTable()))) {
// different witness table, we cannot invoke complex equality call
return false;
}
// Avoid passing nulls to Swift for the isSame check:
if (!currentExecutor.getIdentity() || !executor.getIdentity()) {
return false;
}

return _task_serialExecutor_isSameExclusiveExecutionContext(
currentExecutor.getIdentity(),
executor.getIdentity(),
swift_getObjectType(currentExecutor.getIdentity()),
executor.getSerialExecutorWitnessTable());
}

return false;
}

/// Logging level for unexpected executors:
Expand Down
Loading