# KCL's WASM Compilation Target: Inside the wasm32-wasip1 Implementation

> Explore KCL's wasm32-wasip1 compilation target and its internal WASI preview 1 implementation. Understand how KCL routes plugin calls for WebAssembly modules.

- Repository: [The KCL Programming Language/kcl](https://github.com/kcl-lang/kcl)
- Tags: internals
- Published: 2026-03-05

---

**KCL compiles to WebAssembly via the `wasm32-wasip1` target (WASI preview 1), using conditional compilation in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs) to route plugin calls to host-provided imports when building for WASM instead of native binaries.**

The KCL configuration language supports WebAssembly as a first-class compilation target, enabling sandboxed execution in browsers and WASI-compliant runtimes. In the `kcl-lang/kcl` repository, this functionality centers on the **`wasm32-wasip1` target**, with the build system and runtime code cooperating to replace platform-specific operations with host-driven alternatives. Understanding KCL's WASM compilation target reveals how the language maintains feature parity across native and web environments.

## The wasm32-wasip1 Target and Build System

KCL's WebAssembly support targets **WASI preview 1** through the Rust toolchain's `wasm32-wasip1` architecture. The build process requires specific compiler flags to ensure proper panic handling and exception compatibility within the WASM sandbox.

### Makefile Configuration

The orchestration happens in the root [`Makefile`](https://github.com/kcl-lang/kcl/blob/main/Makefile#L14-L16). The `build-wasm` target invokes Cargo with strict `RUSTFLAGS` that disable legacy exception handling and configure panic behavior for the WASM environment:

```makefile
build-wasm:
	RUSTFLAGS="-Cpanic=abort -Cllvm-args=-wasm-use-legacy-eh=false" \
		cargo build --target=wasm32-wasip1 --release

```

This produces the artifact at `target/wasm32-wasip1/release/kcl.wasm`.

### Installing the Toolchain

Before compilation, developers must install the target architecture. The [`Makefile`](https://github.com/kcl-lang/kcl/blob/main/Makefile#L58-L61) provides the `install-rustc-wasm-wasi` rule for this purpose:

```makefile
install-rustc-wasm-wasi:
	rustup target add wasm32-wasip1

```

## Conditional Compilation Architecture

When compiling for `wasm32`, KCL uses Rust's **cfg attributes** to swap implementations. The pattern `#[cfg(target_arch = "wasm32")]` gates WASM-specific code, while `#[cfg(not(target_arch = "wasm32"))]` preserves native functionality. This allows the same codebase to compile for both targets without source duplication.

### The Plugin Bridge Pattern

The most significant adaptation occurs in **plugin invocation**. In [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs), the public entry point `kcl_plugin_invoke_json` exists in two variants:

**Native implementation** (lines 87-102) forwards calls to a Rust function pointer registered during initialization:

```rust
#[cfg(not(target_arch = "wasm32"))]
pub unsafe extern "C-unwind" fn kcl_plugin_invoke_json(
    method: *const c_char,
    args: *const c_char,
    kwargs: *const c_char,
) -> *const c_char {
    // Calls native function pointer from kcl_plugin_init
    // ...
}

```

**WASM implementation** (lines 102-120) delegates to an imported host function:

```rust
#[cfg(target_arch = "wasm32")]
pub unsafe extern "C-unwind" fn kcl_plugin_invoke_json(
    method: *const c_char,
    args: *const c_char,
    kwargs: *const c_char,
) -> *const c_char {
    unsafe { return kcl_plugin_invoke_json_wasm(method, args, kwargs); }
}

#[cfg(target_arch = "wasm32")]
extern "C-unwind" {
    pub fn kcl_plugin_invoke_json_wasm(
        method: *const c_char,
        args: *const c_char,
        kwargs: *const c_char,
    ) -> *const c_char;
}

```

The host environment—whether JavaScript in a browser or a WASI runtime—must provide `kcl_plugin_invoke_json_wasm`. This design moves all plugin logic outside the WASM sandbox, maintaining security while preserving extensibility.

## Platform-Specific Adaptations

Beyond plugin bridging, KCL stubs out or replaces OS-specific functionality that cannot execute within WebAssembly's capability-based security model.

### Filesystem and Network Stubs

**Filesystem locking** in [`crates/utils/src/fslock.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/utils/src/fslock.rs) (lines 13-29) returns `Ok(())` immediately when targeting WASM, acknowledging that file locking requires host capabilities not available in WASI preview 1:

```rust
#[cfg(target_arch = "wasm32")]
pub fn lock_file(_path: &Path) -> io::Result<()> {
    // TODO: Implement when WASM supports file locking
    Ok(())
}

```

**Network I/O** in [`crates/runtime/src/net/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/net/mod.rs) contains similar `#[cfg(target_arch = "wasm32")]` stubs that disable socket operations.

### Dependency Management

Native-only crates are excluded from WASM builds through conditional dependencies in [`Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/Cargo.toml) files. For example, [`crates/runtime/Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/Cargo.toml) (lines 39-41) specifies:

```toml
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
native-crate = "1.0"

```

This ensures platform-specific libraries (threading, raw filesystem access, system calls) are only linked for native targets.

## Building and Using KCL WASM Modules

### Compiling the WASM Artifact

Execute the following commands from the repository root:

```bash

# Install the WASI preview1 target (once per toolchain)

make install-rustc-wasm-wasi

# Build the WebAssembly module

make build-wasm

```

The output `target/wasm32-wasip1/release/kcl.wasm` is a standards-compliant WASI module ready for embedding.

### JavaScript Host Implementation

When running in Node.js or browsers, the host must provide the imported plugin bridge and memory:

```javascript
const fs = require('fs');

// Read the compiled WASM binary
const wasmBytes = fs.readFileSync('target/wasm32-wasip1/release/kcl.wasm');

const importObject = {
  env: {
    // Required: Implement the plugin bridge
    kcl_plugin_invoke_json_wasm: (methodPtr, argsPtr, kwargsPtr) => {
      // Access linear memory, deserialize arguments, execute plugin logic,
      // serialize results, and return a pointer to allocated memory
      const result = JSON.stringify({ success: true });
      const ptr = allocateStringInMemory(result); // Implementation dependent
      return ptr;
    },
    // Required: Provide linear memory for the module
    memory: new WebAssembly.Memory({ initial: 256, maximum: 512 })
  }
};

WebAssembly.instantiate(wasmBytes, importObject).then(({ instance }) => {
  // KCL exports depend on build configuration; check with wasm-objdump
  if (instance.exports.kcl_main) {
    const exitCode = instance.exports.kcl_main();
    console.log(`KCL execution completed with code: ${exitCode}`);
  }
});

```

### WASI Runtime Execution

Using `wasmtime` or similar WASI-compliant runtimes:

```bash

# Execute with default WASI imports

wasmtime target/wasm32-wasip1/release/kcl.wasm --invoke kcl_main

# Or with explicit preopened directories for file access

wasmtime --dir=. target/wasm32-wasip1/release/kcl.wasm --invoke kcl_main

```

*Note: Plugin functionality requires a custom linker configuration in the runtime to satisfy the `kcl_plugin_invoke_json_wasm` import.*

## Summary

- **KCL targets `wasm32-wasip1`** (WASI preview 1) for WebAssembly output, configured via the `build-wasm` Makefile target with specific `RUSTFLAGS` for panic handling.
- **Conditional compilation** using `#[cfg(target_arch = "wasm32")]` gates platform-specific code, separating native system calls from WASM-compatible stubs.
- **The plugin bridge** in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs) demonstrates the core pattern: native builds use internal function pointers, while WASM builds import `kcl_plugin_invoke_json_wasm` from the host.
- **Filesystem and network modules** contain WASM stubs that return neutral values or errors, preventing linkage failures while maintaining API compatibility.
- **Host environments** must provide implementations for declared `extern` functions and linear memory when instantiating KCL WASM modules.

## Frequently Asked Questions

### What is the exact Rust target triple for KCL's WASM build?

KCL uses **`wasm32-wasip1`** (32-bit WebAssembly with WASI preview 1). This target provides system interfaces for command-line applications while maintaining sandboxed security. The target is installed via `rustup target add wasm32-wasip1` and invoked through `cargo build --target=wasm32-wasip1`.

### How does KCL handle plugins when running as WebAssembly?

When compiled for `wasm32`, KCL's `kcl_plugin_invoke_json` function (defined in [`crates/runtime/src/stdlib/plugin.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/stdlib/plugin.rs)) forwards all calls to an imported host function named `kcl_plugin_invoke_json_wasm`. The embedding environment—whether JavaScript, Python, or a native host—must implement this function to provide plugin capabilities, as WASM modules cannot dynamically load native libraries.

### Why are there "TODO" stubs in the filesystem locking code?

The [`fslock.rs`](https://github.com/kcl-lang/kcl/blob/main/fslock.rs) module (lines 13-29) returns `Ok(())` for WASM targets because WASI preview 1 does not expose file locking primitives required by the POSIX `flock` mechanism. These stubs prevent compilation failures while acknowledging that concurrent file access protection is currently unavailable in the WASM build. Future WASI versions may resolve this limitation.

### Can I use std::fs or std::net in KCL when targeting WASM?

Standard library networking and advanced filesystem operations are **disabled** or **stubbed** in the WASM build. The [`Cargo.toml`](https://github.com/kcl-lang/kcl/blob/main/Cargo.toml) files explicitly exclude native-only dependencies when `target_arch = "wasm32"`, and the runtime replaces socket operations with no-op implementations. For file I/O, use WASI-compliant paths pre-opened by the host runtime; for networking, implement logic in the host and communicate via the plugin bridge interface.