Skip to content

Commit 5a91c06

Browse files
committed
Async: remove task.start and allow task.return to be used by sync functions
1 parent e038588 commit 5a91c06

File tree

7 files changed

+782
-529
lines changed

7 files changed

+782
-529
lines changed

design/mvp/Async.md

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ summary of the motivation and animated sketch of the design in action.
1919
* [Structured concurrency](#structured-concurrency)
2020
* [Waiting](#waiting)
2121
* [Backpressure](#backpressure)
22-
* [Starting](#starting)
2322
* [Returning](#returning)
2423
* [Examples](#examples)
2524
* [Interaction with multi-threading](#interaction-with-multi-threading)
@@ -218,44 +217,30 @@ private resources (like linear memory), requiring the component to be able to
218217
exert *backpressure* to allow some tasks to finish (and release private
219218
resources) before admitting new async export calls. To do this, a component may
220219
call the `task.backpressure` built-in to set a "backpressure" flag that causes
221-
subsequent export calls to immediately return in the [starting](#starting)
222-
state without calling the component's Core WebAssembly code.
220+
subsequent export calls to immediately return in the "starting" state without
221+
calling the component's Core WebAssembly code.
223222

224-
Once backpressure is enabled, the current task can [wait](#waiting) for
225-
existing tasks to finish and release their associated resources. Thus, a task
226-
can [wait](#waiting) with or without backpressure, depending on whether it
223+
Once task enables backpressure, it can [wait](#waiting) for existing tasks to
224+
finish and release their associated resources. Thus, a task can choose to
225+
[wait](#waiting) with or without backpressure enabled, depending on whether it
227226
wants to accept new accept new export calls while waiting or not.
228227

229228
See the [`canon_task_backpressure`] function and [`Task.enter`] method in the
230229
Canonical ABI explainer for the setting and implementation of backpressure.
231230

232-
### Starting
233-
234-
When a component asynchronously lifts a function, instead of the function
235-
eagerly receiving its lowered parameters (as a synchronous function would) the
236-
asynchronously-lifted Core WebAssembly function is passed an empty list of
237-
arguments and must instead call an imported [`task.start`] built-in to lower
238-
and receive its arguments.
239-
240-
The main reason to have `task.start` is so that an overloaded component can
241-
enable [backpressure](#backpressure) and then [wait](#waiting) for existing
242-
tasks to finish before the receiving the arguments to the current task. See the
243-
[`canon_task_start`] function in the Canonical ABI explainer for more details.
244-
245-
Before a task has called `task.start`, it is considered in the "starting"
246-
state. After calling `task.start`, the task is in a "started" state.
231+
Once a task is allowed to start according to these backpressure rules, its
232+
arguments are lowered into the callee's linear memory and the task is in
233+
the "started" state.
247234

248235
### Returning
249236

250-
Symmetric to starting, the way a Core WebAssembly function returns its value is
251-
by calling [`task.return`], passing the core values that are to be lifted.
237+
The way an async Core WebAssembly function returns its value is by calling
238+
[`task.return`], passing the core values that are to be lifted.
252239

253240
The main reason to have `task.return` is so that a task can continue execution
254241
after returning its value. This is useful for various finalization tasks (such
255242
as logging, billing or metrics) that don't need to be on the critical path of
256-
returning a value to the caller. (It also subsumes and generalizes the
257-
Canonical ABI's `post_return` function used by synchronous functions to release
258-
memory.)
243+
returning a value to the caller.
259244

260245
A task may not call `task.return` unless it is in the "started" state. Once
261246
`task.return` is called, the task is in the "returned" state. A task can only
@@ -273,12 +258,9 @@ replaced with `...` to focus on the overall flow of function calls.
273258
(import "fetch" (func $fetch (param "url" string) (result (list u8))))
274259
(core module $Main
275260
(import "" "fetch" (func $fetch (param i32 i32) (result i32)))
276-
(import "" "task.start" (func $task_start (param i32)))
277261
(import "" "task.return" (func $task_return (param i32)))
278262
(import "" "task.wait" (func $wait (param i32) (result i32)))
279-
(func (export "summarize")
280-
...
281-
call $task_start ;; receive the list of strings arguments
263+
(func (export "summarize") (param i32 i32)
282264
...
283265
loop
284266
...
@@ -296,12 +278,10 @@ replaced with `...` to focus on the overall flow of function calls.
296278
)
297279
)
298280
(canon lower $fetch async (core func $fetch'))
299-
(canon task.start (core func $task_start))
300281
(canon task.return (core func $task_return))
301282
(canon task.wait (core func $task_wait))
302283
(core instance $main (instantiate $Main (with "" (instance
303284
(export "fetch" (func $fetch'))
304-
(export "task.start" (func $task_start))
305285
(export "task.return" (func $task_return))
306286
(export "task.wait" (func $task_wait))
307287
))))
@@ -321,13 +301,13 @@ reclaim the memory passed arguments or use the results that have now been
321301
written to the outparam memory.
322302

323303
Because the `summarize` function is `canon lift`ed with `async`, its core
324-
function type has no params or results, since parameters are passed in via
325-
`task.start` and results are passed out via `task.return`. It also means that
326-
multiple `summarize` calls can be active at once: once the first call to
327-
`task.wait` blocks, the runtime will suspend its callstack (fiber) and start a
328-
new stack for the new call to `summarize`. Thus, `summarize` must be careful to
329-
allocate a separate linear-memory stack in its entry point, if one is needed,
330-
and to save and restore this before and after calling `task.wait`.
304+
function type has no results, since results are passed out via `task.return`.
305+
It also means that multiple `summarize` calls can be active at once: once the
306+
first call to `task.wait` blocks, the runtime will suspend its callstack
307+
(fiber) and start a new stack for the new call to `summarize`. Thus,
308+
`summarize` must be careful to allocate a separate linear-memory stack in its
309+
entry point, if one is needed, and to save and restore this before and after
310+
calling `task.wait`.
331311

332312
(Note that, for brevity this example ignores the `memory` and `realloc`
333313
immediates required by `canon lift` and `canon lower` to allocate the `list`
@@ -345,12 +325,9 @@ not externally-visible behavior.
345325
(import "fetch" (func $fetch (param "url" string) (result (list u8))))
346326
(core module $Main
347327
(import "" "fetch" (func $fetch (param i32 i32) (result i32)))
348-
(import "" "task.start" (func $task_start (param i32)))
349328
(import "" "task.return" (func $task_return (param i32)))
350329
(import "" "task.wait" (func $wait (param i32) (result i32)))
351-
(func (export "summarize") (result i32)
352-
...
353-
call $task_start ;; receive the list of strings arguments
330+
(func (export "summarize") (param i32 i32) (result i32)
354331
...
355332
loop
356333
...
@@ -372,12 +349,10 @@ not externally-visible behavior.
372349
)
373350
)
374351
(canon lower $fetch async (core func $fetch'))
375-
(canon task.start (core func $task_start))
376352
(canon task.return (core func $task_return))
377353
(canon task.wait (core func $task_wait))
378354
(core instance $main (instantiate $Main (with "" (instance
379355
(export "fetch" (func $fetch'))
380-
(export "task.start" (func $task_start))
381356
(export "task.return" (func $task_return))
382357
(export "task.wait" (func $task_wait))
383358
))))
@@ -388,9 +363,8 @@ not externally-visible behavior.
388363
```
389364
While this example spawns all the subtasks in the initial call to `summarize`,
390365
subtasks can also be spawned from `cb` (even after the call to `task.return`).
391-
It's also possible for `summarize` to wait to call `task.start` from `cb` or,
392-
conversely, for `task.return` to be called eagerly in the initial call to
393-
`summarize`.
366+
It's also possible for `summarize` to call `task.return` called eagerly in the
367+
initial core `summarize` call.
394368

395369
The `$event` and `$payload` parameters passed to `cb` are the same as the return
396370
values from `task.wait` in the previous example. The precise meaning of these
@@ -476,7 +450,6 @@ features will be added in future chunks to complete "async" in Preview 3:
476450
[Lift and Lower Definitions]: Explainer.md#canonical-definitions
477451
[Lifted]: Explainer.md#canonical-definitions
478452
[Canonical Built-in]: Explainer.md#canonical-built-ins
479-
[`task.start`]: Explainer.md#-async-built-ins
480453
[`task.return`]: Explainer.md#-async-built-ins
481454
[`task.wait`]: Explainer.md#-async-built-ins
482455
[`thread.spawn`]: Explainer.md#-threading-built-ins
@@ -488,7 +461,6 @@ features will be added in future chunks to complete "async" in Preview 3:
488461
[`canon_lower`]: CanonicalABI.md#canon-task-wait
489462
[`canon_task_wait`]: CanonicalABI.md#-canon-taskwait
490463
[`canon_task_backpressure`]: CanonicalABI.md#-canon-taskbackpressure
491-
[`canon_task_start`]: CanonicalABI.md#-canon-taskstart
492464
[`canon_task_return`]: CanonicalABI.md#-canon-taskreturn
493465
[`Task`]: CanonicalABI.md#runtime-state
494466
[`Task.enter`]: CanonicalABI.md#runtime-state

design/mvp/Binary.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -275,24 +275,24 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
275275
| 0x03 rt:<typeidx> => (canon resource.drop rt (core func))
276276
| 0x07 rt:<typdidx> => (canon resource.drop rt async (core func))
277277
| 0x04 rt:<typeidx> => (canon resource.rep rt (core func))
278-
| 0x05 ft:<typeidx> => (canon thread.spawn ft (core func))
279-
| 0x06 => (canon thread.hw_concurrency (core func))
280-
| 0x08 => (canon task.backpressure (core func))
281-
| 0x09 ft:<core:typeidx> => (canon task.start ft (core func))
282-
| 0x0a ft:<core:typeidx> => (canon task.return ft (core func))
283-
| 0x0b => (canon task.wait (core func))
284-
| 0x0c => (canon task.poll (core func))
285-
| 0x0d => (canon task.yield (core func))
286-
| 0x0e => (canon subtask.drop (core func))
278+
| 0x05 ft:<typeidx> => (canon thread.spawn ft (core func)) 🧵
279+
| 0x06 => (canon thread.hw_concurrency (core func)) 🧵
280+
| 0x08 => (canon task.backpressure (core func)) 🔀
281+
| 0x09 ft:<core:typeidx> => (canon task.return ft (core func)) 🔀
282+
| 0x0a => (canon task.wait (core func)) 🔀
283+
| 0x0b => (canon task.poll (core func)) 🔀
284+
| 0x0c => (canon task.yield (core func)) 🔀
285+
| 0x0d => (canon subtask.drop (core func)) 🔀
287286
opts ::= opt*:vec(<canonopt>) => opt*
288287
canonopt ::= 0x00 => string-encoding=utf8
289288
| 0x01 => string-encoding=utf16
290289
| 0x02 => string-encoding=latin1+utf16
291290
| 0x03 m:<core:memidx> => (memory m)
292291
| 0x04 f:<core:funcidx> => (realloc f)
293292
| 0x05 f:<core:funcidx> => (post-return f)
294-
| 0x06 => async
295-
| 0x07 f:<core:funcidx> => (callback f)
293+
| 0x06 => sync-task-return 🔀
294+
| 0x07 => async 🔀
295+
| 0x08 f:<core:funcidx> => (callback f) 🔀
296296
```
297297
Notes:
298298
* The second `0x00` byte in `canon` stands for the `func` sort and thus the

0 commit comments

Comments
 (0)