@@ -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
644720At 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
10251101these 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
11061106Native async support is being proposed incrementally. The following features
0 commit comments