From 403098cf1aee342d7aa88acf59eec583369711e5 Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Wed, 4 Jun 2025 00:48:03 +0900 Subject: [PATCH 1/7] chore: various async fixes This comit adds some fixes/rewordings for the async sections. Signed-off-by: Victor Adossi --- design/mvp/Async.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index 825b40b7..6b9885e1 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -186,22 +186,23 @@ interface filesystem { } } ``` -a bindings generator in a language with `async` would only emit `async` -functions for `read` and `fetch`. Since in many languages `new` expressions -cannot be async, there is no `async constructor`. Use cases requiring -asynchronous construction can instead use `static async` functions, similar to -`from-stream` in this example. +A bindings generator processing the above WIT for a language with `async` would +only emit `async` functions for `read` and `from-stream`. +Since in many languages `new` expressions cannot be async, there is no +`async constructor`. Use cases requiring asynchronous construction can instead +use `static async` functions, similar to `from-stream` in this example. ### Task Every time a lifted function is called (e.g., when a component's export is called by the outside world), a new **task** is created that logically contains all the transitive control-flow state of the export call and will be destroyed -when the export call finishes. When all of a component's exports are lifted -synchronously, there will be at most one task alive at any one time. However, -when a component exports asynchronously-lifted functions, there can be multiple -tasks alive at once. +when the export call finishes. + +When all of a component's exports are lifted synchronously, there will be at most one +task alive at any one time. However, when a component exports asynchronously-lifted + functions, there can be multiple tasks alive at once. In the Canonical ABI explainer, a "task" is represented with the Python [`Task`] class. A new `Task` object is created (by [`canon_lift`]) each time From 2cf3e4b72da31ba3de1b594a2089331f1b4c0115 Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Wed, 4 Jun 2025 01:10:24 +0900 Subject: [PATCH 2/7] refactor: structured concurrency section Signed-off-by: Victor Adossi --- design/mvp/Async.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index 6b9885e1..bb9f2aa1 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -269,16 +269,19 @@ in the Canonical ABI explainer. ### Structured concurrency Calling *into* a component creates a `Task` to track ABI state related to the -*callee* (like "number of outstanding borrows"). Calling *out* of a component -creates a `Subtask` to track ABI state related to the *caller* (like "which -handles have been lent"). When one component calls another, there is thus a -`Subtask`+`Task` pair that collectively maintains the overall state of the call -and enforces that both components uphold their end of the ABI contract. But -when the host calls into a component, there is only a `Task` and, -symmetrically, when a component calls into the host, there is only a `Subtask`. - -Based on this, the call stack at any point in time when a component calls a -host-defined import will have a callstack of the general form: +*callee* (like "number of outstanding borrows"). + +Calling *out* of a component creates a `Subtask` to track ABI state related to +the *caller* (like "which handles have been lent"). + +When one component calls another, there is thus a `Subtask`+`Task` pair that +collectively maintains the overall state of the call and enforces that both +components uphold their end of the ABI contract. But when the host calls into +a component, there is only a `Task` and, symmetrically, when a component calls +into the host, there is only a `Subtask`. + +Based on this, the call stack for a component to host-defined import will be of +the general form: ``` [Host caller] <- [Task] <- [Subtask+Task]* <- [Subtask] <- [Host callee] ``` From 4da97f26032544b71172135803e0c6ea4f338ea1 Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Wed, 4 Jun 2025 03:00:09 +0900 Subject: [PATCH 3/7] chore: clarify the function being referred to Signed-off-by: Victor Adossi --- design/mvp/Async.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index bb9f2aa1..0ca47d7e 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -723,7 +723,8 @@ world w { export foo: func(s: string) -> string; } ``` -the default sync export function signature is: + +The default sync export function signature for export `foo` is: ```wat ;; sync (func (param $s-ptr i32) (param $s-len i32) (result $retp i32)) @@ -739,18 +740,24 @@ The async export ABI provides two flavors: stackful and stackless. The stackful ABI is currently gated by the 🚟 feature. -The async stackful export function signature is: +The async stackful export function signature for export `foo` (defined above +in world `w`) is: + ```wat ;; async, no callback (func (param $s-ptr i32) (param $s-len i32)) ``` -The parameters work just like synchronous parameters. There is no core function -result because a callee [returns](#returning) their value by *calling* the -*imported* `task.return` function which has signature: + +The parameters work just like synchronous parameters. + +There is no core function result because a callee [returns](#returning) their +value by *calling* the *imported* `task.return` function which has signature: + ```wat ;; task.return (func (param $ret-ptr i32) (result $ret-len i32)) ``` + The parameters of `task.return` work the same as if the WIT return type was the WIT parameter type of a synchronous function. For example, if more than 16 core parameters would be needed, a single `i32` pointer into linear memory is @@ -758,14 +765,18 @@ used. ##### Stackless Async Exports -The async stackless export function signature is: +The async stackless export function signature for export `foo` (defined above +in world `w`) is: + ```wat ;; async, callback (func (param $s-ptr i32) (param $s-len i32) (result i32)) ``` + The parameters also work just like synchronous parameters. The callee returns -their value by calling `task.return` just like the stackful case. The `(result -i32)` lets the core function return what it wants the runtime to do next: +their value by calling `task.return` just like the stackful case. + +The `(result i32)` lets the core function return what it wants the runtime to do next: * If the low 4 bits are `0`, the callee completed (and called `task.return`) without blocking. * If the low 4 bits are `1`, the callee wants to yield, allowing other code @@ -777,9 +788,11 @@ i32)` lets the core function return what it wants the runtime to do next: When an async stackless function is exported, a companion "callback" function must also be exported with signature: + ```wat (func (param i32 i32 i32) (result i32)) ``` + The `(result i32)` has the same interpretation as the stackless export function and the runtime will repeatedly call the callback until a value of `0` is returned. The `i32` parameters describe what happened that caused the callback From a0526809b5d0e2854f23daada18e2100c830d6c5 Mon Sep 17 00:00:00 2001 From: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:44:30 +0900 Subject: [PATCH 4/7] fix: errant space Co-authored-by: Luke Wagner --- design/mvp/Async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index 0ca47d7e..f5089624 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -202,7 +202,7 @@ when the export call finishes. When all of a component's exports are lifted synchronously, there will be at most one task alive at any one time. However, when a component exports asynchronously-lifted - functions, there can be multiple tasks alive at once. +functions, there can be multiple tasks alive at once. In the Canonical ABI explainer, a "task" is represented with the Python [`Task`] class. A new `Task` object is created (by [`canon_lift`]) each time From 28e89025563305d8a6aed0f8cf2b1a8e131366fb Mon Sep 17 00:00:00 2001 From: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:47:04 +0900 Subject: [PATCH 5/7] refactor: callstack sentence --- design/mvp/Async.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index f5089624..aca46518 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -280,8 +280,8 @@ components uphold their end of the ABI contract. But when the host calls into a component, there is only a `Task` and, symmetrically, when a component calls into the host, there is only a `Subtask`. -Based on this, the call stack for a component to host-defined import will be of -the general form: +Based on this, the call stack when a component calls a host-defined import will +be a call stack of the general form: ``` [Host caller] <- [Task] <- [Subtask+Task]* <- [Subtask] <- [Host callee] ``` From fb4ca904ecac8f236bd0baa817d629452a08d2e8 Mon Sep 17 00:00:00 2001 From: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:51:33 +0900 Subject: [PATCH 6/7] fix: address code review comments --- design/mvp/Async.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index aca46518..07d0c227 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -283,7 +283,13 @@ into the host, there is only a `Subtask`. Based on this, the call stack when a component calls a host-defined import will be a call stack of the general form: ``` -[Host caller] <- [Task] <- [Subtask+Task]* <- [Subtask] <- [Host callee] +[Host] + ↓ host calls component export +[Component Task] + ↓ component calls import implemented by another component's export 0..N times +[Component Subtask <> Component Task]* + ↓ component calls import implemented by the host +[Component Subtask <> Host task] ``` Here, the `<-` arrow represents the `supertask` relationship that is immutably established when first making the call. A paired `Subtask` and `Task` have the @@ -742,7 +748,6 @@ The stackful ABI is currently gated by the 🚟 feature. The async stackful export function signature for export `foo` (defined above in world `w`) is: - ```wat ;; async, no callback (func (param $s-ptr i32) (param $s-len i32)) @@ -752,7 +757,6 @@ The parameters work just like synchronous parameters. There is no core function result because a callee [returns](#returning) their value by *calling* the *imported* `task.return` function which has signature: - ```wat ;; task.return (func (param $ret-ptr i32) (result $ret-len i32)) @@ -767,7 +771,6 @@ used. The async stackless export function signature for export `foo` (defined above in world `w`) is: - ```wat ;; async, callback (func (param $s-ptr i32) (param $s-len i32) (result i32)) @@ -788,7 +791,6 @@ The `(result i32)` lets the core function return what it wants the runtime to do When an async stackless function is exported, a companion "callback" function must also be exported with signature: - ```wat (func (param i32 i32 i32) (result i32)) ``` From bf3171ae841e0e80edeef7875de5c29379b7e916 Mon Sep 17 00:00:00 2001 From: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> Date: Fri, 6 Jun 2025 02:30:55 +0900 Subject: [PATCH 7/7] fix: prose host defined imports explanation prose Co-authored-by: Luke Wagner --- design/mvp/Async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index 07d0c227..7fea096a 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -281,7 +281,7 @@ a component, there is only a `Task` and, symmetrically, when a component calls into the host, there is only a `Subtask`. Based on this, the call stack when a component calls a host-defined import will -be a call stack of the general form: +have the general form: ``` [Host] ↓ host calls component export