Stream: general

Topic: Jaws - a JS to WASM compiler


view this post on Zulip Piotr Sarnacki (Nov 09 2024 at 17:52):

Hi all!

After reading some articles about WASM GC being a game changer for WebAssembly scripting languages support, I was slightly disappointed there are no implementations of JavaScript on top of WASM GC. I know there is porffor, which is a great and very interesting project, but it purposefully uses only core WASM. Honestly, I had no idea if implementing JavaScript semantics was possible even with WASM GC, so I did the only thing that made sense to me, I started writing the tool myself, to check it myself. I think it's going great, so I'm open sourcing it as Jaws

It's still very much an experimental tool, but I'm fairly certain it's possible to implement 100% of the JavaScript spec that way. In order to confirm it, though, I first wanted to explore implementing JavaScript semantics that are the hardest to implement: scopes/closures, try/catch, async/await and generators. I have the first two and the easier part of async/await (ie. async).

To give you an example, this code will output the same thing that Node.js outputs:

let value = "foo";

async function foo() {
  let bar = function () {
    throw value;
  };

  try {
    bar();
  } catch (err) {
    console.log("caught", err, "rethrowing");
    throw err;
  }
}

foo().then(
  function () {},
  function (value) {
    console.log("error", value);
  },
);

I'm now working on implementing await and then generator functions. My current approach is to introduce CPS transforms into the code. I tried using Asyncify, but it looks like it doesn't work with proposals I'm using, so I'll probably just roll it out by hand (which also might be best anyway, cause there are some specific things I can do to make it fit the code I generate better). stack-switching proposal would be ideal, but I don't think any of the runtimes support it yet, so I'll leave it for now. It's hard enough to run the project anywhere even with the current set of WASM features :sweat_smile:

The project works as follows:

  1. There is quite a lot of hand written WAT code implementing various helpers, types, and functions to allow mimicking JS semantics
  2. A Rust program parses the Javascript code using the parser from Boa interpreter
  3. The JavaScript AST is translated to a WASM AST
  4. WASM AST is translated to WAT and written to a WAT file

My plan is to use WASIp2 for stuff like handling incoming/outgoing HTTP requests, writing to STDOUT/STDERR and files, but also for using timers etc. WasmTime seems to be the only runtime supporting WASIp2/components, but unfortunately it doesn't fully support WASM GC nor does it support exception handling proposal, so I resolved to write a very simple Javascript polyfill for stuff that I'm using (only monotonic clocks and output for now).

Any critique, ideas, questions, or other input are welcomed!

A from-scratch experimental AOT JS engine, written in JS - CanadaHonk/porffor
JavaScript to WASM compiler. Contribute to drogus/jaws development by creating an account on GitHub.
Boa is an embeddable and experimental Javascript engine written in Rust. Currently, it has support for some of the language. - boa-dev/boa

Last updated: Dec 23 2024 at 12:05 UTC