Skip to content

Commit 22b20e7

Browse files
committed
[Concurrency] Change isIsolatingCurrent... to return Bool?
This changes the isIsolatingCurrentContext function to return `Bool?` and removes all the witness table trickery we did previously to detect if it was implemented or not. This comes at a cost of trying to invoke it always, before `checkIsolated`, but it makes for an simpler implementation and more checkable even by third party Swift code which may want to ask this question. Along with the `withSerialExecutor` function, this now enables us to check the isolation at runtime when we have an `any Actor` e.g. from `#isolation`. Updates SE-0471 according to https://forums.swift.org/t/se-0471-improved-custom-serialexecutor-isolation-checking-for-concurrency-runtime/78834/ review discussions
1 parent 1bff8cd commit 22b20e7

20 files changed

+185
-175
lines changed

include/swift/ABI/Executor.h

+37-1
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
#ifndef SWIFT_ABI_EXECUTOR_H
1818
#define SWIFT_ABI_EXECUTOR_H
1919

20-
#include <inttypes.h>
20+
#include "../../../stdlib/public/Concurrency/Error.h"
21+
2122
#include "swift/ABI/Actor.h"
2223
#include "swift/ABI/HeapObject.h"
2324
#include "swift/Runtime/Casting.h"
25+
#include <inttypes.h>
2426

2527
namespace swift {
2628
class AsyncContext;
@@ -413,6 +415,40 @@ class AsyncFunctionPointer {
413415
uint32_t ExpectedContextSize;
414416
};
415417

418+
/// Type-safe wrapper around the return value of `isIsolatingCurrentContext`.
419+
enum class IsIsolatingCurrentContextDecision : int8_t {
420+
// The function call could not determine if the current context is isolated
421+
// by this executor or not. Default value for executors which do not implement
422+
// `isIsolatingCurrentContext`.
423+
Unknown,
424+
// The current context is definitely not isolated by this executor.
425+
NotIsolated,
426+
// The current context is definitely isolated by this executor.
427+
Isolated,
428+
};
429+
430+
inline IsIsolatingCurrentContextDecision
431+
getIsIsolatingCurrentContextDecisionFromInt(int8_t value) {
432+
switch (value) {
433+
case -1: return IsIsolatingCurrentContextDecision::Unknown;
434+
case 0: return IsIsolatingCurrentContextDecision::NotIsolated;
435+
case 1: return IsIsolatingCurrentContextDecision::Isolated;
436+
default:
437+
swift_Concurrency_fatalError(0, "Unexpected IsIsolatingCurrentContextDecision value");
438+
return IsIsolatingCurrentContextDecision::Unknown; // silence warning about missing return
439+
}
440+
}
441+
442+
inline StringRef getIsIsolatingCurrentContextDecisionNameStr(IsIsolatingCurrentContextDecision decision) {
443+
switch (decision) {
444+
case IsIsolatingCurrentContextDecision::Unknown: return "Unknown";
445+
case IsIsolatingCurrentContextDecision::NotIsolated: return "NotIsolated";
446+
case IsIsolatingCurrentContextDecision::Isolated: return "Isolated";
447+
}
448+
swift_Concurrency_fatalError(0, "Unexpected IsIsolatingCurrentContextDecision value");
449+
return "<Unexpected Value>"; // silence warning about missing return
450+
}
451+
416452
}
417453

418454
#endif

include/swift/ABI/Metadata.h

-8
Original file line numberDiff line numberDiff line change
@@ -2984,14 +2984,6 @@ struct TargetProtocolConformanceDescriptor final
29842984
return Demangle::makeSymbolicMangledNameStringRef(this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->type);
29852985
}
29862986

2987-
/// True if this is a conformance to 'SerialExecutor' which has a non-default
2988-
/// (i.e. not the stdlib's default implementation) witness. This means that
2989-
/// the developer has implemented this method explicitly and we should prefer
2990-
/// calling it.
2991-
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext() const {
2992-
return Flags.hasNonDefaultSerialExecutorIsIsolatingCurrentContext();
2993-
}
2994-
29952987
/// Retrieve the protocol conformance of the global actor type to the
29962988
/// GlobalActor protocol.
29972989
const TargetProtocolConformanceDescriptor<Runtime> *

include/swift/ABI/MetadataValues.h

+1-21
Original file line numberDiff line numberDiff line change
@@ -754,14 +754,6 @@ class ConformanceFlags {
754754
IsConformanceOfProtocolMask = 0x01u << 18,
755755
HasGlobalActorIsolation = 0x01u << 19,
756756

757-
// Used to detect if this is a conformance to SerialExecutor that has
758-
// an user defined implementation of 'isIsolatingCurrentContext'. This
759-
// requirement is special in the sense that if a non-default impl is present
760-
// we will avoid calling the `checkIsolated` method which would lead to a
761-
// crash. In other words, this API "soft replaces" 'checkIsolated' so we
762-
// must at runtime the presence of a non-default implementation.
763-
HasNonDefaultSerialExecutorIsIsolatingCurrentContext = 0x01u << 20,
764-
765757
NumConditionalPackDescriptorsMask = 0xFFu << 24,
766758
NumConditionalPackDescriptorsShift = 24
767759
};
@@ -828,15 +820,7 @@ class ConformanceFlags {
828820
: 0));
829821
}
830822

831-
ConformanceFlags withHasNonDefaultSerialExecutorIsIsolatingCurrentContext(
832-
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext) const {
833-
return ConformanceFlags((Value & ~HasNonDefaultSerialExecutorIsIsolatingCurrentContext)
834-
| (hasNonDefaultSerialExecutorIsIsolatingCurrentContext
835-
? HasNonDefaultSerialExecutorIsIsolatingCurrentContext
836-
: 0));
837-
}
838-
839-
/// Retrieve the type reference kind kind.
823+
/// Retrieve the type reference kind.
840824
TypeReferenceKind getTypeReferenceKind() const {
841825
return TypeReferenceKind(
842826
(Value & TypeMetadataKindMask) >> TypeMetadataKindShift);
@@ -880,10 +864,6 @@ class ConformanceFlags {
880864
return Value & HasGlobalActorIsolation;
881865
}
882866

883-
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext() const {
884-
return Value & HasNonDefaultSerialExecutorIsIsolatingCurrentContext;
885-
}
886-
887867
/// Retrieve the # of conditional requirements.
888868
unsigned getNumConditionalRequirements() const {
889869
return (Value & NumConditionalRequirementsMask)

include/swift/Runtime/Concurrency.h

+2-8
Original file line numberDiff line numberDiff line change
@@ -777,12 +777,12 @@ bool swift_task_invokeSwiftCheckIsolated(SerialExecutorRef executor);
777777

778778
/// Invoke an executor's `isIsolatingCurrentContext` implementation;
779779
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
780-
bool swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);
780+
IsIsolatingCurrentContextDecision swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);
781781

782782
/// Invoke a Swift executor's `isIsolatingCurrentContext` implementation; returns
783783
/// `true` if it invoked the Swift implementation, `false` otherwise.
784784
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
785-
bool swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor);
785+
IsIsolatingCurrentContextDecision swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor);
786786

787787
/// A count in nanoseconds.
788788
using JobDelay = unsigned long long;
@@ -1041,12 +1041,6 @@ enum swift_task_is_current_executor_flag : uint64_t {
10411041
/// The routine MUST NOT assert on failure.
10421042
/// Even at the cost of not calling 'checkIsolated' if it is available.
10431043
MustNotAssert = 0x10,
1044-
1045-
/// The routine should use 'isIsolatingCurrentContext' function on the
1046-
/// 'expected' executor instead of `checkIsolated`.
1047-
///
1048-
/// This is a variant of `MustNotAssert`
1049-
UseIsIsolatingCurrentContext = 0x20,
10501044
};
10511045

10521046
SWIFT_EXPORT_FROM(swift_Concurrency)

include/swift/Runtime/ConcurrencyHooks.def

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ SWIFT_CONCURRENCY_HOOK(void, swift_task_enqueueGlobalWithDeadline,
4747
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated,
4848
SerialExecutorRef executor);
4949

50-
SWIFT_CONCURRENCY_HOOK(bool, swift_task_isIsolatingCurrentContext,
50+
SWIFT_CONCURRENCY_HOOK(IsIsolatingCurrentContextDecision, swift_task_isIsolatingCurrentContext,
5151
SerialExecutorRef executor);
5252

5353
SWIFT_CONCURRENCY_HOOK(bool, swift_task_isOnExecutor,

lib/IRGen/GenProto.cpp

-26
Original file line numberDiff line numberDiff line change
@@ -2225,7 +2225,6 @@ namespace {
22252225
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
22262226
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
22272227
Flags = Flags.withHasGlobalActorIsolation(isolation.isGlobalActor());
2228-
Flags = withSerialExecutorCheckingModeFlags(Flags, conf);
22292228
} else {
22302229
Flags = Flags.withIsRetroactive(false)
22312230
.withIsSynthesizedNonUnique(false);
@@ -2442,31 +2441,6 @@ namespace {
24422441
B.addRelativeAddress(globalActorConformanceDescriptor);
24432442
}
24442443

2445-
static ConformanceFlags
2446-
withSerialExecutorCheckingModeFlags(ConformanceFlags Flags, const NormalProtocolConformance *conf) {
2447-
ProtocolDecl *proto = conf->getProtocol();
2448-
auto &C = proto->getASTContext();
2449-
2450-
ConformanceFlags UpdatedFlags = Flags;
2451-
if (proto->isSpecificProtocol(swift::KnownProtocolKind::SerialExecutor)) {
2452-
conf->forEachValueWitness([&](const ValueDecl *req,
2453-
Witness witness) {
2454-
bool nameMatch = witness.getDecl()->getBaseIdentifier() == C.Id_isIsolatingCurrentContext;
2455-
if (nameMatch) {
2456-
if (DeclContext *NominalOrExtension = witness.getDecl()->getDeclContext()) {
2457-
// If the witness is NOT the default implementation in the _Concurrency library,
2458-
// we should record that this is an user provided implementation and we should call it.
2459-
bool hasNonDefaultIsIsolatingCurrentContext =
2460-
!NominalOrExtension->getParentModule()->isConcurrencyModule();
2461-
UpdatedFlags = UpdatedFlags.withHasNonDefaultSerialExecutorIsIsolatingCurrentContext(
2462-
hasNonDefaultIsIsolatingCurrentContext);
2463-
}
2464-
}
2465-
});
2466-
}
2467-
2468-
return UpdatedFlags;
2469-
}
24702444
};
24712445
}
24722446

stdlib/public/Concurrency/Actor.cpp

+32-62
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,6 @@ static void _swift_task_debug_dumpIsCurrentExecutorFlags(
410410
if (options.contains(swift_task_is_current_executor_flag::Assert))
411411
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
412412
hint, "Assert");
413-
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext))
414-
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
415-
hint, "UseIsIsolatingCurrentContext");
416413
}
417414

418415
// Shimming call to Swift runtime because Swift Embedded does not have
@@ -470,13 +467,6 @@ swift_task_is_current_executor_flag swift_bincompat_selectDefaultIsCurrentExecut
470467
// Remove the assert option which is what would cause the "crash" mode
471468
options = swift_task_is_current_executor_flag(
472469
options & ~swift_task_is_current_executor_flag::Assert);
473-
} else if (strcmp(modeStr, "isIsolatingCurrentContext") == 0) {
474-
options = swift_task_is_current_executor_flag(
475-
options | swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext);
476-
// When we're using the isIsolatingCurrentContext we don't want to use crashing APIs,
477-
// so disable it explicitly.
478-
options = swift_task_is_current_executor_flag(
479-
options & ~swift_task_is_current_executor_flag::Assert);
480470
} else if (strcmp(modeStr, "crash") == 0 ||
481471
strcmp(modeStr, "swift6") == 0) {
482472
options = swift_task_is_current_executor_flag(
@@ -501,43 +491,11 @@ extern "C" SWIFT_CC(swift) void _swift_task_enqueueOnExecutor(
501491
Job *job, HeapObject *executor, const Metadata *executorType,
502492
const SerialExecutorWitnessTable *wtable);
503493

504-
/// Check the executor's witness table for specific information about e.g.
505-
/// being able ignore `checkIsolated` and only call `isIsolatingCurrentContext`.
506-
static swift_task_is_current_executor_flag
507-
_getIsolationCheckingOptionsFromExecutorWitnessTable(const SerialExecutorWitnessTable *_wtable) {
508-
const WitnessTable* wtable = reinterpret_cast<const WitnessTable*>(_wtable);
509-
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
510-
auto description = lookThroughOptionalConditionalWitnessTable(
511-
reinterpret_cast<const RelativeWitnessTable*>(wtable))
512-
->getDescription();
513-
#else
514-
auto description = wtable->getDescription();
515-
#endif
516-
if (!description) {
517-
return swift_task_is_current_executor_flag::None;
518-
}
519-
520-
if (description->hasNonDefaultSerialExecutorIsIsolatingCurrentContext()) {
521-
// The specific executor has implemented `isIsolatingCurrentContext` and
522-
// we do not have to call `checkIsolated`.
523-
return swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext;
524-
}
525-
526-
// No changes to the checking mode.
527-
return swift_task_is_current_executor_flag::None;
528-
}
529-
530494
SWIFT_CC(swift)
531495
static bool swift_task_isCurrentExecutorWithFlagsImpl(
532496
SerialExecutorRef expectedExecutor,
533497
swift_task_is_current_executor_flag flags) {
534498
auto current = ExecutorTrackingInfo::current();
535-
if (expectedExecutor.getIdentity() && expectedExecutor.hasSerialExecutorWitnessTable()) {
536-
if (auto *wtable = expectedExecutor.getSerialExecutorWitnessTable()) {
537-
auto executorSpecificMode = _getIsolationCheckingOptionsFromExecutorWitnessTable(wtable);
538-
flags = swift_task_is_current_executor_flag(flags | executorSpecificMode);
539-
}
540-
}
541499

542500
auto options = SwiftTaskIsCurrentExecutorOptions(flags);
543501
_swift_task_debug_dumpIsCurrentExecutorFlags(__FUNCTION__, flags);
@@ -557,16 +515,26 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
557515
// We cannot use 'complexEquality' as it requires two executor instances,
558516
// and we do not have a 'current' executor here.
559517

560-
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext)) {
561-
SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext",
562-
expectedExecutor.getIdentity());
563-
// The executor has the most recent 'isIsolatingCurrentContext' API
564-
// so available so we prefer calling that to 'checkIsolated'.
565-
auto result = swift_task_isIsolatingCurrentContext(expectedExecutor);
566-
567-
SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
568-
expectedExecutor.getIdentity(), result ? "pass" : "fail");
569-
return result;
518+
// Invoke the 'isIsolatingCurrentContext', if "undecided" (i.e. nil), we need to make further calls
519+
SWIFT_TASK_DEBUG_LOG("executor checking, invoke (%p).isIsolatingCurrentContext",
520+
expectedExecutor.getIdentity());
521+
// The executor has the most recent 'isIsolatingCurrentContext' API
522+
// so available so we prefer calling that to 'checkIsolated'.
523+
auto isIsolatingCurrentContextDecision = swift_task_isIsolatingCurrentContext(expectedExecutor);
524+
525+
SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
526+
expectedExecutor.getIdentity(), getIsIsolatingCurrentContextDecisionNameStr(isIsolatingCurrentContextDecision));
527+
switch (isIsolatingCurrentContextDecision) {
528+
case IsIsolatingCurrentContextDecision::Isolated:
529+
// We know for sure that this serial executor is isolating this context, return the decision.
530+
return true;
531+
case IsIsolatingCurrentContextDecision::NotIsolated:
532+
// We know for sure that this serial executor is NOT isolating this context, return this decision.
533+
return false;
534+
case IsIsolatingCurrentContextDecision::Unknown:
535+
// We don't know, so we have to continue trying to check using other methods.
536+
// This most frequently would happen if a serial executor did not implement isIsolatingCurrentContext.
537+
break;
570538
}
571539

572540
// Otherwise, as last resort, let the expected executor check using
@@ -675,18 +643,20 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
675643

676644
// Invoke the 'isIsolatingCurrentContext' function if we can; If so, we can
677645
// avoid calling the `checkIsolated` because their result will be the same.
678-
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext)) {
679-
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext",
680-
expectedExecutor.getIdentity());
646+
SWIFT_TASK_DEBUG_LOG("executor checking: call (%p).isIsolatingCurrentContext",
647+
expectedExecutor.getIdentity());
681648

682-
bool checkResult = swift_task_isIsolatingCurrentContext(expectedExecutor);
649+
const auto isIsolatingCurrentContextDecision = swift_task_isIsolatingCurrentContext(expectedExecutor);
683650

684-
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p",
685-
expectedExecutor.getIdentity(), checkResult ? "pass" : "fail");
686-
return checkResult;
687-
} else {
688-
SWIFT_TASK_DEBUG_LOG("executor checking: can NOT call (%p).isIsolatingCurrentContext",
689-
expectedExecutor.getIdentity());
651+
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p",
652+
expectedExecutor.getIdentity(), getIsIsolatingCurrentContextDecisionNameStr(isIsolatingCurrentContextDecision));
653+
switch (isIsolatingCurrentContextDecision) {
654+
case IsIsolatingCurrentContextDecision::Isolated:
655+
return true;
656+
case IsIsolatingCurrentContextDecision::NotIsolated:
657+
return false;
658+
case IsIsolatingCurrentContextDecision::Unknown:
659+
break;
690660
}
691661

692662
// This provides a last-resort check by giving the expected SerialExecutor the

stdlib/public/Concurrency/ConcurrencyHooks.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ swift::swift_task_checkIsolated(SerialExecutorRef executor) {
111111
swift_task_checkIsolatedOrig(executor);
112112
}
113113

114-
SWIFT_CC(swift) static bool
114+
SWIFT_CC(swift) static IsIsolatingCurrentContextDecision
115115
swift_task_isIsolatingCurrentContextOrig(SerialExecutorRef executor) {
116116
return swift_task_isIsolatingCurrentContextImpl(
117117
*reinterpret_cast<SwiftExecutorRef *>(&executor));
118118
}
119119

120-
bool
120+
IsIsolatingCurrentContextDecision
121121
swift::swift_task_isIsolatingCurrentContext(SerialExecutorRef executor) {
122122
if (SWIFT_UNLIKELY(swift_task_isIsolatingCurrentContext_hook))
123123
return swift_task_isIsolatingCurrentContext_hook(executor, swift_task_isIsolatingCurrentContextOrig);

stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
155155
}
156156

157157
SWIFT_CC(swift)
158-
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
158+
IsIsolatingCurrentContextDecision swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
159159
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
160160
}
161161

0 commit comments

Comments
 (0)