watr

Fast WebAssembly Text Format (WAT) compiler for JavaScript/Node.js.

Supports finished (phase 5) + planned (phase 4) proposals, full spec syntax and passes the WebAssembly test suite.

Install

npm install watr

API

watr`...`

Compile and instantiate, returns exports.

import watr from 'watr'

// basic
const { add } = watr`(func (export "add") (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1)))`
add(2, 3) // 5

// auto-import JS functions
const { test } = watr`(func (export "test") (call \${console.log} (i32.const 42)))`
test() // 42

// interpolate numbers
watr`(global (export "pi") f64 (f64.const \${Math.PI}))`       // f64
watr`(func (export "f") (result i64) (i64.const \${123n}))`    // i64 BigInt

// interpolate config
watr`(memory (export "mem") \${pages})`                        // memory size
watr`(func (export "f") (call \${0}))`                         // indices
watr`(func \${id} ...)`                                        // identifiers

// interpolate binary data
watr`(memory (export "mem") (data \${new Uint8Array([1,2,3])}))`
watr`(data (i32.const 0) \${[1, 2, 3, 4]})`

// interpolate code strings
const ops = '(i32.add (i32.const 1) (i32.const 2))'
watr`(func (export "f") (result i32) \${ops})`

// string argument
watr('(func (export "f") (result i32) (i32.const 42))')

compile(source)

Compile to binary. Accepts a string, AST, or template literal.

import { compile } from 'watr'

compile(`(func (export "f"))`)                       // string
compile(['func', ['export', '"f"']])                 // AST
compile`(func (export "f") (f64.const \${Math.PI}))` // template
// Uint8Array

parse(source, options?)

Parse to AST.

import { parse } from 'watr'

parse('(i32.add (i32.const 1) (i32.const 2))')
// ['i32.add', ['i32.const', 1], ['i32.const', 2]]

// options: comments, annotations
parse('(func ;; note\n)', { comments: true })
// ['func', [';', ' note']]

print(tree, options?)

Format WAT code or AST to string. Accepts a string or AST array.

import { print } from 'watr'

// prettify string
print('(module(func(export "add")(param i32 i32)(result i32)local.get 0 local.get 1 i32.add))')
// (module
//   (func (export "add") (param i32 i32) (result i32)
//     local.get 0
//     local.get 1
//     i32.add))

// print AST
print(['module', ['func', ['export', '"f"'], ['result', 'i32'], ['i32.const', 42]]])
// (module
//   (func (export "f") (result i32)
//     (i32.const 42)))

// minify
print('(module\n  (func (result i32)\n    (i32.const 42)))', { indent: false, newline: false })
// (module (func (result i32) (i32.const 42)))

// options: indent (default '  '), newline (default '\n'), comments (default true)

Syntax

;; folded
(i32.add (i32.const 1) (i32.const 2))

;; flat
i32.const 1
i32.const 2
i32.add

;; abbreviations
(func (export "f") ...)           ;; inline export
(func (import "m" "n") ...)       ;; inline import
(memory 1 (data "hello"))         ;; inline data

;; numbers
42  0x2a  0b101010                ;; integers
3.14  6.02e23  0x1.8p+1           ;; floats
inf  -inf  nan  nan:0x123         ;; special
1_000_000                         ;; underscores

;; comments
(; block ;)

Features

BigInt / i64

watr`(func (export "f") (result i64) (i64.const ${9007199254740993n}))`
watr`(func (export "g") (param i64) (result i64) (i64.mul (local.get 0) (i64.const 2n)))`

Multi-value

(func (result i32 i32) (i32.const 1) (i32.const 2))
(func (param i32 i32) (result i32 i32) (local.get 1) (local.get 0))  ;; swap
(block (result i32 i32) (i32.const 1) (i32.const 2))

Sign extension

(i32.extend8_s (i32.const 0xff))         ;; -1
(i32.extend16_s (i32.const 0xffff))      ;; -1
(i64.extend8_s (i64.const 0xff))         ;; -1
(i64.extend16_s (i64.const 0xffff))      ;; -1
(i64.extend32_s (i64.const 0xffffffff))  ;; -1

Non-trapping conversions

(i32.trunc_sat_f32_s (f32.const 1e30))   ;; clamps instead of trapping
(i32.trunc_sat_f32_u (f32.const -1.0))   ;; 0 instead of trap
(i64.trunc_sat_f64_s (f64.const inf))    ;; max i64 instead of trap

Bulk memory

(memory.copy (i32.const 0) (i32.const 100) (i32.const 10))  ;; dst src len
(memory.fill (i32.const 0) (i32.const 0xff) (i32.const 64)) ;; dst val len
(memory.init $data (i32.const 0) (i32.const 0) (i32.const 4))
(data.drop $data)
(table.copy (i32.const 0) (i32.const 1) (i32.const 2))
(table.init $elem (i32.const 0) (i32.const 0) (i32.const 2))
(elem.drop $elem)

Multiple memories

(memory $a 1)
(memory $b 2)
(i32.load $a (i32.const 0))
(i32.store $b (i32.const 0) (i32.const 42))
(memory.copy $a $b (i32.const 0) (i32.const 0) (i32.const 10))

Memory64

(memory i64 1)                           ;; 64-bit memory
(memory i64 1 100)                       ;; with max
(i64.load (i64.const 0))                 ;; 64-bit addresses
(i32.store (i64.const 0x100000000) (i32.const 42))

SIMD

(v128.const i32x4 1 2 3 4)
(v128.const f32x4 1.0 2.0 3.0 4.0)
(i32x4.add (local.get 0) (local.get 1))
(f32x4.mul (local.get 0) (local.get 1))
(i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  (local.get 0) (local.get 1))
(i32x4.extract_lane 0 (local.get 0))
(i32x4.replace_lane 0 (local.get 0) (i32.const 99))

Relaxed SIMD

(i32x4.relaxed_trunc_f32x4_s (local.get 0))
(f32x4.relaxed_madd (local.get 0) (local.get 1) (local.get 2))  ;; a * b + c
(f32x4.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2)) ;; -(a * b) + c
(i8x16.relaxed_swizzle (local.get 0) (local.get 1))
(f32x4.relaxed_min (local.get 0) (local.get 1))

Tail calls

(func $factorial (param $n i64) (param $acc i64) (result i64)
  (if (result i64) (i64.le_u (local.get $n) (i64.const 1))
    (then (local.get $acc))
    (else (return_call $factorial
      (i64.sub (local.get $n) (i64.const 1))
      (i64.mul (local.get $n) (local.get $acc))))))

(return_call_indirect (type $fn) (local.get 0) (i32.const 0))

Extended const

(global $base i32 (i32.const 1000))
(global $offset i32 (i32.add (global.get $base) (i32.const 100)))
(global i64 (i64.mul (i64.const 1024) (i64.const 1024)))

Reference types

(table $t 10 funcref)
(table.get $t (i32.const 0))
(table.set $t (i32.const 0) (ref.func $f))
(table.size $t)
(table.grow $t (ref.null func) (i32.const 5))
(table.fill $t (i32.const 0) (ref.null func) (i32.const 10))
(global $ext (mut externref) (ref.null extern))
(ref.is_null (local.get 0))

Typed function refs

(type $fn (func (param i32) (result i32)))
(func $double (type $fn) (i32.mul (local.get 0) (i32.const 2)))
(call_ref $fn (i32.const 21) (ref.func $double))  ;; 42
(ref.null $fn)
(ref.is_null (ref.null $fn))
(block (result (ref null $fn)) (ref.null $fn))

GC

;; struct
(type $point (struct (field $x f64) (field $y f64)))
(struct.new $point (f64.const 1.0) (f64.const 2.0))
(struct.get $point $x (local.get $p))
(struct.set $point $y (local.get $p) (f64.const 3.0))

;; array
(type $arr (array (mut i32)))
(array.new $arr (i32.const 0) (i32.const 10))  ;; value, length
(array.get $arr (local.get $a) (i32.const 0))
(array.set $arr (local.get $a) (i32.const 0) (i32.const 42))
(array.len (local.get $a))

;; recursive types
(rec
  (type $node (struct (field $val i32) (field $next (ref null $node)))))

;; casts
(ref.cast (ref $point) (local.get 0))
(br_on_cast $label anyref (ref $point) (local.get 0))

Exceptions

(tag $e (param i32))
(throw $e (i32.const 42))
(try_table (result i32) (catch $e 0)
  (call $might_throw)
  (i32.const 0))
(try_table (catch_all 0) (call $fn))

JS string builtins

(import "wasm:js-string" "length" (func $strlen (param externref) (result i32)))
(import "wasm:js-string" "charCodeAt" (func $charAt (param externref i32) (result i32)))
(import "wasm:js-string" "fromCharCode" (func $fromChar (param i32) (result externref)))
(import "wasm:js-string" "concat" (func $concat (param externref externref) (result externref)))

Threads

(memory 1 10 shared)
(i32.atomic.load (i32.const 0))
(i32.atomic.store (i32.const 0) (i32.const 42))
(i32.atomic.rmw.add (i32.const 0) (i32.const 1))
(i32.atomic.rmw.cmpxchg (i32.const 0) (i32.const 0) (i32.const 1))
(memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const -1))
(memory.atomic.notify (i32.const 0) (i32.const 1))
(atomic.fence)

Wide arithmetic

;; 128-bit addition: (a_lo, a_hi) + (b_lo, b_hi) -> (lo, hi)
(i64.add128 (local.get 0) (local.get 1) (local.get 2) (local.get 3))
(i64.sub128 (local.get 0) (local.get 1) (local.get 2) (local.get 3))
;; wide multiply: a * b -> (lo, hi)
(i64.mul_wide_s (local.get 0) (local.get 1))
(i64.mul_wide_u (local.get 0) (local.get 1))

Annotations

(@custom "name" "content")
(@name "my_module")
(@metadata.code.branch_hint "\00") if ... end  ;; unlikely
(@metadata.code.branch_hint "\01") if ... end  ;; likely

See Also