Wasm2IL 0.1.0-alpha.13
dotnet add package Wasm2IL --version 0.1.0-alpha.13
NuGet\Install-Package Wasm2IL -Version 0.1.0-alpha.13
<PackageReference Include="Wasm2IL" Version="0.1.0-alpha.13" />
<PackageVersion Include="Wasm2IL" Version="0.1.0-alpha.13" />
<PackageReference Include="Wasm2IL" />
paket add Wasm2IL --version 0.1.0-alpha.13
#r "nuget: Wasm2IL, 0.1.0-alpha.13"
#:package Wasm2IL@0.1.0-alpha.13
#addin nuget:?package=Wasm2IL&version=0.1.0-alpha.13&prerelease
#tool nuget:?package=Wasm2IL&version=0.1.0-alpha.13&prerelease
<div align="center">
wasm2il
A WebAssembly to .NET IL bytecode compiler
Seamlessly convert WASM modules into native .NET assemblies
Features • Quick Start • Installation • Usage • API Reference
</div>
What is wasm2il?
wasm2il bridges the gap between WebAssembly and the .NET ecosystem. It takes WebAssembly binary modules (.wasm files) and compiles them directly into .NET assemblies (.dll files), enabling code originally written in C, C++, Rust, or any language that compiles to WebAssembly to run natively on the .NET runtime.
Core Idea
Instead of interpreting WebAssembly bytecode at runtime, wasm2il performs ahead-of-time compilation by translating each WASM instruction into equivalent .NET IL opcodes. The generated assembly can then be JIT-compiled by the .NET runtime, benefiting from all of .NET's runtime optimizations.
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ C / C++ / │ │ │ │ │
│ Rust / etc. │ ───► │ .wasm │ ───► │ .NET Assembly │
│ │ │ │ │ (.dll) │
└─────────────────┘ └─────────────┘ └─────────────────┘
Source Code WebAssembly Native .NET
Example translation:
| WASM Instruction | .NET IL Equivalent |
|---|---|
i32.add |
IL.Add |
i32.load |
IL.Ldind_I4 (pointer dereference) |
call |
IL.Call to the appropriate method |
WASM linear memory is represented as a native byte* pointer, and function tables enable indirect calls for things like callbacks.
Features
| Feature | Description |
|---|---|
| Direct IL Generation | Compiles WASM directly to .NET IL using Mono.Cecil, producing standard .NET assemblies |
| WASM 1.0 Support | Handles most WASM32 1.0 instructions including arithmetic, memory, control flow, and type conversions |
| SIMD Operations | Vector128 support for WASM SIMD instructions |
| C# Interop | Import C# methods into WASM and export WASM functions for C# to call |
| Typed Wrappers | Use AsImplementation<T> to create clean, strongly-typed C# interfaces over WASM exports |
| Automatic Marshaling | Strings and Span<byte> are automatically copied to/from WASM heap memory |
| Memory Access | Direct heap access via spans and pointers for advanced scenarios |
| Optimization | Constant folding and peephole optimization for cleaner IL output |
Not supported Features
| Feature | Description |
|---|---|
| Relaxed SIMD | |
| Sign Extension | |
| Reference Types | |
| Memory64 | clang: -m |
| Wasm64 | |
| Wide Arithmetic | clang: -mwide-arithmetic |
⚠️ Status: Experimental
This project is an experiment and not production-ready.
It is capable of converting most instructions from WASM32 1.0 to IL. WASI (WebAssembly System Interface) is not implemented—you must provide your own C# implementations for system APIs via the import module mechanism.
Quick Start
using Wasm2IL;
// Load and convert WASM to .NET assembly
var transformer = new Transformer();
var asm = transformer.LoadWasmAssembly("mymodule.wasm", "MyModule");
// Call WASM functions directly
int result = (int)asm.Invoke("add", 5, 3);
// Or use typed interfaces for a cleaner API
public interface IMyModule
{
[Wasm("add")]
int Add(int a, int b);
}
var module = asm.AsImplementation<IMyModule>();
Console.WriteLine(module.Add(5, 3)); // Output: 8
Installation
Prerequisites
- .NET 9.0 SDK or later
- wabt (WebAssembly Binary Toolkit) - provides
wat2wasmfor compiling.watfiles - (Optional) clang with WebAssembly target support - only needed for building test WASM files from C
Installing Prerequisites
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y wabt clang lld
macOS (Homebrew):
brew install wabt llvm
Windows: Download wabt binaries from the releases page.
Building
# Clone the repository
git clone https://github.com/rolfrm/Wasm2IL.git
cd Wasm2IL
# Restore dependencies
dotnet restore
# Build the solution
dotnet build
Running Tests
# Run unit tests
dotnet run --project Wasm2IL.UnitTests/Wasm2IL.UnitTests.csproj
Some tests require additional test WASM files. To build callback_test.wasm:
# Download libc.wasm dependency
curl -L -o TestCCode/libc.wasm https://github.com/rolfrm/minlibc/releases/download/prototype1/libc.wasm
# Build test wasm file
cd TestCCode
make callback_test.wasm
Using as a Library
Add the NuGet package to your project:
dotnet add package Wasm2IL --version 0.1.0-alpha.10
Or add it to your .csproj:
<PackageReference Include="Wasm2IL" Version="0.1.0-alpha.10" />
Complete Example: Wrapping a C Library
This section demonstrates how to compile C code to WASM, convert it to a .NET DLL, and use it from C#.
Step 1: Write C Code with Exports and Imports
Create a file mathlib.c:
// Define exported functions with visibility attribute
__attribute__((visibility("default")))
int add(int a, int b) {
return a + b;
}
__attribute__((visibility("default")))
int multiply(int a, int b) {
return a * b;
}
// Import a function from an external module ("env")
// This will be implemented in C#
__attribute__((import_module("env"), import_name("log_value")))
void log_value(int value);
__attribute__((visibility("default")))
int compute_and_log(int a, int b) {
int result = add(a, b) * multiply(a, b);
log_value(result); // Calls into C#
return result;
}
For functions that need filesystem or other system APIs:
// Import filesystem functions from "fs" module
__attribute__((import_module("fs"), import_name("open")))
int fs_open(const char *path, int flags);
__attribute__((import_module("fs"), import_name("read")))
int fs_read(int fd, char *buf, int count);
__attribute__((import_module("fs"), import_name("close")))
int fs_close(int fd);
__attribute__((visibility("default")))
int read_file_size(const char *path) {
int fd = fs_open(path, 0);
if (fd < 0) return -1;
char buf[4096];
int total = 0;
int n;
while ((n = fs_read(fd, buf, sizeof(buf))) > 0) {
total += n;
}
fs_close(fd);
return total;
}
Step 2: Compile C to WebAssembly
Use clang to compile your C code to WASM:
# Basic compilation
clang --target=wasm32 -nostdlib -Wl,--no-entry -Wl,--export-dynamic \
-o mathlib.wasm mathlib.c
# With optimizations
clang --target=wasm32 -O2 -nostdlib -Wl,--no-entry -Wl,--export-dynamic \
-o mathlib.wasm mathlib.c
Key compiler flags:
--target=wasm32- Target WebAssembly 32-bit-nostdlib- Don't link standard library (or link a WASM-compatible one)-Wl,--no-entry- No_startentry point required-Wl,--export-dynamic- Export functions marked with visibility("default")
If you need libc functions (malloc, strlen, etc.), link against a WASM libc:
# Download a minimal libc
curl -L -o libc.wasm https://github.com/rolfrm/minlibc/releases/download/prototype1/libc.wasm
# Compile with libc
clang --target=wasm32 -O2 -Wl,--no-entry -Wl,--export-dynamic \
libc.wasm -o mathlib.wasm mathlib.c
Step 3: Implement Imported APIs in C#
Create a static class with methods matching the imported function signatures:
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Implementation for "env" module imports
public static class EnvModule
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void log_value(int value)
{
Console.WriteLine($"[WASM] Value: {value}");
}
}
// Implementation for "fs" module imports
public static class FsModule
{
private static readonly Dictionary<int, FileStream> _handles = new();
private static int _nextHandle = 3; // 0,1,2 reserved for stdin/out/err
public static int open(CString path, int flags)
{
try
{
var pathStr = path.ToString();
var mode = flags == 0 ? FileMode.Open : FileMode.OpenOrCreate;
var access = flags == 0 ? FileAccess.Read : FileAccess.ReadWrite;
var stream = new FileStream(pathStr, mode, access);
var handle = _nextHandle++;
_handles[handle] = stream;
return handle;
}
catch
{
return -1;
}
}
public static unsafe int read(int fd, byte* buf, int count)
{
if (!_handles.TryGetValue(fd, out var stream))
return -1;
var span = new Span<byte>(buf, count);
return stream.Read(span);
}
public static int close(int fd)
{
if (!_handles.TryGetValue(fd, out var stream))
return -1;
stream.Close();
_handles.Remove(fd);
return 0;
}
}
The CString type is provided by Wasm2IL for null-terminated string parameters.
Step 4: Load WASM and Register Import Modules
using Wasm2IL;
// Create transformer and register import implementations
var transformer = new Transformer();
transformer.LoadImportModule("env", typeof(EnvModule));
transformer.LoadImportModule("fs", typeof(FsModule));
// Load and convert WASM to .NET assembly
var asm = transformer.LoadWasmAssembly(
"mathlib.wasm", // Input WASM file
"MathLib" // Assembly name
);
// Optionally save the DLL for later use
transformer.Transform(
File.OpenRead("mathlib.wasm"),
"MathLib",
File.Create("MathLib.dll")
);
You can also load a pre-compiled DLL directly without re-transforming:
// Load an existing DLL created by wasm2il
var asm = new WasmAssembly(Assembly.LoadFile("MathLib.dll"));
Step 5: Call WASM Functions Directly
You can invoke WASM functions using reflection or the helper methods:
// Using the Invoke helper (handles marshaling automatically)
int sum = (int)asm.Invoke("add", 5, 3); // Returns 8
int product = (int)asm.Invoke("multiply", 4, 7); // Returns 28
int result = (int)asm.Invoke("compute_and_log", 2, 3); // Logs and returns 30
// Using reflection directly
var moduleType = asm.Assembly.ExportedTypes.First();
var addMethod = moduleType.GetMethod("add");
int sum2 = (int)addMethod.Invoke(null, new object[] { 10, 20 });
Step 6: Wrap as a Typed C# Interface
For a cleaner API, define an interface and use AsImplementation<T>:
using Wasm2IL;
// Define your API interface
public interface IMathLib
{
[Wasm("add")]
int Add(int a, int b);
[Wasm("multiply")]
int Multiply(int a, int b);
[Wasm("compute_and_log")]
int ComputeAndLog(int a, int b);
}
// For APIs that work with strings or byte arrays
public interface IFileLib
{
// String parameters are automatically marshaled to WASM heap
[Wasm("read_file_size")]
int ReadFileSize(string path);
}
// Create the typed wrapper
var mathLib = asm.AsImplementation<IMathLib>();
var fileLib = asm.AsImplementation<IFileLib>();
// Use like normal C# code
int sum = mathLib.Add(10, 20);
int product = mathLib.Multiply(5, 6);
int size = fileLib.ReadFileSize("/path/to/file.txt");
Supported parameter types for AsImplementation<T>:
- Primitive types (
int,long,float,double) string- Automatically copied to WASM heap, freed after callSpan<byte>- Copied to heap, changes copied back after callReadOnlySpan<byte>- Copied to heap (read-only)- Pointer types (
byte*,int*) - Direct memory access
Step 7: Working with WASM Memory
For advanced scenarios, you can directly access and manipulate WASM linear memory.
Malloc and Free require your WASM module to export malloc and free functions.
This is typically done by linking a libc (e.g., the libc.wasm shown in Step 2).
// Allocate memory in WASM heap (requires exported malloc)
int ptr = asm.Malloc(100);
// Get a span view of the heap
Span<byte> heap = asm.GetHeap();
Span<byte> slice = asm.GetHeapSpan(ptr, 100);
// Write data to heap
slice[0] = 0x42;
slice[1] = 0x43;
// Copy a string to WASM heap
int strPtr = asm.StringToHeap("Hello, WASM!");
// Read a string from WASM heap
string str = asm.GetHeapString(strPtr);
// Read a struct from heap
var value = asm.GetHeapObject<int>(ptr);
// Free allocated memory
asm.Free(ptr);
asm.Free(strPtr);
Complete Working Example
using Wasm2IL;
public interface IMyWasm
{
[Wasm("add")]
int Add(int a, int b);
[Wasm] // Uses method name as export name
int multiply(int a, int b);
}
public static class EnvModule
{
public static void log_value(int value) => Console.WriteLine($"Value: {value}");
}
class Program
{
static void Main()
{
// Setup
var transformer = new Transformer();
transformer.LoadImportModule("env", typeof(EnvModule));
// Load WASM
var asm = transformer.LoadWasmAssembly("mathlib.wasm", "MathLib");
// Get typed interface
var math = asm.AsImplementation<IMyWasm>();
// Use it
Console.WriteLine($"5 + 3 = {math.Add(5, 3)}");
Console.WriteLine($"4 * 7 = {math.multiply(4, 7)}");
}
}
Contributing
Contributions are welcome! Feel free to:
- Report bugs by opening an issue
- Suggest features or improvements
- Submit pull requests
License
This project is licensed under the MIT License - see the LICENSE file for details.
| Product | Versions 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. |
-
net9.0
- Mono.Cecil (>= 0.11.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-alpha.13 | 140 | 12/21/2025 |
| 0.1.0-alpha.12 | 189 | 12/19/2025 |
| 0.1.0-alpha.11 | 172 | 12/14/2025 |
| 0.1.0-alpha.10 | 153 | 12/4/2025 |
| 0.1.0-alpha.9 | 149 | 12/4/2025 |
| 0.1.0-alpha.8 | 79 | 11/29/2025 |
| 0.1.0-alpha.7 | 140 | 11/27/2025 |
| 0.1.0-alpha.6 | 144 | 11/24/2025 |
| 0.1.0-alpha.5 | 146 | 11/24/2025 |
| 0.1.0-alpha.3 | 218 | 11/21/2025 |
| 0.1.0-alpha.2 | 71 | 9/13/2025 |