[TaskLocals] Cleanly separate locals impl from Task, no need for fragment

This commit is contained in:
Konrad `ktoso` Malawski
2021-03-01 20:54:32 +09:00
parent 53069e2a6b
commit d7169edc21
11 changed files with 381 additions and 335 deletions

View File

@@ -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");

View File

@@ -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"

View 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

View File

@@ -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 {

View File

@@ -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.
///

View File

@@ -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);

View File

@@ -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`.

View File

@@ -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

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {