Skip to content

Commit c4115cc

Browse files
authored
Merge pull request #39115 from mikeash/voucher-propagation
[Concurrency] Propagate Darwin vouchers across async tasks.
2 parents 32d416a + a80a19b commit c4115cc

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
@@ -592,15 +592,18 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
592592
// Initialize the task so that resuming it will run the given
593593
// function on the initial context.
594594
AsyncTask *task = nullptr;
595+
bool captureCurrentVoucher = taskCreateFlags.copyTaskLocals() || jobFlags.task_isChildTask();
595596
if (asyncLet) {
596597
// Initialize the refcount bits to "immortal", so that
597598
// ARC operations don't have any effect on the task.
598599
task = new(allocation) AsyncTask(&taskHeapMetadata,
599600
InlineRefCounts::Immortal, jobFlags,
600-
function, initialContext);
601+
function, initialContext,
602+
captureCurrentVoucher);
601603
} else {
602604
task = new(allocation) AsyncTask(&taskHeapMetadata, jobFlags,
603-
function, initialContext);
605+
function, initialContext,
606+
captureCurrentVoucher);
604607
}
605608

606609
// 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);
@@ -356,11 +364,13 @@ inline bool AsyncTask::isCancelled() const {
356364
}
357365

358366
inline void AsyncTask::flagAsRunning() {
367+
SWIFT_TASK_DEBUG_LOG("%p->flagAsRunning()", this);
359368
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
360369
while (true) {
361370
assert(!oldStatus.isRunning());
362371
if (oldStatus.isLocked()) {
363372
flagAsRunning_slow();
373+
adoptTaskVoucher(this);
364374
swift_task_enterThreadLocalContext(
365375
(char *)&_private().ExclusivityAccessSet[0]);
366376
return;
@@ -375,6 +385,7 @@ inline void AsyncTask::flagAsRunning() {
375385
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
376386
std::memory_order_relaxed,
377387
std::memory_order_relaxed)) {
388+
adoptTaskVoucher(this);
378389
swift_task_enterThreadLocalContext(
379390
(char *)&_private().ExclusivityAccessSet[0]);
380391
return;
@@ -383,13 +394,15 @@ inline void AsyncTask::flagAsRunning() {
383394
}
384395

385396
inline void AsyncTask::flagAsSuspended() {
397+
SWIFT_TASK_DEBUG_LOG("%p->flagAsSuspended()", this);
386398
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
387399
while (true) {
388400
assert(oldStatus.isRunning());
389401
if (oldStatus.isLocked()) {
390402
flagAsSuspended_slow();
391403
swift_task_exitThreadLocalContext(
392404
(char *)&_private().ExclusivityAccessSet[0]);
405+
restoreTaskVoucher(this);
393406
return;
394407
}
395408

@@ -404,6 +417,7 @@ inline void AsyncTask::flagAsSuspended() {
404417
std::memory_order_relaxed)) {
405418
swift_task_exitThreadLocalContext(
406419
(char *)&_private().ExclusivityAccessSet[0]);
420+
restoreTaskVoucher(this);
407421
return;
408422
}
409423
}

0 commit comments

Comments
 (0)