WASM extension

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:

WASM module must export:

exportDescription
memoryWASM linear memory.
bts_alloc(len) -> ptrWrite parameters to the host to apply for memory.
bts_call(id, args_ptr, args_len) -> packed_ptr_lenExecute 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:

ExportDescription
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_lenCalled after worker initialization, the parameter is the runtime configuration object compiled by the host.
bts_shutdown() -> packed_ptr_lenCalled before worker exits normally, used to release extended internal connections or cache.
bts_stats() -> packed_ptr_lenReturns 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:

The core structure in the scaffolding is similar:

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! 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

script will return the bindings object name instead of the low-level ABI label:

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.