Skip to content

Commit d119600

Browse files
docs(wasm abi): Allign docs and clean redaction.
1 parent ab97f28 commit d119600

1 file changed

Lines changed: 28 additions & 28 deletions

File tree

docs/pages/reference/wasm-abi.md

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: "WASM module ABI"
33
description: "The wire format a `.wasm` module must follow to be importable by Edge Python."
44
---
55

6-
> **Sealed contract, plugin ABI v1.** Every signature, op code, tag, and error kind here is the public contract for CDN-distributed `.wasm` plugin modules (Path A). New host packages arrive as new `Op` values, never new imports; a future wire-level break would ship as `env_v2.*` without removing v1. Distinct from the `compiler<->host` interface embedders declare (see [host packages](/reference/writing-modules#path-c-host-capability)), embedders aren't bound by the 6-import limit here.
6+
> **Sealed contract, plugin ABI v1.** Every signature, op code, tag, and error kind here is the public contract for CDN-distributed `.wasm` plugin modules (Path A). New host packages arrive as new `Op` values, never new imports; a future wire-level break would ship as `env_v2.*` without removing v1. Distinct from the `compiler<->host` interface embedders declare (see [host packages](/reference/writing-modules#path-b-host-capability)), embedders aren't bound by the 6-import limit here.
77
88
A `.wasm` module imported via `from "<url>" import <names>` follows the contract below. Handle-based API: the host owns all values, the guest sees only opaque `u32` handles, and one universal dispatch primitive (`edge_op`) covers every operation. New types, methods, and language features become available to existing modules with no ABI change.
99

@@ -22,7 +22,7 @@ extern "C" fn <name>(argv: *const u32, argc: u32, out: *mut u32) -> i32;
2222
| `out` | Pointer (in **guest** linear memory) where the guest writes ONE handle for the return value. |
2323
| return | `0` = success, `1` = error (host pulls the error via `edge_take_error` immediately). |
2424

25-
`argv` handles are host-owned and live for the call. Handles the guest creates via `edge_encode` or `edge_op` are guest-owned until released, the guest must `edge_release` each before returning, except the one written into `*out`.
25+
`argv` handles are host-owned and live for the call. Handles the guest creates via `edge_encode` or `edge_op` are guest-owned until released: the guest must `edge_release` each before returning, except the one written into `*out`.
2626

2727
## Required guest exports
2828

@@ -38,7 +38,7 @@ pub extern "C" fn __edge_abi_version() -> u32;
3838

3939
`__edge_alloc` lets the host stage `argv` arrays in guest linear memory before invoking each export.
4040

41-
`__edge_abi_version` returns the wire-format version (currently `1`). The host MUST read this once at instantiation and refuse unknown versions, otherwise a v2 host would silently decode garbage from a v1 module. (At v1 every loader targets 1 so the bundled `compiler.wasm` shim does not yet read the symbol; the check becomes load-bearing when v2 ships.)
41+
`__edge_abi_version` returns the wire-format version (currently `1`). The host MUST read this once at instantiation and refuse unknown versions; otherwise a v2 host would silently decode garbage from a v1 module. (At v1 every loader targets 1 so the bundled `compiler.wasm` shim does not yet read the symbol; the check becomes load-bearing when v2 ships.)
4242

4343
The reference `wasm-pdk` crate emits both symbols automatically. `EDGE_ABI_VERSION` lives in the shared `wasm-abi` crate (no_std, zero deps) so host and every PDK read the same value.
4444

@@ -103,7 +103,7 @@ Stash an error visible after the guest returns `1`, used when an error did not o
103103
| `NewSet` | 12 | construct set from `argv` items; unhashable item -> error |
104104
| `NewFrozenSet` | 13 | construct frozenset from `argv` items; unhashable item -> error |
105105

106-
`Op::Iter` materialises the receiver into a List handle (set sorted via `vm.sort_set_items`; dict yields keys; str splits to single-char strings); `Op::IterNext` advances it. `NewDict` / `NewList` construct empty composites; `NewTuple` / `NewSet` / `NewFrozenSet` construct from the `argv` items in one call. `TypeOf` returns names matching the Python builtin: `"int"`, `"float"`, `"str"`, `"bytes"`, `"list"`, `"dict"`, `"set"`, `"tuple"`, `"NoneType"`, `"bool"`, `"object"` (user instance), etc. Values `14..u32::MAX` reserved, old hosts return `1` with `kind=Runtime`.
106+
`Op::Iter` materialises the receiver into a List handle (set sorted via `vm.sort_set_items`; dict yields keys; str splits to single-char strings); `Op::IterNext` advances it. `NewDict` / `NewList` construct empty composites; `NewTuple` / `NewSet` / `NewFrozenSet` construct from the `argv` items in one call. `TypeOf` returns names matching the Python builtin: `"int"`, `"float"`, `"str"`, `"bytes"`, `"list"`, `"dict"`, `"set"`, `"tuple"`, `"NoneType"`, `"bool"`, `"object"` (user instance), etc. Values `14..u32::MAX` reserved; old hosts return `1` with `kind=Runtime`.
107107

108108
## Tags (for `edge_encode` / `edge_decode`)
109109

@@ -142,7 +142,7 @@ extern crate alloc;
142142
use alloc::string::String;
143143
use wasm_pdk::*;
144144

145-
wasm_pdk::module!(); // expands to #[global_allocator] + #[panic_handler]
145+
wasm_pdk::module!(); // expands to #[global_allocator] + #[panic_handler]
146146

147147
#[plugin_fn]
148148
fn slugify(s: String) -> String {
@@ -250,14 +250,14 @@ fn hypot(coords: Args) -> Result<f64> {
250250

251251
### Consuming `wasm-pdk` from your own crate
252252

253-
Not on crates.io, depend from GitHub, pinned to a release tag:
253+
Not on crates.io depend from GitHub, pinned to a release tag:
254254

255255
```toml
256256
[dependencies]
257257
wasm-pdk = { git = "https://github.com/dylan-sutton-chavez/edge-python", tag = "v0.1.0" }
258258
```
259259

260-
Cargo resolves `wasm-abi` and `wasm-pdk-macros` transitively. Pinning to a tag (vs `branch = "main"`) gives reproducible builds and a known wire-ABI version, your module compiled against `wasm-pdk vX.Y.Z` is binary-compatible with the `compiler_lib.wasm` of the same release. Bump `tag` + `cargo update -p wasm-pdk` to upgrade. Use `branch = "main"` only for unreleased iteration.
260+
Cargo resolves `wasm-abi` and `wasm-pdk-macros` transitively. Pinning to a tag (vs `branch = "main"`) gives reproducible builds and a known wire-ABI version your module compiled against `wasm-pdk vX.Y.Z` is binary-compatible with the `compiler_lib.wasm` of the same release. Bump `tag` + `cargo update -p wasm-pdk` to upgrade. Use `branch = "main"` only for unreleased iteration.
261261

262262
Use it from a script:
263263

@@ -291,9 +291,9 @@ static A: lol_alloc::LeakingPageAllocator = lol_alloc::LeakingPageAllocator;
291291

292292
#[link(wasm_import_module = "env")]
293293
unsafe extern "C" {
294-
fn edge_op(op: u32, recv: u32, name_ptr: *const u8, name_len: u32, argv_ptr: *const u32, argc: u32, out: *mut u32) -> i32;
295-
fn edge_encode(tag: u32, ptr: *const u8, len: u32) -> u32;
296-
fn edge_release(h: u32);
294+
fn edge_op(op: u32, recv: u32, name_ptr: *const u8, name_len: u32, argv_ptr: *const u32, argc: u32, out: *mut u32) -> i32;
295+
fn edge_encode(tag: u32, ptr: *const u8, len: u32) -> u32;
296+
fn edge_release(h: u32);
297297
}
298298

299299
const OP_CALL: u32 = 0;
@@ -313,7 +313,7 @@ pub extern "C" fn slugify(argv: *const u32, argc: u32, out: *mut u32) -> i32 {
313313
// 1) input.lower()
314314
let mut lower: u32 = 0;
315315
if unsafe { edge_op(OP_CALL, input, b"lower".as_ptr(), 5, core::ptr::null(), 0, &mut lower) } != 0 {
316-
return 1;
316+
return 1;
317317
}
318318

319319
// 2) lower.replace(" ", "-")
@@ -336,28 +336,28 @@ For `from "<url>" import <names>` with a `.wasm` URL: the host fetches bytes (ve
336336

337337
## Constraints and caveats
338338

339-
* **Refcounted handles.** Guest releases every handle it creates via `edge_encode` / `edge_op` except the one returned through `*out`. Host releases argv.
340-
* **`edge_decode` is primitives-only.** For `list`, `dict`, `set`, instances, use `edge_op` (e.g. `Call recv "items"`, `GetItem recv idx`).
341-
* **Trailing kwargs slot.** Every plugin call carries one extra `u32` after the user's positional argv: handle `0` when the caller passed no `name=value` arguments, otherwise a `dict` handle holding the pairs. The `#[plugin_fn]` macro folds it into a `Kwargs` parameter if declared (`fn foo(a: Handle, kw: Kwargs)`); otherwise it is silently absorbed and the function sees only its positional args.
342-
* **Invoking a caller-supplied callable.** From the guest, `edge_op Call recv "__call__" argv` invokes `recv` directly, lambdas, builtins, classes, bound methods all route through the same dispatch the language uses. Use this to wire Python hooks like `default`, `object_hook`, `parse_int`.
343-
* **Reentrance supported.** A guest's `edge_op` runs while the VM is paused on the script's `CallExtern`. Method dispatch routes through the same `vm/handlers/builtin_methods/` descriptor table the language uses internally, adding a method there makes it visible to existing modules with no recompile.
344-
* **Error-as-status, not panic.** Returning `1` does NOT abort the host, the host pulls the error and raises it as a typed Python exception.
345-
* **Memory ownership.** Host only reads guest linear memory at well-defined copy points. Guest-internal allocations stay private.
339+
- **Refcounted handles.** Guest releases every handle it creates via `edge_encode` / `edge_op` except the one returned through `*out`. Host releases argv.
340+
- **`edge_decode` is primitives-only.** For `list`, `dict`, `set`, instances, use `edge_op` (e.g. `Call recv "items"`, `GetItem recv idx`).
341+
- **Trailing kwargs slot.** Every plugin call carries one extra `u32` after the user's positional argv: handle `0` when the caller passed no `name=value` arguments, otherwise a `dict` handle holding the pairs. The `#[plugin_fn]` macro folds it into a `Kwargs` parameter if declared (`fn foo(a: Handle, kw: Kwargs)`); otherwise it is silently absorbed and the function sees only its positional args.
342+
- **Invoking a caller-supplied callable.** From the guest, `edge_op Call recv "__call__" argv` invokes `recv` directly lambdas, builtins, classes, bound methods all route through the same dispatch the language uses. Use this to wire Python hooks like `default`, `object_hook`, `parse_int`.
343+
- **Reentrance supported.** A guest's `edge_op` runs while the VM is paused on the script's `CallExtern`. Method dispatch routes through the same `vm/handlers/builtin_methods/` descriptor table the language uses internally adding a method there makes it visible to existing modules with no recompile.
344+
- **Error-as-status, not panic.** Returning `1` does NOT abort the host the host pulls the error and raises it as a typed Python exception.
345+
- **Memory ownership.** Host only reads guest linear memory at well-defined copy points. Guest-internal allocations stay private.
346346

347347
## Author conveniences
348348

349349
The `wasm-pdk` crate (Plugin Development Kit), bundled in this repo, publishable independently of `compiler.wasm`, provides:
350350

351-
* `#[plugin_fn]`, typed Rust function -> wire-conformant export.
352-
* `#[plugin_const]`, zero-arg fn -> module constant via the `__const_<name>` export convention; the host calls it once at import and binds the value as a module attribute.
353-
* `#[plugin_class]` / `#[plugin_methods]` / `#[plugin_ctor]`, expose a Rust struct as a Python class via the `__class_<Name>_<method>` export convention.
354-
* `module!()`, expands to `#[global_allocator]` + `#[panic_handler]`.
355-
* `FromValue` / `IntoValue` with primitive impls (`i64`, `i128`, `f64`, `bool`, `String`, `&str`, `Bytes`, `Option<T>`, `Handle`). `i64` rejects out-of-range values with `ValueError`; use `i128` for the full range. `Bytes` maps to Python `bytes` over `tag::RAW`.
356-
* `Handle` with `Drop`-driven release plus `call`, `get_attr` / `set_attr`, `get_item` / `set_item`, `len`, `iter` / `iter_next`, `new_dict` / `new_list`, `new_tuple` / `new_set` / `new_frozenset`, `type_of`.
357-
* `Args`, trailing variadic positional params as borrowed handles; declare it as the last param before any `Kwargs`.
358-
* `Kwargs`, thin wrapper around the trailing kwargs handle with `get::<T>(name)` for primitive kwargs and `get_handle(name)` for callables, tuples, dicts.
359-
* `PluginCell<T>`, single-threaded interior mutability cell for static plugin state.
360-
* `__edge_alloc` + `__edge_abi_version` emitted automatically.
351+
- `#[plugin_fn]`, typed Rust function -> wire-conformant export.
352+
- `#[plugin_const]`, zero-arg fn -> module constant via the `__const_<name>` export convention; the host calls it once at import and binds the value as a module attribute.
353+
- `#[plugin_class]` / `#[plugin_methods]` / `#[plugin_ctor]`, expose a Rust struct as a Python class via the `__class_<Name>_<method>` export convention.
354+
- `module!()`, expands to `#[global_allocator]` + `#[panic_handler]`.
355+
- `FromValue` / `IntoValue` with primitive impls (`i64`, `i128`, `f64`, `bool`, `String`, `&str`, `Bytes`, `Option<T>`, `Handle`). `i64` rejects out-of-range values with `ValueError`; use `i128` for the full range. `Bytes` maps to Python `bytes` over `tag::RAW`.
356+
- `Handle` with `Drop`-driven release plus `call`, `get_attr` / `set_attr`, `get_item` / `set_item`, `len`, `iter` / `iter_next`, `new_dict` / `new_list`, `new_tuple` / `new_set` / `new_frozenset`, `type_of`.
357+
- `Args`, trailing variadic positional params as borrowed handles; declare it as the last param before any `Kwargs`.
358+
- `Kwargs`, thin wrapper around the trailing kwargs handle with `get::<T>(name)` for primitive kwargs and `get_handle(name)` for callables, tuples, dicts.
359+
- `PluginCell<T>`, single-threaded interior mutability cell for static plugin state.
360+
- `__edge_alloc` + `__edge_abi_version` emitted automatically.
361361

362362
The macro emits the worked-example boilerplate; manual is around 25 lines for the first function, around 5 per additional.
363363

0 commit comments

Comments
 (0)