Skip to content

Commit e499018

Browse files
authored
Merge pull request #2734 from swiftwasm/main
[pull] swiftwasm from main
2 parents 3ad073a + e6af501 commit e499018

File tree

16 files changed

+1061
-34
lines changed

16 files changed

+1061
-34
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ Swift Next
6161
}
6262
```
6363

64+
* [SE-0298][]:
65+
66+
The "for" loop can be used to traverse asynchronous sequences in asynchronous code:
67+
68+
```swift
69+
for try await line in myFile.lines() {
70+
// Do something with each line
71+
}
72+
```
73+
74+
Asynchronous for loops use asynchronous sequences, defined by the protocol
75+
`AsyncSequence` and its corresponding `AsyncIterator`.
76+
6477
**Add new entries to the top of this section, not here!**
6578

6679
Swift 5.4
@@ -8309,6 +8322,7 @@ Swift 1.0
83098322
[SE-0286]: <https://github.com/apple/swift-evolution/blob/main/proposals/0286-forward-scan-trailing-closures.md>
83108323
[SE-0287]: <https://github.com/apple/swift-evolution/blob/main/proposals/0287-implicit-member-chains.md>
83118324
[SE-0296]: <https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md>
8325+
[SE-0298]: <https://github.com/apple/swift-evolution/blob/main/proposals/0298-asyncsequence.md>
83128326

83138327
[SR-75]: <https://bugs.swift.org/browse/SR-75>
83148328
[SR-106]: <https://bugs.swift.org/browse/SR-106>

include/swift/ABI/MetadataValues.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,9 +1936,9 @@ class JobFlags : public FlagSet<size_t> {
19361936

19371937
// Kind-specific flags.
19381938

1939-
Task_IsChildTask = 24,
1940-
Task_IsFuture = 25,
1941-
Task_IsTaskGroup = 26,
1939+
Task_IsChildTask = 24,
1940+
Task_IsFuture = 25,
1941+
Task_IsTaskGroup = 26
19421942
};
19431943

19441944
explicit JobFlags(size_t bits) : FlagSet(bits) {}
@@ -1965,11 +1965,9 @@ class JobFlags : public FlagSet<size_t> {
19651965
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsFuture,
19661966
task_isFuture,
19671967
task_setIsFuture)
1968-
19691968
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsTaskGroup,
19701969
task_isTaskGroup,
19711970
task_setIsTaskGroup)
1972-
19731971
};
19741972

19751973
/// Kinds of task status record.

include/swift/ABI/Task.h

Lines changed: 261 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,15 @@ class ActiveTaskStatus {
137137
/// ### Fragments
138138
/// An AsyncTask may have the following fragments:
139139
///
140-
/// +------------------+
141-
/// | childFragment? |
142-
/// | groupFragment? |
143-
/// | futureFragment? |*
144-
/// +------------------+
140+
/// +--------------------------+
141+
/// | childFragment? |
142+
/// | taskLocalValuesFragment? |
143+
/// | groupFragment? |
144+
/// | futureFragment? |*
145+
/// +--------------------------+
145146
///
146-
/// The future fragment is dynamic in size, based on the future result type
147-
/// 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.
148149
class AsyncTask : public HeapObject, public Job {
149150
public:
150151
/// The context for resuming the job. When a task is scheduled
@@ -175,13 +176,15 @@ class AsyncTask : public HeapObject, public Job {
175176
void run(ExecutorRef currentExecutor) {
176177
ResumeTask(this, currentExecutor, ResumeContext);
177178
}
178-
179+
179180
/// Check whether this task has been cancelled.
180181
/// Checking this is, of course, inherently race-prone on its own.
181182
bool isCancelled() const {
182183
return Status.load(std::memory_order_relaxed).isCancelled();
183184
}
184185

186+
// ==== Child Fragment -------------------------------------------------------
187+
185188
/// A fragment of an async task structure that happens to be a child task.
186189
class ChildFragment {
187190
/// The parent task of this task.
@@ -205,14 +208,252 @@ class AsyncTask : public HeapObject, public Job {
205208
}
206209
};
207210

208-
// TODO: rename? all other functions are `is...` rather than `has...Fragment`
209-
bool hasChildFragment() const { return Flags.task_isChildTask(); }
211+
bool hasChildFragment() const {
212+
return Flags.task_isChildTask();
213+
}
210214

211215
ChildFragment *childFragment() {
212216
assert(hasChildFragment());
213217
return reinterpret_cast<ChildFragment*>(this + 1);
214218
}
215219

220+
// ==== Task Locals Values ---------------------------------------------------
221+
222+
class TaskLocalValuesFragment {
223+
public:
224+
/// Type of the pointed at `next` task local item.
225+
enum class NextLinkType : uintptr_t {
226+
/// This task is known to be a "terminal" node in the lookup of task locals.
227+
/// In other words, even if it had a parent, the parent (and its parents)
228+
/// are known to not contain any any more task locals, and thus any further
229+
/// search beyond this task.
230+
IsTerminal = 0b00,
231+
/// The storage pointer points at the next TaskLocalChainItem in this task.
232+
IsNext = 0b01,
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`.
241+
IsParent = 0b11
242+
};
243+
244+
/// Values must match `TaskLocalInheritance` declared in `TaskLocal.swift`.
245+
enum class TaskLocalInheritance : uint8_t {
246+
Default = 0,
247+
Never = 1
248+
};
249+
250+
class TaskLocalItem {
251+
private:
252+
/// Mask used for the low status bits in a task local chain item.
253+
static const uintptr_t statusMask = 0x03;
254+
255+
/// Pointer to the next task local item; be it in this task or in a parent.
256+
/// Low bits encode `NextLinkType`.
257+
/// TaskLocalItem *next = nullptr;
258+
uintptr_t next;
259+
260+
public:
261+
/// The type of the key with which this value is associated.
262+
const Metadata *keyType;
263+
/// The type of the value stored by this item.
264+
const Metadata *valueType;
265+
266+
// Trailing storage for the value itself. The storage will be
267+
// uninitialized or contain an instance of \c valueType.
268+
269+
private:
270+
explicit TaskLocalItem(const Metadata *keyType, const Metadata *valueType)
271+
: keyType(keyType),
272+
valueType(valueType),
273+
next(0) { }
274+
275+
public:
276+
/// TaskLocalItem which does not by itself store any value, but only points
277+
/// to the nearest task-local-value containing parent's first task item.
278+
///
279+
/// This item type is used to link to the appropriate parent task's item,
280+
/// when the current task itself does not have any task local values itself.
281+
///
282+
/// When a task actually has its own task locals, it should rather point
283+
/// to the parent's *first* task-local item in its *last* item, extending
284+
/// the TaskLocalItem linked list into the appropriate parent.
285+
static TaskLocalItem* createParentLink(AsyncTask *task, AsyncTask *parent) {
286+
assert(parent);
287+
size_t amountToAllocate = TaskLocalItem::itemSize(/*valueType*/nullptr);
288+
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
289+
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
290+
291+
TaskLocalItem *item =
292+
new(allocation) TaskLocalItem(nullptr, nullptr);
293+
294+
auto parentHead = parent->localValuesFragment()->head;
295+
if (parentHead) {
296+
if (parentHead->isEmpty()) {
297+
switch (parentHead->getNextLinkType()) {
298+
case NextLinkType::IsParent:
299+
// it has no values, and just points to its parent,
300+
// therefore skip also skip pointing to that parent and point
301+
// to whichever parent it was pointing to as well, it may be its
302+
// immediate parent, or some super-parent.
303+
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext());
304+
static_cast<uintptr_t>(NextLinkType::IsParent);
305+
break;
306+
case NextLinkType::IsNext:
307+
assert(false && "empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
308+
"this should not happen, as it implies the parent must have stored some value.");
309+
break;
310+
case NextLinkType::IsTerminal:
311+
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext());
312+
static_cast<uintptr_t>(NextLinkType::IsTerminal);
313+
break;
314+
}
315+
} else {
316+
item->next = reinterpret_cast<uintptr_t>(parentHead) |
317+
static_cast<uintptr_t>(NextLinkType::IsParent);
318+
}
319+
} else {
320+
item->next = reinterpret_cast<uintptr_t>(parentHead) |
321+
static_cast<uintptr_t>(NextLinkType::IsTerminal);
322+
}
323+
324+
return item;
325+
}
326+
327+
static TaskLocalItem* createLink(AsyncTask *task,
328+
const Metadata *keyType,
329+
const Metadata *valueType) {
330+
assert(task);
331+
size_t amountToAllocate = TaskLocalItem::itemSize(valueType);
332+
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
333+
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
334+
TaskLocalItem *item =
335+
new(allocation) TaskLocalItem(keyType, valueType);
336+
337+
auto next = task->localValuesFragment()->head;
338+
auto nextLinkType = next ? NextLinkType::IsNext : NextLinkType::IsTerminal;
339+
item->next = reinterpret_cast<uintptr_t>(next) |
340+
static_cast<uintptr_t>(nextLinkType);
341+
342+
return item;
343+
}
344+
345+
void destroy() {
346+
if (valueType) {
347+
valueType->vw_destroy(getStoragePtr());
348+
}
349+
}
350+
351+
TaskLocalItem *getNext() {
352+
return reinterpret_cast<TaskLocalItem *>(next & ~statusMask);
353+
}
354+
355+
NextLinkType getNextLinkType() {
356+
return static_cast<NextLinkType>(next & statusMask);
357+
}
358+
359+
/// Item does not contain any actual value, and is only used to point at
360+
/// a specific parent item.
361+
bool isEmpty() {
362+
return !valueType;
363+
}
364+
365+
/// Retrieve a pointer to the storage of the value.
366+
OpaqueValue *getStoragePtr() {
367+
return reinterpret_cast<OpaqueValue *>(
368+
reinterpret_cast<char *>(this) + storageOffset(valueType));
369+
}
370+
371+
/// Compute the offset of the storage from the base of the item.
372+
static size_t storageOffset(const Metadata *valueType) {
373+
size_t offset = sizeof(TaskLocalItem);
374+
if (valueType) {
375+
size_t alignment = valueType->vw_alignment();
376+
return (offset + alignment - 1) & ~(alignment - 1);
377+
} else {
378+
return offset;
379+
}
380+
}
381+
382+
/// Determine the size of the item given a particular value type.
383+
static size_t itemSize(const Metadata *valueType) {
384+
size_t offset = storageOffset(valueType);
385+
if (valueType) {
386+
offset += valueType->vw_size();
387+
}
388+
return offset;
389+
}
390+
};
391+
392+
private:
393+
/// A stack (single-linked list) of task local values.
394+
///
395+
/// Once task local values within this task are traversed, the list continues
396+
/// to the "next parent that contributes task local values," or if no such
397+
/// parent exists it terminates with null.
398+
///
399+
/// If the TaskLocalValuesFragment was allocated, it is expected that this
400+
/// value should be NOT null; it either has own values, or at least one
401+
/// parent that has values. If this task does not have any values, the head
402+
/// pointer MAY immediately point at this task's parent task which has values.
403+
///
404+
/// ### Concurrency
405+
/// Access to the head is only performed from the task itself, when it
406+
/// creates child tasks, the child during creation will inspect its parent's
407+
/// task local value stack head, and point to it. This is done on the calling
408+
/// task, and thus needs not to be synchronized. Subsequent traversal is
409+
/// performed by child tasks concurrently, however they use their own
410+
/// pointers/stack and can never mutate the parent's stack.
411+
///
412+
/// The stack is only pushed/popped by the owning task, at the beginning and
413+
/// end a `body` block of `withLocal(_:boundTo:body:)` respectively.
414+
///
415+
/// Correctness of the stack strongly relies on the guarantee that tasks
416+
/// never outline a scope in which they are created. Thanks to this, if
417+
/// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
418+
/// all tasks created inside the `withLocal` body must complete before it
419+
/// returns, as such, any child tasks potentially accessing the value stack
420+
/// are guaranteed to be completed by the time we pop values off the stack
421+
/// (after the body has completed).
422+
TaskLocalItem *head = nullptr;
423+
424+
public:
425+
TaskLocalValuesFragment() {}
426+
427+
void destroy();
428+
429+
/// If the parent task has task local values defined, point to in
430+
/// the task local values chain.
431+
void initializeLinkParent(AsyncTask* task, AsyncTask* parent);
432+
433+
void pushValue(AsyncTask *task, const Metadata *keyType,
434+
/* +1 */ OpaqueValue *value, const Metadata *valueType);
435+
436+
void popValue(AsyncTask *task);
437+
438+
OpaqueValue* get(const Metadata *keType, TaskLocalInheritance inheritance);
439+
};
440+
441+
TaskLocalValuesFragment *localValuesFragment() {
442+
auto offset = reinterpret_cast<char*>(this);
443+
offset += sizeof(AsyncTask);
444+
445+
if (hasChildFragment()) {
446+
offset += sizeof(ChildFragment);
447+
}
448+
449+
return reinterpret_cast<TaskLocalValuesFragment*>(offset);
450+
}
451+
452+
OpaqueValue* localValueGet(const Metadata *keyType,
453+
TaskLocalValuesFragment::TaskLocalInheritance inheritance) {
454+
return localValuesFragment()->get(keyType, inheritance);
455+
}
456+
216457
// ==== TaskGroup ------------------------------------------------------------
217458

218459
class GroupFragment {
@@ -516,12 +757,16 @@ class AsyncTask : public HeapObject, public Job {
516757
GroupFragment *groupFragment() {
517758
assert(isTaskGroup());
518759

760+
auto offset = reinterpret_cast<char*>(this);
761+
offset += sizeof(AsyncTask);
762+
519763
if (hasChildFragment()) {
520-
return reinterpret_cast<GroupFragment *>(
521-
reinterpret_cast<ChildFragment*>(this + 1) + 1);
764+
offset += sizeof(ChildFragment);
522765
}
523766

524-
return reinterpret_cast<GroupFragment *>(this + 1);
767+
offset += sizeof(TaskLocalValuesFragment);
768+
769+
return reinterpret_cast<GroupFragment *>(offset);
525770
}
526771

527772
/// Offer result of a task into this channel.
@@ -647,13 +892,15 @@ class AsyncTask : public HeapObject, public Job {
647892
FutureFragment *futureFragment() {
648893
assert(isFuture());
649894

650-
auto offset = reinterpret_cast<uintptr_t>(this); // TODO: char* instead?
895+
auto offset = reinterpret_cast<char*>(this);
651896
offset += sizeof(AsyncTask);
652897

653898
if (hasChildFragment()) {
654899
offset += sizeof(ChildFragment);
655900
}
656901

902+
offset += sizeof(TaskLocalValuesFragment);
903+
657904
if (isTaskGroup()) {
658905
offset += sizeof(GroupFragment);
659906
}

0 commit comments

Comments
 (0)