Skip to content

Commit 6b46702

Browse files
committed
Replace 'sync' with 'cancellable' in wait/poll/yield
Replace instance-suspension semantics of sync lower with instance-locking semantics in lift
1 parent 00f31f2 commit 6b46702

File tree

7 files changed

+751
-426
lines changed

7 files changed

+751
-426
lines changed

design/mvp/Async.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ additional goals and requirements for native async support:
5858
* Maintain meaningful cross-language call stacks (for the benefit of debugging,
5959
logging and tracing).
6060
* Provide mechanisms for applying and observing backpressure.
61+
* Allow non-reentrant synchronous and event-loop-driven core wasm code (that,
62+
e.g., assumes a single global linear memory stack) to not have to worry about
63+
additional reentrancy.
6164

6265

6366
## High-level Approach
@@ -474,6 +477,16 @@ wants to accept new accept new export calls while waiting or not.
474477
See the [`canon_backpressure_set`] function and [`Task.enter`] method in the
475478
Canonical ABI explainer for the setting and implementation of backpressure.
476479

480+
In addition to *explicit* backpressure set by wasm code, there is also an
481+
*implicit* source of backpressure used to protect non-reentrant core wasm
482+
code. In particular, when an export is lifted synchronously or using an
483+
`async callback`, a component-instance-wide lock is implicitly acquired every
484+
time core wasm is executed. By returning to the event loop after every event
485+
(instead of once at the end of the task), `async callback` exports release
486+
the lock between every event, allowing a higher degree of concurrency than
487+
synchronous exports. `async` (stackful) exports ignore the lock entirely and
488+
thus achieve the highest degree of (cooperative) concurrency.
489+
477490
Once a task is allowed to start according to these backpressure rules, its
478491
arguments are lowered into the callee's linear memory and the task is in
479492
the "started" state.
@@ -607,6 +620,9 @@ defined by the Component Model:
607620
* Whenever a task yields or waits on (or polls) a waitable set with an already
608621
pending event, whether the task "blocks" and transfers execution to its async
609622
caller is nondeterministic.
623+
* If multiple tasks are waiting on [backpressure](#backpressure), and the
624+
backpressure is disabled, the order in which these pending tasks (and new
625+
tasks started while there are still pending tasks) start is nondeterministic.
610626

611627
Despite the above, the following scenarios do behave deterministically:
612628
* If a component `a` asynchronously calls the export of another component `b`,

design/mvp/Binary.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
295295
| 0x05 => (canon task.cancel (core func)) 🔀
296296
| 0x0a 0x7f i:<u32> => (canon context.get i32 i (core func)) 🔀
297297
| 0x0b 0x7f i:<u32> => (canon context.set i32 i (core func)) 🔀
298-
| 0x0c async?:<async>? => (canon yield async? (core func)) 🔀
298+
| 0x0c cancel?:<cancel?> => (canon yield cancel? (core func)) 🔀
299299
| 0x06 async?:<async?> => (canon subtask.cancel async? (core func)) 🔀
300300
| 0x0d => (canon subtask.drop (core func)) 🔀
301301
| 0x0e t:<typeidx> => (canon stream.new t (core func)) 🔀
@@ -316,15 +316,17 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
316316
| 0x1d opts:<opts> => (canon error-context.debug-message opts (core func)) 📝
317317
| 0x1e => (canon error-context.drop (core func)) 📝
318318
| 0x1f => (canon waitable-set.new (core func)) 🔀
319-
| 0x20 async?:<async>? m:<core:memidx> => (canon waitable-set.wait async? (memory m) (core func)) 🔀
320-
| 0x21 async?:<async>? m:<core:memidx> => (canon waitable-set.poll async? (memory m) (core func)) 🔀
319+
| 0x20 cancel?:<cancel?> m:<core:memidx> => (canon waitable-set.wait cancel? (memory m) (core func)) 🔀
320+
| 0x21 cancel?:<cancel?> m:<core:memidx> => (canon waitable-set.poll cancel? (memory m) (core func)) 🔀
321321
| 0x22 => (canon waitable-set.drop (core func)) 🔀
322322
| 0x23 => (canon waitable.join (core func)) 🔀
323323
| 0x40 ft:<typeidx> => (canon thread.spawn_ref ft (core func)) 🧵
324324
| 0x41 ft:<typeidx> tbl:<core:tableidx> => (canon thread.spawn_indirect ft tbl (core func)) 🧵
325325
| 0x42 => (canon thread.available_parallelism (core func)) 🧵
326326
async? ::= 0x00 =>
327327
| 0x01 => async
328+
cancel? ::= 0x00 =>
329+
| 0x01 => cancellable 🚟
328330
opts ::= opt*:vec(<canonopt>) => opt*
329331
canonopt ::= 0x00 => string-encoding=utf8
330332
| 0x01 => string-encoding=utf16
@@ -505,6 +507,8 @@ named once.
505507
* The `0x00` variant of `importname'` and `exportname'` will be removed. Any
506508
remaining variant(s) will be renumbered or the prefix byte will be removed or
507509
repurposed.
510+
* Most built-ins should have a `<canonopt>*` immediate instead of an ad hoc
511+
subset of `canonopt`s.
508512

509513

510514
[`core:byte`]: https://webassembly.github.io/spec/core/binary/values.html#binary-byte

0 commit comments

Comments
 (0)