Since my last post a month ago, I’ve reached the next milestone in my WebAssembly project and wanted to share. I now have a working demo app!
It’s a super-simple “countdown” app, designed to show a few main things:
Elm app code “compiled” to WebAssembly (well, hand-translated to C and compiled to Wasm)
A simple Cmd effect (Process.sleep)
A simple VirtualDom view with an onClick callback
This was just enough to force all the major technical issues to do with the “platform”. Most of the work was focused on interoperation between WebAssembly and JS. The effects have to be in JS because the WebAssembly MVP can’t access Web APIs yet. (In this case, the DOM and setTimeout.)
The demo is assembled from a build script that modifies the output of the standard compiler. That was the easiest way to go for quick experimentation. But now I have a much clearer picture of how everything fits together, the next phase will be focusing on the compiler side. The plan is to add a second code generator, for C instead of JS, and leave everything else alone!
I’ve written up a document on how the JS/Wasm interop works:
This is a cool project. It’s been very interesting to follow your progress.
The plan is to add a second code generator, for C instead of JS, and leave everything else alone!
I have what might be a naive question: what are the advantages of writing a code generator for C instead of converting the existing JS code generator to produce Assembly Script? That has to be easier right?
Going via JS would mean that Ints and Floats are just JS numbers, so end up being floating point and that is why Elm has slightly odd 53-bit integers. Presumably going via C, which differentiates ints and floats would allow Elm with 64-bit integers?
@malaire, AssemblyScript is a subset of Typescript that compiles to WebAssembly. That section of my readme refers to raw WebAssembly so the arguments are a bit different. I think the point is that AssemblyScript is closer syntax to JS so it would be easier to modify the code generator.
To be honest I had never heard of AssemblyScript until last month and I started this project last year! So that’s the real reason!
But I think the big advantage of C is full control over optimisations. Elm has guarantees that TypeScript doesn’t, and those can be taken advantage of in C but not so much in TypeScript/AssemblyScript. AssemblyScript can’t assume there’s no mutation anywhere for example. And I guess there would be a lot of other things that TypeScript programs are allowed to do that Elm programs aren’t. Elm has a lot of deliberate restrictions to take advantage of, which tends to result in fewer assembly instructions.
The C route is also nice for other platforms apart from WebAssembly. Though I know about wasi too.
Yup, that was exactly my reasoning, I should have made that a bit clearer in the last post.
That’s a perfectly valid reason!
The reason I’m asking is that I’ve been messing about with WebAssembly a bit, mainly using Rust/Enscripten. I love Rust and the performance improvements I’m seeing with WA (about an order of magnitude), but all the glue that sticks everything together is a pain. I’m in a similar situation to you where I want to port across JavaScript sequentially, and AssemblyScript seems relatively well suited to that situation. I haven’t actually used it yet, I was just curious to hear your thoughts!
@rupert thanks for bringing up integer size, I’d meant to add something to the “known issues” in the docs about that!
I’m using 32 bits for Elm Int. I could have used 64 bits but there are a few drawbacks to that:
If integers are a different size from pointers (32 bits in Wasm), it’s very hard to implement “unboxed integers” later. This is one of the most obvious low-level optimisations to do and all the browsers do it for optimised JS code, so it would be a pity not to have it in Wasm! A “boxed” integer is a pointer to some heap object containing the integer value along with metadata about size, type, etc. An unboxed integer is just the integer value directly. It’s a huge speed gain, but other things get tricky without that handy metadata.)
The 64-bit integer is the only Wasm number type that can’t be passed from Wasm to JS. You’d have to read it out as two 32-bit numbers and recombine them on the JS side.
The elm/coreBitwise module relies on 32-bit integers, so 64-bit integers would change the API. But on the other hand, Time.now returns a number that takes 41 bits to represent, so one of them has to change if there’s only one Int size!
But a lot of languages have separate types for 32 and 64 bit integers. Maybe Elm could go that direction too? In Wasm and 32-bit platforms, 64-bit integers would be boxed and therefore slower, but still give you the extra bits if you need them.
Super interesting. I’ve also been playing around with WebAssembly but using Rust. Rust does have a lot of control as far as immutable patterns and such, so I’m curious to hear your thoughts about using Rust as the intermediary language.
The only downside I would see right away is the compile time. Upsides include things like memory safety for you, the compiler author, as well as the ability to actually just compile to Rust, which is really growing in popularity, and use that code as a crate in other Rust crates.
This really is just curiosity though. I don’t want to sound like “cool but why not ___?”