{"id":21457897,"url":"https://github.com/kelnishi/wacs","last_synced_at":"2026-04-23T22:01:09.427Z","repository":{"id":262776393,"uuid":"868012676","full_name":"kelnishi/WACS","owner":"kelnishi","description":"WebAssembly Interpreter written in C#/.NET Standard2.1","archived":false,"fork":false,"pushed_at":"2026-04-21T17:26:28.000Z","size":7568,"stargazers_count":110,"open_issues_count":1,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-21T19:50:26.492Z","etag":null,"topics":["csharp","dotnet","unity","wasi","wasm","webassembly"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kelnishi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"kelnishi","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2024-10-05T08:40:51.000Z","updated_at":"2026-04-21T17:25:47.000Z","dependencies_parsed_at":"2024-11-14T08:24:10.950Z","dependency_job_id":"5967703a-3220-45eb-93fa-b9f9c378644e","html_url":"https://github.com/kelnishi/WACS","commit_stats":null,"previous_names":["kelnishi/wacs"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/kelnishi/WACS","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelnishi%2FWACS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelnishi%2FWACS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelnishi%2FWACS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelnishi%2FWACS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kelnishi","download_url":"https://codeload.github.com/kelnishi/WACS/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelnishi%2FWACS/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32200159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T20:19:26.138Z","status":"ssl_error","status_checked_at":"2026-04-23T20:19:23.520Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","unity","wasi","wasm","webassembly"],"created_at":"2024-11-23T06:16:09.463Z","updated_at":"2026-04-23T22:01:09.378Z","avatar_url":"https://github.com/kelnishi.png","language":"C#","funding_links":["https://github.com/sponsors/kelnishi"],"categories":[],"sub_categories":[],"readme":"# WACS (C# WebAssembly Interpreter)\n\n**Project status**\n\u0026nbsp;![CI](https://github.com/kelnishi/WACS/actions/workflows/ci.yml/badge.svg?branch=main)\n\u0026nbsp;![Platform](https://img.shields.io/badge/platform-.NET%20Standard%202.1-blue)\n\u0026nbsp;[![License](https://img.shields.io/github/license/kelnishi/WACS)](LICENSE)\n\n**NuGet packages**\n\u0026nbsp;[![WACS](https://img.shields.io/nuget/v/WACS?label=WACS)](https://www.nuget.org/packages/WACS)\n\u0026nbsp;[![WACS.WASIp1](https://img.shields.io/nuget/v/WACS.WASIp1?label=WACS.WASIp1)](https://www.nuget.org/packages/WACS.WASIp1)\n\u0026nbsp;[![WACS.Transpiler](https://img.shields.io/nuget/v/WACS.Transpiler?label=WACS.Transpiler)](https://www.nuget.org/packages/WACS.Transpiler)\n\u0026nbsp;[![WACS.Transpiler.Lib](https://img.shields.io/nuget/v/WACS.Transpiler.Lib?label=WACS.Transpiler.Lib)](https://www.nuget.org/packages/WACS.Transpiler.Lib)\n\u0026nbsp;[![Downloads](https://img.shields.io/nuget/dt/WACS?label=WACS%20downloads)](https://www.nuget.org/packages/WACS)\n\n## Overview\n\n**Latest releases** (see the [CHANGELOG](CHANGELOG.md) for details):\nWACS `0.8.3` · WACS.WASIp1 `0.9.7` · WACS.Transpiler `0.2.1` · WACS.Transpiler.Lib `0.2.1`\n\n**WACS** is a pure C# WebAssembly Interpreter for running WASM modules in .NET environments, including Godot and AOT environments like Unity's IL2CPP.\n\nWACS supports the latest standardized webassembly feature extensions including **Garbage Collection** and **JSPI**-like async execution. \n\n![Wasm in Unity](UnityScreenshot.png)\n\n## Table of Contents\n\n- [Features](#features)\n- [WebAssembly Feature Extensions](#webassembly-feature-extensions)\n- [Getting Started](#getting-started)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Integration with Unity](#integration-with-unity)\n- [Integration with Godot](#integration-with-godot)\n- [Interop Bindings](#interop-bindings)\n- [Customization](#customization)\n- [Performance](#performance)\n- [WebAssembly Text Format (WAT / WAST)](#webassembly-text-format-wat--wast)\n- [Roadmap](#roadmap)\n- [License](#license)\n\n## Features\n\n- **Unity Compatibility**: Compatible with **Unity 2021.3+** including AOT/IL2CPP modes for iOS.\n- **Godot Compatibility**: Compatible with **Godot Engine - [.NET](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html)**. \n- **Pure C# Implementation**: Written in C# 9.0/.NET Standard 2.1. (No `unsafe` keyword blocks, no raw pointer arithmetic — see [notes on `System.Runtime.CompilerServices.Unsafe` in the switch dispatcher](#running-wacsconsole).)\n- **No Complex Dependencies**: Uses [FluentValidation](https://github.com/FluentValidation/FluentValidation) and [Microsoft.Extensions.ObjectPool](https://www.nuget.org/packages/Microsoft.Extensions.ObjectPool) as its only dependencies.\n- **WebAssembly 3.0 Spec Compliance**: Passes the [WebAssembly 3.0](https://webassembly.github.io/spec/versions/core/WebAssembly-3.0-draft.pdf) spec [test suite](https://github.com/WebAssembly/spec/tree/wasm-3.0).\n- **First-class WAT / WAST**: Pure-C# reader and writer for the WebAssembly text format. `Wacs.Console` takes `.wat` directly; the spec `.wast` suite parses natively with no external `wast2json` / wabt dependency.\n- **Magical Interop**: Host bindings are validated with reflection, no boilerplate code required.\n- **Async Tasks**: [JSPI](https://github.com/WebAssembly/js-promise-integration)-like non-blocking calls for async functions.\n- **WASI:** Wacs.WASIp1 provides a [wasi\\_snapshot\\_preview1](https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md) implementation.\n\n**WACS is for _mobile games_**. \n\nBecause WebAssembly is memory-safe and can be ahead-of-time validated, WACS makes it possible to build safe, verifiable\nUGC, DLC, or plugin systems that include executable logic.\n\n## WebAssembly Feature Extensions\nWACS is based on the [WebAssembly Core 3 draft spec](https://webassembly.github.io/spec/versions/core/WebAssembly-3.0-draft.pdf) and passes the associated [test suite](https://github.com/WebAssembly/spec/tree/wasm-3.0).\n\nSupport for all standardized extensions is listed below.\n\nHarnessed results from [wasm-feature-detect](https://github.com/GoogleChromeLabs/wasm-feature-detect) as compares to [other runtimes](https://webassembly.org/features/):\n\n|Proposal |Features|    |\n|------|-------|----|\n|Phase 5|\n|[JavaScript BigInt to WebAssembly i64 integration](https://github.com/WebAssembly/JS-BigInt-integration)||\u003cspan title=\"Browser idiom, but conceptually supported\"\u003e✳️\u003c/span\u003e|\n|[Bulk memory operations](https://github.com/webassembly/bulk-memory-operations)||✅|\n|[Extended Constant Expressions](https://github.com/WebAssembly/extended-const)|extended_const|✅|\n|[Garbage collection](https://github.com/WebAssembly/gc)|gc|✅|\n|[Multiple memories](https://github.com/WebAssembly/multi-memory)|multi-memory|✅|\n|[Multi-value](https://github.com/WebAssembly/multi-value)|multi_value|✅|\n|[Import/Export of Mutable Globals](https://github.com/WebAssembly/mutable-global)||✅|\n|[Reference Types](https://github.com/WebAssembly/reference-types)||✅|\n|[Relaxed SIMD](https://github.com/webassembly/relaxed-simd)|relaxed_simd|✅|\n|[Non-trapping float-to-int conversions](https://github.com/WebAssembly/nontrapping-float-to-int-conversions)||✅|\n|[Sign-extension operators](https://github.com/WebAssembly/sign-extension-ops)||✅|\n|[Fixed-width SIMD](https://github.com/webassembly/simd)||✅|\n|[Tail call](https://github.com/webassembly/tail-call)|tail_call|✅|\n|[Typed Function References](https://github.com/WebAssembly/function-references)|function-references|✅|\n|Phase 4|\n|[Exception handling](https://github.com/WebAssembly/exception-handling)|exceptions|✅|\n|[JS String Builtins](https://github.com/WebAssembly/js-string-builtins)||❌|\n|[Memory64](https://github.com/WebAssembly/memory64)|memory64|✅|\n|[Threads](https://github.com/webassembly/threads)|threads|✅|\n|Phase 3|\n|[JS Promise Integration](https://github.com/WebAssembly/js-promise-integration)|jspi|\u003cspan title=\"Browser idiom, but conceptually supported\"\u003e✳️\u003c/span\u003e|\n|[Type Reflection for WebAssembly JavaScript API](https://github.com/WebAssembly/js-types)|type-reflection|\u003cspan title=\"Browser idioms, not directly supported\"\u003e🌐\u003c/span\u003e|\n||\n|[Legacy Exception Handling]( https://github.com/WebAssembly/exception-handling)|exceptions|❌|\n|[Streaming Compilation](https://webassembly.github.io/spec/web-api/index.html#streaming-modules)|streaming_compilation|\u003cspan title=\"Browser idioms, not directly supported\"\u003e🌐\u003c/span\u003e|\n\n###### This table was generated with the Feature.Detect test harness.\n\n## Getting Started\n\n### Installation\n\nThe easiest way to use WACS is to add the package from NuGet\n\n```bash\ndotnet add package WACS\ndotnet add package WACS.WASIp1\n````\n\n### AOT Transpiler\n\n`WACS.Transpiler` is a companion package that ahead-of-time transpiles a\n`.wasm` module into a .NET assembly. Installs as a [dotnet global\ntool](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools),\nbacked by the same WACS runtime:\n\n```bash\ndotnet tool install -g WACS.Transpiler\nwasm-transpile -i module.wasm -o module.dll\n```\n\nFor WASI preview1 modules (CoreMark, anything built against `wasi-libc`):\n\n```bash\nwasm-transpile -i coremark.wasm -o coremark.dll --wasi --entry-point _start --run\n```\n\n`--wasi` binds `WACS.WASIp1` to the runtime, forwards all\n`wasi_snapshot_preview1` imports, shares memory with the interpreter\nbindings, and invokes the entry-point export in-process. For custom\nhost imports (`env.sayc`, game bindings, etc.), use the library API\n(`WACS.Transpiler.Lib`, new in v0.2) with `BindHostFunction` + the\nbuilt-in `TranspiledModuleLoader` or a custom `ImportDispatcher` proxy.\nSee [`Wacs.Transpiler/README.md`](Wacs.Transpiler/README.md) for the\nfull flag surface, the seamless-load API, and the remaining v0.3\ntracked items (non-scalar `--emit-main` argv parsing, exotic GC init\nvalues).\n\n### From source\n\nIf you prefer to build WACS from source, you can clone the repo and build it with the .NET SDK:\n\n```bash\ngit clone https://github.com/kelnishi/WACS.git\ncd WACS\ndotnet build\n```\n\n## Usage\n\nBasic usage example, how to load and run a WebAssembly module:\n\n```csharp\nusing System;\nusing System.IO;\nusing Wacs.Core;\nusing Wacs.Core.Runtime;\n\n//Create a runtime\nvar runtime = new WasmRuntime();\n\n//Bind a host function\n//  This can be any regular C# delegate.\n//  The type here will be validated against module imports.\nruntime.BindHostFunction\u003cAction\u003cchar\u003e\u003e((\"env\", \"sayc\"), c =\u003e\n{\n    System.Console.Write(c);\n});\n\n//Load a module from a binary file\nusing var fileStream = new FileStream(\"HelloWorld.wasm\", FileMode.Open);\nvar module = BinaryModuleParser.ParseWasm(fileStream);\n\n//Instantiate the module\nvar modInst = runtime.InstantiateModule(module);\n\n//Register the module to add its exported functions to the export table\nruntime.RegisterModule(\"hello\", modInst);\n\n//Get the module's exported function\nif (runtime.TryGetExportedFunction((\"hello\", \"main\"), out var mainAddr))\n{\n    //For wasm functions you can expect return types as Wacs.Core.Runtime.Value\n    //  Value has implicit conversion to many useful primitive types\n    var mainInvoker = runtime.CreateInvokerFunc\u003cValue\u003e(mainAddr);\n    \n    //Call the wasm function and get the result\n    //  Implicit conversion from Value to int\n    int result = mainInvoker();\n    \n    System.Console.Error.WriteLine($\"hello.main() =\u003e {result}\");\n}\n```\n\n## Integration with Unity\n\n### With Unity Package Manager\n1. Window\u003ePackage Manager\n2. Click + Add package from git URL...\n3. Enter the package repo URL:\n ```text\n git@github.com:kelnishi/WACS-Unity.git\n ```\n4. Click Add\n\nThis will put the DLLs into your project. \nImport the WasmRunner sample to get started.\n\n### Manually\nTo manually add WACS to a Unity project, you'll need to add the following DLLs to your Assets directory:\n- Wacs.Core.dll\n- FluentValidation.dll\n- Microsoft.Extensions.ObjectPool.dll\n\nSet **Player Settings\u003eOther Settings\u003eApi Compatibility Level** to **.NET Standard 2.1**.\n\n## Integration with Godot\nWACS is compatible with **[Godot Engine -.NET](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html)** in C# projects.\n- Add WACS via [NuGet](https://www.nuget.org/packages/WACS) with the commandline or your IDE's NuGet tool.\n- See [sample/GodotSample.cs](https://github.com/kelnishi/WACS/tree/main/sample/GodotSample.cs) for loading wasm files.\n\n## Interop Bindings\n\nWACS simplifies host function bindings, allowing you to easily call .NET functions from WebAssembly modules.\nThis allows seamless communication between your host environment and WebAssembly without boilerplate code.\nSimilarly, calling into wasm code is done by generating a typed delegate.\n\nExample from WASIp1:\n\n```csharp\n//Alias your types for readability\nusing ptr = System.Int32;\n\n//WACS can bit-convert types like Enums and explicit layout structs\n[WasmType(nameof(ValType.I32))]\npublic enum ErrNo : ushort\n{\n    Success = 0,\n    ...\n}\n\n//Supply the delegate definition when binding\n//  ExecContext is an optional first parameter for Memory and Stack manipulation\nruntime.BindHostFunction\u003cFunc\u003cExecContext,ptr,ptr,ErrNo\u003e\u003e(\n   (module, \"args_get\"), ArgsGet);\n\n// WASIp1's args_get\npublic ErrNo ArgsGet(ExecContext ctx, ptr argvPtr, ptr argvBufPtr)\n{\n    var mem = ctx.DefaultMemory;\n            \n    foreach (string arg in _config.Arguments)\n    {\n        // Copy argument string to argvBufPtr.\n        int strLen = mem.WriteUtf8String((uint)argvBufPtr, arg, true);\n                \n        // Write pointer to argument in argvPtr.\n        mem.WriteInt32(argvPtr, argvBufPtr);\n\n        // Update offsets.\n        argvBufPtr += strLen;\n        argvPtr += sizeof(ptr);\n    }\n\n    return ErrNo.Success;\n}\n```\n\n## Customization\n\nIf you'd like to customize the wasm runtime environment, I recommend downloading the full source for examples.\n\nThe `Wacs.WASIp1` implementation is a good starting point for how to set up your own library of bindings.\nIt also contains examples of more advanced usage like binding multiple return values and full operand stack access.\n\nThe `Spec.Test` project runs the wasm spec test suite. This also contains examples for binding other runtime environment\nobjects like Tables, Memories, and Variables.\n\nCustom Instruction implementations can be patched in by replacing or inheriting from `SpecFactory`.\n\n## Performance\n\nWACS is a bytecode (wasm) interpreter running on a bytecode interpreted (or JIT'd) language (CIL/CLR). This is, as you can imagine,\n*not a recipe for raw performance*. However, recognizing this dynamic allows us to make certain optimizations to achieve\nperformance closer to other languages in other VMs.\n\n### The Spec-Defined Implementation\nThe Wasm Virtual Machine is a stack machine. This means that instructions produce operands, place them on the stack, and then other\ninstructions consume them by popping them from the stack. WACS uses a pre-allocated linear stack for more register-like performance.\nHowever, even a virtualized stack is costly to manage as the CLR will still need to manage memory and objects at its boundaries.\nTo optimize further, we'll need to opportunistically use register-machine semantics by swapping out equivalent operations. \n\n### Link-time optimization\nThe design of the WASM VM includes block labelling for branch instructions and a heterogeneous operand/control stack.\nWACS uses a split stack that separates operands and control. This enables us to make some key optimizations:\n- Non-flushing branch jumps. We can leave operands on the stack if intermediate states don't interfere.\n- Precomputed block labels. We can ditch the control frame's label stack entirely!\n- Modern C# ObjectPools and ArrayPools minimize unavoidable allocation\n\n### Super-Instruction Threading (interpreter)\nHere's where we break WASM semantics and go off-road to claw back some performance.\nA linear list of WASM instructions can be inverted into an expression tree. The WAT text format supports both the linear\nand the tree structure; they are conceptually equivalent. We'll use this similarity by applying the transform to the binary AST.\nTake for example, this sequence:\n\u003e i32.const 5   \u003c- Pushes 5 onto the stack\n\u003e \n\u003e i32.const 7   \u003c- Pushes 7 onto the stack\n\u003e \n\u003e i32.add       \u003c- Pops 7, Pops 5, Pushes 12 onto the stack\n\nFor a sequence representing `5+7`, this is performing potentially 8+ function calls, multiple Value.ctors, memory bounds checks, etc.\nAll this, not even including the actual computation (+). Knowing this, we have an alternative.\n\n**Expression Tree Rewriting**\n\n```\n       i32.add\n     /         \\\ni32.const 5  i32.const 7\n```\nWhen enabled (`runtime.SuperInstruction = true`), WACS does a linear pass through the instruction sequences and rolls up interdependent instructions into directed acyclic graphs.\nInstructions are replaced with functionally equivalent expression trees `InstAggregate`. The new aggregate instructions are *in-memory*\nand are implemented with pre-built relational functions. Ultimately, these instructions are compiled by the dotnet build process into bytecode\nto be run by the runtime. The rewriter lives in `Wacs.Core.Runtime.SuperInstruction` (`SuperInstructionRewriter.Rewrite`) with\nthe synthetic instructions in `Wacs.Core.Instructions.SuperInstruction`.\n\nHow does this differ from executing the wasm instructions linearly with the WACS VM?\n- No OpStack manipulation\n- Values are passed directly without casting or boxing\n- The CLR's implementation can use hardware more effectively (use registers instead of heap memory)\n- Avoids instruction fetching and dispatch\n\nThroughput win is roughly 20–25% on compute-bound microbenchmarks (see the\n[Wacs.Console](#running-wacsconsole) section below for CoreMark numbers).\nGains are situational: linking instructions into a tree can't always be\ndetermined across block boundaries, so WASM code heavy on function calls\nor branches sees less benefit — the rewriter passes those sequences\nthrough unaltered.\n\n### Switch Runtime (source-generated dispatcher)\n\nAn alternative interpreter backed by a source-generated monolithic `switch`\nover an annotated bytecode stream. Immediates are pre-decoded at instantiation\n(no LEB128 at runtime), branch targets are resolved to absolute stream offsets,\nand every reachable function is compiled eagerly when `UseSwitchRuntime` is\nset before `InstantiateModule`. AOT-safe — no `Reflection.Emit`, no\n`DynamicMethod`, build-time source generation only.\n\n**Opt-in at the API level:**\n\n```csharp\nvar runtime = new WasmRuntime();\nruntime.UseSwitchRuntime = true;                                   // route wasm→wasm dispatch through the switch runtime\nruntime.ExecContext.Attributes.UseSwitchSuperInstructions = true;  // (optional) run the bytecode-stream super-instruction fuser\n\nvar modInst = runtime.InstantiateModule(module);                   // must happen AFTER UseSwitchRuntime is set\n```\n\nSee the [Running Wacs.Console](#running-wacsconsole) section below for all\nCLI combinations across the polymorphic, super-instruction, switch, and\nAOT paths, plus benchmark numbers.\n\nArchitectural details live in\n[`Wacs.Core/Compilation/SWITCH_RUNTIME.md`](Wacs.Core/Compilation/SWITCH_RUNTIME.md).\nThe polymorphic runtime remains the canonical path; the switch runtime is\na parallel back-end on top of the same parse/validate/link pipeline.\n\n### AOT Transpiler (`WACS.Transpiler`)\nSuper-instruction threading squeezes more out of the interpreter, but each WASM instruction still goes through a dispatch\nlayer. The `WACS.Transpiler` package takes the next step: it *compiles* the module into a real .NET assembly at\nahead-of-time, producing native CLR methods the JIT can optimize like any other managed code.\n\n**Architecture**\n\n- **IL emission.** For every local WASM function, the transpiler walks the parsed instruction stream and emits CIL\n  directly into a `TypeBuilder`, so the JIT sees ordinary static methods — no interpreter, no OpStack, no value boxing.\n- **Typed CLR shapes.** Module exports/imports surface as generated C# interfaces with WASM-qualified names\n  (`long FacSsa(long)`), and `ref`/GC types are emitted as native CLR classes with typed fields rather than boxed\n  `Value[]` wrappers.\n- **Dual SIMD paths.** `v128` ops have two implementations — a spec-compliant scalar reference path (`--simd scalar`,\n  the default) and a `System.Runtime.Intrinsics`-backed path (`--simd intrinsics`). A third mode (`--simd interpreter`)\n  falls back to the scalar SIMD in `Wacs.Core`.\n- **Transpile-time validation.** A `CilValidator` verifies stack balance, typing, and branch targets *as IL is emitted*,\n  so any invalid module trips at transpile time rather than as a runtime `InvalidProgramException`.\n- **Mixed-mode execution.** Transpilation is opportunistic: any function the transpiler declines (e.g. very large bodies\n  under `--max-fn-size`) falls back to the Wacs.Core interpreter for that function only, so the module still runs.\n- **CLI + library.** The `WACS.Transpiler` NuGet package installs the\n  `wasm-transpile` dotnet global tool for one-shot `.wasm → .dll` builds.\n  The programmatic surface ships separately as\n  [`WACS.Transpiler.Lib`](https://www.nuget.org/packages/WACS.Transpiler.Lib)\n  (v0.2) — reference it to drive transpilation and loading from inside\n  a host. The library also includes `TranspiledModuleLoader` for\n  seamless dynamic loading of saved `.dll`s without `Reflection.Emit`.\n  See [`Wacs.Transpiler/README.md`](Wacs.Transpiler/README.md) for the\n  full flag surface.\n\nThe transpiler is spec-equivalent to the interpreter on the WebAssembly 3.0 test suite (473/473), verified on macOS ARM64 and Linux x64.\n\n### Expected Runtime Performance\n\nMeasured CoreMark throughput on a MacBook Pro M3 Max, .NET 8. The\nnative baseline is the EEMBC CoreMark C source built with\n`clang -O3` and run directly on the CPU (single-core, 600 000\niterations, three runs within 0.2% of each other). WACS numbers come\nfrom running `Wacs.Console/Data/coremark.wasm` — the same C source,\ncompiled to WASM with `clang -O3` via emscripten — through each mode.\n\n| Mode | CoreMark iter/s | % of native |\n|---|---:|---:|\n| WACS polymorphic (default) | 274 | 0.79% |\n| WACS `--super` | 337 | 0.98% |\n| WACS `--switch` | 358 | 1.04% |\n| WACS `--switch --super` | 385 | 1.12% |\n| WACS `-t` / `--aot` (Reflection.Emit) | 17 552 | **50.9%** |\n| `wasm-transpile` pre-compiled `.dll` loaded via `TranspiledModuleLoader` | 17 552 | **50.9%** |\n| Native `clang -O3` (same C source, no wasm) | 34 488 | 100% |\n\nBallpark comparison to other WASM runtimes on the same workload (not\nmeasured on this machine — pulled from published numbers; treat as\npositioning, not apples-to-apples):\n\n- **WACS AOT (`-t`) ≈ WAMR \"fast JIT\" / Wasmer.** AOT-class wasm\n  runtimes typically land at 50–70% of native C on CoreMark; WACS's\n  51% is in that band.\n- **WACS interpreter modes (274–385 iter/s) are slower than Wasm3**\n  (typically 4–6 k iter/s on M-series). Wasm3 is heavily tuned for\n  CoreMark-like tight loops; WACS's interpreter prioritises Unity\n  AOT compatibility and WASM 3.0 spec completeness over interpreter\n  micro-benchmarks. Think \"Python-for-WASM\" speed rather than\n  \"Wasm3-class interpreter.\"\n- **All five modes are C#-on-CLR**, so speedups over interpreted\n  Python or IronPython for equivalent logic look larger than the\n  CoreMark-vs-C ratios above would suggest.\n\n**Choosing a mode:**\n\n- **Can you JIT?** (Desktop, server, `dotnet run`, Godot Mono) — use\n  `-t` / `--aot`. It's ~60× the interpreter and within ~2× of native\n  C speed.\n- **AOT-only target?** (Unity IL2CPP, `PublishAot`, iOS, full-AOT\n  Mono) — pre-compile the `.wasm → .dll` on a JIT host with\n  [`wasm-transpile`](Wacs.Transpiler/README.md), ship the `.dll`,\n  load it at runtime with `WACS.Transpiler.Lib`'s\n  `TranspiledModuleLoader`. Same AOT speed, no `Reflection.Emit`\n  dependency at runtime.\n- **Locked-down / policy-reviewed target, can't ship a pre-compiled\n  `.dll`?** — use `--switch --super`. Build-time source-gen +\n  `System.Runtime.CompilerServices.Unsafe` intrinsics only, no\n  runtime codegen, no `unsafe` keyword blocks. ~1.4× over the\n  polymorphic baseline.\n- **Maximum conservatism?** — default polymorphic, or `--super` for a\n  small safe boost. Pure managed, no source-gen, no `Unsafe` usage.\n  Fine for cold-path scripting, UGC validation, or logic that doesn't\n  dominate a frame budget — but don't put it in a hot inner loop.\n\n### Running `Wacs.Console`\n\n`Wacs.Console` is the reference host — it wires WASI, parses argv, and\ndrives execution through any of the available back-ends. All examples\nassume the repo is checked out and you're running `dotnet` from the\nrepo root.\n\n```bash\n# Default (polymorphic interpreter)\ndotnet run --project Wacs.Console -c Release -- Wacs.Console/Data/coremark.wasm\n\n# Polymorphic + super-instruction rewriter\ndotnet run --project Wacs.Console -c Release -- --super Wacs.Console/Data/coremark.wasm\n\n# Source-generated switch runtime\ndotnet run --project Wacs.Console -c Release -- --switch Wacs.Console/Data/coremark.wasm\n\n# Switch runtime + bytecode-stream super-instruction fuser\ndotnet run --project Wacs.Console -c Release -- --switch --super Wacs.Console/Data/coremark.wasm\n\n# AOT transpile to .NET IL and run through the JITted code (-t alias: --aot)\ndotnet run --project Wacs.Console -c Release -- -t Wacs.Console/Data/coremark.wasm\n\n# AOT with hardware SIMD intrinsics + persist the .dll\ndotnet run --project Wacs.Console -c Release -- -t --aot_simd intrinsics --aot_save out.dll Wacs.Console/Data/coremark.wasm\n```\n\nInvoking a specific export with arguments (applies across every mode):\n\n```bash\ndotnet run --project Wacs.Console -c Release -- -i fib Wacs.Bench/fib.wasm 10\n```\n\n**Flag cheatsheet:**\n\n| Flag | Effect |\n|---|---|\n| *(none)* | Polymorphic virtual-dispatch interpreter. Baseline, canonical. |\n| `--super` | Enable super-instruction fusion on whichever runtime ends up executing — the polymorphic block-level rewriter, and (when paired with `--switch`) the switch runtime's bytecode-stream fuser. |\n| `--switch` | Source-generated monolithic-switch interpreter. Build-time code generation (Roslyn) + `System.Runtime.CompilerServices.Unsafe` intrinsics — see note below. |\n| `-t`, `--transpiler`, `--aot` | AOT transpile the **WASM module** to .NET IL and run the generated code. Requires `Reflection.Emit` at runtime — see note below. |\n| `--aot_simd {scalar,intrinsics,interpreter}` | SIMD strategy for the AOT path. |\n| `--aot_save \u003cpath\u003e` | Persist the transpiled assembly to disk. |\n| `--aot_no_tail_calls` | Drop the CIL `tail.` prefix (debugging only). |\n| `--aot_max_fn_size N` | Skip functions larger than N instructions in the transpile pass. |\n| `--aot_data_storage {compressed,raw,static}` | How data segments are embedded in the saved assembly. |\n\n**Sampled CoreMark performance** (M3 Max, .NET 8, `Wacs.Console/Data/coremark.wasm`, default 6000 iterations; single run each):\n\n| Mode | CoreMark (iter/s) | Relative |\n|---|---:|---:|\n| polymorphic | 274 | 1.00× |\n| `--super` | 337 | 1.23× |\n| `--switch` | 358 | 1.31× |\n| `--switch --super` | 385 | 1.40× |\n| `-t` (AOT) | **17 552** | **64×** |\n\nThe AOT path emits ordinary .NET methods that the CLR JIT optimizes as\nnative code, so the ~64× jump over the fastest interpreter mode is the\ngap between \"dispatch + pop/push per op\" and \"inline register arithmetic.\"\nExpect similar ratios on any compute-bound workload; IO-bound / WASI-heavy\nworkloads see a smaller lift because WASI calls still bridge back to the\ninterpreter's host-function machinery.\n\n\u003e **\"AOT\" here means *ahead-of-time compilation of the WASM module*, not\n\u003e that the WACS runtime itself is AOT-safe.** `-t` / `--aot` uses\n\u003e `System.Reflection.Emit` at runtime to synthesize a dynamic .NET\n\u003e assembly containing the transpiled module. That's incompatible with\n\u003e environments that disable dynamic code: **Unity IL2CPP, .NET Native\n\u003e AOT (`PublishAot=true`), iOS, Mono AOT-only builds,** etc. On those\n\u003e platforms use one of the three interpreter modes instead — all of\n\u003e them (including the source-generated `--switch` runtime) are fully\n\u003e AOT-compatible. If you need native-class speed in an IL2CPP target,\n\u003e pre-compile the `.wasm → .dll` on a JIT-capable host with\n\u003e [`wasm-transpile`](Wacs.Transpiler/README.md) and ship the resulting\n\u003e assembly — the saved `.dll` runs without `Reflection.Emit` via\n\u003e `WACS.Transpiler.Lib`'s `TranspiledModuleLoader`.\n\n\u003e **`--switch` notes for restrictive targets.** The switch runtime\n\u003e is AOT-compatible — it uses **no runtime codegen** — but two\n\u003e implementation choices are worth flagging in case your environment\n\u003e forbids them:\n\u003e\n\u003e 1. **Build-time source generation (Roslyn).** The dispatcher method\n\u003e    is emitted by a Roslyn incremental source generator\n\u003e    (`Wacs.Compilation`) during the build of `Wacs.Core`. The shipped\n\u003e    `.dll` contains ordinary IL — there's no runtime Roslyn, no\n\u003e    dynamic code. If your build system runs Roslyn-based compilation\n\u003e    (the standard SDK path does), you're fine. If you consume WACS\n\u003e    as a pre-built NuGet package, the generation already happened\n\u003e    upstream and the binary is self-contained.\n\u003e 2. **`System.Runtime.CompilerServices.Unsafe` intrinsics.** The hot\n\u003e    loop uses `Unsafe.Add` / `Unsafe.ReadUnaligned` for inline array\n\u003e    indexing and LEB-free immediate reads — not `unsafe` pointer\n\u003e    blocks, not `fixed` statements. `System.Runtime.CompilerServices.\n\u003e    Unsafe` is part of the BCL on every .NET 8+ target (including\n\u003e    IL2CPP, PublishAot, iOS, trimmed assemblies). If your policy\n\u003e    reviews flag the keyword name, note that these are JIT/AOT\n\u003e    intrinsics, same family as `MemoryMarshal` — no raw pointer math,\n\u003e    no unverifiable IL.\n\u003e\n\u003e Both the polymorphic (default) and the polymorphic-super (`--super`)\n\u003e modes are pure managed code with no source-gen and no Unsafe usage —\n\u003e pick those if you need the most conservative surface. Expect ~25–40%\n\u003e lower throughput vs `--switch --super` on compute-bound workloads.\n\n## WebAssembly Text Format (WAT / WAST)\n\nWACS ships a pure-C# reader and writer for the WebAssembly text format.\nNo `wabt` / `wast2json` toolchain is required — `.wat` modules and\n`.wast` spec scripts feed directly into the same `Module` and runtime\npipeline the binary parser uses.\n\n### What's supported\n\n- **`.wat` modules.** The full text grammar of WebAssembly 3.0,\n  including every enabled proposal (GC structs / arrays / sub / rec\n  groups, typed function references, tail-call, exception handling,\n  SIMD, relaxed SIMD, multi-memory, memory64, threads/atomics,\n  annotations). Abbreviations (inline imports/exports, implicit\n  typeuse, folded instructions, anonymous blocks) are desugared into\n  the same `Module` shape the binary parser produces.\n- **`.wast` scripts.** `module`, `register`, `invoke`, `get`, and every\n  `assert_*` form — including `(module binary …)` and\n  `(module quote …)` — producing a `ScriptCommand[]` aligned with the\n  existing spec-runner command shape.\n- **Writer.** `TextModuleWriter.Write(module)` emits canonical,\n  parser-friendly WAT that round-trips back through the text parser\n  and produces a structurally equivalent `Module`.\n- **AOT-safe.** No runtime `Reflection.Emit`. Reflection over\n  `[OpCode(\"mnemonic\")]` attributes happens once at static-ctor time\n  to build the mnemonic→ByteCode lookup; everything else is plain\n  managed code. `PublishAot=true` builds continue to pass.\n\n### How it works\n\nThe text pipeline lives under `Wacs.Core.Text`:\n\n- **`Lexer` / `Token` / `SExpr` / `SExprParser`** — a WAT-specific\n  tokenizer (line / block comments, string escapes, annotations,\n  quoted identifiers with full `\\XX` / `\\u{…}` UTF-8 decoding) feeding\n  a lightweight s-expression tree. No WASM semantics at this layer.\n- **`Mnemonics`** — a `FrozenDictionary\u003cstring, ByteCode\u003e` built once\n  by reflecting over the `[OpCode(...)]` attributes already present\n  on every real opcode enum field (`OpCode`, `GcCode`, `ExtCode`,\n  `SimdCode`, `AtomCode`). Parse and render share the same source of\n  truth, so a mnemonic added in one direction is automatically\n  visible in the other.\n- **`TextModuleParser`** — two-pass section driver. Pass 1 pre-declares\n  names and pre-populates the type section (including rec-group\n  flattening for GC); pass 2 resolves `$name` references, synthesizes\n  inline typeuses with rec-isolated dedup, and produces the same\n  `Module` object the binary parser produces. Each instruction's\n  text-specific immediate decoding lives in a per-class `ParseText`\n  hook co-located with the binary `Parse` override — no parallel\n  hierarchy of text decoders.\n- **`TextScriptParser`** — `.wast` → `ScriptCommand[]`. Nested\n  `(module …)` forms inside assertions are re-parsed through the\n  same module parser; `(module binary …)` dispatches to the binary\n  parser on an in-memory stream.\n- **`TextModuleWriter`** — canonical round-trip emitter. Distinct from\n  the existing `ModuleRenderer.RenderWatToStream` (debug/display\n  variant with stack annotations and `(;id;)` comments), which is kept\n  for inspection use.\n\n### Using it\n\n**CLI (`Wacs.Console`).** Pass a `.wat` path exactly like a `.wasm`\npath:\n\n```bash\n# Run a text-format module through the polymorphic interpreter\ndotnet run --project Wacs.Console -c Release -- path/to/module.wat\n\n# Round-trip a binary module out as parser-friendly WAT\n# (writes module.wat next to module.wasm via TextModuleWriter)\ndotnet run --project Wacs.Console -c Release -- -r path/to/module.wasm\n\n# Re-run the emitted .wat through the interpreter to confirm round-trip\ndotnet run --project Wacs.Console -c Release -- path/to/module.wat\n```\n\nEvery back-end (`--super`, `--switch`, `-t` / `--aot`, …) works\nidentically on `.wat` input since the parser produces the same\n`Module` object the binary path does.\n\n**Library.** One entry point, drops into any existing WACS flow:\n\n```csharp\nusing Wacs.Core;\nusing Wacs.Core.Text;\n\nusing var fs = new FileStream(\"module.wat\", FileMode.Open);\nModule module = TextModuleParser.ParseWat(fs);\n\n// Or from a string:\nModule m2 = TextModuleParser.ParseWat(File.ReadAllText(\"module.wat\"));\n\n// Emit canonical, round-trip WAT:\nstring wat = TextModuleWriter.Write(module);\n```\n\n### Coverage\n\nThe `Wacs.Core.Test` xUnit project runs two gates across the full\nWebAssembly 3.0 spec suite (`Spec.Test/spec/test/core/*.wast`):\n\n- **`SpecWastSmokeTests`** — every `.wast` file in the spec suite\n  parses without error. **120 / 120 files. No skipped files.** The\n  `SkipList` is empty.\n- **`SpecWastEquivalenceTests`** — every module embedded in a spec\n  script parses through the text parser *and* through the binary\n  parser (via `(module binary …)` or `wast2json`-produced `.wasm`\n  sidecars), and the two `Module` objects are compared for structural\n  equivalence (types, imports, functions, tables, memories, globals,\n  exports, element segments, data segments, custom sections, plus\n  instruction streams including preserved `try_table` shapes and\n  rec-group layouts). **3457 / 3457 modules match.**\n\nThe text parser is held to the same wasm-3.0 bar as the runtime: GC,\ntyped function references, exception handling, tail-call, SIMD,\nrelaxed SIMD, multi-memory, memory64, threads/atomics, and\nannotations all parse to structurally identical `Module` objects\nregardless of which parser built them.\n\n## Roadmap\n\n- **WebAssembly Component Model**: implement the [component-model\n  proposal](https://github.com/WebAssembly/component-model) on top of\n  the existing WACS core runtime, including Preview 2 WASI wiring.\n\n## Sponsorship \u0026 Collaboration\n\nI built and maintain WACS as a solo developer. \n\nIf you find it useful, please consider supporting me through sponsorship or work opportunities. \nYour support can help me continue improving WACS to make WebAssembly accessible for everyone. \n\n[Sponsor me](https://github.com/sponsors/kelnishi) or [connect with me on LinkedIn](https://www.linkedin.com/in/kelnishi) if you're interested in collaborating!\n\n## License\n\nWACS is distributed under the [Apache 2.0 License](./LICENSE). This permissive license allows you to use WACS freely in both open and closed source projects.\n\n---\n\nI would love for you to get involved and contribute to WACS! Whether it's bug fixes, new features, or improvements to documentation, your help can make WACS better for everyone.\n\n**Star this project on GitHub if you find WACS helpful!** ⭐\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelnishi%2Fwacs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkelnishi%2Fwacs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelnishi%2Fwacs/lists"}