# WASM extension ## Function The WASM extension uses the WASM/WASI module as the backend entry, which is suitable for writing high-performance logic, complex state management, or extensions that require clearer ABI boundaries in Rust. Its `manifest.kind` is `wasm` and its `manifest.abi` is `bts-wasi-1`. BT provides the `bt-extension-sdk` Rust SDK to help extension authors handle BtValueBinary encoding and decoding, WASM linear memory allocation release, call ID distribution and extended object handles. ## Syntax WASM extensions usually use `module.wasm` as the entry point: ```json { "kind": "wasm", "abi": "bts-wasi-1", "entry": "module.wasm" } ``` WASM module must export: | export | Description | | ------ | ------ | | `memory` | WASM linear memory. | | `bts_alloc(len) -> ptr` | Write parameters to the host to apply for memory. | | `bts_call(id, args_ptr, args_len) -> packed_ptr_len` | Execute business logic based on the call ID and return the result buffer. | | `bts_free(ptr, len)` | Release the parameter or return value memory returned by the host. | module can additionally export these functions: | Export | Description | | ------ | ------ | | `bts_set_module_id(module_id)` | Called after the host instantiates the module, the SDK uses the module ID to create an extended object handle. | | `bts_init(config_ptr, config_len) -> packed_ptr_len` | Called after worker initialization, the parameter is the runtime configuration object compiled by the host. | | `bts_shutdown() -> packed_ptr_len` | Called before worker exits normally, used to release extended internal connections or cache. | | `bts_stats() -> packed_ptr_len` | Returns the extension side statistics object for subsequent debugging interface summary by the host. | Lifecycle export is optional. Old WASM extensions will still work without exporting these functions; if they do, the signature must match the table. ## Parameters The parameters received by the WASM extension come from the BtValueBinary encoded array. Entry function parameters are passed in in bindings order; object method parameters will additionally receive the receiver `ExtObject` handle at the front. ## Return Value WASM handler returns `BtValue` through SDK. When returning the original type, it must match the `returns` of bindings; when returning the object type, it must return the same type `ExtObject` created by the current module. ## Rust SDK process Create WASM extension: ```text bt ext new calc_sdk --kind wasm cd calc_sdk rustup target add wasm32-wasip1 cargo build --target wasm32-wasip1 --release copy target\wasm32-wasip1\release\calc_sdk.wasm module.wasm bt ext build . -o calc_sdk.bts ``` The core structure in the scaffolding is similar: ```rust bt_extension! { 1 => entry_create, 2 => entry_add, 3 => entry_value, 4 => entry_close, } ``` The number here must be consistent with the function or method `id` in `bindings.json`. If the extension requires life cycle export, you can use the independent helper macro provided by the SDK: ```rust bt_extension_init!(init_worker); bt_extension_shutdown!(shutdown_worker); bt_extension_stats!(stats_worker); ``` `bt_extension!` is still only responsible for `bts_alloc`, `bts_free`, `bts_set_module_id` and `bts_call`, and will not force old extensions to export life cycle functions. ## Object handles WASM extensions cannot hand Rust objects directly to BT scripts. The correct approach is: 1. The extension internally uses `ObjectStore` to save the real state. 2. Return the `ExtObject` handle to BT. 3. When the script calls the object method, the host passes this handle to the WASM handler as the first parameter. 4. The handler finds the extended internal state based on the object ID and then executes the method. The script side is still a normal chain call: undefined in the ```bt value = calc(3).add(7).value() // Output: 10 print value ``` script will return the bindings object name instead of the low-level ABI label: ```bt object = calc(3) // Output: Calc print type(object) ``` ## BtValueBinary `bts-wasi-1` uses BtValueBinary to transfer values between the host and WASM. It retains the distinction between `empty` and `null`, and supports ordinary values, Bytes, arrays, objects, and extended object handles. Transferring functions, class instances, regexes, standard library objects, tasks, timers, iterators or circular reference arrays/objects through the ABI is not supported. ## shared runtime WASM extension uses `runtime.mode: "thread_local"` by default, and the running state will be cached according to the calling thread. When you need to reuse expensive initialization state, connections, or caches across requests, you can declare `runtime.mode: "shared"` in `manifest.json`. The shared WASM extension will use project-level `ExtensionService`: 1. The calling thread handles parameters and path roles according to bindings. 2. The calling thread encodes the parameter into BtValueBinary bytes. 3. The service posts the call ID, return type, call label, parameter bytes, and reply channel to the bounded worker queue. 4. The worker holds an independent WASM Store/Instance and calls `bts_call`. 5. The worker returns the result bytes and the calling thread decodes them into BT values. 6. If the extended object is returned, the host rewrites the worker's local object ID to `host_object_id`, which is visible to the script; when the object method is called, it rewrites it back to the local object ID that created the worker. The current stage supports shared entry functions and object methods to return original values or extended objects. After the `close()` / `dispose()` method of `lifecycle: "dispose"` is successful, the host will remove the object route, and the old handle will be reported as invalid if called again. Shared workers will enable Wasmtime epoch interrupt. When `call_timeout_ms` expires, the calling thread will mark the target worker and trigger the epoch check; after the worker captures the timeout trap, it will discard the current WASM Store/Instance and rebuild it. Subsequent calls will not be stuck by the permanently occupied worker. ## Notes - WASM modules containing start section will be rejected. - The `thread_local` WASM Runner lazily instantiates modules and caches instances thread-locally. - `shared` WASM extension uses independent bounded worker queue; when the queue is full or the service is shut down, a Chinese error will be returned and will not fall back to the thread local Runner. - `shared` WASM will use host-level object routing when extending multiple workers to avoid conflicts with the same local object IDs in different workers. - `shared` WASM extension call timeout will count the timeout and interrupt the WASM execution of the target worker; other workers will not be forced to rebuild due to the same timeout call. - Optional lifecycle export is only called if present; old extensions that do not export `bts_init`, `bts_shutdown`, `bts_stats` remain compatible. - The ordinary `object` return type can be expressed as `empty` with no result. For example, SQLite `one()` cannot find the row; the specific extended object return type must still return the same module and the same type of object handle. - When WASM returns an object type, it must return an extended object handle of the same module and type. - SDK internal error messages can still use ABI categories such as `ext_object`; the script layer `type()` returns the object type name declared by `bindings.json`. - WASM objects that hold state long-term should provide `close()` or `dispose()` and release the extended internal state in the handler. - `bt ext new --kind wasm` generates the Rust SDK project skeleton; you still need to compile WASM first and copy it to `module.wasm` before packaging.