wss is a transpiler from WebAssembly to CSS, plus an HTML/CSS runtime.
The result runs entirely in pure HTML/CSS; optional JavaScript add-ons are available for quality-of-life features.
Several interactive outputs are checked into examples/ and mirrored online:
- Rust toolchain
- A compiler that can emit WebAssembly, such as
clang - A recent Chromium-based browser for running the generated HTML
- Prepare your WebAssembly file:
// source.c
extern int putchar(int c);
extern int getchar();
int _start() {
putchar('H');
putchar('e');
putchar('l');
putchar('l');
putchar('o');
putchar('\n');
return 0;
}Use any WebAssembly-capable compiler. Export _start as the entry point and (optionally) import putchar and getchar for console I/O.
clang \
--target=wasm32 -Os \
-nostdlib \
-mno-simd128 \
-fno-exceptions \
-mno-bulk-memory \
-mno-multivalue \
-Wfloat-conversion \
-Wl,--gc-sections \
-Wl,--no-stack-first \
-Wl,--allow-undefined \
-Wl,-z,stack-size=512 \
-Wl,--compress-relocations \
-Wl,--strip-all \
-Wl,--global-base=4 \
-Wl,--export=_start \
-o a.wasm source.c- Run the transpiler:
cargo run --release -- a.wasm -o a.htmlOptions:
-o, --output <PATH>: output HTML path. Default:a.html--memory-bytes <N>: runtime linear-memory cap in bytes. Default:1024--stack-slots <N>: runtime callstack cap in 16-bit slots. Default:256--js-clock: enable JS-based clock stepping. This is the default.--no-js-clock: disable JS-based clock stepping.--js-coprocessor: enable the JS coprocessor fordiv/remand bitwise builtins. Conflicts with--no-js-clock.--js-clock-debugger: enable the JS debugger popup. Conflicts with--no-js-clock.--max-phys-regs <N>: register-allocation cap, including reservedr0-r3. Default:256
- The blackbox runner rebuilds
target/release/wssbefore running whenWSS_BINis unset, so the suite uses the current source tree by default. - With no per-case override, blackbox cases default to JS clock enabled to match the CLI default. Cases can still opt out with
"js_clock": false.
- i32 arithmetic — add, sub, mul, div, rem, and, or, xor, shl, shr, rotl, rotr, clz, ctz, popcnt, eqz
- i32 comparisons — eq, ne, lt, gt, le, ge (signed and unsigned)
- i32 memory — load and store with 8-, 16-, and 32-bit widths (signed and unsigned loads)
- Control flow — block, loop, if/else, br, br_if, br_table, return, unreachable
- Function calls with TCO — call, call_indirect, return_call, return_call_indirect
- Locals and globals — get, set, tee
- Select — typed and untyped select
- Exception handling —
try,catch,catch_all,delegate,throw,rethrowon exception tags with either no payload or a singlei32payload. Dedicatedexc_flag/exc_tag/exc_payloadstate channels propagate through calls and returns; an uncaught exception in_starttraps with code-6(uncaught exception). Current gaps: multi-value or non-i32tag payloads,try_table,throw_ref,delegatedepth >0,rethrowdepth >0, andrethrowfromcatch_all.
- Partial i64 integer support
- The transpiler now accepts integer-only wasm signatures using
i32andi64, includingi64_startreturns and a working end-to-end path for basici64constants, direct-call/return plumbing, locals, and simple integer operations. - The lower8/runtime path is still incomplete for the full wasm
i64opcode set, so somei64operations may still fail at transpile time.
- The transpiler now accepts integer-only wasm signatures using
- Floats — f32/f64 and all float ops
- SIMD — v128 and vector ops
- Atomics — all atomic load/store/rmw
- memory.grow — impossible to implement without JS
- GC - No
- Threads - No
This project is inspired by x86CSS.