Docs · Wasm bindings

Describe the meaning that raw Wasm cannot.

A Wasm binary exposes machine signatures. A restricted .hal interface supplies Hara names, semantic types, ownership, lifecycle and host authority. Bindgen verifies the two and emits a deterministic package; it does not require per-library generated JavaScript, Rust, Java, or C glue.

Model

Binary facts plus semantic intent.

Inspection discovers exports, imports, memories and raw signatures without instantiating the module. The interface then states what those values mean. Hara compiles that declaration into one canonical binding plan shared by the native Rust runtime, the JVM/Truffle runtime and the browser runtime.

01library.wasm + interface.halRaw ABI and semantic contract
02canonical binding IRVerified names, types, ownership and target
03portable packageDigests, bindings, conformance and optional adapter

Target selection

Three execution routes.

core.v1

Direct scalar calls.

Use for synchronous, import-free functions whose arguments and result are direct i32, i64, f32, f64, boolean-as-i32, or void. Native package consumers use the direct import route.

(wasm/interface
 {:schema "hara.wasm-interface/0-alpha"
  :namespace example.math
  :module "add.wasm"
  :exports
  {add {:wasm/export "add"
        :arguments [{:name left :hara/type :i64 :wasm/type :i64}
                    {:name right :hara/type :i64 :wasm/type :i64}]
        :returns {:hara/type :i64 :wasm/type :i64}}}})

memory.v1

Strings and exact bytes.

Use for synchronous, import-free libraries that expose one bounded linear memory and explicit allocation/release conventions. Hara lowers pointer/length inputs and lifts packed pointer/length results through a generic host executor.

(wasm/interface
 {:schema "hara.wasm-interface/0-alpha"
  :namespace example.codec
  :module "echo.wasm"
  :memory {:export "memory" :allocate "alloc" :release "free"}
  :exports
  {echo {:wasm/export "echo_bytes"
         :arguments [{:name input
                      :hara/type :bytes
                      :wasm/type :i32
                      :lower [:pointer :length]
                      :ownership :borrowed}]
         :returns {:hara/type :bytes
                   :wasm/type :i64
                   :lift :packed-i64
                   :ownership :caller}}}})

hta.v1

Async and host interaction.

Use HTA when the semantic interface needs asynchronous completion, cancellation, host calls, callbacks, handles, resources, or richer lifecycle rules. Generated HTA packages are loaded through :require; eligible scalar interfaces can receive a deterministic adapter.wasm.

(wasm/interface
 {:schema "hara.wasm-interface/0-alpha"
  :namespace example.math.async
  :module "add.wasm"
  :exports
  {add {:wasm/export "add"
        :arguments [{:name left :hara/type :i64 :wasm/type :i64}
                    {:name right :hara/type :i64 :wasm/type :i64}]
        :returns {:hara/type :i64 :wasm/type :i64}
        :async true
        :operation "add"}}})

No silent downgrade

The interface chooses the boundary.

Bindgen never treats a pointer-bearing or asynchronous declaration as a scalar call merely because the raw Wasm uses integers. Unsupported combinations fail before publication instead of falling back to a weaker ABI.

Authoring workflow

Inspect, describe, bind, verify.

1. Inspect the module

The inspector reads Wasm sections but does not instantiate the module. It deliberately emits unresolved Hara types where the binary cannot reveal whether an integer is a number, pointer, enum, handle, length, or error code.

hara extension inspect library.wasm \
  --out library.interface.hal \
  --namespace example.library

2. Complete the semantic interface

Replace unresolved types with the Hara-visible contract. For non-scalar values, state lowering/lifting and ownership explicitly. The source is parsed as restricted data and is not evaluated during generation.

3. Bind and validate

bind verifies every declared raw export and helper against the exact module bytes, selects the target, computes digests and writes the package atomically.

hara extension bind interface.hal \
  --module library.wasm \
  --out dist

hara extension check dist
hara extension test dist

Generated package

The output is data plus Wasm.

dist/
├── package.edn
├── project.edn
├── interface.hal
├── bindings.edn
├── hara.build-product.edn
├── conformance/
│   └── bindings.edn
├── library.wasm
├── adapter.wasm       # eligible HTA interfaces only
└── adapter.edn        # when adapter.wasm is emitted
interface.hal
canonical semantic interface
bindings.edn
target-specific execution plan
project.edn
Hara extension declaration
package.edn
artifact identity, digests and lifecycle
hara.build-product.edn
tool and input provenance
conformance/
portable claimed-export evidence

memory.v1

Ownership is part of the ABI.

PositionOwnershipHost action
inputborrowedcopy for the call; do not call free
inputtransferredrelease only when invocation does not complete
resultcallercopy, then release exactly once
resultcalleecopy; do not release from the host

Limits

Copying is bounded.

The current alpha contract bounds linear memory to 64 MiB, one transferred value to 16 MiB, input copies to 32 MiB, and aggregate input/output copying to 48 MiB. Pointer arithmetic is checked before access.

Current boundary

reallocate is reserved.

The current memory.v1 implementation supports explicit alloc(i32) -> i32 and free(i32) -> void. A declared reallocator is rejected until a later ABI revision.

Cross-runtime execution

One plan, host-specific executors.

Rust / Wasmtime

The native runtime verifies the package and canonical plan, then uses a generic bounded Wasmtime executor. Direct scalar calls stay on the scalar fast path.

JVM / Truffle

The JVM reads the same bindings.edn plan and mirrors pointer checks, UTF-8 validation, copy limits, ownership cleanup and public-to-raw export aliases without generating Java for the library.

Browser

The browser SDK exposes installMemoryWasmBinding(manifest, interface, bindings, bytes), verifies the canonical plan and module, then exposes the installed namespace through the ordinary Hara runtime.

HTA providers

Rust, browser worker/shared-worker and JVM HTA providers share operation, cancellation, capability and handle semantics. Rich bindings use HTA rather than inventing host-language wrappers.

WIT interoperability

Use WIT as an input, not as Hara's semantic authority.

Supported WIT packages, worlds, interfaces, functions, records, variants, lists, options, results, tuples and resources can seed a deterministic Hara interface. Hara-specific ownership, capabilities, errors, callbacks, handles and HTA lifecycle remain authoritative in .hal. Strict mode fails before writing a lossy projection.

hara extension wit-import api.wit \
  --out api.hal \
  --ir api.edn \
  --module api.wasm \
  --strict

hara extension wit-project api.hal \
  --out api.wit \
  --strict

Sample projects

Start from a complete fixture.

core.v1scalar

example.math

Direct add

A tiny import-free i64 + i64 -> i64 module with the authored interface and inspect/bind/check commands.

memory.v1bytes

example.codec

Echo bytes

A bounded linear-memory module with alloc, free, pointer/length input lowering, packed result lifting and caller-owned cleanup.

hta.v1adapter

example.math.async

Async scalar adapter

The same scalar library described as an asynchronous operation, causing bindgen to select HTA and emit an eligible deterministic adapter package.

Implementation references

Follow the versioned contract.

The public schemas are still alpha. This guide describes the current implementation; exact ABI rules remain with the runtime specification and source.