diff --git a/include/swift/ABI/Executor.h b/include/swift/ABI/Executor.h index bf30342a33699..9da0d5d86a0b3 100644 --- a/include/swift/ABI/Executor.h +++ b/include/swift/ABI/Executor.h @@ -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(ExecutorKind::Ordinary) == 0); + constexpr ExecutorRef(HeapObject *identity, uintptr_t implementation) : Identity(identity), Implementation(implementation) {} @@ -89,7 +105,18 @@ class ExecutorRef { const SerialExecutorWitnessTable *witnessTable) { assert(identity); assert(witnessTable); - return ExecutorRef(identity, reinterpret_cast(witnessTable)); + auto wtable = reinterpret_cast(witnessTable) | + static_cast(ExecutorKind::Ordinary); + return ExecutorRef(identity, wtable); + } + + static ExecutorRef forComplexEquality(HeapObject *identity, + const SerialExecutorWitnessTable *witnessTable) { + assert(identity); + assert(witnessTable); + auto wtable = reinterpret_cast(witnessTable) | + static_cast(ExecutorKind::ComplexEquality); + return ExecutorRef(identity, wtable); } HeapObject *getIdentity() const { @@ -101,6 +128,23 @@ class ExecutorRef { return Identity == 0; } + ExecutorKind getExecutorKind() const { + return static_cast(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; @@ -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; diff --git a/include/swift/AST/Builtins.def b/include/swift/AST/Builtins.def index 948126e0abd8d..a342abf6592ab 100644 --- a/include/swift/AST/Builtins.def +++ b/include/swift/AST/Builtins.def @@ -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) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index bcbdb2058623e..b7ad9130d88c3 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -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) diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 46faae8b54d73..feb833b59010b 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -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) diff --git a/include/swift/Runtime/Metadata.h b/include/swift/Runtime/Metadata.h index ac7dd4a5755f8..a5de3d5851a52 100644 --- a/include/swift/Runtime/Metadata.h +++ b/include/swift/Runtime/Metadata.h @@ -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. /// diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index bccf83b3143cd..acca964880c1e 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -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; } diff --git a/lib/AST/Builtins.cpp b/lib/AST/Builtins.cpp index fcbd6bd1f8a24..f81fd6730e16b 100644 --- a/lib/AST/Builtins.cpp +++ b/lib/AST/Builtins.cpp @@ -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( @@ -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); diff --git a/lib/IRGen/GenBuiltin.cpp b/lib/IRGen/GenBuiltin.cpp index 141e42c4c9112..eef0a60131e4c 100644 --- a/lib/IRGen/GenBuiltin.cpp +++ b/lib/IRGen/GenBuiltin.cpp @@ -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(); diff --git a/lib/IRGen/GenConcurrency.cpp b/lib/IRGen/GenConcurrency.cpp index a8761fa695620..7c61ebe8acdab 100644 --- a/lib/IRGen/GenConcurrency.cpp +++ b/lib/IRGen/GenConcurrency.cpp @@ -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. @@ -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(), {}); diff --git a/lib/IRGen/GenConcurrency.h b/lib/IRGen/GenConcurrency.h index 37996d7eb3fdb..450185df317c3 100644 --- a/lib/IRGen/GenConcurrency.h +++ b/lib/IRGen/GenConcurrency.h @@ -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); diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index 9ee373660956c..eff862b2478d3 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -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) diff --git a/lib/SIL/IR/ValueOwnership.cpp b/lib/SIL/IR/ValueOwnership.cpp index 0facc49ef7f7c..e9889cd1325ea 100644 --- a/lib/SIL/IR/ValueOwnership.cpp +++ b/lib/SIL/IR/ValueOwnership.cpp @@ -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) diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index ecf5f40ff4857..66c34911cdfb6 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -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: diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 920133d0b2590..0a4b751230e1d 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2052,6 +2052,7 @@ class SILVerifier : public SILVerifierBase { } if (builtinKind == BuiltinValueKind::BuildOrdinarySerialExecutorRef || + builtinKind == BuiltinValueKind::BuildComplexEqualitySerialExecutorRef || builtinKind == BuiltinValueKind::BuildDefaultActorExecutorRef) { require(arguments.size() == 1, "builtin expects one argument"); diff --git a/lib/SILGen/SILGenBuiltin.cpp b/lib/SILGen/SILGenBuiltin.cpp index 767c91b798ad0..a93933f5cca20 100644 --- a/lib/SILGen/SILGenBuiltin.cpp +++ b/lib/SILGen/SILGenBuiltin.cpp @@ -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 args, SGFContext C) { + return emitBuildExecutorRef(SGF, loc, subs, args, + BuiltinValueKind::BuildComplexEqualitySerialExecutorRef); +} static ManagedValue emitBuiltinBuildDefaultActorExecutorRef( SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs, ArrayRef args, SGFContext C) { diff --git a/lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp b/lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp index cbf9f870da6fc..01069fe3e2774 100644 --- a/lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp +++ b/lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp @@ -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: diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 4bb5d22377e8e..378fee0284486 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -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. /// diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index c3af38760fb42..6378a8e8c6b20 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -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(currentExecutor.getSerialExecutorWitnessTable()), + reinterpret_cast(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: diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index ae15510560b7e..49c1390dddc4e 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -60,6 +60,27 @@ public protocol SerialExecutor: Executor { /// executor references. @available(SwiftStdlib 5.9, *) func asUnownedSerialExecutor() -> UnownedSerialExecutor + + /// If this executor has complex equality semantics, and the runtime needs to compare + /// two executors, it will first attempt the usual pointer-based equality check, + /// and if it fails it will compare the types of both executors, if they are the same, + /// it will finally invoke this method, in an attempt to let the executor itself decide + /// if this and the `other` executor represent the same serial, exclusive, isolation context. + /// + /// This method must be implemented with great care, as wrongly returning `true` would allow + /// code from a different execution context (e.g. thread) to execute code which was intended + /// to be isolated by another actor. + /// + /// This check is not used when performing executor switching. + /// + /// This check is used when performing `preconditionTaskOnActorExecutor`, `preconditionTaskOnActorExecutor`, + /// `assumeOnActorExecutor` and similar APIs which assert about the same "exclusive serial execution context". + /// + /// - Parameter other: the executor to compare with. + /// - Returns: true, if `self` and the `other` executor actually are mutually exclusive + /// and it is safe–from a concurrency perspective–to execute code assuming one on the other. + @available(SwiftStdlib 5.9, *) + func isSameExclusiveExecutionContext(other: Self) -> Bool } #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @@ -83,6 +104,16 @@ extension SerialExecutor { } } +@available(SwiftStdlib 5.9, *) +extension SerialExecutor { + + @available(SwiftStdlib 5.9, *) + public func isSameExclusiveExecutionContext(other: Self) -> Bool { + return self === other + } + +} + /// An unowned reference to a serial executor (a `SerialExecutor` /// value). /// @@ -125,6 +156,32 @@ public struct UnownedSerialExecutor: Sendable { #endif } + /// Opts the executor into complex "same exclusive execution context" equality checks. + /// + /// This means what when asserting or assuming executors, and the current and expected + /// executor are not the same instance (by object equality), the runtime may invoke + /// `isSameExclusiveExecutionContext` in order to compare the executors for equality. + /// + /// Implementing such complex equality can be useful if multiple executor instances + /// actually use the same underlying serialization context and can be therefore + /// safely treated as the same serial exclusive execution context (e.g. multiple + /// dispatch queues targeting the same serial queue). + @available(SwiftStdlib 5.9, *) + @inlinable + public init(complexEquality executor: __shared E) { + #if compiler(>=5.9) && $BuiltinBuildComplexEqualityExecutor + self.executor = Builtin.buildComplexEqualitySerialExecutorRef(executor) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } + + @_spi(ConcurrencyExecutors) + @available(SwiftStdlib 5.9, *) + public var _isComplexEquality: Bool { + _executor_isComplexEquality(self) + } + } /// Checks if the current task is running on the expected executor. @@ -139,6 +196,11 @@ public struct UnownedSerialExecutor: Sendable { @_silgen_name("swift_task_isOnExecutor") public func _taskIsOnExecutor(_ executor: Executor) -> Bool +@_spi(ConcurrencyExecutors) +@available(SwiftStdlib 5.9, *) +@_silgen_name("swift_executor_isComplexEquality") +public func _executor_isComplexEquality(_ executor: UnownedSerialExecutor) -> Bool + @available(SwiftStdlib 5.1, *) @_transparent public // COMPILER_INTRINSIC @@ -165,6 +227,22 @@ func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer, @_silgen_name("swift_task_getJobTaskId") internal func _getJobTaskId(_ job: UnownedJob) -> UInt64 +@available(SwiftStdlib 5.9, *) +@_silgen_name("_task_serialExecutor_isSameExclusiveExecutionContext") +internal func _task_serialExecutor_isSameExclusiveExecutionContext(current currentExecutor: E, executor: E) -> Bool + where E: SerialExecutor { + currentExecutor.isSameExclusiveExecutionContext(other: executor) +} + +/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`. +/// The obtained executor ref will have all the user-defined flags set on the executor. +@available(SwiftStdlib 5.9, *) +@_silgen_name("_task_serialExecutor_getExecutorRef") +internal func _task_serialExecutor_getExecutorRef(_ executor: E) -> Builtin.Executor + where E: SerialExecutor { + return executor.asUnownedSerialExecutor().executor +} + // Used by the concurrency runtime @available(SwiftStdlib 5.1, *) @_silgen_name("_swift_task_enqueueOnExecutor") diff --git a/stdlib/public/Concurrency/GlobalExecutor.cpp b/stdlib/public/Concurrency/GlobalExecutor.cpp index f58682b335734..031768b54731f 100644 --- a/stdlib/public/Concurrency/GlobalExecutor.cpp +++ b/stdlib/public/Concurrency/GlobalExecutor.cpp @@ -132,11 +132,20 @@ void swift::swift_task_enqueueGlobalWithDeadline( swift_task_enqueueGlobalWithDeadlineImpl(sec, nsec, tsec, tnsec, clock, job); } +// Implemented in Swift because we need to obtain the user-defined flags on the executor ref. +// +// We could inline this with effort, though. +extern "C" SWIFT_CC(swift) +ExecutorRef _task_serialExecutor_getExecutorRef( + HeapObject *executor, + const Metadata *selfType, + const SerialExecutorWitnessTable *wtable); + SWIFT_CC(swift) static bool swift_task_isOnExecutorImpl(HeapObject *executor, const Metadata *selfType, const SerialExecutorWitnessTable *wtable) { - auto executorRef = ExecutorRef::forOrdinary(executor, wtable); + auto executorRef = _task_serialExecutor_getExecutorRef(executor, selfType, wtable); return swift_task_isCurrentExecutor(executorRef); } @@ -150,6 +159,10 @@ bool swift::swift_task_isOnExecutor(HeapObject *executor, return swift_task_isOnExecutorImpl(executor, selfType, wtable); } +bool swift::swift_executor_isComplexEquality(ExecutorRef ref) { + return ref.isComplexEquality(); +} + uint64_t swift::swift_task_getJobTaskId(Job *job) { if (auto task = dyn_cast(job)) { // TaskID is actually: diff --git a/stdlib/public/Concurrency/TaskPrivate.h b/stdlib/public/Concurrency/TaskPrivate.h index 78ef6dce2722f..450fa2a7111d6 100644 --- a/stdlib/public/Concurrency/TaskPrivate.h +++ b/stdlib/public/Concurrency/TaskPrivate.h @@ -123,7 +123,7 @@ _swift_task_getDispatchQueueSerialExecutorWitnessTable() { #endif // The task listed as argument is escalated to a new priority. Pass that -// inforamtion along to the executor that it is enqueued into. +// information along to the executor that it is enqueued into. SWIFT_CC(swift) void swift_executor_escalate(ExecutorRef executor, AsyncTask *task, JobPriority newPriority); diff --git a/stdlib/public/Distributed/DistributedActor.swift b/stdlib/public/Distributed/DistributedActor.swift index 62cef63ac42f3..363dbf82be716 100644 --- a/stdlib/public/Distributed/DistributedActor.swift +++ b/stdlib/public/Distributed/DistributedActor.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Swift -@_spi(ConcurrencyExecutors) import _Concurrency +import _Concurrency // ==== Distributed Actor ----------------------------------------------------- diff --git a/stdlib/public/runtime/Metadata.cpp b/stdlib/public/runtime/Metadata.cpp index 73280d5a32ca7..7503d79d1b0f5 100644 --- a/stdlib/public/runtime/Metadata.cpp +++ b/stdlib/public/runtime/Metadata.cpp @@ -6659,6 +6659,11 @@ const RelativeWitnessTable *swift::swift_getAssociatedConformanceWitnessRelative assocConformance); } +bool swift::swift_compareWitnessTables(const WitnessTable *lhs, + const WitnessTable *rhs) { + return MetadataCacheKey::areWitnessTablesEqual(lhs, rhs); +} + bool swift::swift_compareProtocolConformanceDescriptors( const ProtocolConformanceDescriptor *lhs, const ProtocolConformanceDescriptor *rhs) { diff --git a/stdlib/public/runtime/MetadataCache.h b/stdlib/public/runtime/MetadataCache.h index 8f8099f8d6576..eb8a4862e7090 100644 --- a/stdlib/public/runtime/MetadataCache.h +++ b/stdlib/public/runtime/MetadataCache.h @@ -502,6 +502,7 @@ class MetadataCacheKey { GenericSignatureLayout Layout; uint32_t Hash; +public: /// Compare two witness tables, which may involving checking the /// contents of their conformance descriptors. static bool areWitnessTablesEqual(const WitnessTable *awt, @@ -520,7 +521,6 @@ class MetadataCacheKey { return areConformanceDescriptorsEqual(aDescription, bDescription); } -public: static void installGenericArguments(uint16_t numKeyArguments, uint16_t numPacks, const GenericPackShapeDescriptor *packShapeDescriptors, const void **dst, const void * const *src); diff --git a/test/Concurrency/Runtime/custom_executors_complex_equality.swift b/test/Concurrency/Runtime/custom_executors_complex_equality.swift new file mode 100644 index 0000000000000..5d74caeaf1b40 --- /dev/null +++ b/test/Concurrency/Runtime/custom_executors_complex_equality.swift @@ -0,0 +1,93 @@ +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s + +// REQUIRES: concurrency +// REQUIRES: executable_test +// REQUIRES: libdispatch + +// rdar://106849189 move-only types should be supported in freestanding mode +// UNSUPPORTED: freestanding + +// FIXME: rdar://107112715 test failing on iOS simulator, investigating +// UNSUPPORTED: OS=ios + +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: concurrency_runtime + +@preconcurrency import Dispatch +@_spi(ConcurrencyExecutors) import _Concurrency + +final class NaiveQueueExecutor: SerialExecutor, CustomStringConvertible { + let name: String + let queue: DispatchQueue + + init(name: String, _ queue: DispatchQueue) { + self.name = name + self.queue = queue + } + + public func enqueue(_ unowned: UnownedJob) { + print("\(self): enqueue") + queue.sync { + unowned.runSynchronously(on: self.asUnownedSerialExecutor()) + } + } + + public func asUnownedSerialExecutor() -> UnownedSerialExecutor { + let ref = UnownedSerialExecutor(complexEquality: self) + precondition(ref._isComplexEquality, "expected the ref to have complex equality") + return ref + } + + public func isSameExclusiveExecutionContext(other: NaiveQueueExecutor) -> Bool { + if Set([self.name, other.name]) == Set(["one", "two"]) { + // those we consider equal + print("isSameExclusiveExecutionContext: consider 'one' and 'two' executors as equal context") + return true + } else { + return false + } + } + + var description: Swift.String { + "NaiveQueueExecutor(\(name), \(queue))" + } +} + +actor MyActor { + + nonisolated let executor: NaiveQueueExecutor + nonisolated var unownedExecutor: UnownedSerialExecutor { + print("Get executor of \(self): \(executor.asUnownedSerialExecutor())") + return executor.asUnownedSerialExecutor() + } + + init(executor: NaiveQueueExecutor) { + self.executor = executor + } + + func test(expectedExecutor: NaiveQueueExecutor, expectedQueue: DispatchQueue) { + preconditionTaskOnExecutor(expectedExecutor, message: "Expected deep equality to trigger for \(expectedExecutor) and our \(self.executor)") + print("\(Self.self): [\(self.executor.name)] on same context as [\(expectedExecutor.name)]") + } +} + +@main struct Main { + static func main() async { + print("begin") + let queue = DispatchQueue(label: "RootQueue") + let one = NaiveQueueExecutor(name: "one", queue) + let two = NaiveQueueExecutor(name: "two", queue) + let actor = MyActor(executor: one) + await actor.test(expectedExecutor: one, expectedQueue: queue) + await actor.test(expectedExecutor: two, expectedQueue: queue) + print("end") + } +} + +// CHECK: begin +// CHECK-NEXT: Get executor of main.MyActor: UnownedSerialExecutor(executor: +// CHECK-NEXT: NaiveQueueExecutor(one, +// CHECK-NEXT: MyActor: [one] on same context as [one] +// CHECK-NEXT: isSameExclusiveExecutionContext: consider 'one' and 'two' executors as equal context +// CHECK-NEXT: MyActor: [one] on same context as [two] +// CHECK-NEXT: end diff --git a/test/Concurrency/Runtime/custom_executors_complex_equality_crash.swift b/test/Concurrency/Runtime/custom_executors_complex_equality_crash.swift new file mode 100644 index 0000000000000..89f75a63f2d48 --- /dev/null +++ b/test/Concurrency/Runtime/custom_executors_complex_equality_crash.swift @@ -0,0 +1,99 @@ +// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library) + +// REQUIRES: concurrency +// REQUIRES: executable_test +// REQUIRES: libdispatch + +// rdar://106849189 move-only types should be supported in freestanding mode +// UNSUPPORTED: freestanding + +// FIXME: rdar://107112715 test failing on iOS simulator +// UNSUPPORTED: OS=ios + +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: concurrency_runtime + +@preconcurrency import Dispatch +@_spi(ConcurrencyExecutors) import _Concurrency +import StdlibUnittest + +final class NaiveQueueExecutor: SerialExecutor, CustomStringConvertible { + let name: String + let queue: DispatchQueue + + init(name: String, _ queue: DispatchQueue) { + self.name = name + self.queue = queue + } + + public func enqueue(_ job: __owned Job) { + let unowned = UnownedJob(job) + queue.sync { + unowned.runSynchronously(on: self.asUnownedSerialExecutor()) + } + } + + public func asUnownedSerialExecutor() -> UnownedSerialExecutor { + let ref = UnownedSerialExecutor(complexEquality: self) + precondition(ref._isComplexEquality, "expected the ref to have complex equality") + return ref + } + + public func isSameExclusiveExecutionContext(other: NaiveQueueExecutor) -> Bool { + if Set([self.name, other.name]) == Set(["one", "two"]) { + // those we consider equal + return true + } else { + return false + } + } + + var description: Swift.String { + "NaiveQueueExecutor(\(name), \(queue))" + } +} + +actor MyActor { + + nonisolated let executor: NaiveQueueExecutor + nonisolated var unownedExecutor: UnownedSerialExecutor { + return executor.asUnownedSerialExecutor() + } + + init(executor: NaiveQueueExecutor) { + self.executor = executor + } + + func test(expectedExecutor: NaiveQueueExecutor) { + preconditionTaskOnExecutor(expectedExecutor, message: "Expected deep equality to trigger for \(expectedExecutor) and our \(self.executor)") + print("\(Self.self): [\(self.executor.name)] on same context as [\(expectedExecutor.name)]") + } +} + +@main +struct Runner { + static func main() async { + let tests = TestSuite("Complex executor equality") + + let queue = DispatchQueue(label: "RootQueue") + let one = NaiveQueueExecutor(name: "one", queue) + let two = NaiveQueueExecutor(name: "two", queue) + + tests.test("isSameExclusiveContext=true, causes same executor checks to pass") { + let actor = MyActor(executor: one) + await actor.test(expectedExecutor: one) + await actor.test(expectedExecutor: two) + } + tests.test("isSameExclusiveContext=false, causes same executor checks to crash") { + expectCrashLater(withMessage: "Precondition failed: Incorrect actor executor assumption; " + + "Expected 'NaiveQueueExecutor(unknown)' executor. " + + "Expected deep equality to trigger for ") + + let unknown = NaiveQueueExecutor(name: "unknown", DispatchQueue(label: "unknown")) + let actor = MyActor(executor: one) + await actor.test(expectedExecutor: unknown) + } + + await runAllTestsAsync() + } +} diff --git a/test/Concurrency/Runtime/custom_executors_protocol.swift b/test/Concurrency/Runtime/custom_executors_protocol.swift index 054a968fb2dcc..a2841a734ad64 100644 --- a/test/Concurrency/Runtime/custom_executors_protocol.swift +++ b/test/Concurrency/Runtime/custom_executors_protocol.swift @@ -16,7 +16,7 @@ @preconcurrency import Dispatch protocol WithSpecifiedExecutor: Actor { - nonisolated var executor: SpecifiedExecutor { get } + nonisolated var executor: any SpecifiedExecutor { get } } protocol SpecifiedExecutor: SerialExecutor {} @@ -62,12 +62,12 @@ final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { actor MyActor: WithSpecifiedExecutor { - nonisolated let executor: SpecifiedExecutor + nonisolated let executor: any SpecifiedExecutor // Note that we don't have to provide the unownedExecutor in the actor itself. // We obtain it from the extension on `WithSpecifiedExecutor`. - init(executor: SpecifiedExecutor) { + init(executor: any SpecifiedExecutor) { self.executor = executor }