Chasm WebAssembly Runtime
repository·main·Indexed 42 days ago
https://github.com/charlietap/chasmA WebAssembly (Wasm) runtime built on Kotlin Multiplatform targeting the Wasm 3.0 specification. It supports Wasm GC, Exception Handling, and Tail Calls across JVM, Native, and JS platforms. Includes a Gradle plugin for generating typesafe Kotlin interfaces from .wasm binaries, a low-level API for manual module instantiation, and WASI Preview 1 support.
What's inside Chasm
- The Chasm example project demonstrates how to integrate Chasm with a barebones Android application. It serves as both a functional demonstration and an integration test for Chasm and its plugin. The project is structured into a host Android application and several consumer modules that inject WebAssembly (Wasm) services into the dependency injection (DI) graph.
Understand Sweet output directories and path preservation
mainSweet organizes outputs by source to prevent collisions. Each source has its own dedicated directories:
- Repositories:
build/sweet/repositories/<source>/ - Intermediates:
build/sweet/intermediates/<source>/<relative-wast-path>/ - Generated Tests:
build/generated/sweet/<source>/
Sweet preserves the complete file path found below the
testDirectory. This ensures that files with the same name in different subdirectories (e.g.,wasm-tools/resources.wastandwasmtime/resources.wast) do not overwrite each other.- Repositories:
Use the Arena collection strategy (default)
mainTheArenastrategy is the default collection mode in Chasm. It minimizes overhead by avoiding per-allocation policy checks. Instead, Chasm checks the managed heap againstRuntimeConfig.gcThresholdat the end of every top-level WebAssembly invocation and performs collection only if the threshold has been reached.Use the Manual collection strategy
mainWhen using the
Manualstrategy, Chasm performs no automatic garbage collection. The embedding environment is responsible for triggering collection manually by calling the publicgcfunction./* Trigger collection manually via the public gc function */How BenchmarkStabilizer ensures consistent CPU core placement
mainOn Apple silicon, macOS frequently moves threads between different core classes (Efficiency, Performance, and Super cores), which introduces noise into JVM benchmarks.
BenchmarkStabilizermitigates this by implementing a preference and verification strategy:- It requests macOS to prefer the fastest available core class.
- It waits for the benchmark thread to reach that class.
- It verifies the thread's core class before and after the measured work.
- It rejects results if the core class changed during the measurement.
This approach catches bad placement without adding sampling overhead inside the timed region. On unsupported platforms (Linux, Windows, or Macs where core classes cannot be detected), the API acts as a no-op, allowing benchmarks to run normally without stabilization.
WASI support in Chasm
mainChasm supports the WebAssembly System Interface (WASI) to allow Wasm modules to interact with system-like features.
- WASI Preview 1: Supported via the WASI Emscripten Host library.
- WASI Preview 2/3: Not currently supported as they require the Wasm Component Model proposal.
Use the Traditional collection strategy
mainThe
Traditionalstrategy provides immediate collection when needed. Every allocation of a struct, array, or exception performs a capacity and threshold preflight. If a collection is required, the currently executing value stack is scanned directly before the allocation proceeds.After a collection, the next threshold is adaptively calculated as:
max(configured threshold, 2 * live words, 2 * pending allocation words)If the configured heap is exhausted, Chasm will attempt one collection before reporting a guest heap exhaustion error.
Use the Chasm Gradle plugin for typesafe Kotlin interfaces
mainThe Chasm Gradle plugin automates the creation of a typesafe Kotlin interface from your WebAssembly module. When applied to a Kotlin JVM, Kotlin Multiplatform, or Android module, it reads your
.wasmbinary and generates a class and interface pair containing Kotlin functions for every exported function in the Wasm binary.On JS targets, the plugin leverages the embedded Wasm VM (like V8 or JavaScriptCore), while on JVM and native targets, it uses Chasm.
plugins { ... id("io.github.charlietap.chasm.gradle") version "3.0.0-2.0.0" } chasm { modules { create("ExampleService") { binary = layout.projectDirectory.file("src/main/resources/example.wasm") packageName = "com.example" } } }Manage Corpus baselines and cleanup
mainThe plugin provides specific tasks for managing the lifecycle of corpus data and baselines:
updateCorpusBaseline: Performs a fresh measurement (bypassing build cache) and copies the resulting report tobaselines/corpus/baseline.json. CLI phase overrides apply here.cleanCorpusTests: Removes generated fixture index, test sources, packaged resources, raw fixture results, and the aggregate report. It leaves the synced corpus checkout in place to avoid re-cloning.corpusResultsDirectory: An extension property that defines where raw fixture results are stored (defaults tobuild/wasm-corpus-results).
Invoke WebAssembly functions using the low-level API
mainIf you do not want to use the Gradle plugin, you can use Chasm's low-level API to decode, instantiate, and invoke Wasm modules manually.
Basic Invocation
- Decode: Convert a
.wasmbyte array into aModuleusingmodule(). - Instantiate: Create an
instanceby providing astoreand themodule. - Invoke: Call an exported function by name using
invoke(store, instance, "function_name").
Optimized Invocation with
prepareFunctionFor performance-critical code where the same function is called repeatedly, use
prepareFunction. This resolves the named export and its runtime function upfront, amortizing the resolution cost. The returned handle can be called directly with a list of arguments.// 1. Decode val wasmFileAsByteArray = ... val module = module(wasmFileAsByteArray) // 2. Instantiate val store = store() val instance = instance(store, module) // 3. Invoke (Dynamic) val result = invoke(store, instance, "fibonacci") // OR: Optimized (Prepared) val fibonacci = prepareFunction(store, instance, "fibonacci") .expect("Failed to prepare fibonacci") val result = fibonacci(listOf(NumberValue.I32(10)))- Decode: Convert a
Configure Sweet for Kotlin Multiplatform test generation
mainSweet is used to generate Kotlin Multiplatform (KMP) tests from WebAssembly (WAST) repositories. Configuration is performed via the
sweetextension in your Gradle build script.Key configuration concepts:
- Sources: Each source represents a repository with its own Git URL, revision, and file selection rules.
- Phase Support: You can define the maximum
SemanticPhasesupported by a source (e.g.,DECODING,VALIDATION,EXECUTION). - Phase Limits: Use
PhaseLimitto restrict specific file patterns to a lower phase than the source's default. - Line Excludes: Use
LineExcludeto skip specific one-based lines in a file during a specific phase. This is passed to theScriptRunnerand does not modify the original file. - Path Matching:
includes,excludes, andPhaseLimit.patternsuse normalized forward-slash paths relative to thetestDirectory.
Important: Always use full Git commit hashes for
revisionto ensure reproducible suites. EnsurewasmToolsVersionand the repositoryrevisionare upgraded in tandem to avoid syntax compatibility issues.import io.github.charlietap.sweet.lib.SemanticPhase import io.github.charlietap.sweet.plugin.LineExclude import io.github.charlietap.sweet.plugin.PhaseLimit sweet { wasmToolsVersion = "1.253.0" scriptRunner = "example.runtime.ExampleScriptRunner" testPackageName = "example.runtime.testsuite" sources { register("core") { repositoryUrl = "https://github.com/WebAssembly/testsuite.git" revision = "<full commit>" testDirectory = "." includes = listOf( "*.wast", "proposals/threads/*.wast", ) phaseSupport = SemanticPhase.EXECUTION phaseLimits = listOf( PhaseLimit( patterns = setOf("proposals/threads/**"), phaseSupport = SemanticPhase.VALIDATION, ), ) lineExcludes = listOf( LineExclude( filePath = "proposals/threads/imports.wast", lines = setOf(310, 314), phase = SemanticPhase.VALIDATION, ), ) } register("componentModel") { repositoryUrl = "https://github.com/WebAssembly/component-model.git" revision = "<full commit>" testDirectory = "test" includes = listOf("**/*.wast") phaseSupport = SemanticPhase.DECODING } } }Use the Wasm Corpus Plugin to generate Kotlin tests
mainThe
corpusplugin is a Gradle build that generates Kotlin tests fromwasm-corpusfixtures. These tests run against the local Chasm source tree using a runtime-ownedCorpusRunner.To use the plugin, you can configure a
corpus {}block in yourchasm/build.gradle.ktsfile. This block allows you to filter the fixtures used for testing based on:- Wasm versions
- Source languages
- Required or excluded features
- Included or excluded canonical tags
- Maximum binary size
- Maximum test duration
- Target names
// Example configuration in chasm/build.gradle.kts corpus { // Configuration options like versions, features, and targets go here }