Skip to content

Commit b811b12

Browse files
committed
[Concurrency] TaskLocals lookup "skip" optimization
1 parent 1044723 commit b811b12

File tree

10 files changed

+309
-133
lines changed

10 files changed

+309
-133
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,8 +1938,7 @@ class JobFlags : public FlagSet<size_t> {
19381938

19391939
Task_IsChildTask = 24,
19401940
Task_IsFuture = 25,
1941-
Task_IsTaskGroup = 26,
1942-
Task_HasLocalValues = 27
1941+
Task_IsTaskGroup = 26
19431942
};
19441943

19451944
explicit JobFlags(size_t bits) : FlagSet(bits) {}
@@ -1969,9 +1968,6 @@ class JobFlags : public FlagSet<size_t> {
19691968
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsTaskGroup,
19701969
task_isTaskGroup,
19711970
task_setIsTaskGroup)
1972-
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_HasLocalValues,
1973-
task_hasLocalValues,
1974-
task_setHasLocalValues)
19751971
};
19761972

19771973
/// Kinds of task status record.

include/swift/ABI/Task.h

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,13 @@ class ActiveTaskStatus {
139139
///
140140
/// +--------------------------+
141141
/// | childFragment? |
142-
/// | taskLocalValuesFragment |
142+
/// | taskLocalValuesFragment? |
143143
/// | groupFragment? |
144144
/// | futureFragment? |*
145145
/// +--------------------------+
146146
///
147-
/// The future fragment is dynamic in size, based on the future result type
148-
/// it can hold, and thus must be the *last* fragment.
147+
/// * The future fragment is dynamic in size, based on the future result type
148+
/// it can hold, and thus must be the *last* fragment.
149149
class AsyncTask : public HeapObject, public Job {
150150
public:
151151
/// The context for resuming the job. When a task is scheduled
@@ -217,7 +217,7 @@ class AsyncTask : public HeapObject, public Job {
217217
return reinterpret_cast<ChildFragment*>(this + 1);
218218
}
219219

220-
// ==== Task Locals Values-- -------------------------------------------------
220+
// ==== Task Locals Values ---------------------------------------------------
221221

222222
class TaskLocalValuesFragment {
223223
public:
@@ -230,8 +230,14 @@ class AsyncTask : public HeapObject, public Job {
230230
IsTerminal = 0b00,
231231
/// The storage pointer points at the next TaskLocalChainItem in this task.
232232
IsNext = 0b01,
233-
/// The storage pointer points at a parent AsyncTask,
234-
/// in which we should continue the lookup.
233+
/// The storage pointer points at a parent AsyncTask, in which we should
234+
/// continue the lookup.
235+
///
236+
/// Note that this may not necessarily be the same as the task's parent
237+
/// task -- we may point to a super-parent if we know / that the parent
238+
/// does not "contribute" any task local values. This is to speed up
239+
/// lookups by skipping empty parent tasks during get(), and explained
240+
/// in depth in `createParentLink`.
235241
IsParent = 0b11
236242
};
237243

@@ -272,23 +278,42 @@ class AsyncTask : public HeapObject, public Job {
272278
/// the TaskLocalItem linked list into the appropriate parent.
273279
static TaskLocalItem* createParentLink(AsyncTask *task, AsyncTask *parent) {
274280
assert(parent);
275-
assert(parent->hasTaskLocalValues());
276-
assert(task->hasTaskLocalValues());
277281
size_t amountToAllocate = TaskLocalItem::itemSize(/*valueType*/nullptr);
278282
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
279283
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
280-
fprintf(stderr, "MALLOC parent link item: %d\n", allocation);
281284

282285
TaskLocalItem *item =
283286
new(allocation) TaskLocalItem(nullptr, nullptr);
284287

285-
auto next = parent->localValuesFragment()->head;
286-
auto nextLinkType = next ? NextLinkType::IsParent : NextLinkType::IsTerminal;
287-
item->next = reinterpret_cast<uintptr_t>(next) |
288-
static_cast<uintptr_t>(nextLinkType);
289-
290-
fprintf(stderr, "error: %s [%s:%d] created parent item: task=%d -> parentTask=%d :: item=%d -> item->getNext()=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
291-
task, parent, item, item->getNext());
288+
auto parentHead = parent->localValuesFragment()->head;
289+
if (parentHead) {
290+
if (parentHead->isEmpty()) {
291+
switch (parentHead->getNextLinkType()) {
292+
case NextLinkType::IsParent:
293+
// it has no values, and just points to its parent,
294+
// therefore skip also skip pointing to that parent and point
295+
// to whichever parent it was pointing to as well, it may be its
296+
// immediate parent, or some super-parent.
297+
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext());
298+
static_cast<uintptr_t>(NextLinkType::IsParent);
299+
break;
300+
case NextLinkType::IsNext:
301+
assert(false && "empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
302+
"this should not happen, as it implies the parent must have stored some value.");
303+
break;
304+
case NextLinkType::IsTerminal:
305+
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext());
306+
static_cast<uintptr_t>(NextLinkType::IsTerminal);
307+
break;
308+
}
309+
} else {
310+
item->next = reinterpret_cast<uintptr_t>(parentHead) |
311+
static_cast<uintptr_t>(NextLinkType::IsParent);
312+
}
313+
} else {
314+
item->next = reinterpret_cast<uintptr_t>(parentHead) |
315+
static_cast<uintptr_t>(NextLinkType::IsTerminal);
316+
}
292317

293318
return item;
294319
}
@@ -297,7 +322,6 @@ class AsyncTask : public HeapObject, public Job {
297322
const Metadata *keyType,
298323
const Metadata *valueType) {
299324
assert(task);
300-
assert(task->hasTaskLocalValues());
301325
size_t amountToAllocate = TaskLocalItem::itemSize(valueType);
302326
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
303327
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
@@ -327,9 +351,14 @@ class AsyncTask : public HeapObject, public Job {
327351
return static_cast<NextLinkType>(next & statusMask);
328352
}
329353

354+
/// Item does not contain any actual value, and is only used to point at
355+
/// a specific parent item.
356+
bool isEmpty() {
357+
return !valueType;
358+
}
359+
330360
/// Retrieve a pointer to the storage of the value.
331361
OpaqueValue *getStoragePtr() {
332-
// assert(valueType && "valueType must be set before accessing storage pointer.");
333362
return reinterpret_cast<OpaqueValue *>(
334363
reinterpret_cast<char *>(this) + storageOffset(valueType));
335364
}
@@ -356,7 +385,8 @@ class AsyncTask : public HeapObject, public Job {
356385
};
357386

358387
private:
359-
/// Single-linked list of task local values.
388+
/// A stack (single-linked list) of task local values.
389+
///
360390
/// Once task local values within this task are traversed, the list continues
361391
/// to the "next parent that contributes task local values," or if no such
362392
/// parent exists it terminates with null.
@@ -366,11 +396,28 @@ class AsyncTask : public HeapObject, public Job {
366396
/// parent that has values. If this task does not have any values, the head
367397
/// pointer MAY immediately point at this task's parent task which has values.
368398
///
369-
/// NOTE: Check the higher bits to know if this is a self or parent value.
399+
/// ### Concurrency
400+
/// Access to the head is only performed from the task itself, when it
401+
/// creates child tasks, the child during creation will inspect its parent's
402+
/// task local value stack head, and point to it. This is done on the calling
403+
/// task, and thus needs not to be synchronized. Subsequent traversal is
404+
/// performed by child tasks concurrently, however they use their own
405+
/// pointers/stack and can never mutate the parent's stack.
406+
///
407+
/// The stack is only pushed/popped by the owning task, at the beginning and
408+
/// end a `body` block of `withLocal(_:boundTo:body:)` respectively.
409+
///
410+
/// Correctness of the stack strongly relies on the guarantee that tasks
411+
/// never outline a scope in which they are created. Thanks to this, if
412+
/// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
413+
/// all tasks created inside the `withLocal` body must complete before it
414+
/// returns, as such, any child tasks potentially accessing the value stack
415+
/// are guaranteed to be completed by the time we pop values off the stack
416+
/// (after the body has completed).
370417
TaskLocalItem *head = nullptr;
371418

372419
public:
373-
TaskLocalValuesFragment() {}
420+
TaskLocalValuesFragment() {}
374421

375422
void destroy();
376423

@@ -386,13 +433,7 @@ class AsyncTask : public HeapObject, public Job {
386433
OpaqueValue* get(const Metadata *keyType);
387434
};
388435

389-
bool hasTaskLocalValues() const {
390-
return Flags.task_hasLocalValues();
391-
}
392-
393436
TaskLocalValuesFragment *localValuesFragment() {
394-
assert(hasTaskLocalValues());
395-
396437
auto offset = reinterpret_cast<char*>(this);
397438
offset += sizeof(AsyncTask);
398439

@@ -404,14 +445,7 @@ class AsyncTask : public HeapObject, public Job {
404445
}
405446

406447
OpaqueValue* localValueGet(const Metadata *keyType) {
407-
if (hasTaskLocalValues()) {
408-
return localValuesFragment()->get(keyType);
409-
} else {
410-
// We are guaranteed to have a task-local fragment even if this task has
411-
// no bindings, but its parent tasks do. Thus, if no fragment, we can
412-
// immediately return null.
413-
return nullptr;
414-
}
448+
return localValuesFragment()->get(keyType);
415449
}
416450

417451
// ==== TaskGroup ------------------------------------------------------------
@@ -724,9 +758,7 @@ class AsyncTask : public HeapObject, public Job {
724758
offset += sizeof(ChildFragment);
725759
}
726760

727-
if (hasTaskLocalValues()) {
728-
offset += sizeof(TaskLocalValuesFragment);
729-
}
761+
offset += sizeof(TaskLocalValuesFragment);
730762

731763
return reinterpret_cast<GroupFragment *>(offset);
732764
}
@@ -861,9 +893,7 @@ class AsyncTask : public HeapObject, public Job {
861893
offset += sizeof(ChildFragment);
862894
}
863895

864-
if (hasTaskLocalValues()) {
865-
offset += sizeof(TaskLocalValuesFragment);
866-
}
896+
offset += sizeof(TaskLocalValuesFragment);
867897

868898
if (isTaskGroup()) {
869899
offset += sizeof(GroupFragment);

include/swift/Runtime/Concurrency.h

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -257,31 +257,19 @@ void swift_task_localValuePush(AsyncTask* task,
257257
/* +1 */ OpaqueValue *value,
258258
const Metadata *valueType);
259259

260-
/// Remove task `count` local bindings from the task local binding stack.
261-
/// Crashes if `count` is greater if the number of task locals stored in the task.
260+
/// Remove task a local binding from the task local values stack.
262261
///
263262
/// This must be only invoked by the task itself to avoid concurrent writes.
264263
///
265264
/// Its Swift signature is
266265
///
267266
/// \code
268267
/// public func _taskLocalValuePop(
269-
/// _ task: Builtin.NativeObject,
270-
/// count: Int
268+
/// _ task: Builtin.NativeObject
271269
/// )
272270
/// \endcode
273271
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
274-
void swift_task_localValuePop(AsyncTask* task, int count);
275-
276-
/// Checks if task (or any of its parent tasks) has task local values.
277-
///
278-
/// \code
279-
/// func swift_task_hasTaskLocalValues<Key>(
280-
/// _ task: Builtin.NativeObject,
281-
/// ) -> Bool
282-
/// \endcode
283-
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
284-
bool swift_task_hasTaskLocalValues(AsyncTask* task);
272+
void swift_task_localValuePop(AsyncTask* task);
285273

286274
/// This should have the same representation as an enum like this:
287275
/// enum NearestTaskDeadline {

stdlib/public/Concurrency/Task.cpp

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,7 @@ SWIFT_CC(swift)
135135
static void destroyTask(SWIFT_CONTEXT HeapObject *obj) {
136136
auto task = static_cast<AsyncTask*>(obj);
137137

138-
fprintf(stderr, "destroy task (%d): %d\n",
139-
task->hasTaskLocalValues(), task);
140-
141-
142-
// For a group, destroy the queues and results.
138+
// For a group, destroy the queues and results.
143139
if (task->isTaskGroup()) {
144140
task->groupFragment()->destroy();
145141
}
@@ -150,9 +146,7 @@ static void destroyTask(SWIFT_CONTEXT HeapObject *obj) {
150146
}
151147

152148
// release any objects potentially held as task local values.
153-
if (task->hasTaskLocalValues()) {
154-
task->localValuesFragment()->destroy();
155-
}
149+
task->localValuesFragment()->destroy();
156150

157151
// The task execution itself should always hold a reference to it, so
158152
// if we get here, we know the task has finished running, which means
@@ -242,15 +236,9 @@ AsyncTaskAndContext swift::swift_task_create_future_f(
242236
headerSize += sizeof(AsyncTask::ChildFragment);
243237
}
244238

245-
bool needsTaskLocalsFragment =
246-
flags.task_hasLocalValues() || (parent && parent->hasTaskLocalValues());
247-
fprintf(stderr, "error: %s [%s:%d] prepare task taskHasTaskLocals=%d parentHasLocals=%d needsLocals=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
248-
flags.task_hasLocalValues(), (parent && parent->hasTaskLocalValues()), needsTaskLocalsFragment);
249-
if (needsTaskLocalsFragment) {
250-
headerSize += sizeof(AsyncTask::TaskLocalValuesFragment);
251-
fprintf(stderr, "error: %s [%s:%d] adding values fragment size=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
252-
sizeof(AsyncTask::TaskLocalValuesFragment));
253-
}
239+
headerSize += sizeof(AsyncTask::TaskLocalValuesFragment);
240+
fprintf(stderr, "error: %s [%s:%d] adding values fragment size=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
241+
sizeof(AsyncTask::TaskLocalValuesFragment));
254242

255243
if (flags.task_isTaskGroup()) {
256244
headerSize += sizeof(AsyncTask::GroupFragment);
@@ -287,12 +275,9 @@ AsyncTaskAndContext swift::swift_task_create_future_f(
287275
new (childFragment) AsyncTask::ChildFragment(parent);
288276
}
289277

290-
if (needsTaskLocalsFragment) {
291-
assert(task->hasTaskLocalValues());
292-
auto taskLocalsFragment = task->localValuesFragment();
293-
new (taskLocalsFragment) AsyncTask::TaskLocalValuesFragment();
294-
taskLocalsFragment->initializeLinkParent(task, parent);
295-
}
278+
auto taskLocalsFragment = task->localValuesFragment();
279+
new (taskLocalsFragment) AsyncTask::TaskLocalValuesFragment();
280+
taskLocalsFragment->initializeLinkParent(task, parent);
296281

297282
// Initialize the task group fragment if applicable.
298283
if (flags.task_isTaskGroup()) {
@@ -494,7 +479,6 @@ void swift::swift_task_runAndBlockThread(const void *function,
494479

495480
// Set up a task that runs the runAndBlock async function above.
496481
auto flags = JobFlags(JobKind::Task, JobPriority::Default);
497-
flags.task_setHasLocalValues(true);
498482
auto pair = swift_task_create_f(flags,
499483
/*parent*/ nullptr,
500484
&runAndBlock_start,
@@ -515,27 +499,22 @@ size_t swift::swift_task_getJobFlags(AsyncTask *task) {
515499
return task->Flags.getOpaqueValue();
516500
}
517501

518-
void swift::swift_task_localValuePush(AsyncTask *task,
502+
void swift::swift_task_local_value_push(AsyncTask *task,
519503
const Metadata *keyType,
520504
/* +1 */ OpaqueValue *value, const Metadata *valueType) {
521505
fprintf(stderr, "error: %s [%s:%d] PUSH keyType=%d value=%d *value=%d\n",
522506
__FUNCTION__, __FILE_NAME__, __LINE__,
523507
keyType, value, *reinterpret_cast<int*>(value));
524-
assert(task->hasTaskLocalValues());
525508
task->localValuesFragment()->pushValue(task, keyType, value, valueType);
526509
}
527510

528-
void swift::swift_task_localValuePop(AsyncTask *task, int count) {
529-
assert(task->hasTaskLocalValues());
530-
auto fragment = task->localValuesFragment();
531-
for (int i = 0; i < count; i++) {
532-
fprintf(stderr, "error: %s [%s:%d] POP task=%d %d / %d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
533-
task, i, count);
534-
fragment->popValue(task);
535-
}
511+
void swift::swift_task_local_value_pop(AsyncTask *task) {
512+
fprintf(stderr, "error: %s [%s:%d] POP task=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
513+
task);
514+
task->localValuesFragment()->popValue(task);
536515
}
537516

538-
OpaqueValue* swift::swift_task_localValueGet(AsyncTask *task,
517+
OpaqueValue* swift::swift_task_local_value_get(AsyncTask *task,
539518
const Metadata *keyType) {
540519
auto value = task->localValueGet(keyType);
541520
fprintf(stderr, "error: %s [%s:%d] lookup keyType=%d value=%d\n",
@@ -544,10 +523,6 @@ OpaqueValue* swift::swift_task_localValueGet(AsyncTask *task,
544523
return value;
545524
}
546525

547-
bool swift::swift_task_hasTaskLocalValues(AsyncTask *task) {
548-
return task->hasTaskLocalValues();
549-
}
550-
551526
namespace {
552527

553528
/// Structure that gets filled in when a task is suspended by `withUnsafeContinuation`.

0 commit comments

Comments
 (0)