mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[TaskLocals] Cleanly separate locals impl from Task, no need for fragment
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#ifndef SWIFT_ABI_TASK_H
|
||||
#define SWIFT_ABI_TASK_H
|
||||
|
||||
#include "swift/ABI/TaskLocal.h"
|
||||
#include "swift/ABI/Executor.h"
|
||||
#include "swift/ABI/HeapObject.h"
|
||||
#include "swift/ABI/Metadata.h"
|
||||
@@ -24,8 +25,7 @@
|
||||
#include "swift/Runtime/Config.h"
|
||||
#include "swift/Basic/STLExtras.h"
|
||||
#include "bitset"
|
||||
#include "string"
|
||||
#include "queue"
|
||||
#include "queue" // TODO: remove and replace with our own mpsc
|
||||
|
||||
namespace swift {
|
||||
class AsyncTask;
|
||||
@@ -147,7 +147,6 @@ public:
|
||||
/// An AsyncTask may have the following fragments:
|
||||
///
|
||||
/// +--------------------------+
|
||||
/// | taskLocalValuesFragment |
|
||||
/// | childFragment? |
|
||||
/// | groupChildFragment? |
|
||||
/// | futureFragment? |*
|
||||
@@ -173,12 +172,16 @@ public:
|
||||
/// Reserved for the use of the task-local stack allocator.
|
||||
void *AllocatorPrivate[4];
|
||||
|
||||
/// Task local values storage container.
|
||||
TaskLocal::Storage Local;
|
||||
|
||||
AsyncTask(const HeapMetadata *metadata, JobFlags flags,
|
||||
TaskContinuationFunction *run,
|
||||
AsyncContext *initialContext)
|
||||
: HeapObject(metadata), Job(flags, run),
|
||||
ResumeContext(initialContext),
|
||||
Status(ActiveTaskStatus()) {
|
||||
Status(ActiveTaskStatus()),
|
||||
Local(TaskLocal::Storage()) {
|
||||
assert(flags.isAsyncTask());
|
||||
}
|
||||
|
||||
@@ -196,237 +199,24 @@ public:
|
||||
return Status.load(std::memory_order_relaxed).isCancelled();
|
||||
}
|
||||
|
||||
// ==== Task Locals Values ---------------------------------------------------
|
||||
// ==== Task Local Values ----------------------------------------------------
|
||||
|
||||
/// Storage fragment for task local values.
|
||||
class TaskLocalValuesFragment {
|
||||
public:
|
||||
/// Type of the pointed at `next` task local item.
|
||||
enum class NextLinkType : uintptr_t {
|
||||
/// This task is known to be a "terminal" node in the lookup of task locals.
|
||||
/// In other words, even if it had a parent, the parent (and its parents)
|
||||
/// are known to not contain any any more task locals, and thus any further
|
||||
/// search beyond this task.
|
||||
IsTerminal = 0b00,
|
||||
/// The storage pointer points at the next TaskLocalChainItem in this task.
|
||||
IsNext = 0b01,
|
||||
/// The storage pointer points at a parent AsyncTask, in which we should
|
||||
/// continue the lookup.
|
||||
///
|
||||
/// Note that this may not necessarily be the same as the task's parent
|
||||
/// task -- we may point to a super-parent if we know / that the parent
|
||||
/// does not "contribute" any task local values. This is to speed up
|
||||
/// lookups by skipping empty parent tasks during get(), and explained
|
||||
/// in depth in `createParentLink`.
|
||||
IsParent = 0b11
|
||||
};
|
||||
void localValueInitializeLinkParent(AsyncTask *parent) {
|
||||
Local.initializeLinkParent(this, parent);
|
||||
}
|
||||
|
||||
/// Values must match `TaskLocalInheritance` declared in `TaskLocal.swift`.
|
||||
enum class TaskLocalInheritance : uint8_t {
|
||||
Default = 0,
|
||||
Never = 1
|
||||
};
|
||||
|
||||
class TaskLocalItem {
|
||||
private:
|
||||
/// Mask used for the low status bits in a task local chain item.
|
||||
static const uintptr_t statusMask = 0x03;
|
||||
|
||||
/// Pointer to the next task local item; be it in this task or in a parent.
|
||||
/// Low bits encode `NextLinkType`.
|
||||
/// TaskLocalItem *next = nullptr;
|
||||
uintptr_t next;
|
||||
|
||||
public:
|
||||
/// The type of the key with which this value is associated.
|
||||
const Metadata *keyType;
|
||||
/// The type of the value stored by this item.
|
||||
const Metadata *valueType;
|
||||
|
||||
// Trailing storage for the value itself. The storage will be
|
||||
// uninitialized or contain an instance of \c valueType.
|
||||
|
||||
private:
|
||||
explicit TaskLocalItem(const Metadata *keyType, const Metadata *valueType)
|
||||
: next(0),
|
||||
keyType(keyType),
|
||||
valueType(valueType) { }
|
||||
|
||||
public:
|
||||
/// TaskLocalItem which does not by itself store any value, but only points
|
||||
/// to the nearest task-local-value containing parent's first task item.
|
||||
///
|
||||
/// This item type is used to link to the appropriate parent task's item,
|
||||
/// when the current task itself does not have any task local values itself.
|
||||
///
|
||||
/// When a task actually has its own task locals, it should rather point
|
||||
/// to the parent's *first* task-local item in its *last* item, extending
|
||||
/// the TaskLocalItem linked list into the appropriate parent.
|
||||
static TaskLocalItem* createParentLink(AsyncTask *task, AsyncTask *parent) {
|
||||
assert(parent);
|
||||
size_t amountToAllocate = TaskLocalItem::itemSize(/*valueType*/nullptr);
|
||||
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
|
||||
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
|
||||
|
||||
TaskLocalItem *item =
|
||||
new(allocation) TaskLocalItem(nullptr, nullptr);
|
||||
|
||||
auto parentHead = parent->localValuesFragment()->head;
|
||||
if (parentHead) {
|
||||
if (parentHead->isEmpty()) {
|
||||
switch (parentHead->getNextLinkType()) {
|
||||
case NextLinkType::IsParent:
|
||||
// it has no values, and just points to its parent,
|
||||
// therefore skip also skip pointing to that parent and point
|
||||
// to whichever parent it was pointing to as well, it may be its
|
||||
// immediate parent, or some super-parent.
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsParent);
|
||||
break;
|
||||
case NextLinkType::IsNext:
|
||||
assert(false && "empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
|
||||
"this should not happen, as it implies the parent must have stored some value.");
|
||||
break;
|
||||
case NextLinkType::IsTerminal:
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsTerminal);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsParent);
|
||||
}
|
||||
} else {
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsTerminal);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
static TaskLocalItem* createLink(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
const Metadata *valueType) {
|
||||
assert(task);
|
||||
size_t amountToAllocate = TaskLocalItem::itemSize(valueType);
|
||||
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
|
||||
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator rdar://74218679
|
||||
TaskLocalItem *item =
|
||||
new(allocation) TaskLocalItem(keyType, valueType);
|
||||
|
||||
auto next = task->localValuesFragment()->head;
|
||||
auto nextLinkType = next ? NextLinkType::IsNext : NextLinkType::IsTerminal;
|
||||
item->next = reinterpret_cast<uintptr_t>(next) |
|
||||
static_cast<uintptr_t>(nextLinkType);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (valueType) {
|
||||
valueType->vw_destroy(getStoragePtr());
|
||||
}
|
||||
}
|
||||
|
||||
TaskLocalItem *getNext() {
|
||||
return reinterpret_cast<TaskLocalItem *>(next & ~statusMask);
|
||||
}
|
||||
|
||||
NextLinkType getNextLinkType() {
|
||||
return static_cast<NextLinkType>(next & statusMask);
|
||||
}
|
||||
|
||||
/// Item does not contain any actual value, and is only used to point at
|
||||
/// a specific parent item.
|
||||
bool isEmpty() {
|
||||
return !valueType;
|
||||
}
|
||||
|
||||
/// Retrieve a pointer to the storage of the value.
|
||||
OpaqueValue *getStoragePtr() {
|
||||
return reinterpret_cast<OpaqueValue *>(
|
||||
reinterpret_cast<char *>(this) + storageOffset(valueType));
|
||||
}
|
||||
|
||||
/// Compute the offset of the storage from the base of the item.
|
||||
static size_t storageOffset(const Metadata *valueType) {
|
||||
size_t offset = sizeof(TaskLocalItem);
|
||||
if (valueType) {
|
||||
size_t alignment = valueType->vw_alignment();
|
||||
return (offset + alignment - 1) & ~(alignment - 1);
|
||||
} else {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the size of the item given a particular value type.
|
||||
static size_t itemSize(const Metadata *valueType) {
|
||||
size_t offset = storageOffset(valueType);
|
||||
if (valueType) {
|
||||
offset += valueType->vw_size();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
/// A stack (single-linked list) of task local values.
|
||||
///
|
||||
/// Once task local values within this task are traversed, the list continues
|
||||
/// to the "next parent that contributes task local values," or if no such
|
||||
/// parent exists it terminates with null.
|
||||
///
|
||||
/// If the TaskLocalValuesFragment was allocated, it is expected that this
|
||||
/// value should be NOT null; it either has own values, or at least one
|
||||
/// parent that has values. If this task does not have any values, the head
|
||||
/// pointer MAY immediately point at this task's parent task which has values.
|
||||
///
|
||||
/// ### Concurrency
|
||||
/// Access to the head is only performed from the task itself, when it
|
||||
/// creates child tasks, the child during creation will inspect its parent's
|
||||
/// task local value stack head, and point to it. This is done on the calling
|
||||
/// task, and thus needs not to be synchronized. Subsequent traversal is
|
||||
/// performed by child tasks concurrently, however they use their own
|
||||
/// pointers/stack and can never mutate the parent's stack.
|
||||
///
|
||||
/// The stack is only pushed/popped by the owning task, at the beginning and
|
||||
/// end a `body` block of `withLocal(_:boundTo:body:)` respectively.
|
||||
///
|
||||
/// Correctness of the stack strongly relies on the guarantee that tasks
|
||||
/// never outline a scope in which they are created. Thanks to this, if
|
||||
/// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
|
||||
/// all tasks created inside the `withLocal` body must complete before it
|
||||
/// returns, as such, any child tasks potentially accessing the value stack
|
||||
/// are guaranteed to be completed by the time we pop values off the stack
|
||||
/// (after the body has completed).
|
||||
TaskLocalItem *head = nullptr;
|
||||
|
||||
public:
|
||||
TaskLocalValuesFragment() {}
|
||||
|
||||
void destroy();
|
||||
|
||||
/// If the parent task has task local values defined, point to in
|
||||
/// the task local values chain.
|
||||
void initializeLinkParent(AsyncTask* task, AsyncTask* parent);
|
||||
|
||||
void pushValue(AsyncTask *task, const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value, const Metadata *valueType);
|
||||
|
||||
void popValue(AsyncTask *task);
|
||||
|
||||
OpaqueValue* get(const Metadata *keType, TaskLocalInheritance inheritance);
|
||||
};
|
||||
|
||||
TaskLocalValuesFragment *localValuesFragment() {
|
||||
auto offset = reinterpret_cast<char*>(this);
|
||||
offset += sizeof(AsyncTask);
|
||||
return reinterpret_cast<TaskLocalValuesFragment*>(offset);
|
||||
void localValuePush(const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value, const Metadata *valueType) {
|
||||
Local.pushValue(this, keyType, value, valueType);
|
||||
}
|
||||
|
||||
OpaqueValue* localValueGet(const Metadata *keyType,
|
||||
TaskLocalValuesFragment::TaskLocalInheritance inheritance) {
|
||||
return localValuesFragment()->get(keyType, inheritance);
|
||||
TaskLocal::TaskLocalInheritance inherit) {
|
||||
return Local.getValue(this, keyType, inherit);
|
||||
}
|
||||
|
||||
void localValuePop() {
|
||||
Local.popValue(this);
|
||||
}
|
||||
|
||||
// ==== Child Fragment -------------------------------------------------------
|
||||
@@ -476,7 +266,6 @@ public:
|
||||
|
||||
auto offset = reinterpret_cast<char*>(this);
|
||||
offset += sizeof(AsyncTask);
|
||||
offset += sizeof(TaskLocalValuesFragment);
|
||||
|
||||
return reinterpret_cast<ChildFragment*>(offset);
|
||||
}
|
||||
@@ -516,7 +305,6 @@ public:
|
||||
|
||||
auto offset = reinterpret_cast<char*>(this);
|
||||
offset += sizeof(AsyncTask);
|
||||
offset += sizeof(TaskLocalValuesFragment);
|
||||
if (hasChildFragment())
|
||||
offset += sizeof(ChildFragment);
|
||||
|
||||
@@ -625,7 +413,6 @@ public:
|
||||
assert(isFuture());
|
||||
auto offset = reinterpret_cast<char*>(this);
|
||||
offset += sizeof(AsyncTask);
|
||||
offset += sizeof(TaskLocalValuesFragment);
|
||||
if (hasChildFragment())
|
||||
offset += sizeof(ChildFragment);
|
||||
if (hasGroupChildFragment())
|
||||
@@ -665,7 +452,7 @@ private:
|
||||
};
|
||||
|
||||
// The compiler will eventually assume these.
|
||||
static_assert(sizeof(AsyncTask) == 12 * sizeof(void*),
|
||||
static_assert(sizeof(AsyncTask) == 14 * sizeof(void*),
|
||||
"AsyncTask size is wrong");
|
||||
static_assert(alignof(AsyncTask) == 2 * alignof(void*),
|
||||
"AsyncTask alignment is wrong");
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
#define SWIFT_ABI_TASK_GROUP_H
|
||||
|
||||
#include "swift/ABI/Task.h"
|
||||
#include "swift/Runtime/Concurrency.h"
|
||||
#include "swift/Basic/RelativePointer.h"
|
||||
#include "swift/ABI/HeapObject.h"
|
||||
#include "swift/Runtime/Concurrency.h"
|
||||
#include "swift/Runtime/Config.h"
|
||||
#include "swift/Basic/RelativePointer.h"
|
||||
#include "swift/Basic/STLExtras.h"
|
||||
#include "bitset"
|
||||
#include "string"
|
||||
|
||||
221
include/swift/ABI/TaskLocal.h
Normal file
221
include/swift/ABI/TaskLocal.h
Normal file
@@ -0,0 +1,221 @@
|
||||
//===--- TaskLocal.h - ABI of task local values -----------------*- C++ -*-===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Swift ABI describing tasks.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SWIFT_ABI_TASKLOCAL_H
|
||||
#define SWIFT_ABI_TASKLOCAL_H
|
||||
|
||||
#include "swift/ABI/HeapObject.h"
|
||||
#include "swift/ABI/Metadata.h"
|
||||
#include "swift/ABI/MetadataValues.h"
|
||||
|
||||
namespace swift {
|
||||
class AsyncTask;
|
||||
struct OpaqueValue;
|
||||
struct SwiftError;
|
||||
class TaskStatusRecord;
|
||||
class TaskGroup;
|
||||
|
||||
// ==== Task Locals Values ---------------------------------------------------
|
||||
|
||||
class TaskLocal {
|
||||
public:
|
||||
/// Type of the pointed at `next` task local item.
|
||||
enum class NextLinkType : uintptr_t {
|
||||
/// This task is known to be a "terminal" node in the lookup of task locals.
|
||||
/// In other words, even if it had a parent, the parent (and its parents)
|
||||
/// are known to not contain any any more task locals, and thus any further
|
||||
/// search beyond this task.
|
||||
IsTerminal = 0b00,
|
||||
/// The storage pointer points at the next TaskLocal::Item in this task.
|
||||
IsNext = 0b01,
|
||||
/// The storage pointer points at a item stored by another AsyncTask.
|
||||
///
|
||||
/// Note that this may not necessarily be the same as the task's parent
|
||||
/// task -- we may point to a super-parent if we know / that the parent
|
||||
/// does not "contribute" any task local values. This is to speed up
|
||||
/// lookups by skipping empty parent tasks during get(), and explained
|
||||
/// in depth in `createParentLink`.
|
||||
IsParent = 0b11
|
||||
};
|
||||
|
||||
/// Values must match `TaskLocalInheritance` declared in `TaskLocal.swift`.
|
||||
enum class TaskLocalInheritance : uint8_t {
|
||||
/// Default task local value behavior
|
||||
///
|
||||
/// Values declared with a default-inherited key are accessible from:
|
||||
/// - the current task that has bound the value,
|
||||
/// - any child task of the current task (e.g. created by async let or groups)
|
||||
///
|
||||
/// Such values are *not* carried through detached tasks.
|
||||
Default = 0,
|
||||
|
||||
/// Special semantics which confine a task's local value to *only* the current
|
||||
/// task. In other words, they ave never inherited by any child task created
|
||||
/// by the current task.
|
||||
///
|
||||
/// Values declared with a never-inherited key only accessible:
|
||||
/// - specifically from the current task itself
|
||||
///
|
||||
/// Such values are *not* accessible from child tasks or detached tasks.
|
||||
Never = 1
|
||||
};
|
||||
|
||||
class Item {
|
||||
private:
|
||||
/// Mask used for the low status bits in a task local chain item.
|
||||
static const uintptr_t statusMask = 0x03;
|
||||
|
||||
/// Pointer to the next task local item; be it in this task or in a parent.
|
||||
/// Low bits encode `NextLinkType`.
|
||||
/// Item *next = nullptr;
|
||||
uintptr_t next;
|
||||
|
||||
public:
|
||||
/// The type of the key with which this value is associated.
|
||||
const Metadata *keyType;
|
||||
/// The type of the value stored by this item.
|
||||
const Metadata *valueType;
|
||||
|
||||
// Trailing storage for the value itself. The storage will be
|
||||
// uninitialized or contain an instance of \c valueType.
|
||||
|
||||
private:
|
||||
explicit Item(const Metadata *keyType, const Metadata *valueType)
|
||||
: next(0),
|
||||
keyType(keyType),
|
||||
valueType(valueType) {}
|
||||
|
||||
public:
|
||||
/// Item which does not by itself store any value, but only points
|
||||
/// to the nearest task-local-value containing parent's first task item.
|
||||
///
|
||||
/// This item type is used to link to the appropriate parent task's item,
|
||||
/// when the current task itself does not have any task local values itself.
|
||||
///
|
||||
/// When a task actually has its own task locals, it should rather point
|
||||
/// to the parent's *first* task-local item in its *last* item, extending
|
||||
/// the Item linked list into the appropriate parent.
|
||||
static Item *createParentLink(AsyncTask *task, AsyncTask *parent);
|
||||
|
||||
static Item *createLink(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
const Metadata *valueType);
|
||||
|
||||
void destroy(AsyncTask *task) {
|
||||
if (valueType) {
|
||||
valueType->vw_destroy(getStoragePtr());
|
||||
}
|
||||
}
|
||||
|
||||
Item *getNext() {
|
||||
return reinterpret_cast<Item *>(next & ~statusMask);
|
||||
}
|
||||
|
||||
NextLinkType getNextLinkType() {
|
||||
return static_cast<NextLinkType>(next & statusMask);
|
||||
}
|
||||
|
||||
/// Item does not contain any actual value, and is only used to point at
|
||||
/// a specific parent item.
|
||||
bool isEmpty() {
|
||||
return !valueType;
|
||||
}
|
||||
|
||||
/// Retrieve a pointer to the storage of the value.
|
||||
OpaqueValue *getStoragePtr() {
|
||||
return reinterpret_cast<OpaqueValue *>(
|
||||
reinterpret_cast<char *>(this) + storageOffset(valueType));
|
||||
}
|
||||
|
||||
/// Compute the offset of the storage from the base of the item.
|
||||
static size_t storageOffset(const Metadata *valueType) {
|
||||
size_t offset = sizeof(Item);
|
||||
if (valueType) {
|
||||
size_t alignment = valueType->vw_alignment();
|
||||
return (offset + alignment - 1) & ~(alignment - 1);
|
||||
} else {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the size of the item given a particular value type.
|
||||
static size_t itemSize(const Metadata *valueType) {
|
||||
size_t offset = storageOffset(valueType);
|
||||
if (valueType) {
|
||||
offset += valueType->vw_size();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Storage {
|
||||
friend class TaskLocal::Item;
|
||||
private:
|
||||
/// A stack (single-linked list) of task local values.
|
||||
///
|
||||
/// Once task local values within this task are traversed, the list continues
|
||||
/// to the "next parent that contributes task local values," or if no such
|
||||
/// parent exists it terminates with null.
|
||||
///
|
||||
/// If the TaskLocalValuesFragment was allocated, it is expected that this
|
||||
/// value should be NOT null; it either has own values, or at least one
|
||||
/// parent that has values. If this task does not have any values, the head
|
||||
/// pointer MAY immediately point at this task's parent task which has values.
|
||||
///
|
||||
/// ### Concurrency
|
||||
/// Access to the head is only performed from the task itself, when it
|
||||
/// creates child tasks, the child during creation will inspect its parent's
|
||||
/// task local value stack head, and point to it. This is done on the calling
|
||||
/// task, and thus needs not to be synchronized. Subsequent traversal is
|
||||
/// performed by child tasks concurrently, however they use their own
|
||||
/// pointers/stack and can never mutate the parent's stack.
|
||||
///
|
||||
/// The stack is only pushed/popped by the owning task, at the beginning and
|
||||
/// end a `body` block of `withLocal(_:boundTo:body:)` respectively.
|
||||
///
|
||||
/// Correctness of the stack strongly relies on the guarantee that tasks
|
||||
/// never outline a scope in which they are created. Thanks to this, if
|
||||
/// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
|
||||
/// all tasks created inside the `withLocal` body must complete before it
|
||||
/// returns, as such, any child tasks potentially accessing the value stack
|
||||
/// are guaranteed to be completed by the time we pop values off the stack
|
||||
/// (after the body has completed).
|
||||
TaskLocal::Item *head;
|
||||
|
||||
public:
|
||||
|
||||
void initializeLinkParent(AsyncTask *task, AsyncTask *parent);
|
||||
|
||||
void pushValue(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value, const Metadata *valueType);
|
||||
|
||||
OpaqueValue* getValue(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
TaskLocalInheritance inheritance);
|
||||
|
||||
void popValue(AsyncTask *task);
|
||||
|
||||
void destroy(AsyncTask *task);
|
||||
};
|
||||
};
|
||||
|
||||
TaskLocal::Storage* swift_task_localValueStorage(AsyncTask *task);
|
||||
|
||||
} // end namespace swift
|
||||
|
||||
#endif
|
||||
@@ -20,8 +20,8 @@
|
||||
#ifndef SWIFT_ABI_TASKSTATUS_H
|
||||
#define SWIFT_ABI_TASKSTATUS_H
|
||||
|
||||
#include "swift/ABI/MetadataValues.h"
|
||||
#include "swift/ABI/Task.h"
|
||||
#include "swift/ABI/MetadataValues.h"
|
||||
|
||||
namespace swift {
|
||||
|
||||
|
||||
@@ -122,10 +122,6 @@ void swift_task_cancel(AsyncTask *task);
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
|
||||
void swift_task_cancel_group_child_tasks(AsyncTask *task, TaskGroup *group);
|
||||
|
||||
/// Get 'active' AsyncTask, depending on platform this may use thread local storage.
|
||||
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
|
||||
AsyncTask* swift_task_get_active();
|
||||
|
||||
/// Escalate the priority of a task and all of its child tasks.
|
||||
///
|
||||
/// This can be called from any thread.
|
||||
@@ -343,8 +339,6 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
|
||||
void swift_task_removeCancellationHandler(
|
||||
AsyncTask *task, CancellationNotificationStatusRecord *record);
|
||||
|
||||
using TaskLocalValuesFragment = AsyncTask::TaskLocalValuesFragment;
|
||||
|
||||
/// Get a task local value from the passed in task. Its Swift signature is
|
||||
///
|
||||
/// \code
|
||||
@@ -358,7 +352,7 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
|
||||
OpaqueValue*
|
||||
swift_task_localValueGet(AsyncTask* task,
|
||||
const Metadata *keyType,
|
||||
TaskLocalValuesFragment::TaskLocalInheritance inheritance);
|
||||
TaskLocal::TaskLocalInheritance inheritance);
|
||||
|
||||
/// Add a task local value to the passed in task.
|
||||
///
|
||||
|
||||
@@ -135,11 +135,6 @@ SWIFT_RUNTIME_DECLARE_THREAD_LOCAL(
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
AsyncTask*
|
||||
swift::swift_task_get_active() {
|
||||
return ActiveTask::get();
|
||||
}
|
||||
|
||||
void swift::swift_job_run(Job *job, ExecutorRef executor) {
|
||||
ExecutorTrackingInfo trackingInfo;
|
||||
trackingInfo.enterAndShadow(executor);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "swift/Runtime/Concurrency.h"
|
||||
#include "swift/ABI/Task.h"
|
||||
#include "swift/ABI/TaskLocal.h"
|
||||
#include "swift/ABI/Metadata.h"
|
||||
#include "swift/Runtime/Mutex.h"
|
||||
#include "swift/Runtime/HeapObject.h"
|
||||
@@ -32,8 +33,7 @@
|
||||
using namespace swift;
|
||||
using FutureFragment = AsyncTask::FutureFragment;
|
||||
using TaskGroup = swift::TaskGroup;
|
||||
using TaskLocalValuesFragment = AsyncTask::TaskLocalValuesFragment;
|
||||
using TaskLocalInheritance = AsyncTask::TaskLocalValuesFragment::TaskLocalInheritance;
|
||||
using TaskLocalInheritance = TaskLocal::TaskLocalInheritance;
|
||||
|
||||
void FutureFragment::destroy() {
|
||||
auto queueHead = waitQueue.load(std::memory_order_acquire);
|
||||
@@ -146,13 +146,14 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
|
||||
SWIFT_CC(swift)
|
||||
static void destroyTask(SWIFT_CONTEXT HeapObject *obj) {
|
||||
auto task = static_cast<AsyncTask*>(obj);
|
||||
|
||||
// For a future, destroy the result.
|
||||
if (task->isFuture()) {
|
||||
task->futureFragment()->destroy();
|
||||
}
|
||||
|
||||
// Release any objects potentially held as task local values.
|
||||
task->localValuesFragment()->destroy();
|
||||
task->Local.destroy(task);
|
||||
|
||||
// The task execution itself should always hold a reference to it, so
|
||||
// if we get here, we know the task has finished running, which means
|
||||
@@ -255,9 +256,6 @@ AsyncTaskAndContext swift::swift_task_create_group_future_f(
|
||||
// Figure out the size of the header.
|
||||
size_t headerSize = sizeof(AsyncTask);
|
||||
|
||||
/// Every task is able to store task local values.
|
||||
headerSize += sizeof(AsyncTask::TaskLocalValuesFragment);
|
||||
|
||||
if (parent) {
|
||||
headerSize += sizeof(AsyncTask::ChildFragment);
|
||||
}
|
||||
@@ -291,9 +289,7 @@ AsyncTaskAndContext swift::swift_task_create_group_future_f(
|
||||
function, initialContext);
|
||||
|
||||
// Initialize task locals fragment.
|
||||
auto taskLocalsFragment = task->localValuesFragment();
|
||||
new (taskLocalsFragment) AsyncTask::TaskLocalValuesFragment();
|
||||
taskLocalsFragment->initializeLinkParent(task, parent);
|
||||
task->Local.initializeLinkParent(task, parent);
|
||||
|
||||
// Initialize the child fragment if applicable.
|
||||
if (parent) {
|
||||
@@ -552,23 +548,6 @@ size_t swift::swift_task_getJobFlags(AsyncTask *task) {
|
||||
return task->Flags.getOpaqueValue();
|
||||
}
|
||||
|
||||
void swift::swift_task_localValuePush(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value,
|
||||
const Metadata *valueType) {
|
||||
task->localValuesFragment()->pushValue(task, keyType, value, valueType);
|
||||
}
|
||||
|
||||
void swift::swift_task_localValuePop(AsyncTask *task) {
|
||||
task->localValuesFragment()->popValue(task);
|
||||
}
|
||||
|
||||
OpaqueValue* swift::swift_task_localValueGet(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
TaskLocalInheritance inheritance) {
|
||||
return task->localValueGet(keyType, inheritance);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// Structure that gets filled in when a task is suspended by `withUnsafeContinuation`.
|
||||
|
||||
@@ -508,7 +508,7 @@ extension Task {
|
||||
///
|
||||
/// The returned value must not be accessed from tasks other than the current one.
|
||||
public static var unsafeCurrent: UnsafeCurrentTask? {
|
||||
guard let _task = _getActiveAsyncTask() else {
|
||||
guard let _task = _getCurrentAsyncTask() else {
|
||||
return nil
|
||||
}
|
||||
// FIXME: This retain seems pretty wrong, however if we don't we WILL crash
|
||||
@@ -583,8 +583,8 @@ extension UnsafeCurrentTask: Equatable {
|
||||
|
||||
// ==== Internal ---------------------------------------------------------------
|
||||
|
||||
@_silgen_name("swift_task_get_active")
|
||||
func _getActiveAsyncTask() -> Builtin.NativeObject?
|
||||
@_silgen_name("swift_task_getCurrent")
|
||||
func _getCurrentAsyncTask() -> Builtin.NativeObject?
|
||||
|
||||
@_silgen_name("swift_task_getJobFlags")
|
||||
func getJobFlags(_ task: Builtin.NativeObject) -> Task.JobFlags
|
||||
|
||||
@@ -10,71 +10,162 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/ABI/TaskLocal.h"
|
||||
#include "swift/Runtime/Concurrency.h"
|
||||
#include "swift/ABI/Task.h"
|
||||
#include "swift/ABI/Metadata.h"
|
||||
|
||||
using namespace swift;
|
||||
using TaskLocalValuesFragment = AsyncTask::TaskLocalValuesFragment;
|
||||
|
||||
// =============================================================================
|
||||
// ==== ABI --------------------------------------------------------------------
|
||||
|
||||
TaskLocal::Storage* swift::swift_task_localValueStorage(AsyncTask *task) {
|
||||
return &task->Local;
|
||||
}
|
||||
|
||||
void swift::swift_task_localValuePush(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value,
|
||||
const Metadata *valueType) {
|
||||
task->localValuePush(keyType, value, valueType);
|
||||
}
|
||||
|
||||
OpaqueValue* swift::swift_task_localValueGet(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
TaskLocal::TaskLocalInheritance inheritance) {
|
||||
return task->localValueGet(keyType, inheritance);
|
||||
}
|
||||
|
||||
void swift::swift_task_localValuePop(AsyncTask *task) {
|
||||
task->localValuePop();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ==== Initialization ---------------------------------------------------------
|
||||
|
||||
void TaskLocal::Storage::initializeLinkParent(AsyncTask* task,
|
||||
AsyncTask* parent) {
|
||||
assert(!head && "task local storage was already initialized with parent");
|
||||
if (parent) {
|
||||
head = TaskLocal::Item::createParentLink(task, parent);
|
||||
}
|
||||
}
|
||||
|
||||
TaskLocal::Item*
|
||||
TaskLocal::Item::createParentLink(AsyncTask *task, AsyncTask *parent) {
|
||||
assert(parent);
|
||||
size_t amountToAllocate = Item::itemSize(/*valueType*/nullptr);
|
||||
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
|
||||
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
|
||||
|
||||
Item *item =
|
||||
new(allocation) Item(nullptr, nullptr);
|
||||
|
||||
auto parentHead = parent->Local.head;
|
||||
// auto parentLocalStorage = swift_task_localValueStorage(parent);
|
||||
// auto parentHead = parentLocalStorage->head;
|
||||
if (parentHead) {
|
||||
if (parentHead->isEmpty()) {
|
||||
switch (parentHead->getNextLinkType()) {
|
||||
case NextLinkType::IsParent:
|
||||
// it has no values, and just points to its parent,
|
||||
// therefore skip also skip pointing to that parent and point
|
||||
// to whichever parent it was pointing to as well, it may be its
|
||||
// immediate parent, or some super-parent.
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsParent);
|
||||
break;
|
||||
case NextLinkType::IsNext:
|
||||
assert(false &&
|
||||
"empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
|
||||
"this should not happen, as it implies the parent must have stored some value.");
|
||||
break;
|
||||
case NextLinkType::IsTerminal:
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsTerminal);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsParent);
|
||||
}
|
||||
} else {
|
||||
item->next = reinterpret_cast<uintptr_t>(parentHead) |
|
||||
static_cast<uintptr_t>(NextLinkType::IsTerminal);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
TaskLocal::Item*
|
||||
TaskLocal::Item::createLink(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
const Metadata *valueType) {
|
||||
assert(task);
|
||||
size_t amountToAllocate = Item::itemSize(valueType);
|
||||
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
|
||||
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator rdar://74218679
|
||||
Item *item =
|
||||
new(allocation) Item(keyType, valueType);
|
||||
|
||||
auto nextStorage = swift_task_localValueStorage(task);
|
||||
auto next = task->Local.head;
|
||||
auto nextLinkType = next ? NextLinkType::IsNext
|
||||
: NextLinkType::IsTerminal;
|
||||
item->next = reinterpret_cast<uintptr_t>(next) |
|
||||
static_cast<uintptr_t>(nextLinkType);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ==== destroy ----------------------------------------------------------------
|
||||
|
||||
void TaskLocalValuesFragment::destroy() {
|
||||
void TaskLocal::Storage::destroy(AsyncTask *task) {
|
||||
auto item = head;
|
||||
head = nullptr;
|
||||
TaskLocalItem *next;
|
||||
TaskLocal::Item *next;
|
||||
while (item) {
|
||||
switch (item->getNextLinkType()) {
|
||||
case TaskLocalValuesFragment::NextLinkType::IsNext:
|
||||
case TaskLocal::NextLinkType::IsNext:
|
||||
next = item->getNext();
|
||||
item->destroy();
|
||||
item->destroy(task);
|
||||
free(item);
|
||||
item = next;
|
||||
break;
|
||||
|
||||
case TaskLocalValuesFragment::NextLinkType::IsParent:
|
||||
case TaskLocalValuesFragment::NextLinkType::IsTerminal:
|
||||
case TaskLocal::NextLinkType::IsParent:
|
||||
case TaskLocal::NextLinkType::IsTerminal:
|
||||
// we're done here, we must not destroy values owned by the parent task.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ==== Initialization ---------------------------------------------------------
|
||||
|
||||
void TaskLocalValuesFragment::initializeLinkParent(AsyncTask* task,
|
||||
AsyncTask* parent) {
|
||||
assert(!head && "fragment was already initialized");
|
||||
if (parent) {
|
||||
head = TaskLocalItem::createParentLink(task, parent);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ==== push / pop / get -------------------------------------------------------
|
||||
|
||||
void TaskLocalValuesFragment::pushValue(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value,
|
||||
const Metadata *valueType) {
|
||||
void TaskLocal::Storage::pushValue(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
/* +1 */ OpaqueValue *value,
|
||||
const Metadata *valueType) {
|
||||
assert(value && "Task local value must not be nil");
|
||||
|
||||
auto item = TaskLocalItem::createLink(task, keyType, valueType);
|
||||
auto item = Item::createLink(task, keyType, valueType);
|
||||
valueType->vw_initializeWithTake(item->getStoragePtr(), value);
|
||||
head = item;
|
||||
}
|
||||
|
||||
void TaskLocalValuesFragment::popValue(AsyncTask *task) {
|
||||
void TaskLocal::Storage::popValue(AsyncTask *task) {
|
||||
assert(head && "attempted to pop value off empty task-local stack");
|
||||
head->destroy();
|
||||
head->destroy(task);
|
||||
head = head->getNext();
|
||||
}
|
||||
|
||||
OpaqueValue *TaskLocalValuesFragment::get(
|
||||
const Metadata *keyType,
|
||||
const TaskLocalInheritance inherit) {
|
||||
OpaqueValue* TaskLocal::Storage::getValue(AsyncTask *task,
|
||||
const Metadata *keyType,
|
||||
const TaskLocalInheritance inherit) {
|
||||
assert(keyType && "Task.Local key must not be null.");
|
||||
|
||||
auto item = head;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
////
|
||||
//// This source file is part of the Swift.org open source project
|
||||
////
|
||||
//// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
//// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors
|
||||
//// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
////
|
||||
//// See https://swift.org/LICENSE.txt for license information
|
||||
@@ -71,12 +71,13 @@ extension Task {
|
||||
/// bound in the current (or any parent) tasks.
|
||||
public static func local<Key>(_ keyPath: KeyPath<TaskLocalValues, Key>)
|
||||
-> Key.Value where Key: TaskLocalKey {
|
||||
guard let task = Task.unsafeCurrent else {
|
||||
guard let _task = Task.unsafeCurrent?._task else {
|
||||
fatalError("No async task!")
|
||||
return Key.defaultValue
|
||||
}
|
||||
|
||||
let value = _taskLocalValueGet(
|
||||
task._task, keyType: Key.self, inheritance: Key.inherit.rawValue)
|
||||
_task, keyType: Key.self, inheritance: Key.inherit.rawValue)
|
||||
guard let rawValue = value else {
|
||||
return Key.defaultValue
|
||||
}
|
||||
@@ -100,10 +101,10 @@ extension Task {
|
||||
boundTo value: Key.Value,
|
||||
operation: () async throws -> BodyResult
|
||||
) async rethrows -> BodyResult where Key: TaskLocalKey {
|
||||
let task = Builtin.getCurrentAsyncTask()
|
||||
let _task = Task.unsafeCurrent!._task // !-safe, guaranteed to have task available inside async function
|
||||
|
||||
_taskLocalValuePush(task, keyType: Key.self, value: value)
|
||||
defer { _taskLocalValuePop(task) }
|
||||
_taskLocalValuePush(_task, keyType: Key.self, value: value)
|
||||
defer { _taskLocalValuePop(_task) }
|
||||
|
||||
return try await operation()
|
||||
}
|
||||
@@ -112,29 +113,6 @@ extension Task {
|
||||
|
||||
// ==== ------------------------------------------------------------------------
|
||||
|
||||
/// A type-erased `TaskLocalKey` used when iterating through the `Baggage` using its `forEach` method.
|
||||
struct AnyTaskLocalKey {
|
||||
let keyType: Any.Type
|
||||
let valueType: Any.Type
|
||||
|
||||
init<Key>(_: Key.Type) where Key: TaskLocalKey {
|
||||
self.keyType = Key.self
|
||||
self.valueType = Key.Value.self
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyTaskLocalKey: Hashable {
|
||||
static func ==(lhs: AnyTaskLocalKey, rhs: AnyTaskLocalKey) -> Bool {
|
||||
return ObjectIdentifier(lhs.keyType) == ObjectIdentifier(rhs.keyType)
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self.keyType))
|
||||
}
|
||||
}
|
||||
|
||||
// ==== ------------------------------------------------------------------------
|
||||
|
||||
@_silgen_name("swift_task_localValuePush")
|
||||
public func _taskLocalValuePush<Value>(
|
||||
_ task: Builtin.NativeObject,
|
||||
|
||||
@@ -146,6 +146,7 @@ func withLocal_body_mustNotEscape() async {
|
||||
await Task.withLocal(\.string, boundTo: "xxx") {
|
||||
something = "very nice"
|
||||
}
|
||||
_ = something // silence not used warning
|
||||
}
|
||||
|
||||
@main struct Main {
|
||||
|
||||
Reference in New Issue
Block a user