Skip to content

Commit a80a19b

Browse files
committed
[Concurrency] Propagate Darwin vouchers across async tasks.
Darwin OSes support vouchers, which are key/value sets that can be adopted on a thread to influence its execution, or sent to another process. APIs like Dispatch propagate vouchers to worker threads when running async code. This change makes Swift Concurrency do the same. The change consists of a few different parts: 1. A set of shims (in VoucherShims.h) which provides declarations for the necessary calls when they're not available from the SDK, and stub implementations for non-Darwin platforms. 2. One of Job's reserved fields is now used to store the voucher associated with a job. 3. Jobs grab the current thread's voucher when they're created. 4. A VoucherManager class manages adoption of vouchers when running a Job, and replacing vouchers in suspended tasks. 5. A VoucherManager instance is maintained in ExecutionTrackingInfo, and is updated as necessary throughout a Job/Task's lifecycle. rdar://76080222
1 parent 1f9fbed commit a80a19b

File tree

7 files changed

+647
-26
lines changed

7 files changed

+647
-26
lines changed

include/swift/ABI/Task.h

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/ABI/Metadata.h"
2424
#include "swift/ABI/MetadataValues.h"
2525
#include "swift/Runtime/Config.h"
26+
#include "swift/Runtime/VoucherShims.h"
2627
#include "swift/Basic/STLExtras.h"
2728
#include "bitset"
2829
#include "queue" // TODO: remove and replace with our own mpsc
@@ -72,7 +73,13 @@ class alignas(2 * alignof(void*)) Job :
7273
// Derived classes can use this to store a Job Id.
7374
uint32_t Id = 0;
7475

75-
void *Reserved[2] = {};
76+
/// The voucher associated with the job. Note: this is currently unused on
77+
/// non-Darwin platforms, with stub implementations of the functions for
78+
/// consistency.
79+
voucher_t Voucher = nullptr;
80+
81+
/// Reserved for future use.
82+
void *Reserved = nullptr;
7683

7784
// We use this union to avoid having to do a second indirect branch
7885
// when resuming an asynchronous task, which we expect will be the
@@ -88,23 +95,32 @@ class alignas(2 * alignof(void*)) Job :
8895
Job(JobFlags flags, JobInvokeFunction *invoke,
8996
const HeapMetadata *metadata = &jobHeapMetadata)
9097
: HeapObject(metadata), Flags(flags), RunJob(invoke) {
98+
Voucher = voucher_copy();
9199
assert(!isAsyncTask() && "wrong constructor for a task");
92100
}
93101

94102
Job(JobFlags flags, TaskContinuationFunction *invoke,
95-
const HeapMetadata *metadata = &jobHeapMetadata)
103+
const HeapMetadata *metadata = &jobHeapMetadata,
104+
bool captureCurrentVoucher = true)
96105
: HeapObject(metadata), Flags(flags), ResumeTask(invoke) {
106+
if (captureCurrentVoucher)
107+
Voucher = voucher_copy();
97108
assert(isAsyncTask() && "wrong constructor for a non-task job");
98109
}
99110

100111
/// Create a job with "immortal" reference counts.
101112
/// Used for async let tasks.
102113
Job(JobFlags flags, TaskContinuationFunction *invoke,
103-
const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal)
114+
const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal,
115+
bool captureCurrentVoucher = true)
104116
: HeapObject(metadata, immortal), Flags(flags), ResumeTask(invoke) {
117+
if (captureCurrentVoucher)
118+
Voucher = voucher_copy();
105119
assert(isAsyncTask() && "wrong constructor for a non-task job");
106120
}
107121

122+
~Job() { swift_voucher_release(Voucher); }
123+
108124
bool isAsyncTask() const {
109125
return Flags.isAsyncTask();
110126
}
@@ -229,8 +245,9 @@ class AsyncTask : public Job {
229245
/// Private.initialize separately.
230246
AsyncTask(const HeapMetadata *metadata, JobFlags flags,
231247
TaskContinuationFunction *run,
232-
AsyncContext *initialContext)
233-
: Job(flags, run, metadata),
248+
AsyncContext *initialContext,
249+
bool captureCurrentVoucher)
250+
: Job(flags, run, metadata, captureCurrentVoucher),
234251
ResumeContext(initialContext) {
235252
assert(flags.isAsyncTask());
236253
Id = getNextTaskId();
@@ -243,8 +260,9 @@ class AsyncTask : public Job {
243260
AsyncTask(const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal,
244261
JobFlags flags,
245262
TaskContinuationFunction *run,
246-
AsyncContext *initialContext)
247-
: Job(flags, run, metadata, immortal),
263+
AsyncContext *initialContext,
264+
bool captureCurrentVoucher)
265+
: Job(flags, run, metadata, immortal, captureCurrentVoucher),
248266
ResumeContext(initialContext) {
249267
assert(flags.isAsyncTask());
250268
Id = getNextTaskId();

include/swift/Runtime/VoucherShims.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===--- VoucherShims.h - Shims for OS vouchers --------------------*- C++ -*-//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Shims for interfacing with OS voucher calls.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_CONCURRENCY_VOUCHERSHIMS_H
18+
#define SWIFT_CONCURRENCY_VOUCHERSHIMS_H
19+
20+
#include "Config.h"
21+
22+
// swift-corelibs-libdispatch has os/voucher_private.h but it doesn't work for
23+
// us yet, so only look for it on Apple platforms.
24+
#if __APPLE__ && __has_include(<os/voucher_private.h>)
25+
#define SWIFT_HAS_VOUCHER_HEADER 1
26+
#include <os/voucher_private.h>
27+
#endif
28+
29+
// A "dead" voucher pointer, indicating that a voucher has been removed from
30+
// a Job, distinct from a NULL voucher that could just mean no voucher was
31+
// present. This allows us to catch problems like adopting a voucher from the
32+
// same Job twice without restoring it.
33+
#define SWIFT_DEAD_VOUCHER ((voucher_t)-1)
34+
35+
// The OS has voucher support if it has the header or if it has ObjC interop.
36+
#if SWIFT_HAS_VOUCHER_HEADER || SWIFT_OBJC_INTEROP
37+
#define SWIFT_HAS_VOUCHERS 1
38+
#endif
39+
40+
#if SWIFT_HAS_VOUCHERS
41+
42+
// If the header isn't available, declare the necessary calls here.
43+
#if !SWIFT_HAS_VOUCHER_HEADER
44+
45+
#include <os/object.h>
46+
47+
#pragma clang diagnostic push
48+
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
49+
OS_OBJECT_DECL_CLASS(voucher);
50+
#pragma clang diagnostic pop
51+
52+
extern "C" voucher_t _Nullable voucher_copy(void);
53+
54+
// Consumes argument, returns retained value.
55+
extern "C" voucher_t _Nullable voucher_adopt(voucher_t _Nullable voucher);
56+
57+
static inline bool voucher_needs_adopt(voucher_t _Nullable voucher) {
58+
return true;
59+
}
60+
61+
#endif // __has_include(<os/voucher_private.h>)
62+
63+
static inline void swift_voucher_release(voucher_t _Nullable voucher) {
64+
// This NULL check isn't necessary, but NULL vouchers will be common, so
65+
// optimize for that.
66+
if (!voucher)
67+
return;
68+
if (voucher == SWIFT_DEAD_VOUCHER)
69+
return;
70+
os_release(voucher);
71+
}
72+
73+
#else // __APPLE__
74+
75+
// Declare some do-nothing stubs for OSes without voucher support.
76+
typedef void *voucher_t;
77+
static inline voucher_t _Nullable voucher_copy(void) { return nullptr; }
78+
static inline voucher_t _Nullable voucher_adopt(voucher_t _Nullable voucher) {
79+
return nullptr;
80+
}
81+
static inline bool voucher_needs_adopt(voucher_t _Nullable voucher) {
82+
return true;
83+
}
84+
static inline void swift_voucher_release(voucher_t _Nullable voucher) {}
85+
#endif // __APPLE__
86+
87+
#endif

stdlib/public/Concurrency/Actor.cpp

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "llvm/Config/config.h"
3838
#include "llvm/ADT/PointerIntPair.h"
3939
#include "TaskPrivate.h"
40+
#include "VoucherSupport.h"
4041

4142
#if !SWIFT_CONCURRENCY_COOPERATIVE_GLOBAL_EXECUTOR
4243
#include <dispatch/dispatch.h>
@@ -132,6 +133,8 @@ class ExecutorTrackingInfo {
132133
/// unless the passed-in executor is generic.
133134
bool AllowsSwitching = true;
134135

136+
VoucherManager voucherManager;
137+
135138
/// The tracking info that was active when this one was entered.
136139
ExecutorTrackingInfo *SavedInfo;
137140

@@ -150,23 +153,9 @@ class ExecutorTrackingInfo {
150153
ActiveInfoInThread.set(this);
151154
}
152155

153-
/// Initialize a tracking state on the current thread if there
154-
/// isn't one already, or else update the current tracking state.
155-
///
156-
/// Returns a pointer to the active tracking info. If this is the
157-
/// same as the object on which this was called, leave() must be
158-
/// called before the object goes out of scope.
159-
ExecutorTrackingInfo *enterOrUpdate(ExecutorRef currentExecutor) {
160-
if (auto activeInfo = ActiveInfoInThread.get()) {
161-
activeInfo->ActiveExecutor = currentExecutor;
162-
return activeInfo;
163-
}
156+
void swapToJob(Job *job) { voucherManager.swapToJob(job); }
164157

165-
ActiveExecutor = currentExecutor;
166-
SavedInfo = nullptr;
167-
ActiveInfoInThread.set(this);
168-
return this;
169-
}
158+
void restoreVoucher(AsyncTask *task) { voucherManager.restoreVoucher(task); }
170159

171160
ExecutorRef getActiveExecutor() const {
172161
return ActiveExecutor;
@@ -192,6 +181,7 @@ class ExecutorTrackingInfo {
192181
}
193182

194183
void leave() {
184+
voucherManager.leave();
195185
ActiveInfoInThread.set(SavedInfo);
196186
}
197187
};
@@ -242,7 +232,9 @@ void swift::runJobInEstablishedExecutorContext(Job *job) {
242232
assert(ActiveTask::get() == nullptr &&
243233
"active task wasn't cleared before susspending?");
244234
} else {
245-
// There's no extra bookkeeping to do for simple jobs.
235+
// There's no extra bookkeeping to do for simple jobs besides swapping in
236+
// the voucher.
237+
ExecutorTrackingInfo::current()->swapToJob(job);
246238
job->runSimpleInFullyEstablishedContext();
247239
}
248240

@@ -253,6 +245,14 @@ void swift::runJobInEstablishedExecutorContext(Job *job) {
253245
_swift_tsan_release(job);
254246
}
255247

248+
void swift::adoptTaskVoucher(AsyncTask *task) {
249+
ExecutorTrackingInfo::current()->swapToJob(task);
250+
}
251+
252+
void swift::restoreTaskVoucher(AsyncTask *task) {
253+
ExecutorTrackingInfo::current()->restoreVoucher(task);
254+
}
255+
256256
SWIFT_CC(swift)
257257
AsyncTask *swift::swift_task_getCurrent() {
258258
return ActiveTask::get();
@@ -676,6 +676,12 @@ class DefaultActorImpl : public HeapObject {
676676

677677
friend class ProcessInlineJob;
678678
union {
679+
// When the ProcessInlineJob storage is initialized, its metadata pointer
680+
// will point to Job's metadata. When it isn't, the metadata pointer is
681+
// NULL. Use HeapObject to initialize the metadata pointer to NULL and allow
682+
// it to be checked without fully initializing the ProcessInlineJob.
683+
HeapObject JobStorageHeapObject{nullptr};
684+
679685
ProcessInlineJob JobStorage;
680686
};
681687

@@ -685,6 +691,7 @@ class DefaultActorImpl : public HeapObject {
685691
auto flags = Flags();
686692
flags.setIsDistributedRemote(isDistributedRemote);
687693
new (&CurrentState) std::atomic<State>(State{JobRef(), flags});
694+
JobStorageHeapObject.metadata = nullptr;
688695
}
689696

690697
/// Properly destruct an actor, except for the heap header.
@@ -812,6 +819,8 @@ void DefaultActorImpl::deallocate() {
812819
}
813820

814821
void DefaultActorImpl::deallocateUnconditional() {
822+
if (JobStorageHeapObject.metadata != nullptr)
823+
JobStorage.~ProcessInlineJob();
815824
auto metadata = cast<ClassMetadata>(this->metadata);
816825
swift_deallocObject(this, metadata->getInstanceSize(),
817826
metadata->getInstanceAlignMask());
@@ -848,6 +857,8 @@ void DefaultActorImpl::scheduleNonOverrideProcessJob(JobPriority priority,
848857
if (hasActiveInlineJob) {
849858
job = new ProcessOutOfLineJob(this, priority);
850859
} else {
860+
if (JobStorageHeapObject.metadata != nullptr)
861+
JobStorage.~ProcessInlineJob();
851862
job = new (&JobStorage) ProcessInlineJob(priority);
852863
}
853864
swift_task_enqueueGlobal(job);
@@ -1455,6 +1466,7 @@ static void swift_job_runImpl(Job *job, ExecutorRef executor) {
14551466

14561467
trackingInfo.enterAndShadow(executor);
14571468

1469+
SWIFT_TASK_DEBUG_LOG("%s(%p)", __func__, job);
14581470
runJobInEstablishedExecutorContext(job);
14591471

14601472
trackingInfo.leave();

stdlib/public/Concurrency/Task.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,15 +586,18 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
586586
// Initialize the task so that resuming it will run the given
587587
// function on the initial context.
588588
AsyncTask *task = nullptr;
589+
bool captureCurrentVoucher = taskCreateFlags.copyTaskLocals() || jobFlags.task_isChildTask();
589590
if (asyncLet) {
590591
// Initialize the refcount bits to "immortal", so that
591592
// ARC operations don't have any effect on the task.
592593
task = new(allocation) AsyncTask(&taskHeapMetadata,
593594
InlineRefCounts::Immortal, jobFlags,
594-
function, initialContext);
595+
function, initialContext,
596+
captureCurrentVoucher);
595597
} else {
596598
task = new(allocation) AsyncTask(&taskHeapMetadata, jobFlags,
597-
function, initialContext);
599+
function, initialContext,
600+
captureCurrentVoucher);
598601
}
599602

600603
// Initialize the child fragment if applicable.

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ void _swift_task_dealloc_specific(AsyncTask *task, void *ptr);
8282
/// related to the active task.
8383
void runJobInEstablishedExecutorContext(Job *job);
8484

85+
/// Adopt the voucher stored in `task`. This removes the voucher from the task
86+
/// and adopts it on the current thread.
87+
void adoptTaskVoucher(AsyncTask *task);
88+
89+
/// Restore the voucher for `task`. This un-adopts the current thread's voucher
90+
/// and stores it back into the task again.
91+
void restoreTaskVoucher(AsyncTask *task);
92+
8593
/// Initialize the async let storage for the given async-let child task.
8694
void asyncLet_addImpl(AsyncTask *task, AsyncLet *asyncLet,
8795
bool didAllocateInParentTask);
@@ -354,11 +362,13 @@ inline bool AsyncTask::isCancelled() const {
354362
}
355363

356364
inline void AsyncTask::flagAsRunning() {
365+
SWIFT_TASK_DEBUG_LOG("%p->flagAsRunning()", this);
357366
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
358367
while (true) {
359368
assert(!oldStatus.isRunning());
360369
if (oldStatus.isLocked()) {
361370
flagAsRunning_slow();
371+
adoptTaskVoucher(this);
362372
swift_task_enterThreadLocalContext(
363373
(char *)&_private().ExclusivityAccessSet[0]);
364374
return;
@@ -373,6 +383,7 @@ inline void AsyncTask::flagAsRunning() {
373383
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
374384
std::memory_order_relaxed,
375385
std::memory_order_relaxed)) {
386+
adoptTaskVoucher(this);
376387
swift_task_enterThreadLocalContext(
377388
(char *)&_private().ExclusivityAccessSet[0]);
378389
return;
@@ -381,13 +392,15 @@ inline void AsyncTask::flagAsRunning() {
381392
}
382393

383394
inline void AsyncTask::flagAsSuspended() {
395+
SWIFT_TASK_DEBUG_LOG("%p->flagAsSuspended()", this);
384396
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
385397
while (true) {
386398
assert(oldStatus.isRunning());
387399
if (oldStatus.isLocked()) {
388400
flagAsSuspended_slow();
389401
swift_task_exitThreadLocalContext(
390402
(char *)&_private().ExclusivityAccessSet[0]);
403+
restoreTaskVoucher(this);
391404
return;
392405
}
393406

@@ -402,6 +415,7 @@ inline void AsyncTask::flagAsSuspended() {
402415
std::memory_order_relaxed)) {
403416
swift_task_exitThreadLocalContext(
404417
(char *)&_private().ExclusivityAccessSet[0]);
418+
restoreTaskVoucher(this);
405419
return;
406420
}
407421
}

0 commit comments

Comments
 (0)