@@ -100,26 +100,20 @@ to define their imports and exports.
100100
101101To provide wasm runtimes with additional optimization opportunities for
102102languages with "stackless" concurrency (e.g. languages using ` async ` /` await ` ),
103- two ` async ` ABI sub-options are provided: a "stackless" ABI selected by
104- providing a ` callback ` function and a "stackful" ABI selected by * not*
105- providing a ` callback ` function. The stackless ABI allows core wasm to
106- repeatedly return to an [ event loop] to receive events concerning a selected
107- set of "waitables", thereby clearing the native stack when waiting for events
108- and allowing the runtime to reuse stack segments between events. In the
109- [ future] ( #TODO ) , a ` strict-callback ` option may be added to require (via
110- runtime traps) * all* waiting to happen via the event loop, thereby giving the
111- engine more up-front information that the engine can use to avoid allocating
112- [ fibers] in more cases. In the meantime, to support complex applications with
113- mixed dependencies and concurrency models, the ` callback ` immediate allows
114- * both* returning to the event loop * and* making blocking calls to wait for
115- event.
103+ two async ABI sub-options are provided: a "stackless" async ABI selected by
104+ providing a ` callback ` function and a "stackful" async ABI selected by * not*
105+ providing a ` callback ` function. The stackless async ABI allows core wasm to
106+ repeatedly return to an event loop to receive events (delivered to the
107+ ` callback ` ), thereby clearing the native stack for the benefit of the wasm
108+ runtime while waiting in the event loop.
116109
117110To propagate backpressure, it's necessary for a component to be able to say
118- "there are too many async export calls already in progress, don't start any
119- more until I let some of them complete". Thus, the low-level async ABI provides
120- a way to apply and release backpressure.
111+ "there are too many concurrent export calls already in progress, don't start
112+ any more until I let some of them complete". Thus, the Component Model provides
113+ a built-in way for a component instance to apply and release backpressure that
114+ callers must always be prepared to handle.
121115
122- With this backpressure protocol in place, there is a natural way for sync and
116+ With this backpressure mechanism in place, there is a natural way for sync and
123117async code to interoperate:
1241181 . If an async component calls a sync component and the sync component blocks,
125119 execution is immediately returned to the async component, effectively
@@ -291,20 +285,22 @@ in the Canonical ABI explainer.
291285
292286### Structured concurrency
293287
294- Calling * into* a component creates a ` Task ` to track ABI state related to the
295- * callee* (like "number of outstanding borrows").
296-
297- Calling * out* of a component creates a ` Subtask ` to track ABI state related to
298- the * caller* (like "which handles have been lent").
299-
300- When one component calls another, there is thus a ` Subtask ` +` Task ` pair that
301- collectively maintains the overall state of the call and enforces that both
302- components uphold their end of the ABI contract. But when the host calls into
303- a component, there is only a ` Task ` and, symmetrically, when a component calls
304- into the host, there is only a ` Subtask ` .
305-
306- Based on this, the call stack when a component calls a host-defined import will
307- have the general form:
288+ As mentioned above, calling a component export creates a task to track the
289+ state used to enforce Canonical ABI rules that apply to the callee (an example
290+ being: the number of received borrowed handles that still need to be dropped
291+ before the call returns).
292+
293+ Symmetrically, calling a component * import* creates a ** subtask** to track the
294+ state used to enforce Canonical ABI rules that apply to the * caller* (an
295+ example being: which handles have been lent that the caller can't drop until
296+ the call resolves).
297+
298+ When one component calls another, there is thus a new task+subtask pair created
299+ to ensure that both components uphold their end of the Canonical ABI rules. But
300+ when the host calls a component export, there is only a task and,
301+ symmetrically, when a component calls a host-defined import, there is only a
302+ subtask. Thus, the ** async call stack** at the point when a component calls a
303+ host-defined import will have the general form:
308304```
309305[Host]
310306 ↓ host calls component export
@@ -314,27 +310,28 @@ have the general form:
314310 ↓ component calls import implemented by the host
315311[Component Subtask <> Host task]
316312```
317- Here, the ` <- ` arrow represents the ` supertask ` relationship that is immutably
318- established when first making the call. A paired ` Subtask ` and ` Task ` have the
319- same ` supertask ` and can thus be visualized as a single node in the callstack.
320-
321- (These concepts are represented in the Canonical ABI Python code via the
322- [ ` Task ` ] and [ ` Subtask ` ] classes.)
323-
324- One semantically-observable use of this async call stack is to distinguish
325- between hazardous ** recursive reentrance** , in which a component instance is
326- reentered when one of its tasks is already on the callstack, from
327- business-as-usual ** sibling reentrance** , in which a component instance is
328- freshly reentered when its other tasks are suspended waiting on I/O. Recursive
329- reentrance currently always traps, but may be allowed (and indicated to core
330- wasm) in an opt-in manner in the [ future] ( #TODO ) .
313+ Here, the ` ↓ ` arrow represents the ** subtask** relationship (the dual of which
314+ is the ** supertask** relationship). Since a task+subtask pair have the same
315+ supertask, they can be thought of as a single node in the async call stack.
316+
317+ A subtask/supertask relationship is immutably established when an import is
318+ called, setting the [ current task] ( #current-task ) as the supertask
319+ of the new subtask created for the import call.
320+
321+ A semantically-observable use of the async call stack is to distinguish between
322+ hazardous ** recursive reentrance** , in which a component instance is reentered
323+ when one of its tasks is already on the callstack, from business-as-usual
324+ ** sibling reentrance** , in which a component instance is reentered for the
325+ first time on a particular async call stack. Recursive reentrance currently
326+ always traps, but will be allowed (and indicated to core wasm) in an opt-in
327+ manner in the [ future] ( #TODO ) .
331328
332329The async call stack is also useful for non-semantic purposes such as providing
333- backtraces when debugging, profiling and distributed tracing. While particular
334- languages can and do maintain their own async call stacks in core wasm state,
335- without the Component Model's async call stack, linkage * between* different
336- languages would be lost at component boundaries, leading to a loss of overall
337- context in multi-component applications.
330+ backtraces when debugging, profiling and tracing. While particular languages
331+ can and do maintain their own async call stacks in core wasm state, without the
332+ Component Model's async call stack, linkage * between* different languages would
333+ be lost at component boundaries, leading to a loss of overall context in
334+ multi-component applications.
338335
339336There is an important nuance to the Component Model's minimal form of
340337Structured Concurrency compared to Structured Concurrency support that appears
@@ -464,7 +461,7 @@ the Canonical-ABI-defined "yield" code to the event loop.
464461
465462### Backpressure
466463
467- Once a component exports asynchronously-lifted functions, multiple concurrent
464+ Once a component exports functions using the async ABI , multiple concurrent
468465export calls can start piling up, each consuming some of the component's finite
469466private resources (like linear memory), requiring the component to be able to
470467exert * backpressure* to allow some tasks to finish (and release private
@@ -473,26 +470,23 @@ call the [`backpressure.set`] built-in to set a component-instance-wide
473470"backpressure" flag that causes subsequent export calls to immediately return
474471in the "starting" state without calling the component's Core WebAssembly code.
475472
476- See the [ ` canon_backpressure_set ` ] function and [ ` Task.enter ` ] method in the
477- Canonical ABI explainer for the setting and implementation of backpressure.
478-
479473In addition to * explicit* backpressure set by wasm code, there is also an
480- * implicit* source of backpressure used to protect non-reentrant core wasm
481- code. In particular, when an export is lifted synchronously or using an
482- ` async callback ` , a component-instance-wide lock is implicitly acquired every
483- time core wasm is executed. By returning to the event loop after every event
484- (instead of once at the end of the task), ` async callback ` exports release
485- the lock between every event, allowing a higher degree of concurrency than
486- synchronous exports. ` async ` (stackful) exports ignore the lock entirely and
487- thus achieve the highest degree of (cooperative) concurrency.
474+ * implicit* source of backpressure used to protect non-reentrant core wasm code.
475+ In particular, when an export uses the sync ABI or the stackless async ABI, a
476+ component-instance-wide lock is implicitly acquired every time core wasm is
477+ executed. By returning to the event loop after every event (instead of once at
478+ the end of the task), stackless async exports release the lock between every
479+ event, allowing a higher degree of concurrency than synchronous exports.
480+ Stackfull async exports ignore the lock entirely and thus achieve the highest
481+ degree of (cooperative) concurrency.
488482
489483Once a task is allowed to start according to these backpressure rules, its
490484arguments are lowered into the callee's linear memory and the task is in
491485the "started" state.
492486
493487### Returning
494488
495- The way an async function returns its value is by calling [ ` task.return ` ] ,
489+ The way an async export call returns its value is by calling [ ` task.return ` ] ,
496490passing the core values that are to be lifted as * parameters* .
497491
498492Returning values by calling ` task.return ` allows a task to continue executing
@@ -507,9 +501,7 @@ loop interleaving `stream.read`s (of the readable end passed for `in`) and
507501` stream.write ` s (of the writable end it ` stream.new ` ed) before exiting the
508502task.
509503
510- Once ` task.return ` is called, the task is in the "returned" state and can
511- finish execution any time thereafter. See the [ ` canon_task_return ` ] function in
512- the Canonical ABI explainer for more details.
504+ Once ` task.return ` is called, the task is in the "returned" state.
513505
514506### Borrows
515507
@@ -527,10 +519,10 @@ callee task that is decremented when the `borrow` handle is dropped. If a
527519callee task attempts to return when its ` num_borrows ` is greater than zero, the
528520callee traps.
529521
530- In an asynchronous setting, the only generalization necessary is that, since
531- there can be multiple overlapping async tasks executing in a component
532- instance, a borrowed handle must track * which * task's ` num_borrow ` s was
533- incremented so that the correct counter can be decremented .
522+ In an asynchronous setting, since there can be multiple overlapping async tasks
523+ executing in a component instance, a borrowed handle must track * which * task's
524+ ` num_borrow ` s was incremented so that the correct counter can be decremented
525+ and there is a trap upon ` task.return ` if ` num_borrows ` is nonzero .
534526
535527### Cancellation
536528
@@ -584,28 +576,28 @@ Canonical ABI explainer for more details.
584576
585577### Nondeterminism
586578
587- Given the general goal of supporting concurrency, Component Model async
588- necessarily introduces a degree of nondeterminism. Async concurrency is however
589- [ cooperative] , meaning that nondeterministic behavior can only be observed at
590- well-defined points in the program. This contrasts with non-cooperative
591- [ multithreading] in which nondeterminism can be observed at every core wasm
592- instruction.
593-
594- One inherent source of potential nondeterminism that is independent of async is
595- the behavior of host-defined import and export calls. Async extends this
596- host-dependent nondeterminism to the behavior of the ` read ` and ` write `
597- built-ins called on ` stream ` s and ` future ` s that have been passed to and from
598- the host via host-defined import and export calls. However, just as with import
599- and export calls, it is possible for a host to define a deterministic ordering
600- of ` stream ` and ` future ` ` read ` and ` write ` behavior such that overall
601- component execution is deterministic.
579+ Component Model concurrency support necessarily introduces a degree of
580+ nondeterminism. However, until Core WebAssembly adds
581+ [ shared-everything-threads] , Component Model concurrency is [ cooperative] ,
582+ which means that nondeterministic behavior can only be observed at well-defined
583+ points in the program. Once [ shared-everything-threads] is added,
584+ WebAssembly's full [ weak memory model] will be observable, but only within
585+ components that use the new ` shared ` attribute on functions.
586+
587+ One inherent source of potential nondeterminism that is independent of the
588+ Component Model is the behavior of host-defined import and export calls.
589+ Component Model concurrency extends this host-dependent nondeterminism to the
590+ behavior of the ` read ` and ` write ` built-ins called on ` stream ` s and ` future ` s
591+ that have been passed to and from the host. However, just as with import and
592+ export calls, it is possible for a host to define a deterministic ordering of
593+ ` stream ` and ` future ` ` read ` and ` write ` behavior such that overall component
594+ execution is deterministic.
602595
603596In addition to the inherent host-dependent nondeterminism, the Component Model
604597adds several internal sources of nondeterministic behavior that are described
605598next. However, each of these sources of nondeterminism can be removed by a host
606- implementing the WebAssembly [ Determinsic Profile] , maintaining the ability for
607- a host to provide spec-defined deterministic component execution for components
608- even when they use async.
599+ implementing the WebAssembly [ Deterministic Profile] , maintaining the ability for
600+ a host to provide spec-defined deterministic component execution for components.
609601
610602The following sources of nondeterminism arise via internal built-in operations
611603defined by the Component Model:
0 commit comments