WACS.Cli 1.7.4

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global WACS.Cli --version 1.7.4
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local WACS.Cli --version 1.7.4
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=WACS.Cli&version=1.7.4
                    
nuke :add-package WACS.Cli --version 1.7.4
                    

WACS

The unified WebAssembly toolchain for .NET. One CLI — wacs — covers running, compiling, inspecting, and generating bindings for WebAssembly modules and components, backed by the WACS interpreter, the WACS.Transpiler.Lib AOT engine, and the WACS.ComponentModel.Bindgen.Lib binding generator.

Note: This tool supersedes wasm-transpile (WACS.Transpiler). The legacy package is deprecated; install WACS.Cli instead. The package id is WACS.Cli (the bare WACS id is the runtime library, Wacs.Core); the tool command users type is wacs.

Installation

dotnet tool install -g WACS.Cli
wacs --help

Verb structure

wacs uses a verb-based subcommand layout (matches wasmtime / wasmer industry precedent — keeps "run" flags from cluttering the "compile" surface):

Verb Purpose Engine
run Execute a .wasm module interpreter (default) or transpiler
build Transpile to a .dll transpiler
aot Build a self-contained NativeAOT native binary (transpile + scaffold + dotnet publish) transpiler + ILC
inspect Diagnostics + WAT ↔ wasm format conversion parse-only
bindgen Generate C# bindings from WIT (forward) or regenerate WIT + bindings from a transpiled .dll (reverse) parse-only
wast2json Convert a .wast spec-test script into a wast2json bundle (one .json + side-car .wasm per module) parse-only

Direct-run shortcut

If the first argument is a .wasm / .wat file path that exists, wacs defaults to the run verb:

wacs my.wasm                            # → wacs run my.wasm
wacs build app.wasm -o app.dll          # explicit verb
wacs inspect app.wasm --stats           # explicit verb

Verb keywords (run / build / inspect / help / --help / -h / version / --version) bypass the shortcut. Anything else that isn't a verb keyword and doesn't look like a wasm path is treated as a verb name (so a typo gives a parse error rather than running the wrong file).

Engine choice

--engine interpreter (default for run) parses + executes via the WACS interpreter — the AOT-safe path with full instrumentation hooks (gas counter, dotTrace bracket, per-instruction logging, stats).

--engine transpiler JITs the module to .NET IL via Reflection.Emit, then runs through CLR-native dispatch with imports proxied back to the interpreter (mixed-mode). Roughly 64× the interpreter's throughput on compute-bound workloads (CoreMark on M3 Max: 17 552 iter/s vs 274 for the polymorphic interpreter — see the root README for the full table).

build always uses the transpiler — its job is to produce a .dll.

run and build both auto-detect component vs core wasm via the layer header byte; component-mode routing happens transparently when you pass .component.wasm input.

wacs run — execution

wacs run [files]... [options]              [-- argv...]

Examples

Run with _start (WASI command):

wacs run app.wasm -e PATH=/usr/bin -d ./data
# WASI Preview 1: env vars, preopened directory, _start dispatch

Mount a host directory into a wasip2 component:

wacs run app.component.wasm --wasip2 -d ./models::/models
# `host::guest` mount-pair syntax — the host side is `./models`,
# the guest sees `/models`. Bare `-d models` mounts at `/models`
# (Preview1's rooting convention so the same flag form works on
# both engines). Both sides reach the guest through the
# transpiler's direct-link emit for
# wasi:filesystem/preopens.get-directories — no shim needed.

Invoke a specific export with arguments:

wacs run module.wasm --call add -- 7 35
# → Result:[i32=42]

Trailing args after -- are forwarded to the chosen export (parsed as the function's wasm parameter types) or to WASI argv when running _start.

Run a component with WASI Preview 2 (direct-linked):

wacs run app.component.wasm --wasip2
# auto-routes to transpiler engine + WasiPreview2Bundle when
# --wasip2 / --host-package is set (those flags only make sense
# with the bundle path).
#
# Command components don't need --call — `wacs run --wasip2` looks
# for the canonical wasi:cli/run@<version>#run export and dispatches
# it automatically. Falls back to _start, then to a helpful error
# listing the available exports. Pass --call <export> (or
# --invoke <export>) to override.

Run a component that imports wasi:nn (ONNX inference):

wacs run my.component.wasm --wasip2 --wasi-nn -d ./models
# --wasi-nn loads Wacs.WASI.NN.OnnxRuntime (bundled with the CLI)
# and wires its IBindable adapter into the runtime. For other
# wasi-nn backends (ML.NET, LlamaSharp), pass the package name
# through --bind directly.

Multi-module composition via ModuleLinker:

wacs run a.wasm b.wasm --call quadruple -- 7
# → 28   (B's quadruple → A's double, twice, via shared runtime)

Each input registers under its filename basename so cross-module imports resolve through the runtime's binding table. The chosen export runs on the last input.

Through the AOT transpiler (mixed-mode):

wacs run app.wasm --engine transpiler --wasi
# transpiles in-process via Reflection.Emit, then runs through
# CLR-native dispatch with WASI imports proxied back to the
# interpreter

Profile a hot path:

wacs run app.wasm --profile
# JetBrains dotTrace measure-profiler bracket; snapshot lands
# in the OS-default profiler temp dir

Instrumented runs (interpreter only):

wacs run app.wasm --gas-limit 1000000 --log-gas
# trap if total instructions exceed 1M; print final count

wacs run app.wasm --log-execution Calls --calculate-lines
# log every call instruction with its source line number

wacs run app.wasm --stats Function
# per-function instruction counts after the run

Custom host bindings:

wacs run app.wasm --bind ./MyGameHost.dll
# load + activate every IBindable in MyGameHost.dll, wire into runtime

run flag reference

Flag Default Notes
--call <export> auto Function to invoke. Args after -- are parsed per its wasm signature. Auto: components dispatch wasi:cli/run@<v>#run if present; cores dispatch _start. --invoke is an alias.
--engine interpreter interpreter or transpiler (Reflection.Emit AOT, mixed-mode imports).
-m, --module <name> _ Name to register the instantiated module under.
-e, --env K=V WASI Preview 1 environ. Repeat or comma-separate.
-d, --dir <path> Preopen directory. Bare path mounts at /<basename>; host::guest syntax (matches wasmtime) sets the guest mount explicitly. Honored on both --wasi (Preview 1) and --wasip2 (Preview 2). Repeat or comma-separate.
--wasi off Bind WASI Preview 1 host imports.
--bind <asm> Load IBindable host packages. Accepts a file path (Assembly.LoadFrom) or assembly name (Assembly.Load). Repeat or comma-separate.
--host-package <name> Component-mode [WitSource] host package(s). Accepts assembly name or file path.
--wasip2 off Shorthand: --host-package Wacs.WASI.Preview2 + Wacs.WASI.Preview2.DependencyInjection. The DI sibling carries the SourceGen-shape impl classes the transpiler instantiates for [constructor]X; both halves are needed for direct-link emit. See docs/COMPONENT_CHAINING.md.
--wasi-nn off Shorthand: adds Wacs.WASI.NN + Wacs.WASI.NN.DependencyInjection + Wacs.WASI.NN.OnnxRuntime. ONNX is the default (bundled) backend. For non-ONNX backends — ML.NET, LlamaSharp (GGUF), TorchSharp (PyTorch / TorchScript) — use --bind <path-to-backend.dll> directly; --bind auto-pulls Wacs.WASI.NN + .DependencyInjection when the bound assembly's identity starts with Wacs.WASI.NN. (gap 24a). The composite WasiPreview2NNBundle is auto-discovered when both --wasip2 and a wasi-nn backend are wired.
--wasi-threads off Shorthand --bind Wacs.WASI.Threads. Wires wasi:thread-spawn; module must declare/import shared memory and export wasi_thread_start (param i32 i32).
--profile off JetBrains dotTrace measure-profiler session.
--log-gas off Print total instructions executed.
--gas-limit <N> 0 (∞) Trap if instructions exceed N.
--log-progress <N> -1 (off) Print . every N instructions.
--log-execution <flags> None None\|Computes\|Calls\|Branches\|Memory\|All.
--calculate-lines off Line-number mapping for instruction logs.
--stats <detail> None None\|Total\|Instruction\|Function.
--super off Super-instruction fusion (interpreter only).
--switch off Source-generated switch runtime (interpreter only).
--simd scalar --engine transpiler SIMD strategy: interpreter | scalar | intrinsics.
--no-tail-calls off --engine transpiler only.
--max-fn-size <N> 0 --engine transpiler only. Skip large fns.
--data-storage compressed --engine transpiler only: compressed | raw | static.
--no-validate off Skip module validation after parse.
-v, --verbose off Parser timing + diagnostics on stderr.

wacs build — transpile to .dll

wacs build [files]... -o <output> [options]

Examples

Single-file core wasm:

wacs build app.wasm -o app.dll

Multi-file linker composition:

wacs build a.wasm b.wasm -o b.dll
# → wrote a.dll, b.dll  (siblings land at <basename>.dll alongside)

Component with WASI Preview 2 + runnable Main:

wacs build app.component.wasm --wasip2 --emit-main \
    --entry-point greet -o app.dll
# Component-mode: --wasip2 resolves WASI imports to inline IL
# (no delegate hop). --emit-main bakes Program.Main(string[])
# into the output that constructs the bundle, instantiates the
# module, and invokes greet.

Tune the output:

wacs build app.wasm -o app.dll \
    --simd intrinsics \
    --data-storage static \
    --namespace MyApp.Wasm
# SIMD via Vector128<T> hardware intrinsics; data segments as
# static byte[] fields; root namespace MyApp.Wasm

build flag reference

Flag Default Notes
-o, --output <path> (required) Output .dll path. With multi-input, names the LAST input; siblings → <basename>.dll.
--namespace CompiledWasm Root namespace for generated types.
-m, --module <name> WasmModule Generated Module class name.
--wasi off Bake WASI Preview 1 bindings into the build runtime.
--bind <asm> Custom IBindable host libraries (build-time).
--host-package <name> Component-mode [WitSource] packages.
--wasip2 off Shorthand: adds Wacs.WASI.Preview2 + Wacs.WASI.Preview2.DependencyInjection. See docs/COMPONENT_CHAINING.md for which packages each capability needs on the load path.
--emit-main off Bake Program.Main(string[]) into the output.
--entry-point <export> _start Export Main invokes.
--main-class <name> Program Generated Program class name.
--simd scalar interpreter \| scalar \| intrinsics.
--no-tail-calls off Disable CIL tail. prefix.
--max-fn-size <N> 0 Skip transpilation of large functions.
--data-storage compressed compressed \| raw \| static.
--gc-checking <flags> None Extra GC type-check layers.
--no-validate off Skip module validation.
-v, --verbose off Diagnostics + per-function counts.

wacs aot — wasm → NativeAOT native binary

wacs aot <input.wasm> [-o <output>] [options] [-- argv...]

End-to-end: transpile the input wasm to a stable-named .dll, scaffold a throwaway consumer csproj that statically references it plus the WACS runtime support assemblies, and run dotnet publish -p:PublishAot=true -r <rid>. The resulting native binary is copied to the output path; the temp build dir is removed unless --keep-temp.

Cold-start equivalent of transpiler-aot-linked from docs/COLDSTART.md: new Module() → typed direct call into the wasm function. No JIT, no Reflection.Emit, no Assembly.Load, no MethodInfo.Invoke at run time.

Examples

Compute-only module to native binary:

wacs aot fib.wasm -o fib
./fib                 # native exe — no .NET runtime required

WASI Preview 1 (the wasi-libc / wasi-sdk world):

wacs aot coremark.wasm --wasi -o coremark
./coremark 1 1 1 1    # trailing argv forwarded to the guest

--wasi references WACS.WASI.Preview1 from the scaffolded consumer; the source generator in WACS.HostBindings.SourceGen emits an IImports adapter that wires the wasm's wasi_snapshot_preview1.* imports straight to the [WacsImport]-annotated statics. No reflection, no DispatchProxy, fully NativeAOT-trim-safe.

Component with WASI Preview 2:

wacs aot app.component.wasm --wasip2 --entry-point greet -o app
./app

The component's wasi:* imports are direct-linked at transpile time against the typed C# host interfaces in WACS.WASI.Preview2; the consumer constructs the WasiPreview2Bundle via Microsoft.Extensions.DependencyInjection before invoking the named export.

Pick the AotLinked emission target (smaller binary):

wacs aot fib.wasm --aot-linked -o fib
# Skips the codec wrapper. Default emission keeps it; AotLinked
# inlines memory/data/globals/tables straight into the Module ctor
# and lets the trimmer dead-strip the codec machinery.

aot flag reference

Flag Default Notes
-o, --output <path> <inputBasename> in cwd Path for the produced native binary (no .exe suffix by default — set explicitly on Windows).
--rid <rid> host RID Target .NET runtime identifier (osx-arm64, linux-x64, win-x64).
--entry-point <export> _start WASM export the emitted Program.Main invokes (scalar args only).
--namespace <name> WacsAot Root namespace for generated types in the transpiled .dll.
--simd scalar interpreter \| scalar \| intrinsics.
--aot-linked off Use the EmissionTarget.AotLinked emission target — skips the codec wrapper for a smaller binary. Covers memory / data / globals / tables / element segments.
--wasi off Bake WACS.WASI.Preview1 bindings into the produced binary.
--wasip2 off Component-mode counterpart to --wasi — direct-links wasi:* imports against WACS.WASI.Preview2 + .DependencyInjection. See docs/COMPONENT_CHAINING.md.
--preopen <H::G> WASI directory preopen <host-path>::<guest-path>. Repeat for multiple. Only with --wasi.
--keep-temp off Don't delete the scaffolded build dir (useful for inspecting the generated csproj / Program.cs).
-v, --verbose off Print each step (transpile, scaffold, publish, copy).

Trailing positional args after the input file are forwarded to the guest as argv when --wasi is set.

wacs inspect — diagnostics + format conversion

wacs inspect <file> [options]

Parse-only. No instantiation, no execution, no transpilation.

Examples

Stats summary (default behavior with no flags):

$ wacs inspect module.wasm
file        module.wasm
kind        core wasm module
types       3
functions   12 (4 imported)
exports     5
memories    1
tables      1
globals     0
data        2 segment(s), 1024 bytes total
elements    1 segment(s)

Component stats:

$ wacs inspect app.component.wasm
file              app.component.wasm
kind              wasm component
core modules      3 (768 bytes total)
nested components 0
types             7
canons            4
exports           1
custom sections   1
raw sections      26

List exports / imports:

wacs inspect module.wasm --exports
wacs inspect module.wasm --imports

For components, --imports enumerates each top-level instance import with its package + version — useful for predicting which host packages to wire before running:

$ wacs inspect my.component.wasm --imports
=== component-level imports ===
  Instance  wasi:nn/inference@0.2.0-rc-2024-10-28
  Instance  wasi:nn/graph@0.2.0-rc-2024-10-28
  Instance  wasi:cli/stdout@0.2.9
  Instance  wasi:cli/exit@0.2.9
  …

A component with wasi:nn/* imports needs --wasi-nn (or --bind Wacs.WASI.NN.<backend> for non-ONNX backends). wasi:cli/* needs --wasip2. Version mismatches (e.g. component compiled against wasi:cli/stdout@0.2.9 but Wacs.WASI.Preview2 ships 0.2.0) surface as direct-link resolution failures at instantiation.

WAT ↔ wasm round-trip:

# binary → text
wacs inspect module.wasm --dump-wat                 # WAT to stdout
wacs inspect module.wasm --dump-wat --output-dir .  # writes module.wat

# text → binary
wacs inspect module.wat  --dump-wasm                # raw bytes to stdout (pipe target)
wacs inspect module.wat  --dump-wasm --output-dir . # writes module.wasm

Round-trips preserve function $names via the standard name custom section and preserve WAT comments / (@…) annotations via a wacs.trivia custom section (WACS-specific, ignored by other engines). A WAT → wasm → WAT cycle recovers identifiers and trivia; the canonical re-render is line-stable but doesn't preserve original whitespace exactly.

inspect flag reference

Flag Notes
--stats Default when no other flag is given.
--exports List exports (kind + name).
--imports List imports (kind + module.name).
--dump-wat Render parser-friendly WAT from a .wat or .wasm input (core only — components route to their embedded core modules).
--dump-wasm Render canonical wasm binary from a .wat or .wasm input. WAT inputs propagate $names + comments / annotations into the binary's custom sections.
--output-dir <path> Write <basename>.wat / <basename>.wasm here instead of stdout.

wacs bindgen — WIT ↔ C# bindings

wacs bindgen <input> -o <output-dir> [options]

Two directions on one verb, auto-detected by input shape:

  • Forward — a .wit file or a directory tree of WIT files (recurses into deps/) → [WitSource]-tagged C# interfaces. Use this when authoring components: pre-generate the host binding surface for offline AOT targets that can't use the runtime transpiler.
  • Reverse — a transpiled .dll carrying embedded component-type metadata → regenerated WIT + C# bindings. Use this when the original .wit is gone but the shipped .dll is still on hand.

Examples

Forward, single WIT file:

wacs bindgen ./wit/hello.wit -o ./Generated/

Forward, WIT directory tree (with deps/):

wacs bindgen ./wit -o ./Generated/
# Recurses into deps/ subdirectories. Headerless files attribute
# to the parent package per the wit-bindgen-csharp convention.

Reverse, regenerate from a transpiled .dll:

wacs bindgen ./app.dll -o ./regenerated/
# Extracts the embedded component-type metadata, decodes it to
# WIT, and regenerates the [WitSource]-tagged C# binding surface.
# Errors with exit code 3 if the .dll has no embedded WIT.

Reverse with raw bytes preserved:

wacs bindgen ./app.dll -o ./regen --write-wit
# Also writes <basename>.componenttype.bin alongside the .cs files
# (useful for `wasm-tools component wit` round-trip inspection).

bindgen flag reference

Flag Notes
<input> (positional) .wit / WIT directory / .dll. Direction inferred from extension.
-o, --output <dir> (required) Output directory. One .cs file per emitted interface / world / package.
--namespace <name> Root C# namespace override. Currently a warning — Phase 1d uses pinned wit-bindgen-csharp conventions; explicit override is a follow-up.
--write-wit Reverse mode only. Persist the raw extracted component-type bytes alongside the .cs files.
-v, --verbose Per-file emission progress.

wacs wast2json — spec-test bundle

wacs wast2json <input.wast> -o <output-dir> [options]

Convert a .wast spec-test script into the canonical wast2json bundle: a .json file listing commands plus one side-car .wasm per referenced module. Mirrors wabt's wast2json shape, so the resulting directory is consumable by any spec-test runner that reads the format (WACS's own Spec.Test runner included).

# Smallest spec testcase:
$ wacs wast2json Spec.Test/spec/test/core/forward.wast -o out/
wrote out/forward.json (5 commands, 1 side-car modules)

$ ls out/
forward.0.wasm  forward.json

# Larger fixture exercising assert_invalid / assert_trap:
$ wacs wast2json Spec.Test/spec/test/core/i32.wast -o out/
wrote out/i32.json (460 commands, 86 side-car modules)

wast2json flag reference

Flag Notes
-o, --output-dir <path> Required. Directory to write the .json + side-car .wasm files into. Created if it doesn't exist.
--base-name <stem> Stem for the generated files (default: the input file's name without extension). Produces <stem>.json plus <stem>.0.wasm, <stem>.1.wasm, … for each module.

Output shape (excerpt)

{
  "source_filename": "forward.wast",
  "commands": [
    { "type": "module", "line": 1, "filename": "forward.0.wasm" },
    {
      "type": "assert_return",
      "line": 17,
      "action": {
        "type": "invoke",
        "field": "even",
        "args": [{ "type": "i32", "value": "13" }]
      },
      "expected": [{ "type": "i32", "value": "0" }]
    }
  ]
}

Floats are emitted as their unsigned IEEE-754 bit pattern in decimal (e.g. 1.0"value": "1065353216") so NaN payloads round-trip exactly. nan:canonical / nan:arithmetic ride along as string sentinels in expected-value position.

Migration from wasm-transpile

The legacy wasm-transpile (WACS.Transpiler) CLI keeps working unchanged — it ships a stderr deprecation banner pointing at wacs but every flag still functions. Concrete migrations:

wasm-transpile wacs
wasm-transpile -i x.wasm -o x.dll wacs build x.wasm -o x.dll
wasm-transpile -i x.wasm -o x.dll --run wacs run x.wasm
wasm-transpile -i x.wasm -o x.dll --wasi --run wacs run x.wasm --wasi
wasm-transpile -i x.wasm -o x.dll --wasip2 --emit-main wacs build x.wasm --wasip2 --emit-main -o x.dll
wasm-transpile -i a.wasm,b.wasm -o b.dll wacs build a.wasm b.wasm -o b.dll
wasm-transpile -i x.wasm -o x.dll --engine interpreter --run wacs run x.wasm --engine interpreter

The standalone WACS.ComponentModel.Bindgen package (wit-bindgen-wacs CLI) was rolled into the bindgen verb here before its first NuGet release; users only ever see wacs bindgen. The WACS.ComponentModel.Bindgen.Lib package (programmatic surface) is unaffected — source generators and build-time integrations should keep referencing it directly.

The -i short flag is retired (Console used it for --invoke, Transpiler for --input — incompatible). Inputs are positional in wacs; the --call long flag replaces --invoke.

License

WACS is distributed under the Apache 2.0 License.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.10.0 95 5/18/2026
1.8.0 95 5/16/2026
1.7.6 92 5/14/2026
1.7.5 84 5/14/2026
1.7.4 96 5/12/2026
1.7.2 87 5/12/2026
1.6.7 94 5/12/2026
1.5.26 107 5/11/2026
1.5.21 103 5/10/2026
1.5.18 98 5/10/2026
1.5.14 93 5/10/2026
1.4.1 100 5/9/2026
1.3.0 97 5/7/2026
1.2.0 110 5/1/2026
1.1.0 99 5/1/2026