Kotlin Multiplatform WebAssembly Plugin Framework · Cross-Platform WASI Execution Runtime
中文文档 · English · Documentation
Wasmline is a Kotlin Multiplatform framework for loading and calling WASI-compliant WebAssembly plugins in Android, iOS, Desktop, and Web applications.
Define the service contract (commonMain):
// shared/src/commonMain/kotlin/com/example/EchoService.kt
import crow.wasmline.WasmlineService
interface EchoService : WasmlineService {
fun echo(message: String): String
}Register the implementation in the plugin (wasmWasiMain):
// plugin/src/wasmWasiMain/kotlin/Main.kt
import crow.wasmline.Wasmline
import crow.wasmline.bind
fun main() {
Wasmline.current.bind(object : EchoService {
override fun echo(message: String): String {
return "Response from WASI plugin: $message"
}
})
}Load the plugin and invoke services from the host:
import crow.wasmline.WasmlineConfig
import crow.wasmline.WasmlineLoadResult
import crow.wasmline.link
import crow.wasmline.loader.WasmlineLoader
import crow.wasmline.loader.WasmlineLoadOptions
import crow.wasmline.loader.WasmlineTrustedKeySet
import crow.wasmline.network.ktor.KtorNetworkClient
suspend fun main() {
// Local paths and remote URLs are both supported — http(s):// loads the remote manifest
val module = when (val result = WasmlineLoader.load(
source = "https://example.com/plugin/manifest.wlm",
options = WasmlineLoadOptions(
runtimeConfig = WasmlineConfig(),
networkClient = KtorNetworkClient(),
trustedKeys = WasmlineTrustedKeySet.Builder()
.addHex(
algorithm = "Ed25519",
keyId = "release",
publicKeyHex = "5a778289bee0c57b05a1c48c8ef312da6ce8e4e4f13fc1a2e8e5aa4cde7ae0db",
)
.build(),
),
)) {
is WasmlineLoadResult.Success -> result.wasmline
is WasmlineLoadResult.Failure -> error(result.failure.message)
}
val response = module.link<EchoService>().echo("ping")
module.close()
}Remote artifacts use a streaming, atomically published content-addressed file cache. Set WasmlineLoadOptions.maxCacheBytes to change its 512 MiB default capacity.
WasmlineLoader.load is a suspending API. Local artifacts and local manifests do not require a network adapter. A remote manifest needs wasmline-network-ktor, wasmline-network-okhttp, or a custom resolver only when its fresh manifest or selected artifact is missing from the configured cache. The runtime never hardcodes an HTTP engine.
API ownership is explicit: WasmlineLoader resolves, verifies, selects, and loads artifacts; WasmlineRuntime owns process-wide preload, engine warm-up, runtime information, and shutdown; each loaded Wasmline is an independently closeable artifact handle. Loading is lazy, so applications do not need an explicit runtime initialization call before WasmlineLoader.load().
Note
link<T>() and bind(impl) are rewrite targets of the Kotlin IR compiler plugin. If wasmline-kotlin-plugin is not applied to the compilation unit, these calls throw UnsupportedOperationException at runtime.
A plugin release uses one manifest.wlm for every configured Wasmtime AOT
compatibility profile and physical target. Plugin authors select complete
Wasmtime x.y.z versions; Wasmline resolves immutable, backend-specific profile
IDs from its catalog.
import crow.wasmline.gradle.WasmtimeTarget
wasmline {
wasmtime {
aotCompatibility {
wasmtimeVersions.set(listOf("47.0.3", "48.0.1"))
}
targets = listOf(
WasmtimeTarget.PULLEY_64,
WasmtimeTarget.X86_64_LINUX,
WasmtimeTarget.X86_64_WINDOWS,
)
autoDownload.set(true)
}
}The package stores artifacts by SHA-256:
{pluginId}-{version}/
├── manifest.wlm
├── artifacts/sha256/{prefix}/{digest}.wasm|cwasm|pwasm
└── debug/
├── manifest.json
├── aot-build-record.json
└── artifact-index.json
Core Web .wasm is generated and stored once; it is not repeated for every
Wasmtime version. Native CWASM and PWASM variants are compiled only for profiles
with the same backend. The offline ZIP contains the complete matrix. Remote
loading fetches the manifest and one selected artifact, not the ZIP or unrelated
targets.
Pulley selects pulley32 or pulley64 by pointer width. Cranelift requires an
exact profile, operating system, architecture, pointer width, and CPU feature
match. It may use PWASM only when no compatible CWASM exists and the runtime
reports a matching Pulley profile and PWASM capability. Artifact download or
digest failure does not trigger fallback.
Compiler archives are catalog-locked and cached by digest under
~/.wasmline/toolchains/wasmtime/compiler-assets/sha256/. Builds do not accept
an arbitrary local compiler executable.
Wasmline supports four explicit host-side invocation paths:
| Execution model | Invocation protocol | Input | Result |
|---|---|---|---|
CORE_WASM |
WASMLINE_SERVICE |
action name and byte payload | WasmlineCallResult<ByteArray> |
CORE_WASM |
RAW_EXPORT |
CoreWasmModule/CoreWasmSession numeric values, synchronous imports, and linear memory |
WasmlineCallResult<List<RawValue>> |
COMPONENT_MODEL |
WASMLINE_SERVICE |
action name and byte payload through wasmline.wit |
WasmlineCallResult<ByteArray> |
COMPONENT_MODEL |
COMPONENT_EXPORT |
declared Component Model values | WasmlineCallResult<WasmlineComponentCallResult> |
The runtime side of the Component Model path loads an already compiled component
binary. The optional plugin build pipeline can generate bindings and create that
binary from WIT through wasmline-plugin-core, the Gradle plugin, or the CLI;
the loader itself does not run those tools. contractMetadata describes the
call contract when needed; it is not a WIT compiler input. See the
Wasmline Service guide.
The browser runtime supports both Core Service and Core Raw Export paths. Web
uses raw .wasm, WebAssembly.Module/WebAssembly.Instance, synchronous
imports, and checked linear memory; native uses the Wasmtime bridge with
.cwasm/.pwasm AOT artifacts. Component Model typed calls remain native-only.
For RAW_EXPORT, load a CoreWasmModule, register synchronous RawImport
handlers before instantiate(), invoke RawValue exports, and use
RawMemory for bulk data. WasmlineWeb.registerBytes() is the browser path
for embedded .wasm; native selection remains AOT-only. Signed packages store
export signatures, imports, memory, and required features in
runtimeContract.rawAbi, not in free-form contractMetadata entries.
Core Wasmline calls return results instead of using exceptions for normal call failures:
import crow.wasmline.callResult
import crow.wasmline.invocation.WasmlineCallResult
import crow.wasmline.invocation.WasmlineErrorCode
when (val result = module.callResult("echo", payload)) {
is WasmlineCallResult.Success -> usePayload(result.value)
is WasmlineCallResult.Failure -> {
if (result.failure.code == WasmlineErrorCode.ACTION_NOT_BOUND) {
log("The plugin did not bind this action.")
}
log(result.failure.message)
}
}An unbound action returns ACTION_NOT_BOUND. It does not return an empty payload and does not crash the host. The same result-first rule applies to unknown actions, invalid payloads, traps, and handler failures. throwOnFailure() is an explicit adapter for code that chooses exception-style handling; it is not used by the result API by default.
WasmlineFailure is the canonical non-throwing failure value,
WasmlineException is reserved for explicit throwing adapters, and
WasmlineLoadFailure describes failures before module creation. The
failure property is the single authoritative failure payload across result
APIs.
The WASMLINE_SERVICE response frame starts with the four-byte WLMF magic marker and a one-byte frameVersion whose current value is 1. The magic marker identifies the frame format; it is not a security check. frameVersion identifies the response byte layout; it is not a Wasmtime, Kotlin, framework, or business API version. Raw Export and Component Model calls do not use this Core response frame.
Loading manifest.wlm is the standard path. The Loader verifies the package and
copies its execution model, invocation protocol, target identity, artifact
format, and AOT compatibility profile into the selected descriptor. A direct,
caller-trusted AOT descriptor must provide all of those fields explicitly; a
path such as component.cwasm is not enough to prove compatibility.
| Platform | Architecture | Artifact Support | Loading |
|---|---|---|---|
| Android | v8a, x86_64 | .cwasm / .pwasm |
wasmtime |
| Android | v7a, x86 | .pwasm only |
wasmtime |
| iOS | arm64 | .pwasm |
wasmtime |
| macOS | arm64 | .cwasm / .pwasm |
wasmtime |
| Linux | x86_64 | .cwasm / .pwasm |
wasmtime |
| Windows | x86_64 | .cwasm / .pwasm |
wasmtime |
| Web (Kotlin/JS · Kotlin/WasmJS) | Browser JS engine | Raw .wasm only |
web |
Native selection uses the AOT compatibility profile IDs reported by the linked engine. It does not infer compatibility from a Maven version or filename.
Note
Wasmline is under active development. Detailed installation and integration documentation will be available at wuya.click/wasmline.
Use the BOM on JVM and Android so the runtime, Loader, network adapter, and engine resolve to one strict Wasmline version:
dependencies {
implementation(platform("crow.wasmline:wasmline-bom:1.0.0"))
implementation("crow.wasmline:wasmline-loader")
implementation("crow.wasmline:wasmline-network-ktor")
implementation("crow.wasmline:wasmline-engine-cranelift")
}Kotlin Multiplatform source sets should use one shared version variable for all Wasmline coordinates. Engine modules are not versioned independently. Native startup validates the Wasmline release identity and bridge ABI before loading an AOT artifact.
After applying the released Wasmline Gradle plugin, run:
# Build a debug package for local testing
./gradlew wasmlineAssembleDebug
# Build a release package for distribution
./gradlew wasmlineAssembleRelease
# Build and serve the configured package
./gradlew wasmlineServerDeploySelect the package served by wasmlineServerDeploy with a typed value:
import crow.wasmline.gradle.WasmlineBuildVariant
wasmline {
server {
deployVariant = WasmlineBuildVariant.RELEASE
}
}The default is DEBUG, served at http://localhost:8080. Required AOT and
Component pipeline tasks run automatically. See the
Gradle plugin task reference for the
current task set and registration conditions.
Wasmline is distributed under the Apache License, Version 2.0. See LICENSE for the complete license text.





