Skip to content

Commit db3967f

Browse files
committed
Future-proof the layout of AsyncTask.
1 parent 7739d78 commit db3967f

File tree

11 files changed

+253
-173
lines changed

11 files changed

+253
-173
lines changed

Diff for: include/swift/ABI/MetadataValues.h

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ enum {
4747
/// in a default actor.
4848
NumWords_DefaultActor = 10,
4949

50+
/// The number of words in a task.
51+
NumWords_AsyncTask = 16,
52+
5053
/// The number of words in a task group.
5154
NumWords_TaskGroup = 32,
5255

Diff for: include/swift/ABI/Task.h

+50-66
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class alignas(2 * alignof(void*)) Job :
126126
};
127127

128128
// The compiler will eventually assume these.
129-
#if defined(__LP64__) || defined(_WIN64)
129+
#if SWIFT_POINTER_IS_8_BYTES
130130
static_assert(sizeof(Job) == 6 * sizeof(void*),
131131
"Job size is wrong");
132132
#else
@@ -136,46 +136,6 @@ static_assert(sizeof(Job) == 8 * sizeof(void*),
136136
static_assert(alignof(Job) == 2 * alignof(void*),
137137
"Job alignment is wrong");
138138

139-
/// The current state of a task's status records.
140-
class ActiveTaskStatus {
141-
enum : uintptr_t {
142-
IsCancelled = 0x1,
143-
IsLocked = 0x2,
144-
RecordMask = ~uintptr_t(IsCancelled | IsLocked)
145-
};
146-
147-
uintptr_t Value;
148-
149-
public:
150-
constexpr ActiveTaskStatus() : Value(0) {}
151-
ActiveTaskStatus(TaskStatusRecord *innermostRecord,
152-
bool cancelled, bool locked)
153-
: Value(reinterpret_cast<uintptr_t>(innermostRecord)
154-
+ (locked ? IsLocked : 0)
155-
+ (cancelled ? IsCancelled : 0)) {}
156-
157-
/// Is the task currently cancelled?
158-
bool isCancelled() const { return Value & IsCancelled; }
159-
160-
/// Is there an active lock on the cancellation information?
161-
bool isLocked() const { return Value & IsLocked; }
162-
163-
/// Return the innermost cancellation record. Code running
164-
/// asynchronously with this task should not access this record
165-
/// without having first locked it; see swift_taskCancel.
166-
TaskStatusRecord *getInnermostRecord() const {
167-
return reinterpret_cast<TaskStatusRecord*>(Value & RecordMask);
168-
}
169-
170-
static TaskStatusRecord *getStatusRecordParent(TaskStatusRecord *ptr);
171-
172-
using record_iterator =
173-
LinkedListIterator<TaskStatusRecord, getStatusRecordParent>;
174-
llvm::iterator_range<record_iterator> records() const {
175-
return record_iterator::rangeBeginning(getInnermostRecord());
176-
}
177-
};
178-
179139
class NullaryContinuationJob : public Job {
180140

181141
private:
@@ -212,6 +172,16 @@ class NullaryContinuationJob : public Job {
212172
/// it can hold, and thus must be the *last* fragment.
213173
class AsyncTask : public Job {
214174
public:
175+
// On 32-bit targets, there is a word of tail padding remaining
176+
// in Job, and ResumeContext will fit into that, at offset 28.
177+
// Private then has offset 32.
178+
// On 64-bit targets, there is no tail padding in Job, and so
179+
// ResumeContext has offset 48. There is therefore another word
180+
// of reserved storage prior to Private (which needs to have
181+
// double-word alignment), which has offset 64.
182+
// We therefore converge and end up with 16 words of storage on
183+
// all platforms.
184+
215185
/// The context for resuming the job. When a task is scheduled
216186
/// as a job, the next continuation should be installed as the
217187
/// ResumeTask pointer in the job header, with this serving as
@@ -222,36 +192,57 @@ class AsyncTask : public Job {
222192
/// prevent it from being corrupted in flight.
223193
AsyncContext * __ptrauth_swift_task_resume_context ResumeContext;
224194

225-
/// The currently-active information about cancellation.
226-
std::atomic<ActiveTaskStatus> Status;
195+
#if SWIFT_POINTER_IS_8_BYTES
196+
void *Reserved64;
197+
#endif
227198

228-
/// Reserved for the use of the task-local stack allocator.
229-
void *AllocatorPrivate[4];
199+
struct PrivateStorage;
230200

231-
/// Task local values storage container.
232-
TaskLocal::Storage Local;
201+
/// Private storage for the use of the runtime.
202+
struct alignas(2 * alignof(void*)) OpaquePrivateStorage {
203+
void *Storage[8];
233204

205+
/// Initialize this storage during the creation of a task.
206+
void initialize(AsyncTask *task);
207+
void initializeWithSlab(AsyncTask *task,
208+
void *slab, size_t slabCapacity);
209+
210+
/// React to the completion of the enclosing task's execution.
211+
void complete(AsyncTask *task);
212+
213+
/// React to the final destruction of the enclosing task.
214+
void destroy();
215+
216+
PrivateStorage &get();
217+
const PrivateStorage &get() const;
218+
};
219+
PrivateStorage &_private();
220+
const PrivateStorage &_private() const;
221+
222+
OpaquePrivateStorage Private;
223+
224+
/// Create a task.
225+
/// This does not initialize Private; callers must call
226+
/// Private.initialize separately.
234227
AsyncTask(const HeapMetadata *metadata, JobFlags flags,
235228
TaskContinuationFunction *run,
236229
AsyncContext *initialContext)
237230
: Job(flags, run, metadata),
238-
ResumeContext(initialContext),
239-
Status(ActiveTaskStatus()),
240-
Local(TaskLocal::Storage()) {
231+
ResumeContext(initialContext) {
241232
assert(flags.isAsyncTask());
242233
Id = getNextTaskId();
243234
}
244235

245236
/// Create a task with "immortal" reference counts.
246237
/// Used for async let tasks.
238+
/// This does not initialize Private; callers must call
239+
/// Private.initialize separately.
247240
AsyncTask(const HeapMetadata *metadata, InlineRefCounts::Immortal_t immortal,
248241
JobFlags flags,
249242
TaskContinuationFunction *run,
250243
AsyncContext *initialContext)
251244
: Job(flags, run, metadata, immortal),
252-
ResumeContext(initialContext),
253-
Status(ActiveTaskStatus()),
254-
Local(TaskLocal::Storage()) {
245+
ResumeContext(initialContext) {
255246
assert(flags.isAsyncTask());
256247
Id = getNextTaskId();
257248
}
@@ -269,25 +260,18 @@ class AsyncTask : public Job {
269260

270261
/// Check whether this task has been cancelled.
271262
/// Checking this is, of course, inherently race-prone on its own.
272-
bool isCancelled() const {
273-
return Status.load(std::memory_order_relaxed).isCancelled();
274-
}
263+
bool isCancelled() const;
275264

276265
// ==== Task Local Values ----------------------------------------------------
277266

278267
void localValuePush(const HeapObject *key,
279-
/* +1 */ OpaqueValue *value, const Metadata *valueType) {
280-
Local.pushValue(this, key, value, valueType);
281-
}
268+
/* +1 */ OpaqueValue *value,
269+
const Metadata *valueType);
282270

283-
OpaqueValue* localValueGet(const HeapObject *key) {
284-
return Local.getValue(this, key);
285-
}
271+
OpaqueValue *localValueGet(const HeapObject *key);
286272

287273
/// Returns true if storage has still more bindings.
288-
bool localValuePop() {
289-
return Local.popValue(this);
290-
}
274+
bool localValuePop();
291275

292276
// ==== Child Fragment -------------------------------------------------------
293277

@@ -529,7 +513,7 @@ class AsyncTask : public Job {
529513
};
530514

531515
// The compiler will eventually assume these.
532-
static_assert(sizeof(AsyncTask) == 14 * sizeof(void*),
516+
static_assert(sizeof(AsyncTask) == NumWords_AsyncTask * sizeof(void*),
533517
"AsyncTask size is wrong");
534518
static_assert(alignof(AsyncTask) == 2 * alignof(void*),
535519
"AsyncTask alignment is wrong");

Diff for: include/swift/ABI/TaskStatus.h

-5
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ class TaskStatusRecord {
8282
}
8383
};
8484

85-
inline TaskStatusRecord *
86-
ActiveTaskStatus::getStatusRecordParent(TaskStatusRecord *ptr) {
87-
return ptr->getParent();
88-
}
89-
9085
/// A deadline for the task. If this is reached, the task will be
9186
/// automatically cancelled. The deadline can also be queried and used
9287
/// in other ways.

Diff for: include/swift/Basic/Compiler.h

+9
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,13 @@
128128
#define SWIFT_ASSERT_ONLY(...) do { __VA_ARGS__; } while (false)
129129
#endif
130130

131+
#if defined(__LP64__) || defined(_WIN64)
132+
#define SWIFT_POINTER_IS_8_BYTES 1
133+
#define SWIFT_POINTER_IS_4_BYTES 0
134+
#else
135+
// TODO: consider supporting 16-bit targets
136+
#define SWIFT_POINTER_IS_8_BYTES 0
137+
#define SWIFT_POINTER_IS_4_BYTES 1
138+
#endif
139+
131140
#endif // SWIFT_BASIC_COMPILER_H

Diff for: stdlib/public/Concurrency/AsyncLet.cpp

-5
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ static AsyncLetImpl *asImpl(const AsyncLet *alet) {
8686
const_cast<AsyncLet*>(alet));
8787
}
8888

89-
static AsyncLet *asAbstract(AsyncLetImpl *alet) {
90-
return reinterpret_cast<AsyncLet*>(alet);
91-
}
92-
9389
// =============================================================================
9490
// ==== start ------------------------------------------------------------------
9591

@@ -134,7 +130,6 @@ SWIFT_CC(swiftasync)
134130
static void swift_asyncLet_waitImpl(
135131
OpaqueValue *result, SWIFT_ASYNC_CONTEXT AsyncContext *rawContext,
136132
AsyncLet *alet, Metadata *T) {
137-
auto waitingTask = swift_task_getCurrent();
138133
auto task = alet->getTask();
139134
swift_task_future_wait(result, rawContext, task, T);
140135
}

Diff for: stdlib/public/Concurrency/Task.cpp

+18-33
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,7 @@ AsyncTask::~AsyncTask() {
194194
futureFragment()->destroy();
195195
}
196196

197-
// Release any objects potentially held as task local values.
198-
//
199-
// This must be called last when destroying a task - to keep stack discipline of the allocator.
200-
// because it may have created some task-locals immediately upon creation,
201-
// e.g. if the task is spawned with async{} and inherited some task-locals.
202-
Local.destroy(this);
197+
Private.destroy();
203198
}
204199

205200
SWIFT_CC(swift)
@@ -288,13 +283,7 @@ static void completeTaskImpl(AsyncTask *task,
288283
reinterpret_cast<char *>(context) - sizeof(AsyncContextPrefix));
289284
asyncContextPrefix->errorResult = error;
290285

291-
// Destroy and deallocate any remaining task local items.
292-
// We need to do this before we destroy the task local deallocator.
293-
task->Local.destroy(task);
294-
295-
// Tear down the task-local allocator immediately;
296-
// there's no need to wait for the object to be destroyed.
297-
_swift_task_alloc_destroy(task);
286+
task->Private.complete(task);
298287

299288
#if SWIFT_TASK_PRINTF_DEBUG
300289
fprintf(stderr, "[%p] task %p completed\n", pthread_self(), task);
@@ -526,6 +515,19 @@ static AsyncTaskAndContext swift_task_create_group_future_commonImpl(
526515
fprintf(stderr, "[%p] creating task %p with parent %p\n", pthread_self(), task, parent);
527516
#endif
528517

518+
// Initialize the task-local allocator.
519+
if (isAsyncLetTask) {
520+
initialContext->ResumeParent = reinterpret_cast<TaskContinuationFunction *>(
521+
&completeTask);
522+
assert(parent);
523+
void *initialSlab = (char*)allocation + amountToAllocate;
524+
task->Private.initializeWithSlab(task, initialSlab, initialSlabSize);
525+
} else {
526+
initialContext->ResumeParent = reinterpret_cast<TaskContinuationFunction *>(
527+
closureContext ? &completeTaskWithClosure : &completeTaskAndRelease);
528+
task->Private.initialize(task);
529+
}
530+
529531
// Perform additional linking between parent and child task.
530532
if (parent) {
531533
// If the parent was already cancelled, we carry this flag forward to the child.
@@ -535,6 +537,9 @@ static AsyncTaskAndContext swift_task_create_group_future_commonImpl(
535537
// so they may have been spawned in any case still.
536538
if (swift_task_isCancelled(parent))
537539
swift_task_cancel(task);
540+
541+
// Initialize task locals with a link to the parent task.
542+
task->_private().Local.initializeLinkParent(task, parent);
538543
}
539544

540545
// Configure the initial context.
@@ -547,26 +552,6 @@ static AsyncTaskAndContext swift_task_create_group_future_commonImpl(
547552
initialContext->Flags = AsyncContextKind::Ordinary;
548553
initialContext->Flags.setShouldNotDeallocateInCallee(true);
549554

550-
// Initialize the task-local allocator.
551-
if (isAsyncLetTask) {
552-
initialContext->ResumeParent = reinterpret_cast<TaskContinuationFunction *>(
553-
&completeTask);
554-
assert(parent);
555-
void *initialSlab = (char*)allocation + amountToAllocate;
556-
_swift_task_alloc_initialize_with_slab(task, initialSlab, initialSlabSize);
557-
} else {
558-
initialContext->ResumeParent = reinterpret_cast<TaskContinuationFunction *>(
559-
closureContext ? &completeTaskWithClosure : &completeTaskAndRelease);
560-
_swift_task_alloc_initialize(task);
561-
}
562-
563-
// TODO: if the allocator would be prepared earlier we could do this in some
564-
// other existing if-parent if rather than adding another one here.
565-
if (parent) {
566-
// Initialize task locals with a link to the parent task.
567-
task->Local.initializeLinkParent(task, parent);
568-
}
569-
570555
return {task, initialContext};
571556
}
572557

Diff for: stdlib/public/Concurrency/TaskAlloc.cpp

+1-29
Original file line numberDiff line numberDiff line change
@@ -20,49 +20,25 @@
2020
#include "swift/Runtime/Concurrency.h"
2121
#include "swift/ABI/Task.h"
2222
#include "TaskPrivate.h"
23-
#include "Error.h"
2423

25-
#define SWIFT_FATAL_ERROR swift_Concurrency_fatalError
26-
#include "../runtime/StackAllocator.h"
2724
#include <stdlib.h>
2825

2926
using namespace swift;
3027

3128
namespace {
3229

33-
/// The size of an allocator slab.
34-
///
35-
/// TODO: find the optimal value by experiment.
36-
static constexpr size_t SlabCapacity = 1024;
37-
38-
using TaskAllocator = StackAllocator<SlabCapacity>;
39-
4030
struct GlobalAllocator {
4131
TaskAllocator allocator;
4232
void *spaceForFirstSlab[64];
4333

4434
GlobalAllocator() : allocator(spaceForFirstSlab, sizeof(spaceForFirstSlab)) {}
4535
};
4636

47-
static_assert(alignof(TaskAllocator) <= alignof(decltype(AsyncTask::AllocatorPrivate)),
48-
"task allocator must not be more aligned than "
49-
"allocator-private slot");
50-
5137
} // end anonymous namespace
5238

53-
void swift::_swift_task_alloc_initialize(AsyncTask *task) {
54-
new (task->AllocatorPrivate) TaskAllocator();
55-
}
56-
57-
void swift::_swift_task_alloc_initialize_with_slab(AsyncTask *task,
58-
void *firstSlabBuffer,
59-
size_t bufferCapacity) {
60-
new (task->AllocatorPrivate) TaskAllocator(firstSlabBuffer, bufferCapacity);
61-
}
62-
6339
static TaskAllocator &allocator(AsyncTask *task) {
6440
if (task)
65-
return reinterpret_cast<TaskAllocator &>(task->AllocatorPrivate);
41+
return task->Private.get().Allocator;
6642

6743
// FIXME: this fall-back shouldn't be necessary, but it's useful
6844
// for now, since the current execution tests aren't setting up a task
@@ -71,10 +47,6 @@ static TaskAllocator &allocator(AsyncTask *task) {
7147
return global.allocator;
7248
}
7349

74-
void swift::_swift_task_alloc_destroy(AsyncTask *task) {
75-
allocator(task).~TaskAllocator();
76-
}
77-
7850
void *swift::swift_task_alloc(size_t size) {
7951
return allocator(swift_task_getCurrent()).alloc(size);
8052
}

0 commit comments

Comments
 (0)