#moonrockz/extism/engine

    A small WASM engine layer with one API on two engines (design ENG-1): the WAMR 2.4.5 fast interpreter on native, built from the vendored C source in wamr/ with the consumer's C compiler, and the WebAssembly API of Node.js and browsers on js. It knows nothing of Extism.

    • Engine::compile reads the module, refuses a feature that the engine lacks with FeatureUnsupported and the feature name (from the same pure scan on both engines), and compiles it.
    • Module::instantiate binds each import to a MoonBit function or to an exported function of another instance, per instance, with a memory ceiling in pages. The start function of the module runs after the instance exists, as a call with the budget and the deadline of the instantiation; when it fails, the instantiation fails with Start.
    • Instance::call calls an export with typed values, an instruction budget and a deadline.
    • Instance::memory reads and writes guest memory; it is also the WASI GuestMemory.

    #Bounds per engine

    BoundNative (WAMR)js (WebAssembly)
    Memory ceilingEnforced: the maximum pages of the instantiationEnforced: a rewrite of the memory maximum
    Refused memory.growMemoryLimit (enlarge-memory callback)MemoryLimit (an appended grow function)
    Instruction budgetEnforced: WAMR metering, 1 to 9223372036854775807 instructionsNot enforced
    DeadlineEnforced: a watchdog thread calls wasm_runtime_terminate; it is always joinedNot enforced: end the worker
    Host workNot metered: a host function and the kernel's forwarded calls count no instructions; the deadline bounds themNot metered
    Features beyond the core setsimd (SIMDe headers)simd

    ///|
    test "add two i64 values" {
    let engine = @engine.Engine::default()
    let instance = engine
    .compile(@fixtures.add())
    .instantiate(@engine.Imports::new())
    assert_eq(instance.call("add", [I64(2), I64(40)]), [I64(42)])
    }

    ///|
    test "a host function for an import" {
    let signature = @engine.Signature::new([I64, I64], [I64])
    let imports = @engine.Imports::new().host("env", "add", signature, (_, args) => {
    guard args is [I64(a), I64(b)] else {
    raise @engine.HostError("bad arguments")
    }
    [I64(a + b)]
    })
    let instance = @engine.Engine::default()
    .compile(@fixtures.host())
    .instantiate(imports)
    assert_eq(instance.call("call_add", [I64(1), I64(2)]), [I64(3)])
    }

    ///|
    test "a memory ceiling" {
    let instance = @engine.Engine::default()
    .compile(@fixtures.grow())
    .instantiate(
    @engine.Imports::new(),
    memory_ceiling=@engine.PageCount::parse(2),
    )
    assert_eq(instance.call("grow", [I32(1)]), [I32(1)])
    assert_eq(instance.call("grow", [I32(1)]), [I32(-1)])
    }

    CallError

    pub(all) suberror CallError {
    Trap(TrapKind)
    BudgetExhausted
    Interrupted
    MemoryLimit
    Host(HostError)
    UnknownExport(String)
    ArgumentMismatch(String, String)
    Unusable
    } derive(
    Debug
    )

    An error of a call.

    EngineError

    pub suberror EngineError {
    Invalid(String)
    FeatureUnsupported(Feature)
    LinkError(String, String)
    CeilingBelowInitial(Int, Int)
    Start(CallError)
    } derive(
    Debug
    )

    An error of a module load or an instantiation.

    HostError

    pub(all) suberror HostError {
    HostError(String)
    Exit(Int)
    } derive(Eq,
    Debug
    )

    The error of a host function, with its message.

    HostError::equal

    fn HostError::equal(HostError, HostError) -> Bool

    HostError::not_equal

    fn HostError::not_equal(x : HostError, y : HostError) -> Bool

    LimitError

    pub suberror LimitError {
    LimitError(String, Int64, String)
    }

    A limit value that breaks its rule: the name of the limit, the value and the rule.

    MemoryError

    pub(all) suberror MemoryError {
    OutOfBounds(Int64, Int)
    } derive(Eq,
    Debug
    )

    A guest memory access that is outside the memory: the offset and the length.

    MemoryError::equal

    fn MemoryError::equal(MemoryError, MemoryError) -> Bool

    MemoryError::not_equal

    fn MemoryError::not_equal(x : MemoryError, y : MemoryError) -> Bool

    Bound

    pub(all) enum Bound {
    Enforced
    NotEnforced
    } derive(Eq,
    Debug
    )

    Whether an engine enforces a bound.

    Bound::equal

    fn Bound::equal(Bound, Bound) -> Bool

    Bound::not_equal

    fn Bound::not_equal(x : Bound, y : Bound) -> Bool

    Bound::to_repr

    Bounds

    pub(all) struct Bounds {
    memory : Bound
    budget : Bound
    interruption : Bound
    } derive(Eq,
    Debug
    )

    The bounds that an engine enforces.

    Bounds::equal

    fn Bounds::equal(Bounds, Bounds) -> Bool

    Bounds::not_equal

    fn Bounds::not_equal(x : Bounds, y : Bounds) -> Bool

    Bounds::to_repr

    Budget

    pub struct Budget {
    // private fields
    } derive(Eq,
    Debug
    )

    An instruction budget for one call, from 1 to 9223372036854775807 instructions (the Int64 maximum). The native engine counts in a 64-bit integer (wamr/UPSTREAM.md).

    Budget::equal

    fn Budget::equal(Budget, Budget) -> Bool

    Budget::instructions

    fn Budget::instructions(self : Budget) -> Int64

    The number of instructions of the budget, from 1 to 9223372036854775807.

    Budget::not_equal

    fn Budget::not_equal(x : Budget, y : Budget) -> Bool

    Budget::parse

    fn Budget::parse(instructions : Int64) -> Budget raise LimitError

    Makes a budget of instructions for one call. It raises LimitError when instructions is less than 1. Every positive Int64 is a budget.

    Budget::to_repr

    Caller

    pub struct Caller {
    // private fields
    }

    The instance whose host function a guest calls.

    Caller::memory

    fn Caller::memory(self : Caller) -> Memory?

    The memory of the calling instance, or None when it has none.

    Deadline

    pub struct Deadline {
    // private fields
    } derive(Eq,
    Debug
    )

    A deadline for one call, in milliseconds, from 1 to 2147483647.

    Deadline::equal

    fn Deadline::equal(Deadline, Deadline) -> Bool

    Deadline::milliseconds

    fn Deadline::milliseconds(self : Deadline) -> Int

    Deadline::not_equal

    fn Deadline::not_equal(x : Deadline, y : Deadline) -> Bool

    Deadline::parse

    fn Deadline::parse(milliseconds : Int) -> Deadline raise LimitError

    Deadline::to_repr

    Engine

    pub struct Engine {
    // private fields
    }

    The WASM engine of the target: WAMR on native, WebAssembly on js.

    Engine::bounds

    fn Engine::bounds(self : Engine) -> Bounds

    The bounds that the engine enforces.

    Engine::compile

    fn Engine::compile(self : Engine, bytes : Bytes) -> Module raise EngineError

    Compiles bytes. A module that needs a feature that the engine does not support fails with FeatureUnsupported and the name of the feature, before any guest code runs.

    Engine::default

    fn Engine::default() -> Engine

    The engine of the target.

    Engine::features

    fn Engine::features(self : Engine) -> Array[Feature]

    The features beyond the core set that the engine supports.

    ExportDescriptor

    pub(all) struct ExportDescriptor {
    name : String
    kind : ExternKind
    } derive(Eq,
    Debug
    )

    ExportDescriptor::equal

    ExportDescriptor::not_equal

    fn ExportDescriptor::not_equal(x : ExportDescriptor, y : ExportDescriptor) -> Bool

    ExternKind

    pub(all) enum ExternKind {
    Function(Signature?)
    Table
    Memory
    Global
    Tag
    } derive(Eq,
    Debug
    )

    The kind and the type of an import or an export.

    ExternKind::equal

    fn ExternKind::equal(ExternKind, ExternKind) -> Bool

    ExternKind::not_equal

    fn ExternKind::not_equal(x : ExternKind, y : ExternKind) -> Bool

    Feature

    pub(all) enum Feature {
    Simd
    Threads
    Memory64
    ExceptionHandling
    Gc
    TailCall
    MultiMemory
    Unknown(String)
    } derive(Eq,
    Debug
    )

    A WASM feature beyond the core set that both engines take: WASM 1.0 with mutable globals, sign extension, saturating conversions, multi-value, bulk memory and reference types.

    Feature::equal

    fn Feature::equal(Feature, Feature) -> Bool

    Feature::name

    fn Feature::name(self : Feature) -> String

    The name of a feature, for example simd or exception-handling.

    Feature::not_equal

    fn Feature::not_equal(x : Feature, y : Feature) -> Bool

    Feature::scan

    fn Feature::scan(bytes : Bytes) -> Array[Feature] raise EngineError

    The features that a module needs, from a pure scan of its bytes. Both engines run this scan before they compile.

    Feature::to_repr

    Function

    pub struct Function {
    // private fields
    }

    An exported function of an instance.

    Function::signature

    fn Function::signature(self : Function) -> Signature

    ImportDescriptor

    pub(all) struct ImportDescriptor {
    module_ : String
    name : String
    kind : ExternKind
    } derive(Eq,
    Debug
    )

    ImportDescriptor::equal

    ImportDescriptor::not_equal

    fn ImportDescriptor::not_equal(x : ImportDescriptor, y : ImportDescriptor) -> Bool

    Imports

    pub struct Imports {
    // private fields
    }

    The host imports of one instantiation: MoonBit functions and exported functions of other instances, by module name and field name.

    Imports::function

    fn Imports::function(self : Imports, module_ : String, name : String, function : Function) -> Imports

    Binds the import module_.name to an exported function of another instance, so that a guest call of the import runs in that instance.

    Imports::host

    fn Imports::host(self : Imports, module_ : String, name : String, signature : Signature, function : (Caller, Array[Value]) -> Array[Value] raise HostError) -> Imports

    Binds the import module_.name to a MoonBit function with the signature signature. The function gets the calling instance and the arguments, and gives the results.

    Imports::new

    fn Imports::new() -> Imports

    Instance

    pub struct Instance {
    // private fields
    }

    An instance of a module, with its own memory.

    Instance::call

    fn Instance::call(self : Instance, name : String, args : Array[Value], budget? : Budget, deadline? : Deadline) -> Array[Value] raise CallError

    Calls the exported function name with args. With a budget, the call stops with BudgetExhausted when it runs more instructions; with a deadline, it stops with Interrupted when the deadline passes, where the engine enforces these bounds. After a trap, an exhausted budget, an interruption, a memory limit or a host error the instance takes no more calls; a host function's Exit(0) ends the call and leaves the instance usable.

    Instance::export_function

    fn Instance::export_function(self : Instance, name : String) -> Function?

    The exported function name, to link it as an import of another instance.

    Instance::has_function

    fn Instance::has_function(self : Instance, name : String) -> Bool

    Whether the instance exports a function with the name name. No guest code runs.

    Instance::is_usable

    fn Instance::is_usable(self : Instance) -> Bool

    Whether the instance takes more calls: false after a trap of the instance or of an instance whose exports it calls.

    Instance::memory

    fn Instance::memory(self : Instance) -> Memory?

    The memory of the instance, or None when it has none.

    Instance::take_refused_grow

    fn Instance::take_refused_grow(self : Instance) -> Bool

    Whether a memory.grow of this instance was refused since the last call of this function. A caller that links instances, such as the plugin API with its kernel, reads it for each call.

    InstanceHandle

    type InstanceHandle

    Memory

    pub struct Memory {
    // private fields
    }

    The linear memory of an instance.

    Memory::read

    fn Memory::read(self : Memory, offset : Int64, length : Int) -> Bytes raise MemoryError

    The length bytes at offset. An access outside the memory raises OutOfBounds and reads nothing.

    Memory::size

    fn Memory::size(self : Memory) -> Int64

    The size of the memory in bytes.

    Memory::write

    fn Memory::write(self : Memory, offset : Int64, bytes : BytesView) -> Unit raise MemoryError

    Writes bytes at offset. An access outside the memory raises OutOfBounds and writes nothing.

    Module

    pub struct Module {
    // private fields
    }

    A compiled module.

    Module::exports

    fn Module::exports(self : Module) -> Array[ExportDescriptor]

    The exports of the module, in order.

    Module::imports

    fn Module::imports(self : Module) -> Array[ImportDescriptor]

    The imports of the module, in order.

    Module::initial_pages

    fn Module::initial_pages(self : Module) -> Int?

    The initial pages of the module's memory, or None when it has no memory.

    Module::instantiate

    fn Module::instantiate(self : Module, imports : Imports, memory_ceiling? : PageCount, budget? : Budget, deadline? : Deadline) -> Instance raise EngineError

    Instantiates the module with imports and a memory ceiling of memory_ceiling pages. A memory.grow past the ceiling returns -1 to the guest. The start function of the module, if it has one, runs after the instance exists, as a call with budget and deadline; when it fails, the instantiation fails with Start.

    ModuleHandle

    type ModuleHandle

    PageCount

    pub struct PageCount {
    // private fields
    } derive(Eq,
    Debug
    )

    A number of 64 KiB pages of linear memory, from 1 to 65536.

    PageCount::default_ceiling

    fn PageCount::default_ceiling() -> PageCount

    The default memory ceiling: 4096 pages (256 MiB).

    PageCount::equal

    fn PageCount::equal(PageCount, PageCount) -> Bool

    PageCount::not_equal

    fn PageCount::not_equal(x : PageCount, y : PageCount) -> Bool

    PageCount::pages

    fn PageCount::pages(self : PageCount) -> Int

    PageCount::parse

    fn PageCount::parse(pages : Int) -> PageCount raise LimitError

    Signature

    pub(all) struct Signature {
    params : Array[ValueType]
    results : Array[ValueType]
    } derive(Eq,
    Debug
    )

    The parameter and result types of a function.
    impl Show for Signature

    Signature::equal

    fn Signature::equal(Signature, Signature) -> Bool

    Signature::new

    fn Signature::new(params : Array[ValueType], results : Array[ValueType]) -> Signature

    Signature::not_equal

    fn Signature::not_equal(x : Signature, y : Signature) -> Bool

    Signature::output

    fn Signature::output(self : Signature, logger : &Logger) -> Unit

    Signature::to_string

    fn Signature::to_string(self : Signature) -> String

    TrapKind

    pub(all) enum TrapKind {
    Unreachable
    MemoryOutOfBounds
    DivideByZero
    IntegerOverflow
    InvalidConversion
    StackExhausted
    IndirectCall
    TableOutOfBounds
    Host
    Other
    } derive(Eq,
    Debug
    )

    The kind of a trap.

    TrapKind::equal

    fn TrapKind::equal(TrapKind, TrapKind) -> Bool

    TrapKind::not_equal

    fn TrapKind::not_equal(x : TrapKind, y : TrapKind) -> Bool

    TrapKind::to_repr

    Value

    pub(all) enum Value {
    I32(Int)
    I64(Int64)
    F32(Float)
    F64(Double)
    } derive(Eq,
    Debug
    )

    A WASM number value.

    Value::equal

    fn Value::equal(Value, Value) -> Bool

    Value::not_equal

    fn Value::not_equal(x : Value, y : Value) -> Bool

    Value::to_repr

    Value::value_type

    fn Value::value_type(self : Value) -> ValueType

    The type of a value.

    ValueType

    pub(all) enum ValueType {
    I32
    I64
    F32
    F64
    } derive(Eq,
    Debug
    )

    A WASM number type of a host import or an export.

    ValueType::equal

    fn ValueType::equal(ValueType, ValueType) -> Bool

    ValueType::not_equal

    fn ValueType::not_equal(x : ValueType, y : ValueType) -> Bool

    host_entry_count

    fn host_entry_count() -> Int

    The number of entries in the host function table, after the entries of freed instances go.

    live_watchdogs

    fn live_watchdogs() -> Int

    The number of deadline watchdog threads that run now, as the threads count themselves: a thread that a call did not join is counted. After every call it is 0.