Skip to content

[Concurrency][SE-0471] Change isIsolatingCurrent... to return Bool? #81134

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
19 changes: 18 additions & 1 deletion include/swift/ABI/Executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
#ifndef SWIFT_ABI_EXECUTOR_H
#define SWIFT_ABI_EXECUTOR_H

#include <inttypes.h>
#include "swift/ABI/Actor.h"
#include "swift/ABI/HeapObject.h"
#include "swift/Runtime/Casting.h"
#include <inttypes.h>

namespace swift {
class AsyncContext;
Expand Down Expand Up @@ -413,6 +413,23 @@ class AsyncFunctionPointer {
uint32_t ExpectedContextSize;
};

/// Type-safe wrapper around the return value of `isIsolatingCurrentContext`.
enum class IsIsolatingCurrentContextDecision : int8_t {
// The function call could not determine if the current context is isolated
// by this executor or not. Default value for executors which do not implement
// `isIsolatingCurrentContext`.
Unknown,
// The current context is definitely not isolated by this executor.
NotIsolated,
// The current context is definitely isolated by this executor.
Isolated,
};

IsIsolatingCurrentContextDecision
getIsIsolatingCurrentContextDecisionFromInt(int8_t value);

StringRef getIsIsolatingCurrentContextDecisionNameStr(IsIsolatingCurrentContextDecision decision);

}

#endif
8 changes: 0 additions & 8 deletions include/swift/ABI/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -2984,14 +2984,6 @@ struct TargetProtocolConformanceDescriptor final
return Demangle::makeSymbolicMangledNameStringRef(this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->type);
}

/// True if this is a conformance to 'SerialExecutor' which has a non-default
/// (i.e. not the stdlib's default implementation) witness. This means that
/// the developer has implemented this method explicitly and we should prefer
/// calling it.
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext() const {
return Flags.hasNonDefaultSerialExecutorIsIsolatingCurrentContext();
}

/// Retrieve the protocol conformance of the global actor type to the
/// GlobalActor protocol.
const TargetProtocolConformanceDescriptor<Runtime> *
Expand Down
22 changes: 1 addition & 21 deletions include/swift/ABI/MetadataValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -754,14 +754,6 @@ class ConformanceFlags {
IsConformanceOfProtocolMask = 0x01u << 18,
HasGlobalActorIsolation = 0x01u << 19,

// Used to detect if this is a conformance to SerialExecutor that has
// an user defined implementation of 'isIsolatingCurrentContext'. This
// requirement is special in the sense that if a non-default impl is present
// we will avoid calling the `checkIsolated` method which would lead to a
// crash. In other words, this API "soft replaces" 'checkIsolated' so we
// must at runtime the presence of a non-default implementation.
HasNonDefaultSerialExecutorIsIsolatingCurrentContext = 0x01u << 20,

NumConditionalPackDescriptorsMask = 0xFFu << 24,
NumConditionalPackDescriptorsShift = 24
};
Expand Down Expand Up @@ -828,15 +820,7 @@ class ConformanceFlags {
: 0));
}

ConformanceFlags withHasNonDefaultSerialExecutorIsIsolatingCurrentContext(
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext) const {
return ConformanceFlags((Value & ~HasNonDefaultSerialExecutorIsIsolatingCurrentContext)
| (hasNonDefaultSerialExecutorIsIsolatingCurrentContext
? HasNonDefaultSerialExecutorIsIsolatingCurrentContext
: 0));
}

/// Retrieve the type reference kind kind.
/// Retrieve the type reference kind.
TypeReferenceKind getTypeReferenceKind() const {
return TypeReferenceKind(
(Value & TypeMetadataKindMask) >> TypeMetadataKindShift);
Expand Down Expand Up @@ -880,10 +864,6 @@ class ConformanceFlags {
return Value & HasGlobalActorIsolation;
}

bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext() const {
return Value & HasNonDefaultSerialExecutorIsIsolatingCurrentContext;
}

/// Retrieve the # of conditional requirements.
unsigned getNumConditionalRequirements() const {
return (Value & NumConditionalRequirementsMask)
Expand Down
10 changes: 2 additions & 8 deletions include/swift/Runtime/Concurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -777,12 +777,12 @@ bool swift_task_invokeSwiftCheckIsolated(SerialExecutorRef executor);

/// Invoke an executor's `isIsolatingCurrentContext` implementation;
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);
IsIsolatingCurrentContextDecision swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);

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

/// A count in nanoseconds.
using JobDelay = unsigned long long;
Expand Down Expand Up @@ -1041,12 +1041,6 @@ enum swift_task_is_current_executor_flag : uint64_t {
/// The routine MUST NOT assert on failure.
/// Even at the cost of not calling 'checkIsolated' if it is available.
MustNotAssert = 0x10,

/// The routine should use 'isIsolatingCurrentContext' function on the
/// 'expected' executor instead of `checkIsolated`.
///
/// This is a variant of `MustNotAssert`
UseIsIsolatingCurrentContext = 0x20,
};

SWIFT_EXPORT_FROM(swift_Concurrency)
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Runtime/ConcurrencyHooks.def
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ SWIFT_CONCURRENCY_HOOK(void, swift_task_enqueueGlobalWithDeadline,
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated,
SerialExecutorRef executor);

SWIFT_CONCURRENCY_HOOK(bool, swift_task_isIsolatingCurrentContext,
SWIFT_CONCURRENCY_HOOK(IsIsolatingCurrentContextDecision, swift_task_isIsolatingCurrentContext,
SerialExecutorRef executor);

SWIFT_CONCURRENCY_HOOK(bool, swift_task_isOnExecutor,
Expand Down
26 changes: 0 additions & 26 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2225,7 +2225,6 @@ namespace {
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
Flags = Flags.withHasGlobalActorIsolation(isolation.isGlobalActor());
Flags = withSerialExecutorCheckingModeFlags(Flags, conf);
} else {
Flags = Flags.withIsRetroactive(false)
.withIsSynthesizedNonUnique(false);
Expand Down Expand Up @@ -2442,31 +2441,6 @@ namespace {
B.addRelativeAddress(globalActorConformanceDescriptor);
}

static ConformanceFlags
withSerialExecutorCheckingModeFlags(ConformanceFlags Flags, const NormalProtocolConformance *conf) {
ProtocolDecl *proto = conf->getProtocol();
auto &C = proto->getASTContext();

ConformanceFlags UpdatedFlags = Flags;
if (proto->isSpecificProtocol(swift::KnownProtocolKind::SerialExecutor)) {
conf->forEachValueWitness([&](const ValueDecl *req,
Witness witness) {
bool nameMatch = witness.getDecl()->getBaseIdentifier() == C.Id_isIsolatingCurrentContext;
if (nameMatch) {
if (DeclContext *NominalOrExtension = witness.getDecl()->getDeclContext()) {
// If the witness is NOT the default implementation in the _Concurrency library,
// we should record that this is an user provided implementation and we should call it.
bool hasNonDefaultIsIsolatingCurrentContext =
!NominalOrExtension->getParentModule()->isConcurrencyModule();
UpdatedFlags = UpdatedFlags.withHasNonDefaultSerialExecutorIsIsolatingCurrentContext(
hasNonDefaultIsIsolatingCurrentContext);
}
}
});
}

return UpdatedFlags;
}
};
}

Expand Down
94 changes: 32 additions & 62 deletions stdlib/public/Concurrency/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,6 @@ static void _swift_task_debug_dumpIsCurrentExecutorFlags(
if (options.contains(swift_task_is_current_executor_flag::Assert))
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
hint, "Assert");
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext))
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
hint, "UseIsIsolatingCurrentContext");
}

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

/// Check the executor's witness table for specific information about e.g.
/// being able ignore `checkIsolated` and only call `isIsolatingCurrentContext`.
static swift_task_is_current_executor_flag
_getIsolationCheckingOptionsFromExecutorWitnessTable(const SerialExecutorWitnessTable *_wtable) {
const WitnessTable* wtable = reinterpret_cast<const WitnessTable*>(_wtable);
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
auto description = lookThroughOptionalConditionalWitnessTable(
reinterpret_cast<const RelativeWitnessTable*>(wtable))
->getDescription();
#else
auto description = wtable->getDescription();
#endif
if (!description) {
return swift_task_is_current_executor_flag::None;
}

if (description->hasNonDefaultSerialExecutorIsIsolatingCurrentContext()) {
// The specific executor has implemented `isIsolatingCurrentContext` and
// we do not have to call `checkIsolated`.
return swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext;
}

// No changes to the checking mode.
return swift_task_is_current_executor_flag::None;
}

SWIFT_CC(swift)
static bool swift_task_isCurrentExecutorWithFlagsImpl(
SerialExecutorRef expectedExecutor,
swift_task_is_current_executor_flag flags) {
auto current = ExecutorTrackingInfo::current();
if (expectedExecutor.getIdentity() && expectedExecutor.hasSerialExecutorWitnessTable()) {
if (auto *wtable = expectedExecutor.getSerialExecutorWitnessTable()) {
auto executorSpecificMode = _getIsolationCheckingOptionsFromExecutorWitnessTable(wtable);
flags = swift_task_is_current_executor_flag(flags | executorSpecificMode);
}
}

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

if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext)) {
SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext",
expectedExecutor.getIdentity());
// The executor has the most recent 'isIsolatingCurrentContext' API
// so available so we prefer calling that to 'checkIsolated'.
auto result = swift_task_isIsolatingCurrentContext(expectedExecutor);

SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
expectedExecutor.getIdentity(), result ? "pass" : "fail");
return result;
// Invoke the 'isIsolatingCurrentContext', if "undecided" (i.e. nil), we need to make further calls
SWIFT_TASK_DEBUG_LOG("executor checking, invoke (%p).isIsolatingCurrentContext",
expectedExecutor.getIdentity());
// The executor has the most recent 'isIsolatingCurrentContext' API
// so available so we prefer calling that to 'checkIsolated'.
auto isIsolatingCurrentContextDecision = swift_task_isIsolatingCurrentContext(expectedExecutor);

SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
expectedExecutor.getIdentity(), getIsIsolatingCurrentContextDecisionNameStr(isIsolatingCurrentContextDecision));
switch (isIsolatingCurrentContextDecision) {
case IsIsolatingCurrentContextDecision::Isolated:
// We know for sure that this serial executor is isolating this context, return the decision.
return true;
case IsIsolatingCurrentContextDecision::NotIsolated:
// We know for sure that this serial executor is NOT isolating this context, return this decision.
return false;
case IsIsolatingCurrentContextDecision::Unknown:
// We don't know, so we have to continue trying to check using other methods.
// This most frequently would happen if a serial executor did not implement isIsolatingCurrentContext.
break;
}

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

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

bool checkResult = swift_task_isIsolatingCurrentContext(expectedExecutor);
const auto isIsolatingCurrentContextDecision = swift_task_isIsolatingCurrentContext(expectedExecutor);

SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p",
expectedExecutor.getIdentity(), checkResult ? "pass" : "fail");
return checkResult;
} else {
SWIFT_TASK_DEBUG_LOG("executor checking: can NOT call (%p).isIsolatingCurrentContext",
expectedExecutor.getIdentity());
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p",
expectedExecutor.getIdentity(), getIsIsolatingCurrentContextDecisionNameStr(isIsolatingCurrentContextDecision));
switch (isIsolatingCurrentContextDecision) {
case IsIsolatingCurrentContextDecision::Isolated:
return true;
case IsIsolatingCurrentContextDecision::NotIsolated:
return false;
case IsIsolatingCurrentContextDecision::Unknown:
break;
}

// This provides a last-resort check by giving the expected SerialExecutor the
Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/Concurrency/ConcurrencyHooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ swift::swift_task_checkIsolated(SerialExecutorRef executor) {
swift_task_checkIsolatedOrig(executor);
}

SWIFT_CC(swift) static bool
SWIFT_CC(swift) static IsIsolatingCurrentContextDecision
swift_task_isIsolatingCurrentContextOrig(SerialExecutorRef executor) {
return swift_task_isIsolatingCurrentContextImpl(
*reinterpret_cast<SwiftExecutorRef *>(&executor));
}

bool
IsIsolatingCurrentContextDecision
swift::swift_task_isIsolatingCurrentContext(SerialExecutorRef executor) {
if (SWIFT_UNLIKELY(swift_task_isIsolatingCurrentContext_hook))
return swift_task_isIsolatingCurrentContext_hook(executor, swift_task_isIsolatingCurrentContextOrig);
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
}

SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
IsIsolatingCurrentContextDecision swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
}

Expand Down
34 changes: 27 additions & 7 deletions stdlib/public/Concurrency/Executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,25 @@ public protocol SerialExecutor: Executor {
@available(SwiftStdlib 6.0, *)
func checkIsolated()

/// Checks if the current execution context is isolated by this executor.
///
/// This function can be called by the runtime in order to perform assertions,
/// or attempt to issue warnings about unexpected isolation.
///
/// This method will be invoked _before_ `checkIsolated` and may also be invoked
/// when crashing is not an acceptable outcome of a check (e.g. when attempting to issue isolation _warnings_).
///
/// Implementations should prefer to implement this method rather than `checkIsolated()` since it can often
/// result in more tailored error messages. It is allowed, and useful for backwards compatibility with old
/// runtimes which are not able to invoke `isIsolatingCurrentContext()` to implement `checkIsolated()`,
/// even if an implementation is able to implement this method. Often times an implementation of `checkIsolated()`,
/// would then invoke `isIsolatingCurrentContext()` and crash if the returned value was `false`.
///
/// The default implementation returns `nil` is used to indicate that it is "unknown" if the current context is
/// isolated by this serial executor. The runtime then _may_ proceed to invoke `checkIsolated()` as a last-resort
/// attempt to verify the isolation of the current context.
@available(SwiftStdlib 6.2, *)
func isIsolatingCurrentContext() -> Bool
func isIsolatingCurrentContext() -> Bool?

}

Expand Down Expand Up @@ -381,9 +398,8 @@ extension SerialExecutor {
extension SerialExecutor {

@available(SwiftStdlib 6.2, *)
public func isIsolatingCurrentContext() -> Bool {
self.checkIsolated()
return true
public func isIsolatingCurrentContext() -> Bool? {
return nil
}
}

Expand Down Expand Up @@ -865,9 +881,13 @@ internal func _task_serialExecutor_checkIsolated<E>(executor: E)

@available(SwiftStdlib 6.2, *)
@_silgen_name("_task_serialExecutor_isIsolatingCurrentContext")
internal func _task_serialExecutor_isIsolatingCurrentContext<E>(executor: E) -> Bool
where E: SerialExecutor {
return executor.isIsolatingCurrentContext()
internal func _task_serialExecutor_isIsolatingCurrentContext<E>(executor: E) -> Int8
where E: SerialExecutor {
switch executor.isIsolatingCurrentContext() {
case nil: -1 // unknown
case .some(false): 0 // not isolated
case .some(true): 1 // isolated!
}
}

/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
Expand Down
Loading