Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Jaws – a JavaScript to WASM ahead-of-time compiler (github.com/drogus)
290 points by drogus 33 days ago | hide | past | favorite | 107 comments
I've open sourced a JavaScript to WASM compiler. It's an experimental tool, but given the semantics I already implemented, I'm fairly certain I am able to eventually cover 100% of JavaScript spec. Any ideas, questions or critique welcomed! If you are interested in WASM, especially with new proposals like WASM GC or exception handling, it might be a good source of seeing these features in action - the project has a few thousand lines of hand written WAT so far.



Really clever use of the new WASM GC proposal. All the JS -> WASM compilers so far have basically just been shipping a whole JS engine - this is the first one I've seen that actually tries to map JS constructs directly to WASM primitives.


I think both Porffor https://porffor.dev/ and Static Hermes https://hermesengine.dev/ also take a compilation approach. Would be interesting to see how Jaws compares.


I haven't seen Static Hermes before, I'll take a look, thanks for the link!

With regards to porffor. It's a very good project and people behind it are probably much better at writing interpreters/compilers, but the biggest difference is in what WASM features the projects use. porffor uses only core WASM, which means they have to implement a lot more features themselves. Data types like arrays, structs/objects, garbage collection, exception handling etc. I am using WASM features added in proposals standardized very recently, like WASM GC or exception handling, which means I get a lot of the features almost for free. And I suppose that's also why semantics like scopes/closures are really hard to do for projects like porffor or even AssemblyScript and were relatively easy in Jaws.

The trade off is mainly in support. porffor compiled binaries can run on a lot of different WASM runtimes, I think there are even some runtimes for Core WASM for embedded devices. In case of Jaws, the runtime needs to support the proposals I use. Currently there are only two runtimes, that I know of, supporting both WASM GC and exception handling: V8 and WasmEdge. I believe more runtimes will get there, like for example WasmTime people are working towards exception handling, but it will take some time. Which is not a problem for me, cause it will definitely take a bit to reach any production level JS compatibility - I think the runtimes will have time to catch up with the proposals by then.


There is also Wasmnizer-ts, which compiles TypeScript to WasmGC:

https://github.com/web-devkits/Wasmnizer-ts

How does Jaws compare to that?


I haven't seen Wasmnizer before. This looks like a cool project and I'll be definitely taking a closer look when I have some time, but for now I just took a quick peek.

The main difference is obviously TypeScript vs JavaScript. Because they are not bothering with JavaScript, some stuff is much easier, like for example if a function specifies an argument as `number`, you can statically compile it to use a type representing a number (possibly even without heap allocations) instead of passing something like `anyref` everywhere and doing typechecks.

The second big difference is that they seem to use types imported form the host. This means they can implement part of the types/builtins/methods on the host (which is easier, cause in JavaScript you simply use JavaScript for defining host stuff) at a cost of doing more calls from WASM to the host. And then you need the host to implement those functions (here is an example for JS host: https://github.com/web-devkits/Wasmnizer-ts/blob/main/tools/...). My plan for Jaws is to only rely on WASI, so that you can run your program on any runtime that supports WASI.

I'm also not sure about their approach to semantics. I can't quite grasp how they implement for example scopes. I tried to compile a small TS script that relies on scope, like:

  let message: string = 'Hello, World!';

  function test() {
    console.log(message);
  }

  test()
And the result was console.log printing null. I'm not sure if I'm doing something wrong or is it expected, but I'll ask them in an issue later.


Do you have any plans to be able to take TypeScript types to do type optimizations? That sounds like a bonus to me to be able to have easier implementation and faster run times.


Yup, that's something that I would definitely like to do. In performance critical applications adding type information can speed up things quite a lot, especially when you think about numbers. At the moment I have to pass numbers as structs with one f64 field, because an argument to a function in Javascript can be any available type. If the type is specified, the signature or return type can use f64 types directly and don't even do heap allocations. But even for heap allocated types it may result in better performance, cause at the moment for each object I have to check what type it is and what path to take in order to proceed.


Interesting, thanks! And good luck with your project.


Thanks! That’s a really helpful explanation.

Good luck! I’m interested in seeing how JS to WASM compilers can speed up edge rendering use cases. WASM opens the possibility of really cheap request isolation, far faster than process forking. But JS interpreters in WASM are currently so much slower that they still lose out performance wise overall.


Thanks! Although, to be fair, it's a clever use mostly cause I'm too dumb to write the full interpreter on top of WASM lol


Back in the day I did an almost-Typescript (though much closer than assembly script) to embedded ARM compiler. Some of the techniques may be useful.

https://www.microsoft.com/en-us/research/uploads/prod/2019/0...


Nice! I'll definitely take a look!


> As much as I love writing Rust, I also know it's not a widely popular language

Is this true? Rust is hyped like crazy and seems to be used everywhere these days.


Yet to find any non-crypto related jobs in it. And in my country, Estonia, zero local jobs (as per local job boards that people use). I would say it's definitely not popular in any sense that actually matters.


What worked for me in Estonia was to find a C++ job (robotics) then gradually push for Rust as I gained seniority and autonomy. Now maybe 70% of the code I write is Rust, both robot side and backend :)


That sounds like tedious work, while suffering and having to write C++.


Curious -- how many people are writing Rust in your company?


Probably 10 at most.


Here's a non-crypto example: I work on a mobile analytics library that has a core written in Rust and the platform-specific code written in Java and Objective-C.

We were actually hiring for a couple of roles, although we just sent out two offers, so we won't be if they get accepted.


Lots of people want to work in rust. That matters if you're starting a project in rust because it means you'll be able to find people to hire. It just takes a long time for that kind of effect you show up if you're on the get-hired side of the equation.


Hyped and visible on socials doesn't necessarily mean it's widely used, but I guess it also depends on how you define widely. According to most stats I've seen Rust usage is maybe about 5-10% of languages like JavaScript and Python when you look at StackOverflow, indexes like PyPl, or GitHub statistics.


Which puts it already in the second tier of most used languages with big guys like Java, C#. I think Rust will soon join the ranks of JavaScript, Python and C.


Seriously doubt it but time will tell.

There aren't that may new projects where rust would be a good fit. And projects in C++ won't be rewritten in rust.


I have gut feel (perhaps only because my echo chamber includes the Rust Evangelism Task Force) that Rust/WASM is likely to become the go to stack for complex web apps.

Google (collectively) have written quite a bit about Rust adoption going back at least a couple of years. It'd kinda surprise me if there weren't some drive to write gmail/googledocs in Rust and deliver them as WASM. Meta/Facebook have some strong internal Rust evangelism going on.

I doubt it's a good idea, but it's likely my first trial "lets write a Rust project" will be an in-browser Rust/WASM thing.


> that Rust/WASM is likely to become the go to stack for complex web apps

What kind of apps?

On the backend there are plenty of mature ecosystems already that have been around for decades. Even focusing only on performance there's not a huge difference with say .NET or Java when looking at web benchmarks like TechEmpower[1].

On the frontend nothing can really beat the DX of using Vite with JS (hot reload of JS components, styles, etc). I don't know if it's even possible to get hot reload with WASM.

I could be wrong but it sounds like doing a complex web project in Rust would be a pain with not a lot of benefits over more popular and mature solutions.

[1] https://www.techempower.com/benchmarks/#hw=ph&test=fortune&s...


> complex web apps

Heh? I really like Rust, but it has absolutely zero advantage for this domain compared to the existing million choices. Unless you are doing some low-end web stuff with packets, any managed language will have a much better developer experience. Async rust is not particularly easy, and something like Java's virtual threads will give you both the ergonomics and the throughput, and then we haven't even talked about how much bigger the relevant ecosystem is (remember that the only order of magnitude improvement in dev productivity is from code you don't have to write)


I think the advantage is just that the same code runs on the server and in the browser, same as with a lot of code for Node.js.

For example, I wrote an ssg in rust but I was able to fully compile it to wasm and it works in the browser.


WebAssembly adds a virtual machine so you run any high level language in the browser.

C++, Java, Go, Erlang, Rust, PHP, JavaScript, C#, whatever


Google has been doing it 2 decades ago with Gmail and the like. They could compile java to js. More recently, clojure and c#'s blazor come to mind that have the same ability.


> There aren't that may new projects where rust would be a good fit

Aren't almost all new projects that otherwise would have been written in C or C++ good fits for Rust?


Almost all? I doubt it.

The C++ ecosystem is too strong.

There are many industries like game dev and audio dev where C++ is not going to be replaced any time soon, even for new projects.

Also plenty of apps that need high performance crossplatform GUI will probably keep relying on QT. Apps like Da Vinci Resolve or Autodesk Maya.


In theory, the language might be a decent fit. But it’s not the language (mostly), it’s the library and ecosystem. Definitely not enough to unseat C++ yet for many new projects.


"Almost all" is a stretch.

Certainly a lot, and most new C/C++ (and even Go) projects will mention that they considered Rust before choosing a different language.


Rust is often used for things that you wouldn't necessarily use C++, though. Web backends (but mostly APIs at least as far as I've seen), CLI tools, various bots (like at my day job we have a Discord bot written in Rust) etc.


Rust holds a lot of appeal for web devs looking to leverage performance gains in build-time tooling (e.g. Biome replacing ESLint and Prettier) and bridging the native desktop gap (eg Tauri replacing Electron). There are more examples but those were front-of-mind.


I could be wrong but my impression is that Go is more popular than Rust in this area.


Respectfully, I think your impression is mistaken. I gave specific examples of Rust adoption for webdev build tooling, but am not aware of any comparable Go equivalents. (When it comes to runtime services, that's another story, where Go has made more significant inroads.)


How is it even remotely in the same category as Java?


Because Java mindshare is going down fast while Rust is going up faster?


First of all, past performance is not indicative of future results.

Second, it's obviously much easier to double your user base from 2 to 4, than from 10 million to 20. New languages tend to have a fast initial growth, but then the curve flattens and no significant change comes after. Java hasn't been on a decline in the absolute sense, the whole market has just grown and has more players now, so the same mindshare gives less of a percentage now. Nonetheless, no language has come even close to the top 3 languages (js, python, java) in popularity that were released in the last 20 years, so what makes you think that Rust will be the one that avoids the flattening of its growth?


I'd like some references of "Java mindshare is going down fast".

Sure, lots of new project/code uses languages like Go these days. But Java is still very dominant, especially at companies like Amazon. It currently sits ar #3 on TIOBE.


> I'm fairly certain I am able to eventually cover 100% of JavaScript spec. Any ideas, questions or critique welcomed!

Do you have the results of test262_runner.rb? I came to know about test262 at a talk by the porffor's author and something like https://github.com/CanadaHonk/porffor?tab=readme-ov-file#tes... in README would be great to show this progress. Great project by the way!


Yeah, at the moment it's passing about 12% of tests, but there is a lot of low hanging fruits to implement, especially considering I started the project two weeks ago. This doesn't mean I will hit 100% in linear time, unfortunately, cause there is a long tail of builtin types and functions, but as I started by implementing the "hard parts" I left some easy parts not done. For example I implemented only enough syntax to allow running conditionals and a while loop cause it was needed for the test262 harness, but I left all the other loops (for, for in, for of, do while) and conditional expressions (switch) unimplemented. Implementing them will be more or less analogous to existing implementations for if/else and while.

Once I finish implementing `await` and generators, which are the last hard to implement semantic concepts, I will be implementing those low hanging fruits. It's hard to say how much coverage that will give me, but just to give an example: currently 1200 tests fails, cause `object["foo"]` syntax is not implemented. Ie. `object.foo` works, but `object["foo"]` does not. It doesn't mean that those 1200 tests automatically will pass, cause they might be testing other stuff, but there is a lot of such relatively simple syntax ommissions that make hundreds of tests fail.

And yes, I would love to have a nice graph like porffor has! :D


Yes now I'm looking at the tests I can see that the coverage won't increase linearly, but 12% (with some of the hard parts) in two weeks is impressive. I starred the repo, good luck with your journey :)


I read the README.md of the project but I'm still not sure: What's the expected usage of this? How does the outputed WASM code then interacts with a runtime (and with which, is it intended to be a tool compatible with browsers and other WASM runtimes or is it only compatible with a runtime linked to the project)?

Somewhat linked questions: How does it react if it encounters e.g. web APIs inside the JavaScript code or other global identifiers only defined in some environment (e.g. a recent browser, Node.js etc.)? Or if it's not intended for those environments, how are you supposed to do I/O when using this?


These are very good questions, I'll respond here, but I'll also add more info to the README. This project is mainly targeting WebAssembly usage on the server, cause I think it makes little sense to run JavaScript in WebAssembly in JavaScript (although time will tell, maybe it will be useful for sandboxing frontend plugins?). Regardless if it's running in the browser or a backend runtime like WasmTime or WasmEdge, at the moment running JavaScript inside WebAssembly is not ideal. You either have to compile a JS engine like V8 or SpiderMonkey to WASM and then use it to run your script or you have to settle for an "almost JavaScript" language like AssemblyScript. This is a limiting factor for running server workloads. For example Fastly uses SpiderMonkey for their WASM workers, but it means that each instance uses 5-10MBs of memory even for a hello world. Shopify, on the other hand, uses WASM for customizing server side of their shops, and they decided they only allow WASM binaries up to 250KBs, which is a no-go for embedding any interpreter. Thus their "blessed" language is AssemblyScript. They outline reasons for that here: https://shopify.engineering/shopify-webassembly

This is all due to a fact that historically WASM was a very simple runtime. It was relatively easy to compile C code to WASM, just like you compile C code to machine code, but even though a WebAssembly is a kind of interpreter by itself, it wasn't easy to interpret higher level languages on top of it.

With new proposals being standardized, like garbage collection support or exception handling support, WebAssembly becomes much more powerful interpreter, with stuff like structs, arrays, function references etc.

Jaws leverages that fact translating JS code to WASM code in a way that WASM interprets the resulting code, without the need of a JS engine like SpiderMonkey. In practice it mainly means that a binary generated by Jaws will be probably under 50KBs vs 10MBs when you compile SpiderMonkey to WASM and run your script on top of that. Memory usage will be also significantly lower. For companies like Fastly this would mean orders of magnitude lower memory usage and thus server costs. For companies like Shopify it would mean they could leverage JavaScript code already available (think NPM packkages) and JavaScript ecosystem for people writing plugins for Shopify's backend.

> is it intended to be a tool compatible with browsers and other WASM runtimes or is it only compatible with a runtime linked to the project

The only runtime the project uses is WebAssembly. The generated code is mostly 3k lines of WAT code form this file: https://github.com/drogus/jaws/blob/main/src/wat/template.wa... and whatever your JS code is translated to. For example for a very simple program like "console.log('foo')" the entire "generated" part is this: https://gist.github.com/drogus/1c49c25ed0b14804b2f27e10d2a79..., which more or less prepares an argument (with new_static_string) and then calls console.log. Right now I need a bit of glue code on the host, but eventually it will be possible to execute such a binary with any runtime that supports WASIp2, WASM GC and exception handling proposals.

> Somewhat linked questions: How does it react if it encounters e.g. web APIs inside the JavaScript code or other global identifiers only defined in some environment (e.g. a recent browser, Node.js etc.)? Or if it's not intended for those environments, how are you supposed to do I/O when using this?

None of this is implemented yet, but I can tell you how it will work. I plan to support Node.js APIs through WASI. WASI is a standard for communicating between WASM programs and the outside world. For example WASI defines a standard set of functions you can use to send an HTTP request, or write to STDOUT, or read/write to a file. So when I get to APIs like `fetch` or `fs`, it should work with any runtime that supports WASI preview2. Browsers could also be supported with polyfills, but in this case I/O support is more custom. Like, if you decide you allow WASM programs to write or read files, you would have to provide a mechanism to do that, for example save files to localStorage or an SQLite database compiled to WASM (or I guess even send them to S3 or something along the lines).


nice, "run JS without (browser) runtime" is coming. perforr, jaws, or another project will eventually succeed.




You mean like Node?


I guess "yes" wouldn't be an incorrect answer, but a more nuanced look might want to consider that node does actually contain the js runtime of a browser, just minus all the rest of that browser.

A wasm runtime can be far more lightweight than node, not only because node itself is a wasm runtime, plus a lot of other things. Wasm could (or does, already?) occupy a sweet spot where platform independent extensibility is desired, but where that is not enough of a core feature to make inclusion of a heavier runtime advisable. Kind of like how Lua has its place, but with more focus on near-native speed and less focus on ad-hoc programming (aka scripting).


bun?


Bun is a runtime.. If you're referring to the fact it can produce a single binary, Node.js can do that too.


Yeah, bun will still include V8 in the generated binary


Bun will include the Bun runtime with JavaScriptCore, Node.js will include the Node.js runtime with V8.


Oh, thanks for the clarification, I somehow thought Bun is also V8!


That would be Deno. It can also produce a single binary using the same principle.


Bun uses JavaScriptCore as its runtime, not V8


I really like this approach. Building for WASM directly, rather than trying to also directly generate binaries, means you can rely on WASM GC and the async support that (I think?) is supposed to be part of WASI 0.3.


How are string encoding discrepancies and related utilities dealt with? My vague understanding is that WASM supports UTF-8 while JS supports (potentially malformed) UTF-16


Like a CPU ISA, the WASM abstract machine doesn't have a concept of strings or encoding. It's just bytes in a linear memory. You implement whatever encoding you want.

WASM specifies UTF-8 for the encoding of names in its file formats, but that doesn't involve the runtime VM.


Or, as some of us call it, a compiler. Nice work btw!


Haha, I haven't even noticed it doesn't make too much sense when I was writing the title :D


So does this execute faster than just running the same code in js or is this for interop for other languages?


Hard to say at this point, but I really doubt it will be ever faster than SpiderMonkey or V8 with JIT enabled. Modern JavaScript compilers are quite good at optimizing hot paths through JIT. The use case of this project is to be able to run JavaScript in WebAssembly sandboxed environment. Like, for example Shopify allows you to extend their backend code using WebAssembly, but there is a binary limit capped at 250KBs. At that size you can't really use JavaScript at the moment, cause even simple interpreters like QuickJS take a few MBs compiled to WASM.


I get a build error where it can not find "prepend.js". Indeed it appears to not be in the repo.


thanks for reporting and sorry about that! I was prepending some JavaScript code to be compiled for test262 harness before, but at the moment it was just an empty file, so I removed it and pushed a fix. Please try again now and let me know if you run into any issues!


Does it implement JS standard library, for example Map, Set etc?


Very cool!

Does it support ArrayBuffers?


Not yet, it's very early stage where I'm mostly implementing full JavaScript semantics (and hopefully finding some funding/support for the project), but as soon as I'm done with async/await and a few simpler missing pieces, I will start implementing JS builtin types


Cool, thanks and all the best with the project.


Just when I was thinking: do we need more compilers or do they all exist already?

Thank you for working on this! I think it’s a great idea.


Just one more compiler, bro. Just one more, I promise. This is the last one, just one more


Can you make it deterministic?


[deleted]



Off-topic, but, why does he pronounce JavaScript "YavaScript"?


The joke is that he's speaking from the future where they inexplicably pronounce javascript "yavascript". This is how you know he's really from the future.


Uh, okay, I must have missed that part :)


He definitely never mentions it but the opening line of the presentation is "This talk is a 40 year history beginning in 1995 and going until 2035" and he speaks in the past tense so he sorta just launches into it without setting it up much.


Is the speaker from the Nordics? Everyone in the Nordics say "J" as "Y" because that's how it's pronounced in their languages and they think English speakers (and speakers of every other language that uses the Latin alphabet) are just mispronouncing it.


Latin alphabet using languages that do it similarly as nordics: german, dutch, czech, polish, hungarian, latvian, etc. Also the international phonetic alphabet.


Are you arguing that the sound of "J" in English (and the very different sounds of it in Portuguese, Spanish, Italian etc.) is wrong? Sounds of words change over time and that's probably why the same letters are pronounced so differently in different languages, though they might have been used to represent the same sounds originally.


Not arguing that, these langugaes just use the alphabet differently. I was mainly reacting to the "speakers of every other language that uses the Latin alphabet" part to give some counterexamples.


How else would you pronounce it?


J as in Jacques, 'script' like 'esprit'. Zhavascree, stress on final syllable.


Never heard anyone pronounce it like that.


Probably because you didn't watch the later (2173) related holovid from the Neonapoleonic Empire period titled À la recherche du temps perdu. Long but worth it.


Why do we say 'wat' instead of hwæt? Same thing.


JavaScript is named after Java the programming language, which is named after Java the island, which is correctly pronounced that way.


IIRC Silicon Valley was (will be) nuked, hence the implication Dutch (top of head example) becomes dominant.


JAWS is a well-known screen reader for blind people with a more then 30 years history.


I know about Jaws as a screen reader, but I don't think it is much of a conflict. WebAssembly is a niche within a niche (like a tool in a WebAssembly ecosystem within a programming ecosystem), so it will probably never grow to a popularity where it's higher in search results making life of people using screen readers worse. But even now, when you enter Jaws into google now, the whole first page is about Jaws, the movie. So if anyone wants to find either of the projects by name they have to specify it more anyway, just like you often have to do with other projects (for example I always search for "phoenix elixir" instead of just Phoenix). My take is that with the amount of tools and programs there is no way to use simple names and not have a conflict here or there.


Please consider changing the name - it's already hard to find the pages out there talking about how to fix technical issues or use the javascript developer console or such as a jaws user, and your project will end up hiding some of those or at least causing confusion. How about JAWSM (pronounced like awesome), like benmccann suggests?


Coming from an actual JAWS user that has a lot more validity and if you say it will make it harder for people, I will definitely consider changing the name. I actually like Jawsm or JAWSM, so if @benmccann is fine with it, I will probably rename


Even worse, JAWS is a well-known movie with a 49 year history. I strongly believe the screen reader and the WASM compiler both knew this at the time, quite damning.


I am painfully aware the project will never be the first hit in Google by just typing it's name lol

Btw, I am very bad at naming, I chose the name cause it has most of the letters that "JS-WASM" has (or all of them if you consider W is just inverted M)


Don't even think about it :) I'm just teasing the person I'm replying to, its a way of saying "yeah I don't think that name collision is significant" that A) assumes they desire conversation, so it's okay to reply B) engages without asserting C) expresses disagreement in a jovial matter, rather than confrontational


JAWSM might be a more unique alternative if you're looking for any.

Anyway, it's a cool project. Thanks for sharing!


I love the name!


We're gonna need a bigger namespace.


Apples and peaches. Jaws the movie is another section, the movie section. Jaws and JAWS are both in the software section. There's the potential for conflicted naming. Sorry to be emotionless here, but at least you could respect the fact that there is a popular software with the same name, without making jokes about it.


There is no "software section" anymore, alas :( RIP Computer City.

FWIW, for the audience, both you and I know the game here is "can we define a broad enough, yet narrow enough, category that someone could confuse the screenreader with the WASM compiler, such that it can be claimed to be 'emotionless' to say there's a significant collision and the WASM compiler needs a rename."

First, there's certainly room for conversation, emotionless doesn't mean provably unambiguously correct and everyone else needs to agree and not reply. :)

Second, it's clear you do have emotions, ex. see how you're policing whether its okay to "joke", where "joke" is me going to great lengths to indirectly indicate the possibility this is less than 100% unambiguously correct in as non-personal a way as possible. I did that because the initial curt, policing, tone indicated strongly that you would react poorly to any indication this was anything other than unambiguous. And here we are.



You can imagine why naming a software "Mission Control" in dev ops is so infuriating for people who actually work in actual space or satellite mission control.


Yes. Well done. This is a different Jaws though.


Title might need to include "Show HN".

Very cool and interesting project! How are build times? And how big are the artifacts?

I'll for sure keep an eye on this, and add it to my ever expanding list of tech to explore.

Thank you for sharing!


The binaries are a few KBs at the moment. I haven't measured the build times, cause it's too early for it to mean much. The amount of supported types and functions is very limited, so it will change a lot over time as I add more stuff.

One interesting thing is that `eval()` support will require custom WebAssembly host functions, cause you can't do custom code generation in WASM. Thus by default the project will assume "no eval" compilation. In this mode it will be possible to do a lot of optimizations, like for example remove unused parts of the language/types, do certain optimiztions knowing exactly what types the script is dealing with etc. So a simple script that doesn't use a lot of the builtins should eventually result in a fairly small binary.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: