jude hunter

jude hunter

Creating Wazum, the WebAssembly compilation library

thumbnail

For long now, Binaryen has been the only tool to generate WebAssembly programmatically. C developers could call methods to generate and optimize a WASM module. JavaScript developers had to rely on binaryen.js, a machine generated port, with minimal documentation, outdated typing, and many little quirks and even bugs. Other language developers had to rely on more niche ports, or nothing at all.

Wazum is my attempt to bring a better DX into the WebAssembly field. It will function as a binaryen.js alternative with stellar documentation, strong typing, full test coverage, quality of life features, and a focus on usability for both compilation and hand-writing of WebAssembly.

Wazum would still use binaryen.js for the optimization part, but it would generate the WebAssembly textual format (WAT) by itself.

Here’s an example of how one might define a simple function with wazum:

w.func(
  'add',
  {
    params: [
      ['i32', 'a'],
      ['i32', 'b'],
    ],
    locals: [['i32', 'ret']],
    returnType: 'i32',
  },
  w.block(null, 'i32', [
    w.drop(
      w.add(
        'i32',
        w.local.get('i32', 'a'),
        w.local.get('i32', 'b')
      )
    ),
    w.local.set(
      'i32',
      'ret',
      w.add(
        'i32',
        w.local.get('i32', 'a'),
        w.local.get('i32', 'b')
      ),
    ),
    w.local.get('i32', 'ret'),
  ]),
);

Notice the multiple datatype (i32) definitions. This was a design choice to maxime correctness over type inference. For instance, the type system will not allow you to call

w.add(
  'i32',
  w.local.get('i64', 'a'),
  // ERR: `i64` is not assignable to `i32`
  w.local.get('i32', b)
)

Additionally, wazum encourages the usage of named locals. Binaryen on the other hand only allows numeric indexes.

Wazum will not restrict any user from manipulating the generated tree. You’d be able to assemble the whole node tree for functions and their bodies first (maybe manipulate them, duplicate functions, reuse subtrees, dynamically change some stuff, etc.), and only then add your functions to the module.

Binaryen on the other hand works in a sort of roundabout way. This is what roughly happens there:

// binaryen.js user calls a function:
i32.local.get(0);
// binaryen creates a representation for the
// `get 0` operation internally
// then, it stores that representation in an internal
// list and only returns its index, making it
// impossible for users to modify the representation

// this operation in binaryen:
i32.add(i32.local.get(0), i32.local.get(1))
// will first become this:
i32.add(123, 456)
// and then this
789

In comparison, the functions in wazum simply return the whole representation allowing the user to have maximum control and flexibility.

i32.add(i32.local.get('a'), i32.local.get('b'))
// becomes:
{
  __nodeType: 'add',
  dataType: 'i32',
  left: {
    __nodeType: 'localGet',
    dataType: 'i32',
    name: 'a',
  },
  right: {
    __nodeType: 'localGet',
    dataType: 'i32',
    name: 'b',
  },
}

At this point, wazum is still very much in its initial phases.

Stay tuned for more updates.


Thanks for reading.
HI, YOU’VE REACHED
jude hunter
OPEN TO WORK
Front-end engineer, advocate of free-as-in-freedom software and an active social progressivist
LEAVE A MESSAGE AT THE TONE
get in contact
made with ❤️ and 🏳️‍🌈
by jude hunter ©
legal name: Mateusz Sowinski-Smetny