Skip to content

Commit 5e37cd0

Browse files
committed
initial impl with the but cannot do this
1 parent 766e623 commit 5e37cd0

15 files changed

+573
-35
lines changed

include/swift/ABI/Executor.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,12 @@ class SerialExecutorRef {
155155
return reinterpret_cast<DefaultActor*>(Identity);
156156
}
157157

158+
bool hasSerialExecutorWitnessTable() const {
159+
return !isGeneric() && !isDefaultActor();
160+
}
161+
158162
const SerialExecutorWitnessTable *getSerialExecutorWitnessTable() const {
159-
assert(!isGeneric() && !isDefaultActor());
163+
assert(hasSerialExecutorWitnessTable());
160164
auto table = Implementation & WitnessTableMask;
161165
return reinterpret_cast<const SerialExecutorWitnessTable*>(table);
162166
}

include/swift/Runtime/Concurrency.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,11 @@ void swift_task_enqueue(Job *job, SerialExecutorRef executor);
715715
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
716716
void swift_task_enqueueGlobal(Job *job);
717717

718+
/// Invoke an executor's `checkIsolated` or otherwise equivalent API,
719+
/// that will crash if the current executor is NOT the passed executor.
720+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
721+
void swift_task_checkIsolated(SerialExecutorRef executor);
722+
718723
/// A count in nanoseconds.
719724
using JobDelay = unsigned long long;
720725

@@ -729,6 +734,9 @@ void swift_task_enqueueGlobalWithDeadline(long long sec, long long nsec,
729734
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
730735
void swift_task_enqueueMainExecutor(Job *job);
731736

737+
/// WARNING: This method is expected to CRASH when caller is not on the
738+
/// expected executor.
739+
///
732740
/// Return true if the caller is running in a Task on the passed Executor.
733741
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
734742
bool swift_task_isOnExecutor(
@@ -781,6 +789,12 @@ SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDeadline_hook)(
781789
int clock, Job *job,
782790
swift_task_enqueueGlobalWithDeadline_original original);
783791

792+
typedef SWIFT_CC(swift) void (*swift_task_checkIsolated_original)(SerialExecutorRef executor);
793+
SWIFT_EXPORT_FROM(swift_Concurrency)
794+
SWIFT_CC(swift) void (*swift_task_checkIsolated_hook)(
795+
SerialExecutorRef executor, swift_task_checkIsolated_original original);
796+
797+
784798
typedef SWIFT_CC(swift) bool (*swift_task_isOnExecutor_original)(
785799
HeapObject *executor,
786800
const Metadata *selfType,

stdlib/public/Concurrency/Actor.cpp

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "../CompatibilityOverride/CompatibilityOverride.h"
2323
#include "swift/ABI/Actor.h"
2424
#include "swift/ABI/Task.h"
25+
#include "TaskPrivate.h"
2526
#include "swift/Basic/ListMerger.h"
2627
#include "swift/Concurrency/Actor.h"
2728
#include "swift/Runtime/AccessibleFunction.h"
@@ -314,39 +315,98 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
314315
const SerialExecutorWitnessTable *wtable);
315316

316317
SWIFT_CC(swift)
317-
static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef executor) {
318+
static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor) {
318319
auto current = ExecutorTrackingInfo::current();
319320

320321
if (!current) {
321322
// TODO(ktoso): checking the "is main thread" is not correct, main executor can be not main thread, relates to rdar://106188692
322-
return executor.isMainExecutor() && isExecutingOnMainThread();
323+
if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) {
324+
return true;
325+
} else {
326+
swift_Concurrency_fatalError(
327+
0,
328+
"Incorrect executor assumption, expected MainActor.executor (i.e. 'main thread')");
329+
}
323330
}
324331

325-
auto currentExecutor = current->getActiveExecutor();
326-
if (currentExecutor == executor) {
332+
SerialExecutorRef currentExecutor = current->getActiveExecutor();
333+
if (currentExecutor == expectedExecutor) {
334+
fprintf(stderr, "[%s:%d](%s) POINTER SAME\n", __FILE_NAME__, __LINE__, __FUNCTION__);
335+
return true;
336+
}
337+
fprintf(stderr, "[%s:%d](%s) POINTER IS NOT SAME::::\n", __FILE_NAME__, __LINE__, __FUNCTION__);
338+
fprintf(stderr, "[%s:%d](%s) POINTER IS NOT SAME: expectedExecutor.identity = %p; is main = %d\n", __FILE_NAME__, __LINE__, __FUNCTION__,
339+
expectedExecutor.getIdentity(),
340+
expectedExecutor.isMainExecutor());
341+
fprintf(stderr, "[%s:%d](%s) POINTER IS NOT SAME: current.identity = %p; is main = %d\n", __FILE_NAME__, __LINE__, __FUNCTION__,
342+
currentExecutor.getIdentity(),
343+
currentExecutor.isMainExecutor());
344+
if (currentExecutor.isMainExecutor() && expectedExecutor.isMainExecutor()) {
327345
return true;
328346
}
329347

330-
if (executor.isComplexEquality()) {
348+
if (expectedExecutor.isDefaultActor()) {
349+
fprintf(stderr, "[%s:%d](%s) EXPECTED IS DEFAULT ACTOR = %p\n", __FILE_NAME__, __LINE__, __FUNCTION__,
350+
expectedExecutor.getIdentity());
351+
fprintf(stderr, "[%s:%d](%s) EXPECTED ?? CURRENT::: %p == %p = (%d)\n", __FILE_NAME__, __LINE__, __FUNCTION__,
352+
expectedExecutor.getIdentity(),
353+
currentExecutor.getIdentity(),
354+
expectedExecutor.getIdentity() == currentExecutor.getIdentity());
355+
356+
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
357+
return false;
358+
}
359+
360+
if (expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) {
361+
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected MainActor expectedExecutor, but was different");
362+
return false;
363+
} else if (!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor()) {
364+
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected actor expectedExecutor other than MainActor expectedExecutor");
365+
return false;
366+
}
367+
368+
if (expectedExecutor.isComplexEquality()) {
331369
if (!swift_compareWitnessTables(
332370
reinterpret_cast<const WitnessTable*>(currentExecutor.getSerialExecutorWitnessTable()),
333-
reinterpret_cast<const WitnessTable*>(executor.getSerialExecutorWitnessTable()))) {
371+
reinterpret_cast<const WitnessTable*>(expectedExecutor.getSerialExecutorWitnessTable()))) {
334372
// different witness table, we cannot invoke complex equality call
335373
return false;
336374
}
337375
// Avoid passing nulls to Swift for the isSame check:
338-
if (!currentExecutor.getIdentity() || !executor.getIdentity()) {
376+
if (!currentExecutor.getIdentity() || !expectedExecutor.getIdentity()) {
339377
return false;
340378
}
341379

342380
return _task_serialExecutor_isSameExclusiveExecutionContext(
343381
currentExecutor.getIdentity(),
344-
executor.getIdentity(),
382+
expectedExecutor.getIdentity(),
345383
swift_getObjectType(currentExecutor.getIdentity()),
346-
executor.getSerialExecutorWitnessTable());
384+
expectedExecutor.getSerialExecutorWitnessTable());
347385
}
348386

349-
return false;
387+
// This provides a last-resort check by giving the expected SerialExecutor the
388+
// chance to perform a check using some external knowledge if perhaps we are,
389+
// after all, on this executor, but the Swift concurrency runtime was just not
390+
// aware.
391+
//
392+
// Unless handled in `swift_task_checkIsolated` directly, this should call
393+
// through to the executor's `SerialExecutor.checkIsolated`.
394+
//
395+
// This call is expected to CRASH, unless it has some way of proving that
396+
// we're actually indeed running on this executor.
397+
//
398+
// For example, when running outside of Swift concurrency tasks, but trying to
399+
// `MainActor.assumeIsolated` while executing DIRECTLY on the main dispatch
400+
// queue, this allows Dispatch to check for this using its own tracking
401+
// mechanism, and thus allow the assumeIsolated to work correctly, even though
402+
// the code executing is not even running inside a Task.
403+
//
404+
// Note that this only works because the closure in assumeIsolated is
405+
// synchronous, and will not cause suspensions, as that would require the
406+
// presence of a Task.
407+
swift_task_checkIsolated(expectedExecutor);
408+
409+
return true;
350410
}
351411

352412
/// Logging level for unexpected executors:

stdlib/public/Concurrency/CooperativeGlobalExecutor.inc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ static void insertDelayedJob(Job *newJob, JobDeadline deadline) {
136136
*position = newJob;
137137
}
138138

139+
SWIFT_CC(swift)
140+
static void swift_task_checkIsolatedImpl(SerialExecutorRef executor) {
141+
_task_serialExecutor_checkIsolated(
142+
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
143+
executor.getSerialExecutorWitnessTable());
144+
}
145+
139146
/// Insert a job into the cooperative global queue with a delay.
140147
SWIFT_CC(swift)
141148
static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,

stdlib/public/Concurrency/DispatchGlobalExecutor.inc

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
/// swift_task_enqueueGlobalImpl
1919
/// swift_task_enqueueGlobalWithDelayImpl
2020
/// swift_task_enqueueMainExecutorImpl
21+
/// swift_task_checkIsolated
2122
/// as well as any Dispatch-specific functions for the runtime.
2223
///
2324
///===------------------------------------------------------------------===///
@@ -233,7 +234,7 @@ static void swift_task_enqueueGlobalImpl(Job *job) {
233234

234235
SWIFT_CC(swift)
235236
static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,
236-
Job *job) {
237+
Job *job) {
237238
assert(job && "no job provided");
238239

239240
dispatch_function_t dispatchFunction = &__swift_run_job;
@@ -383,3 +384,43 @@ void swift::swift_task_enqueueOnDispatchQueue(Job *job,
383384
auto queue = reinterpret_cast<dispatch_queue_t>(_queue);
384385
dispatchEnqueue(queue, job, (dispatch_qos_class_t)priority, queue);
385386
}
387+
388+
/// Recognize if the SerialExecutor is specifically a `DispatchSerialQueue`
389+
/// by comparing witness tables and return it if true.
390+
static dispatch_queue_s *getAsDispatchSerialQueue(SerialExecutorRef executor) {
391+
if (!executor.hasSerialExecutorWitnessTable()) {
392+
return nullptr;
393+
}
394+
395+
auto executorWitnessTable = reinterpret_cast<const WitnessTable *>(
396+
executor.getSerialExecutorWitnessTable());
397+
auto serialQueueWitnessTable = reinterpret_cast<const WitnessTable *>(
398+
_swift_task_getDispatchQueueSerialExecutorWitnessTable());
399+
400+
if (swift_compareWitnessTables(executorWitnessTable,
401+
serialQueueWitnessTable)) {
402+
return reinterpret_cast<dispatch_queue_s *>(executor.getIdentity());
403+
} else {
404+
return nullptr;
405+
}
406+
}
407+
408+
/// If the executor is a `DispatchSerialQueue` we're able to invoke the
409+
/// dispatch's precondition API directly -- this is more efficient than going
410+
/// through the runtime call to end up calling the same API, and also allows us
411+
/// to perform this assertion on earlier platforms, where the `checkIsolated`
412+
/// requirement/witness was not shipping yet.
413+
SWIFT_CC(swift)
414+
static void swift_task_checkIsolatedImpl(SerialExecutorRef executor) {
415+
if (auto queue = getAsDispatchSerialQueue(executor)) {
416+
fprintf(stderr, "[%s:%d](%s) FALLBACK; dispatch_assert_queue\n", __FILE_NAME__, __LINE__, __FUNCTION__);
417+
dispatch_assert_queue(queue);
418+
} else if (executor.hasSerialExecutorWitnessTable()) {
419+
fprintf(stderr, "[%s:%d](%s) FALLBACK; checkExecutor call\n", __FILE_NAME__, __LINE__, __FUNCTION__);
420+
_task_serialExecutor_checkIsolated(
421+
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
422+
executor.getSerialExecutorWitnessTable());
423+
}
424+
425+
fprintf(stderr, "[%s:%d](%s) FALLBACK; CANNOT CHECK, assume OK\n", __FILE_NAME__, __LINE__, __FUNCTION__);
426+
}

stdlib/public/Concurrency/Executor.swift

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,42 @@ public protocol SerialExecutor: Executor {
100100
/// perspective–to execute code assuming one on the other.
101101
@available(SwiftStdlib 5.9, *)
102102
func isSameExclusiveExecutionContext(other: Self) -> Bool
103+
104+
/// Last resort "fallback" isolation check, called when the concurrency runtime
105+
/// is comparing executors e.g. during ``assumeIsolated()`` and is unable to prove
106+
/// serial equivalence between the expected (this object), and the current executor.
107+
///
108+
/// During executor comparison, the Swift concurrency runtime attempts to compare
109+
/// current and expected executors in a few ways (including "complex" equality
110+
/// between executors (see ``isSameExclusiveExecutionContext(other:)``), and if all
111+
/// those checks fail, this method is invoked on the expected executor.
112+
///
113+
/// This method MUST crash if it is unable to prove that the current execution
114+
/// context belongs to this executor. At this point usual executor comparison would
115+
/// have already failed, though the executor may have some external tracking of
116+
/// threads it owns, and may be able to prove isolation nevertheless.
117+
///
118+
/// A default implementation is provided that unconditionally crashes the
119+
/// program, and prevents calling code from proceeding with potentially
120+
/// not thread-safe execution.
121+
///
122+
/// - Warning: This method must crash and halt program execution if unable
123+
/// to prove the isolation of the calling context.
124+
@available(SwiftStdlib 5.11, *)
125+
func checkIsolated() // FIXME(Concurrency): pass through message: String
126+
127+
}
128+
129+
@available(SwiftStdlib 5.11, *)
130+
extension SerialExecutor {
131+
@available(SwiftStdlib 5.11, *)
132+
public func checkIsolated() {
133+
#if !$Embedded
134+
fatalError("Unexpected isolation context, expected to be executing on \(Self.self)")
135+
#else
136+
Builtin.int_trap()
137+
#endif
138+
}
103139
}
104140

105141
/// An executor that may be used as preferred executor by a task.
@@ -311,13 +347,32 @@ extension UnownedTaskExecutor: Equatable {
311347
}
312348
}
313349

314-
/// Checks if the current task is running on the expected executor.
350+
351+
/// Returns either `true` or will CRASH if called from a different executor
352+
/// than the passed `executor`.
353+
///
354+
/// - SeeAlso: ``_taskCheckOnExecutor(_:)``
355+
@available(SwiftStdlib 5.9, *)
356+
@_silgen_name("swift_task_isOnExecutor")
357+
@available(*, deprecated, renamed: "_taskCheckOnExecutor(_:)")
358+
internal func _taskIsOnExecutor<SE: SerialExecutor>(_ executor: SE) -> Bool // TODO: this used to be public? can we remove?
359+
360+
/// Returns either `true` or will CRASH if called from a different executor
361+
/// than the passed `executor`.
362+
///
363+
/// This method will attempt to verify the current executor against `executor`,
364+
/// and as a last-resort call through to `SerialExecutor.checkIsolated`.
365+
///
366+
/// This method will never return `false`. It either can verify we're on the
367+
/// correct executor, or will crash the program. It should be used in
368+
/// isolation correctness guaranteeing APIs.
315369
///
316370
/// Generally, Swift programs should be constructed such that it is statically
317371
/// known that a specific executor is used, for example by using global actors or
318372
/// custom executors. However, in some APIs it may be useful to provide an
319373
/// additional runtime check for this, especially when moving towards Swift
320374
/// concurrency from other runtimes which frequently use such assertions.
375+
///
321376
/// - Parameter executor: The expected executor.
322377
@available(SwiftStdlib 5.9, *)
323378
@_silgen_name("swift_task_isOnExecutor")
@@ -361,6 +416,13 @@ internal func _task_serialExecutor_isSameExclusiveExecutionContext<E>(current cu
361416
currentExecutor.isSameExclusiveExecutionContext(other: executor)
362417
}
363418

419+
@available(SwiftStdlib 9999, *)
420+
@_silgen_name("_task_serialExecutor_checkIsolated")
421+
internal func _task_serialExecutor_checkIsolated<E>(executor: E)
422+
where E: SerialExecutor {
423+
executor.checkIsolated()
424+
}
425+
364426
/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
365427
/// The obtained executor ref will have all the user-defined flags set on the executor.
366428
@available(SwiftStdlib 5.9, *)

stdlib/public/Concurrency/ExecutorAssertions.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,24 @@ extension Actor {
106106
return
107107
}
108108

109+
// _taskIsCurrentExecutor was designed for Builtin.Executor
110+
// OLD: let expectationCheck = _taskIsCurrentExecutor(self.unownedExecutor.executor)
111+
112+
// the _taskCheckOnExecutor that we're not using was designed for <SE: SerialExecutor>...
113+
// We could abuse this not-used entrypoint from always-emit-into-client `preconditionIsolated` funcs
114+
// but... how do we call it...?
115+
_taskCheckOnExecutor(???)
116+
// FIXME: how form such call from Swift:
117+
// HeapObject *executor,
118+
// const Metadata *selfType,
119+
// const SerialExecutorWitnessTable *wtable
120+
121+
// NOTE: This method will CRASH in new runtime versions,
122+
// if it would have previously returned `false`.
123+
// It will call through to SerialExecutor.checkIsolated` as a last resort.
109124
let expectationCheck = _taskIsCurrentExecutor(self.unownedExecutor.executor)
110125

111-
// TODO: offer information which executor we actually got
112126
precondition(expectationCheck,
113-
// TODO: figure out a way to get the typed repr out of the unowned executor
114127
"Incorrect actor executor assumption; Expected '\(self.unownedExecutor)' executor. \(message())",
115128
file: file, line: line)
116129
}
@@ -247,8 +260,6 @@ extension Actor {
247260
}
248261

249262
guard _taskIsCurrentExecutor(self.unownedExecutor.executor) else {
250-
// TODO: offer information which executor we actually got
251-
// TODO: figure out a way to get the typed repr out of the unowned executor
252263
let msg = "Incorrect actor executor assumption; Expected '\(self.unownedExecutor)' executor. \(message())"
253264
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
254265
assertionFailure(msg, file: file, line: line) // short-cut so we get the exact same failure reporting semantics

0 commit comments

Comments
 (0)