@gasm-compiler/core
v0.9.0
Published
Rust/Wasm Gasm compiler for WebAssembly to WGSL (WebGPU Shading Language)
Downloads
677
Maintainers
Readme
@gasm-compiler/core
Compile WebAssembly to WGSL for GPU Execution
Transform WebAssembly binaries into WGSL (WebGPU Shading Language) compute shaders. Write your GPU kernels in any language that compiles to WebAssembly (C, C++, Rust, Go, AssemblyScript, or hand-written WAT), then run them on the GPU via WebGPU.
Gasm v0.2 is the default through the Rust/Wasm compiler backend. Archived Gasm v0.1 behavior remains available through an explicit compatibility option.
Installation
npm install @gasm-compiler/coreThe compiler core is distributed as a Rust-generated WebAssembly artifact. No TypeScript runtime dependency is required by consumers.
Quick Start
import { compile } from "@gasm-compiler/core";
// Load a WebAssembly binary (.wasm file)
const wasmBytes = new Uint8Array(await fetch("shader.wasm").then(r => r.arrayBuffer()));
// Compile to WGSL
const wgslCode = compile(wasmBytes);
// Use with WebGPU
const shaderModule = device.createShaderModule({ code: wgslCode });Features
- 120+ WebAssembly instructions mapped to WGSL equivalents
- Full L0 conformance (i32, i64, f32, f64 scalar operations)
- Control flow restructuring — WebAssembly's unstructured branches become WGSL loops/blocks
- Sub-word memory access — i8/i16 loads/stores lowered to word-aligned operations
- Gasm built-in globals for GPU thread identification
- Optional math extension for direct WGSL built-in math calls
- Works in Node.js, Deno, and browsers
Usage
Specification Versions
Gasm v0.2 is the default:
const wgsl = compile(wasmBytes);The default enables strict v0.2 validation and lowering. Existing consumers can keep the archived v0.1 compiler behavior explicitly:
const wgsl = compile(wasmBytes, { specVersion: "0.1" });Compile a v0.2 artifact and metadata sidecar through the Rust/Wasm compiler:
import {
compileToArtifact,
validateGasmMetadataSchema,
} from "@gasm-compiler/core";
const result = compileToArtifact(wasmBytes);
if (!result.ok) {
throw new Error(
`${result.diagnostics.errors[0]?.code}: ` +
`${result.diagnostics.errors[0]?.message}`,
);
}
const schema = validateGasmMetadataSchema(result.metadata);
if (!schema.valid) throw new Error(schema.errors.join(", "));All Gasm compiler semantics, including explicit v0.1 compatibility mode, are
implemented in packages/core-rs and distributed through its Wasm artifact.
The TypeScript package code is limited to host integration, artifact metadata,
source-map comments, preparation helpers, and execution APIs. Deno remains the
runtime for the CLI and the live core/CLI test suites, including headless
WebGPU execution.
The CLI also defaults to v0.2:
gasm-compiler compile input.wasm \
--output output.wgsl \
--metadata output.gasm.jsonUse --spec-version 0.1 when compiling archived v0.1 inputs.
Migrating from 0.5.x
- The Rust/Wasm compiler is now the only compiler backend. The
GASM_CORE_BACKENDenvironment variable no longer changes compilation. - Browser backend selection APIs have been removed:
setBrowserCompilerBackend(),getBrowserCompilerBackend(), andBrowserCompilerBackend. - TypeScript/Rust comparison APIs and their result types have been removed:
compileWithBackendComparison(),compileWithBackendComparisonAsync(),BackendComparisonResult, andBackendMismatchDetails. - Remove backend-selection and comparison UI from integrator applications and
call
compile(),compileWithDiagnostics(), orcompileWithDiagnosticsAsync()directly. - Consumers no longer need to install TypeScript as a peer dependency of
@gasm-compiler/core.
Migrating from 0.3.x
- Compilation now defaults to strict Gasm v0.2 validation.
- Pass
{ specVersion: "0.1" }to retain archived v0.1 behavior. - Gasm v0.2 extensions must be declared in the module's
gasm.extensionscustom section. - Math extension scalar imports use explicit type suffixes such as
sin_f32. Legacy unsuffixed imports remain accepted for compatibility.
Required extensions are declared by the module's gasm.extensions custom
section. The CLI does not inject extensions, so validation reflects the exact
module that will be distributed.
Integrator Workflow
Playgrounds, compiler backends, and other embedders can prepare transient Wasm without modifying their producer toolchain:
import {
compileWithRuntimeInfo,
prepareModule,
} from "@gasm-compiler/core/browser";
const prepared = prepareModule(wasmBytes, {
demotionPolicy: { f64: "allow", i64: "allow-lossy" },
});
if (prepared.errors.length > 0) {
throw new Error(prepared.errors[0].message);
}
const result = compileWithRuntimeInfo(prepared.wasmBytes, {
...prepared.compileOptions,
mode: "strict",
});
if (!result.ok) throw new Error(result.diagnostics.errors[0].message);prepareModule() defaults to integrator mode. It infers required extensions,
merges a stable gasm.extensions section, and returns binding and mutable-global
initialization data. Modules with undeclared mutable defined globals use the
v0.1 private-global compatibility path to avoid shared-storage races.
Use { mode: "strict" } to analyze a distribution artifact without changing
its bytes. Strict compilation through compile() and
compileWithDiagnostics() remains unchanged and never injects extensions.
compileWithRuntimeInfo() is also available from the main and browser entry
points. It combines preparation, compilation, binding metadata, dispatch
information, and mutable-global initialization into one result.
Basic Compilation
import { compile } from "@gasm-compiler/core";
const wgslCode = compile(wasmBytes);Compilation Options
import { compile } from "@gasm-compiler/core";
const wgslCode = compile(wasmBytes, {
workgroupSize: [128, 1, 1], // Default: [64, 1, 1]
minify: true, // Optimize and emit compact, single-line WGSL
mathExtension: true, // Enable gasm:math built-ins
});minify: true enables optimization, shortens internal identifiers and
literals, removes comments and formatting, and emits a single-line shader.
Exported entry-point names and metadata-visible resource names are preserved.
Minification cannot be combined with optimize: false, source mapping, or
compiler metadata.
Math Extension
Enable direct mapping of WebAssembly imports to WGSL built-in math functions:
const wgslCode = compile(wasmBytes, {
mathExtension: true, // Enable all levels (M0, M1, M2)
// or: mathExtension: "M0" // Core scalar functions only
// or: mathExtension: "M1" // Core + vector functions
// or: mathExtension: "M2" // All functions
});When enabled, WebAssembly imports like
(import "gasm" "sin_f32" (func ...)) compile to direct WGSL built-in calls
(sin(...)) with zero overhead. Unsuffixed scalar names such as sin remain
accepted as legacy aliases.
Math Levels:
- M0 (Core): sin, cos, sqrt, abs, min, max, clamp, floor, ceil, etc.
- M1 (Vector): length, dot, cross, normalize, reflect, refract, etc.
- M2 (Advanced): modf, frexp, ldexp, degrees, radians, bit manipulation
See also:
@gasm-compiler/math-referencefor CPU-side reference implementations of these math functions.
Error Handling
import { compile, isCompileError } from "@gasm-compiler/core";
try {
const wgslCode = compile(wasmBytes);
} catch (error) {
if (isCompileError(error)) {
console.error("Compile Error:", error.message);
if (error.location) {
console.error(` at line ${error.location.line}, column ${error.location.column}`);
}
} else {
throw error;
}
}Browser Usage
import { compile } from "@gasm-compiler/core/browser";
const wgslCode = compile(wasmBytes);The compiler has no required browser initialization step. For applications that want to fetch and cache the Wasm compiler ahead of time, the browser entry point also exposes an optional async path:
import { compileAsync, preloadCompiler } from "@gasm-compiler/core/browser";
await preloadCompiler();
const wgslCode = await compileAsync(wasmBytes);Using with WebGPU
import { compile } from "@gasm-compiler/core";
// 1. Compile Wasm → WGSL
const wgslCode = compile(wasmBytes, { workgroupSize: [256, 1, 1] });
// 2. Create shader module
const shaderModule = device.createShaderModule({ code: wgslCode });
// 3. Create compute pipeline
const pipeline = device.createComputePipeline({
layout: "auto",
compute: { module: shaderModule, entryPoint: "main" },
});
// 4. Dispatch
const commandEncoder = device.createCommandEncoder();
const pass = commandEncoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(numWorkgroups);
pass.end();
device.queue.submit([commandEncoder.finish()]);How It Works
The compiler transforms WebAssembly into WGSL through a multi-phase pipeline:
WebAssembly Binary (.wasm)
↓
Parse & Validate (Gasm restrictions)
↓
Convert to SSA form
↓
Restructure control flow (Relooper)
↓
Lower memory operations
↓
WGSL Code Generation
↓
WGSL Compute Shader (.wgsl)Gasm Restrictions
Your WebAssembly module must conform to the Gasm subset:
- Single memory — max 1 linear memory, exported as
"memory" - Single table — max 1 table (for indirect calls)
- Global imports only — no function or memory imports (except Gasm built-in globals)
- Structured control flow — all branches must be restructurable
Gasm Built-in Globals
Import GPU thread identifiers as WebAssembly globals from the "gasm" module:
| Import Name | Maps to WGSL | Type |
|-------------|--------------|------|
| global_invocation_id_x/y/z | @builtin(global_invocation_id) | u32 |
| local_invocation_id_x/y/z | @builtin(local_invocation_id) | u32 |
| workgroup_id_x/y/z | @builtin(workgroup_id) | u32 |
| num_workgroups_x/y/z | @builtin(num_workgroups) | u32 |
Example (WAT):
(module
(import "gasm" "global_invocation_id_x" (global $gid_x i32))
(memory (export "memory") 1)
(func (export "main")
;; Use $gid_x to index into memory per-thread
))Instruction Support
| Category | Examples | WGSL Output |
|----------|---------|-------------|
| Arithmetic | i32.add, f32.mul | a + b, a * b |
| Bitwise | i32.and, i32.shl | a & b, a << (b & 31u) |
| Comparisons | i32.lt_s, f32.eq | a < b, a == b |
| Memory | i32.load, i32.store8 | memory[addr/4u], bit-masking |
| Conversions | i32.trunc_f32_s | i32(trunc(a)) |
| Control Flow | block, loop, br_if | loop { ... }, if (c) { break; } |
| Variables | local.get, global.set | var_N, global_N = val |
API Reference
compile(source, options?)
function compile(source: Uint8Array, options?: CompileOptions): stringCompiles a WebAssembly binary to WGSL.
Parameters:
source— WebAssembly binary asUint8Arrayoptions— OptionalCompileOptions
Returns: WGSL shader code as a string
Throws: CompileError on validation or compilation failure
compileAsync(source, options?)
function compileAsync(source: Uint8Array, options?: CompileOptions): Promise<string>Async equivalent of compile. In browsers this can be paired with
preloadCompiler() to fetch and instantiate the compiler artifact before the
first compilation.
compileWithDiagnostics(source, options?)
function compileWithDiagnostics(
source: Uint8Array,
options?: CompileOptions,
): CompileWithDiagnosticsResultCompiles a WebAssembly binary and returns diagnostics instead of throwing.
compileWithDiagnosticsAsync(source, options?)
function compileWithDiagnosticsAsync(
source: Uint8Array,
options?: CompileOptions,
): Promise<CompileWithDiagnosticsResult>Async equivalent of compileWithDiagnostics.
preloadCompiler(options?)
function preloadCompiler(options?: { wasmUrl?: string | URL }): Promise<void>Optional performance hook for browser applications that want to load the
compiler before the first call to compileAsync.
CompileOptions
interface CompileOptions {
workgroupSize?: [number, number, number]; // Default: [64, 1, 1]
debug?: boolean; // Emit debug comments (default: false)
mathExtension?: boolean | "M0" | "M1" | "M2"; // Enable math built-ins
}CompileError
interface CompileError {
type: "CompileError";
message: string;
location?: { line: number; column: number };
context?: string;
}isCompileError(error)
function isCompileError(error: unknown): error is CompileErrorType guard for CompileError.
Related Packages
@gasm-compiler/cli— Command-line compiler for Wasm/AssemblyScript → WGSL@gasm-compiler/math-reference— CPU-side reference implementations of Gasm math functions
License
See LICENSE for details.
