Extended operating mechanism

Extended operating mechanism

Extended operating mechanism

Function

The extension mechanism separates the API that the script can see and how the extension is executed internally. The script only calls the extension through the entries, objects and methods declared in bindings.json; the BT host is responsible for loading the .bts package, verifying the description file, establishing the registry, and distributing the calls to the corresponding Runner.

Syntax

The extension entry call syntax is the same as that of ordinary functions:

calc comes from the public entry declaration of bindings.json, add and value come from the object method declaration.

Parameters

extension call are determined by bindings. The host will process the value passed in by the script according to the number, type and role of bindings parameters, and then hand the call to the corresponding Runner.

Return Value

The extension call can return the original value or the extended object handle. The object handle only represents "an object in an extension module", and the real state is saved by the extension backend.

The script can use type(object) to view the object type name of the extended object declared in bindings.json:

Loading process

When the project starts, BT will process the extension according to the following process:

1. Find extensions/ in the project root directory.

2. Read the .bts files in the directory in file name order.

3. Verify the .bts suffix, zip entry path, number of entries, single file size and total decompression size.

4. Read and verify manifest.json.

5. Read and verify bindings.json.

6. Initialize a pure BT Runner, thread-local WASM Runner, or project-level ExtensionService based on manifest.kind and manifest.runtime.mode.

7. Create an extended registry and check whether the public entry names conflict.

8. Inject the public entry into the user global environment of the current VM.

When loading fails, the project startup will fail and a Chinese error will be output; BT will not load a semi-successful expansion pack.

Calling process

When the script calls the extension entry:

The host will find the function declaration in bindings according to the entry name calc, check the number and type of parameters, and then hand the call to the Runner or shared service to which the extension belongs. If the return value is a normal value, it is returned directly to the script; if it is an extended object, the script gets an object handle managed by the host.

When a script calls an object method, the host will first find the extension module and object type based on the object handle, and then find the method name. When the WASM method is called, the receiver object handle will be passed to WASM as the first parameter; when the pure BT method is called, the host will call the method on the corresponding object in the internal VM. The WASM extension of

runtime.mode=shared will first encode the parameters into BtValueBinary bytes, and then deliver them to the bounded worker queue of the project-level ExtensionService. The worker thread only receives the call ID, return type, call label, parameter bytes and reply channel. After the result bytes are returned, they are decoded by the calling thread to avoid passing the VM internal Value across threads.

When the shared extension returns an object, the worker returns the local object ID. The host will register it as host_object_id visible to the script and record host_object_id -> worker_id + local_object_id + type_id. Subsequent object method calls will first check this table, change the recipient back to the worker's local object ID, and deliver it to the worker who created the object. After the lifecycle=dispose method returns successfully, the host will remove this route.

shared worker uses Wasmtime epoch interrupt to handle call_timeout_ms. When calling a timeout, the calling thread will mark the target worker and advance the epoch of the module Engine; only the marked worker will return a timeout trap in the epoch callback. The worker then cleans up the timeout mark, discards the old Store/Instance and rebuilds the running state, and other workers continue to execute.

If the WASM module exports bts_init, bts_shutdown, or bts_stats, the Runner calls them during worker initialization, normal exit, or the statistics read path; older extensions that do not export these functions remain compatible.

kind and abi

kind are backend types, which determine who executes the entry file. abi is the calling protocol version that determines how the host and backend exchange parameters, return values, and object handles.

kindabiRunner
btbts-bt-1Pure BT Runner, reusing BT Parser, Compiler and VM.
wasmbts-wasi-1Thread-local WASM Runner or shared ExtensionService, using WASI P1 and BtValueBinary.

These two fields cannot be combined arbitrarily. kind=bt must use bts-bt-1, and kind=wasm must use bts-wasi-1.

Code Examples

The same bindings.json style can correspond to different backends. The script-side calls remain consistent:

The difference is only within the extension package: the pure BT extension is implemented by src/lib.bt; the WASM extension is implemented by module.wasm.

Notes

  • Public entries will become global constants and cannot be reassigned by scripts.
  • env('calc') can read the extension entry, has_env('calc') returns true.
  • envs('calc') and has_envs('calc') only represent system functions and system constants, and do not regard extension entries as system capabilities.
  • The extension entry name cannot conflict with the BT system environment name, nor can it be repeated with other extension entries.
- type() returns the script-visible type name of the extension object, such as Calc; the actual method routing is still done by the host based on the module ID, type ID, and object ID.

- Extended objects should not cross boundaries that do not support object handles, such as task() snapshots.

- shared WASM uses the host-level object handle table to route object methods; old or released handles return an expired error.

  • shared WASM timeout interrupt only guarantees that workers will not permanently occupy service capacity; if WASM is executing a long-blocking hostcall, subsequent host import itself still needs to provide cancellation boundaries.