Skip to content

Commit 0e8e13a

Browse files
committed
Move the 'ABI' and 'Examples' sections to the end of Async.md
1 parent 0c5fa47 commit 0e8e13a

1 file changed

Lines changed: 78 additions & 78 deletions

File tree

design/mvp/Async.md

Lines changed: 78 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ summary of the motivation and animated sketch of the design in action.
2424
* [Borrows](#borrows)
2525
* [Cancellation](#cancellation)
2626
* [Nondeterminism](#nondeterminism)
27+
* [Interaction with the start function](#interaction-with-the-start-function)
28+
* [Interaction with multi-threading](#interaction-with-multi-threading)
2729
* [Async ABI](#async-abi)
2830
* [Async Import ABI](#async-import-abi)
2931
* [Async Export ABI](#async-export-abi)
3032
* [Examples](#examples)
31-
* [Interaction with the start function](#interaction-with-the-start-function)
32-
* [Interaction with multi-threading](#interaction-with-multi-threading)
3333
* [TODO](#todo)
3434

3535

@@ -639,6 +639,82 @@ Despite the above, the following scenarios do behave deterministically:
639639
the operations are performed).
640640

641641

642+
## Interaction with the start function
643+
644+
Since any component-level function with an empty signature can be used as a
645+
[`start`] function, there's nothing to stop an `async`-lifted function from
646+
being used as a `start` function. Async start functions are useful when
647+
executing general-purpose code at initialization time, e.g.:
648+
* If the top-level scripts of a scripting language are executed by the `start`
649+
function, asychrony arises from regular use of the language's concurrency
650+
features. For example, in JS, this takes the form of [top-level `await`].
651+
* If C++ or other OOPLs global object constructors are executed by the `start`
652+
function, these can execute general-purpose code which may use concurrent
653+
I/O APIs.
654+
655+
Since component `start` functions are already defined to be executed
656+
synchronously before the component is considered initialized and ready for its
657+
exports to be called, the natural thing for `start` to do when calling an
658+
`async`-lifted function is wait for the callee to reach the ["returned"
659+
state](#returning). This gives `async` `start` functions a simple way to do
660+
concurrent initialization and signal completion using the same language
661+
bindings as regular `async` `export` functions.
662+
663+
However, as explained above, an async task can always continue executing after
664+
reaching the "returned" state and thus an async task spawned by `start` may
665+
continue executing even after the component instance is initialized and
666+
receiving export calls. These post-return `start`-tasks can be used by the
667+
language toolchain to implement traditional "background tasks" (e.g., the
668+
`setInterval()` or `requestIdleCallback()` JavaScript APIs). From the
669+
perspective of [structured concurrency], these background tasks are new task
670+
tree roots (siblings to the roots created when component exports are
671+
called by the host). Thus, subtasks and threads spawned by the background task
672+
will have proper async callstacks as used to define reentrancy and support
673+
debugging/profiling/tracing.
674+
675+
In future, when [runtime instantiation] is added to the Component Model, the
676+
component-level function used to create a component instance could be lowered
677+
with `async` to allow a parent component to instantiate child components
678+
concurrently, relaxing the fully synchronous model of instantiation supported
679+
by declarative instantiation and `start` above.
680+
681+
682+
## Interaction with multi-threading
683+
684+
For now, the integration between multi-threading (via [`thread.spawn*`]) and
685+
native async is limited. In particular, because all [lift and lower definitions]
686+
produce non-`shared` functions, any threads spawned by a component via
687+
`thread.spawn*` will not be able to directly call imports (synchronously *or*
688+
asynchronously) and will thus have to use Core WebAssembly `atomics.*`
689+
instructions to switch back to a non-`shared` function running on the "main"
690+
thread (i.e., whichever thread was used to call the component's exports).
691+
692+
However, a future addition to this proposal (in the [TODO](#todo)s below) would
693+
be to allow lifting and lowering with `async` + `shared`. What's exciting about
694+
this approach is that a non-`shared` component-level function could be safely
695+
lowered with `async shared`. In the case that the lifted function being lowered
696+
was also `async shared`, the entire call could happen on the non-main thread
697+
without a context switch. But if the lifting side was non-`shared`, then the
698+
Component Model could automatically handle the synchronization of enqueuing a
699+
call to the export (as in the backpressure case mentioned above), returning a
700+
subtask for the async caller to wait on as usual. Thus, the sync+async
701+
composition story described above could naturally be extended to a
702+
sync+async+shared composition story, continuing to avoid the "what color is
703+
your function" problem (where `shared` is the [color]).
704+
705+
Even without any use of [`thread.spawn*`], native async provides an opportunity
706+
to achieve some automatic parallelism "for free". In particular, due to the
707+
shared-nothing nature of components, each component instance could be given a
708+
separate thread on which to interleave all tasks executing in that instance.
709+
Thus, in a cross-component call from `C1` to `C2`, `C2`'s task can run in a
710+
separate thread that is automatically synchronized with `C1` by the runtime.
711+
This is analogous to how traditional OS processes can run in separate threads,
712+
except that the component model is *allowing*, but not *requiring* the separate
713+
threads. While it's unclear how much parallelism this would unlock in practice,
714+
it does present interesting opportunities to experiment with optimizations over
715+
time as applications are built with more components.
716+
717+
642718
## Async ABI
643719

644720
At an ABI level, native async in the Component Model defines for every WIT
@@ -1025,82 +1101,6 @@ return values from `task.wait` in the previous example. The precise meaning of
10251101
these values is defined by the Canonical ABI.
10261102

10271103

1028-
## Interaction with the start function
1029-
1030-
Since any component-level function with an empty signature can be used as a
1031-
[`start`] function, there's nothing to stop an `async`-lifted function from
1032-
being used as a `start` function. Async start functions are useful when
1033-
executing general-purpose code at initialization time, e.g.:
1034-
* If the top-level scripts of a scripting language are executed by the `start`
1035-
function, asychrony arises from regular use of the language's concurrency
1036-
features. For example, in JS, this takes the form of [top-level `await`].
1037-
* If C++ or other OOPLs global object constructors are executed by the `start`
1038-
function, these can execute general-purpose code which may use concurrent
1039-
I/O APIs.
1040-
1041-
Since component `start` functions are already defined to be executed
1042-
synchronously before the component is considered initialized and ready for its
1043-
exports to be called, the natural thing for `start` to do when calling an
1044-
`async`-lifted function is wait for the callee to reach the ["returned"
1045-
state](#returning). This gives `async` `start` functions a simple way to do
1046-
concurrent initialization and signal completion using the same language
1047-
bindings as regular `async` `export` functions.
1048-
1049-
However, as explained above, an async task can always continue executing after
1050-
reaching the "returned" state and thus an async task spawned by `start` may
1051-
continue executing even after the component instance is initialized and
1052-
receiving export calls. These post-return `start`-tasks can be used by the
1053-
language toolchain to implement traditional "background tasks" (e.g., the
1054-
`setInterval()` or `requestIdleCallback()` JavaScript APIs). From the
1055-
perspective of [structured concurrency], these background tasks are new task
1056-
tree roots (siblings to the roots created when component exports are
1057-
called by the host). Thus, subtasks and threads spawned by the background task
1058-
will have proper async callstacks as used to define reentrancy and support
1059-
debugging/profiling/tracing.
1060-
1061-
In future, when [runtime instantiation] is added to the Component Model, the
1062-
component-level function used to create a component instance could be lowered
1063-
with `async` to allow a parent component to instantiate child components
1064-
concurrently, relaxing the fully synchronous model of instantiation supported
1065-
by declarative instantiation and `start` above.
1066-
1067-
1068-
## Interaction with multi-threading
1069-
1070-
For now, the integration between multi-threading (via [`thread.spawn*`]) and
1071-
native async is limited. In particular, because all [lift and lower definitions]
1072-
produce non-`shared` functions, any threads spawned by a component via
1073-
`thread.spawn*` will not be able to directly call imports (synchronously *or*
1074-
asynchronously) and will thus have to use Core WebAssembly `atomics.*`
1075-
instructions to switch back to a non-`shared` function running on the "main"
1076-
thread (i.e., whichever thread was used to call the component's exports).
1077-
1078-
However, a future addition to this proposal (in the [TODO](#todo)s below) would
1079-
be to allow lifting and lowering with `async` + `shared`. What's exciting about
1080-
this approach is that a non-`shared` component-level function could be safely
1081-
lowered with `async shared`. In the case that the lifted function being lowered
1082-
was also `async shared`, the entire call could happen on the non-main thread
1083-
without a context switch. But if the lifting side was non-`shared`, then the
1084-
Component Model could automatically handle the synchronization of enqueuing a
1085-
call to the export (as in the backpressure case mentioned above), returning a
1086-
subtask for the async caller to wait on as usual. Thus, the sync+async
1087-
composition story described above could naturally be extended to a
1088-
sync+async+shared composition story, continuing to avoid the "what color is
1089-
your function" problem (where `shared` is the [color]).
1090-
1091-
Even without any use of [`thread.spawn*`], native async provides an opportunity
1092-
to achieve some automatic parallelism "for free". In particular, due to the
1093-
shared-nothing nature of components, each component instance could be given a
1094-
separate thread on which to interleave all tasks executing in that instance.
1095-
Thus, in a cross-component call from `C1` to `C2`, `C2`'s task can run in a
1096-
separate thread that is automatically synchronized with `C1` by the runtime.
1097-
This is analogous to how traditional OS processes can run in separate threads,
1098-
except that the component model is *allowing*, but not *requiring* the separate
1099-
threads. While it's unclear how much parallelism this would unlock in practice,
1100-
it does present interesting opportunities to experiment with optimizations over
1101-
time as applications are built with more components.
1102-
1103-
11041104
## TODO
11051105

11061106
Native async support is being proposed incrementally. The following features

0 commit comments

Comments
 (0)