Execution Modes¶
KRWA requires a platform execution engine. JVM, Android, and iOS use the Wasmtime backend when it is linked; wasmJs uses the host browser or Node WebAssembly engine.
JVM, Android, and iOS execution requires Wasmtime; wasmJs execution requires the host WebAssembly engine.
On wasmJs, Instance.builder(module) uses ExecutionBackend.AUTO by default
and instantiates parsed modules with the native browser or Node WebAssembly
engine. The wasmJs target already runs in an environment with a WebAssembly
engine, so AUTO does not fall back to another backend. If native
instantiation fails, including when imports require host objects the native
engine cannot accept, the error is reported directly. A
WebAssembly.RuntimeError becomes NativeWasmRuntimeException.
val module = WasmParser.parse(bytes)
val instance =
Instance.builder(module)
.build()
val result = instance.export("add").apply(1, 2)
val memory = instance.exports().memory("memory")
The common builder path exposes function exports plus exported memories through
the regular Instance API. WasmJsExecution.instantiate remains available as a
wasmJs-specific native facade for exported globals, tables, and exception tags
where callers need the native wrapper objects.
ExecutionBackend.PULLEY selects the historical Wasmtime provider boundary
where it is linked. It does not by itself mean that Wasmtime must use the
Pulley compiler target. WasmtimeExecutionConfig.target defaults to
WasmtimeAutomaticTarget (auto), which selects native Wasmtime on desktop JVM
and Pulley on iOS and Android. Callers can instead require
WasmtimePulleyTarget (pulley64) or WasmtimeNativeTarget (native, normally
Cranelift); an explicitly unsupported target fails closed. A platform execution backend owns native
exports and native linear memories while still receiving KRWA ImportValues for
host callbacks. The JVM implementation loads the Wasmtime C API from
krwa.wasmtime.library, KRWA_WASMTIME_LIBRARY, or common system library
locations, and requires JVM native access. Selecting PULLEY before the
platform links a Wasmtime binding fails fast instead of falling through to
another engine, so runtime measurements cannot accidentally report the wrong
engine.
Use ExecutionBackend.PULLEY.availability() or .isAvailable() before exposing
Wasmtime as a user-selectable mode. The availability check reports the same
platform/linking requirements that explicit PULLEY execution would enforce.
Wasmtime execution can be configured per instance during instantiation. Prefer
withExecutionPolicy, because it applies the engine, target, memory, and fuel
settings as one value instead of allowing a partially updated builder.
WasmtimeExecutionConfig exposes Wasmtime store and engine limits for maximum
linear memory bytes, maximum Wasm stack bytes, table elements, instances,
tables, memories, and guest execution fuel. Optional count limits and maxFuel
use WasmtimeUnlimitedResourceLimit (-1) for unlimited:
val instance =
Instance.builder(module)
.withExecutionPolicy(
WasmExecutionPolicy.Wasmtime(
WasmtimeExecutionConfig(
maxMemoryBytes = 64L * 1024L * 1024L,
maxWasmStackBytes = 256L * 1024L,
maxTableElements = WasmtimeUnlimitedResourceLimit,
maxInstances = 1,
maxTables = 32,
maxMemories = 4,
maxFuel = 5_000_000,
),
),
)
.build()
Fuel is consumed only while Wasmtime executes guest Wasm instructions. Keeping an instance alive while it waits for the host to call an export does not consume fuel, and host work blocked outside Wasmtime is not metered by fuel.
Embedders that package a platform-specific Wasmtime binding can install a provider
without changing call sites that already select ExecutionBackend.PULLEY. This
is the intended integration point for Android JNI and iOS cinterop bindings:
PulleyExecutionProviders.install(androidPulleyProvider)
val instance =
Instance.builder(module)
.withExecutionBackend(ExecutionBackend.PULLEY)
.build()
On the JVM, a PulleyExecutionProvider can also be discovered through
ServiceLoader. A manually installed provider takes precedence over
ServiceLoader and over the built-in desktop Wasmtime FFM binding. KRWA checks
the provider's availability before instantiation and rejects providers that do
not return a PlatformInstanceExecution with ExecutionBackend.PULLEY, so a
broken Android or iOS native binding fails at the backend boundary instead of
silently falling through to another engine.
On wasmJs, host imports are supplied with NativeWasmImports. Exported native
memories, globals, tables, and exception tags are available as
NativeWasmMemory, NativeWasmGlobal, NativeWasmTable, and NativeWasmTag;
NativeWasmMemory implements the runtime Memory API for read/write access
from Kotlin/Wasm code, including native shared-memory atomics when the browser or
Node environment enables them. NativeWasmTag uses the host WebAssembly.Tag
API for exception handling modules; host callbacks can throw an imported tag
with NativeWasmTag.throwException(instance, ...) and let native Wasm catch
blocks handle it. Reference values use the raw Long call surface: store
JavaScript values with NativeWasmInstance.storeReference before passing them as
externref, anyref, or funcref, and call referenceValue to recover a
returned reference handle. Function reference tables and globals are mapped to
the JS API's anyfunc descriptor. Existing ImportFunction values can be reused
with NativeWasmImports.fromImportValues, including imports backed by exports
from another Instance; shared memories, globals, tables, and tags should be
provided with the native wrapper types because they are owned by the host
WebAssembly engine.
Use NativeWasmFeatures before relying on host-dependent browser or Node
features:
if (
NativeWasmFeatures.available() &&
NativeWasmFeatures.supportsValueType(ValType.I64) &&
(!needsThreads || NativeWasmFeatures.supportsSharedMemory())
) {
val instance = Instance.builder(module).build()
}
supportsExceptionTags, supportsValueType, supportsTableElement, and
supportsTag expose host-dependent features such as exception handling and GC
reference descriptors. v128 values are not bridged through the JavaScript call
surface; modules may still use SIMD internally when the host engine supports the
module, but JS-exported or imported functions should not expose v128.
Choosing A Mode¶
The effective platform matrix is intentionally asymmetric:
| Platform | Raw Wasm | Pulley CWasm | Cranelift CWasm |
|---|---|---|---|
| JVM / Android with the Suvio provider | Pulley | Pulley | Cranelift |
| iOS | Pulley | Pulley | unavailable |
| wasmJs | host WebAssembly |
unavailable | unavailable |
CWasm is a Wasmtime-specific serialized artifact and is therefore never used
on wasmJs. iOS and Android resolve auto to Pulley. iOS builds only the
Pulley target; an explicit native/Cranelift target must fail availability
checks there instead of silently selecting another mode.
- Use the default
ExecutionBackend.AUTOfor normal hosts. It requires a linked Wasmtime backend on JVM, Android, and iOS, and uses the host WebAssembly engine onwasmJs. - Use explicit
ExecutionBackend.PULLEYonly when an embedder wants to require the Wasmtime provider boundary and fail if it is not linked. - Do not expose a backend selector on
wasmJs;AUTOalready means the browser or Node WebAssembly engine. UseWasmJsExecutiononly for wasmJs-specific native wrappers. Wasmtime-only controls such asmaxFuelare unavailable on this path.
For untrusted modules, pair platform execution with explicit CPU limits, memory limits, and narrow host capabilities.
iOS artifacts include their statically linked Pulley provider and select it automatically; an
embedder-installed provider still takes precedence. The standalone sample's
runIosShowcase task runs the iOS simulator showcase for the portable parser,
runtime, host import, exported function, structured-control-flow,
cross-module Store, trap, linear-memory, WIT parsing, WASIp3 metadata, and
WASIp3 preopened-storage APIs through the same src/kmpShowcaseMain
runKmpShowcase runner used by JVM and wasmJs.