WASM extension
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:
{ "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:
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:
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:
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
value = calc(3).add(7).value() // Output: 10 print value
script will return the bindings object name instead of the low-level ABI label:
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.
-
sharedWASM 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. -
sharedWASM will use host-level object routing when extending multiple workers to avoid conflicts with the same local object IDs in different workers. -
sharedWASM 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.
bts_init, bts_shutdown, bts_stats remain compatible.
- The ordinary
objectreturn type can be expressed asemptywith no result. For example, SQLiteone()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 layertype()returns the object type name declared bybindings.json.
close() or dispose() and release the extended internal state in the handler.
-
bt ext new --kind wasmgenerates the Rust SDK project skeleton; you still need to compile WASM first and copy it tomodule.wasmbefore packaging.