top | item 5227274

Asm.js: a strict subset of js for compilers – working draft

217 points| shardling | 13 years ago |asmjs.org | reply

199 comments

order
[+] tachyonbeam|13 years ago|reply
Sitting about 10 feet away from Luke Wagner right now. He has told me that asm.js is Mozilla's response to NaCl. You compile code with Clang (or another compiler) into the asm.js subset of JavaScript, which they know how to optimize well, and their JIT compiler will offer you performance very close to that of native C (they claim a factor of at most 2x). They use special tricks, like (a+b)|0, to force results to be in the int32 range, and avoid overflow checks. The heap views uses multiple typed arrays pointing to the same data to give asm.js code a typed heap of aligned values, avoiding the JS garbage collector (you can manage this heap as you wish).

They already have sample apps, including some using libraries like Qt (I believe OpenGL as well), compiling to asm.js. I believe it has potential, so long as they have good support for standard and common C libraries (i.e.: porting to asm.js is almost effortless).

[+] duaneb|13 years ago|reply
> They use special tricks, like (a+b)|0, to force results to be in the int32 range, and avoid overflow checks.

Why is this not seen as the worst idea ever? Why this great resistance to moving away from javascript?

[+] lucian1900|13 years ago|reply
I'd really much prefer something lower level, so anything can be easily implemented on top with predictable performance (including JavaScript).

I don't understand the resistance to PNaCl, it's plugins done right.

[+] cromwellian|13 years ago|reply
This seems very much targeted at emscripten and not to cross-compilers that start with GC'ed languages like GWT, Dart, ClojureScript, et al. If you are cross-compiling Java or C# to asm.js, you don't really want to manage memory manually. I work on GWT, and asm.js as an output target is very interesting to me (I've worked on a number of performance Web games using it, GwtQuake, AngryBirds, etc), but the starting assumption is GC, so I want to leverage non-boxed numerics, and all of the other nice stuff, but don't want to stuff everything into a TypedArray.

It's also unclear to me how this solves the problem of startup time on mobile. A giant glob of Javascript takes a non-trivial amount of time to load even on today's fastest smartphones. The spec seems to argue that Browser VMs could recognize asm.js and if I read between the lines, employ snapshoting the app and caching it for later quick startup?

In all likelihood, the majority of asm.js outputs would be actually be non-human readable output of optimizing cross-compilers, so there isn't much benefit from having a readable syntax that humans could read, so what's the real justification for using JS as an intermediate representation over say, a syntax specifically designed for minimum network overhead and maximum startup speed? Seems like it might be worthwhile for Mozilla to also work on efficient representations of asm.js that minimize this overhead. The usual response is minify + gzip, but it's not a panacea.

[+] dherman|13 years ago|reply
We have plans!

First of all, we do care very much about supporting compilers for managed languages like Java and C#, but we're starting with this first version that only supports zero GC and atomic primitive types. We have plans to grow outwards from there to support structured binary data, based on the ES6 binary data API, and controlled interaction with the GC. Luke has ideas about how to do this without losing the predictable performance for lower-level compilers like Emscripten.

We do have plans for startup time. I hope to pitch a very small, simple API for a kind of opaque compiled Function. Internally we've been calling it FunctionBlob (we'll bikeshed the name later). The idea is that `new FunctionBlob(src)` is almost identical to `new Function(src)` except the object is (a) an opaque wrapper that can be converted to a function via `fblob.toFunction()` and (b) entirely stateless and therefore compatible for transfer between Workers as well as offline storage. This would essentially make it possible to do things like background compilation of asm.js on a Worker thread, and caching of the results of compilation in offline storage. That way next time you startup you don't have to download or optimize the source. (This could work especially well with the app experience where you could perform these download/optimize steps as part of the installation process.)

As for the use of JS, this is purely pragmatic. The code works today in browsers, so people can start using it and it works -- and even quite fast; Emscripten is already shockingly performant in Firefox and Chrome -- but over time it'll see better and better optimization.

[+] andhow|13 years ago|reply
(Luke Wagner from Mozilla here.)

> This seems very much targeted at emscripten and not to cross-compilers that start with GC'ed languages like GWT, Dart, ClojureScript, et al.

That's correct, although one could implement a garbage collector on top of the typedarray heap (we have working examples of this). There are some limitations to GCing the typed array manually, though, such as not taking advantage of the browser's ability to better schedule GCs.

Looking further in the future, though, it would be completely reasonable to extend asm.js to allow the super-optimizable use of the upcoming BinaryData API [1] in the style of JVM/CLR-style objects. Again, though, this is speculative; BinaryData isn't even standardized yet.

> It's also unclear to me how this solves the problem of startup time on mobile.

We have several strategies to improve this. The "use asm" directive, in addition to allowing us to produce useful diagnostic messages to devs when there is an asm.js type error, allows us to confidently attempt eager compilation which can happen on another thread while, e.g., the browser is downloading art assets. Looking farther in the future again, we could make some relatively simple extensions to the set of Transferrable [2] objects that would allow efficient programmer-controlled caching of asm.js code including jit code using the IndexedDB object store.

> In all likelihood, the majority of asm.js outputs would be actually be non-human readable output of optimizing cross-compilers, so there isn't much benefit from having a readable syntax that humans could read, so what's the real justification for using JS as an intermediate representation over say, a syntax specifically designed for minimum network overhead and maximum startup speed?

Before minification, asm.js is fairly readable once you understand the basic patterns, assuming your compiler keeps symbolic names (Emscripten does). The primary benefit is that asm.js runs right now, rather efficiently, in all major browsers. It also poses zero standardization effort (no new semantics) and rather low implementation effort (the asm.js type system can be implemented with a simple recursive traversal on the parse tree the generates IR using the JS VM's existing JIT backend). This increases the chances that other engines will adopt the same optimization scheme. A solution for native performance is only a solution if it is portable and we want to maximize that probability.

> The usual response is minify + gzip, but it's not a panacea.

In addition to minify+gzip, one can also write a decompressor in asm.js that unpacks the larger program. Also, see [3] for how minified gzipped Emscripten code is comparable to gzipped object files.

[1] http://wiki.ecmascript.org/doku.php?id=harmony:binary_data [2] http://www.whatwg.org/specs/web-apps/current-work/multipage/... [3] http://mozakai.blogspot.com/2011/11/code-size-when-compiling...

[+] azakai|13 years ago|reply
> This seems very much targeted at emscripten and not to cross-compilers that start with GC'ed languages like GWT, Dart, ClojureScript

Those languages can benefit as well (but might not make sense in all cases). You can compile Java to C and then to JS, here's a demo

http://xmlvm.org/html5/

Yes, this has downsides like compiling your own GC. For GC heavy code it might be slow. But for raw computation you would probably get faster results than source-to-source like GWT does - you get LLVM optimizations, and you get asm.js which is easier for JS engines to run quickly.

> It's also unclear to me how this solves the problem of startup time on mobile.

Yeah, that is a problem. It's a problem on normal JS too, and also for things like PNaCl. Only shipping final native binaries can fully avoid that (but that is nonportable).

> The spec seems to argue that Browser VMs could recognize asm.js and if I read between the lines, employ snapshoting the app and caching it for later quick startup?

Yes, that is one possibility. It could work for normal JS too, I'm not sure why it hasn't been done yet. Worth investigating.

> In all likelihood, the majority of asm.js outputs would be actually be non-human readable output of optimizing cross-compilers,

Yes.

> so there isn't much benefit from having a readable syntax that humans could read, so what's the real justification for using JS as an intermediate representation over say, a syntax specifically designed for minimum network overhead and maximum startup speed?

The justification is that asm.js code will work, right now, in all browsers. (And it can fairly easily be optimized by them to run much faster than compiled code in JS.) Any new format would require standardization and take a long time. asm.js is just JS.

For network transmission, we should implement a special minifier for it (written in JS of course).

[+] pcwalton|13 years ago|reply
"I work on GWT, and asm.js as an output target is very interesting to me (I've worked on a number of performance Web games using it, GwtQuake, AngryBirds, etc), but the starting assumption is GC, so I want to leverage non-boxed numerics, and all of the other nice stuff, but don't want to stuff everything into a TypedArray."

I'll let dherman elaborate in more detail, but he is working on it. :)

"The spec seems to argue that Browser VMs could recognize asm.js and if I read between the lines, employ snapshoting the app and caching it for later quick startup?"

This is really a problem with any portable code delivery format that wants to run compiled code. You either have to stick to native code, in which case you aren't portable, or you have to compile on the client, which adds startup time. Caching of compiled artifacts is going to be necessary in any portable solution, and I don't see any reason off the top of my head why this would be particularly different for asm.js.

"Seems like it might be worthwhile for Mozilla to also work on efficient representations of asm.js that minimize this overhead. The usual response is minify + gzip, but it's not a panacea."

I agree, and I've talked to Alon about this. I think that it would be an interesting project to develop a format that compresses asm.js down as small as possible. Then you can simply uncompress and eval on the client side to ensure backwards compatibility.

[+] evilpie|13 years ago|reply
https://bugzilla.mozilla.org/show_bug.cgi?id=840282 has some measurements on how fast the implementation of asm.js (called OdinMonkey) already is. "sm" is SpiderMonkey, that is the normal JS engine. v8 is Chrome's JavaScript engine.

Current results on large Emscripten codebases are as follows, reported as factor slowdown compared to gcc -O2 (so 1.0 would mean "same speed")

               odin (now)  odin (next)  sm      v8  
  skinning     2.80        2.46         12.90   59.35  
  zlib         2.02        1.61         5.15    5.95  
  bullet       2.16        1.79         12.31   9.30
[+] shardling|13 years ago|reply
I kinda hope that someone at Mozilla keeps a count of just how many monkeys have gone into the code base. Off the top of my head, there's SpiderMonkey, TraceMonkey, JagerMonkey, IonMonkey, ScreamingMonkey, IronMonkey, and perhaps you should count Tamarin. It would be neat to see mascot like versions of them all... :)
[+] willvarfar|13 years ago|reply
The cool thing is that those of us who have small performance-critical javascript routines e.g. game engines have a whole new cheatsheet of 'optimal' javascript. I can't wait for box2d, octrees and matrix libraries to adopt it; a whole new generation of hand-optimised assembler!
[+] shardling|13 years ago|reply
Last I read up on it, the version of box2d compiled with emscripten performed a lot better than any of the "hand tuned" js versions! :)
[+] mhd|13 years ago|reply
Reminds me a bit of efforts like C--, which sought to seek a simpler pseudo-assembly used as some kind of intermediate language for compilers. But those efforts never gained much traction, whereas I think that some transpilers actually exploited a few features beyond even normal, full-fleged C -- namely GCC extensions -- to make some features easier and/or faster (it's been a while, but it was probably some trampolining optimization).

Let's see how it turns out.

As for something completely different, I've always wondered how it would be to program a webapp in a rather different language than JS -- most transpiled languages aren't that fundamentally different from JS. And Emscripten seems mostly used to port some code that "runs in a box". Wonder how far one could come doing some stereotypically Web 2.0 things in e.g. Pascal.

[+] adamnemecek|13 years ago|reply
I've become increasingly convinced that a standardized VM in the browser that other languages could target would be the best idea. And we could forget that the whole JS thing ever happened.
[+] dherman|13 years ago|reply
Best of luck... ;-P

Seriously, asm.js already comes pretty close to a standardized VM at a low level. But we intend to grow it to include integration with structured binary data and GC, to the point where it'll provide very similar functionality to VM's like the JVM or CLR.

The big real-world benefits of asm.js over a boil-the-ocean VM are that (a) the content is already 100% compatible and quite highly optimized in existing browsers and engines, and (b) it's far easier for engines to implement on top of their existing technology.

The biggest downside is that the syntax is weird. But that's just not a big deal for compilers. They can generate whatever weird syntax they want. You could even implement your preferred bytecode format as an IR and compile that to asm.js if you wanted, but I'm not sure compiler-writers would even care that much. They'd do just as well to have IR data structures in the internals of their compiler, without needing a special surface syntax for it.

[+] jws|13 years ago|reply
There was a company in the '90s that was working on a byte code representation of programs which was designed to be “optimally” small by encoding more at the AST level than the ASM level. There were papers, but they were acquired by Microsoft (I think) and vanished. Does anyone remember them? The papers would be interesting in light of this, and conveniently near the end of patent lifetimes if patented and solid prior art if not.

Edit: Maybe I'm thinking of Michael Franz and Thomas Kistler's work on “Slim Binaries”. It matches the content if not the ambiance that I remember. It's been a while. My brain tapes could have print-through.

“A Tree-Based Alternative to Jave Byte-Codes” http://www.ics.uci.edu/~franz/Site/pubs-pdf/C05.pdf

“Adaptive Compression of Syntax Trees and Iterative Dynamic Code Optimization: Two Basic Technologies for Mobile-Object Systems” http://www.ics.uci.edu/~franz/Site/pubs-pdf/BC01.pdf

[+] kibwen|13 years ago|reply
I really can't tell what you're advocating here. It seems as though you're against JS in general, but I think you'll have a tough time arguing that JS isn't: 1) standardized, 2) a VM, 3) in the browser, or 4) a target for other languages.
[+] chc|13 years ago|reply
Forcing everybody to use the a ubiquitous, homogeneous VM implementation is probably not going to work. Browser makers like being able to improve their implementations on their own at a brisk clip. Also, while standardization is a good thing, competing implementations are also a good thing.
[+] tinco|13 years ago|reply
I thought we had made a standardized VM language called Javascript so we could forget this whole activeX thing ever happened..?

Not that I disagree that a VM would be very awesome, but it should take as many clues from the collected knowledge of Javascript security as possible, obviously just hooking your 'sandboxed' VM to the browser doesn't cut it. As demonstrated by SUN's Java applet :)

[+] jws|13 years ago|reply
LLVM byte code in an OS secured jail/sandbox? The JIT is already there and BSD licensed so all the players can use it. You'd really have to trust your sandbox though.

The API for what you what you can do out of your sandbox would be the hard part. Every capability you add to the API is also a lurking attack vector in each implementation.

[+] dysoco|13 years ago|reply
I agree... I don't know who thinks having Javascript as "bytecode for the web" is any good: Even other languages(Fay, Clojurescript) need to compile to JS.

Wouldn't it be better if we had a standarized, fast, language that other languages like the aforementioned can target?

[+] onassar|13 years ago|reply
Any thoughts on where this could be useful? The context and purpose of it goes a little over my head.
[+] dherman|13 years ago|reply
Compilers like Emscripten and Mandreel, which already generate code similar to asm.js, can be modified (we already have it implemented for Emscripten, it's not a big change) to generate valid asm.js. Then engines that recognize it can optimize the code even further than existing optimizations. Some of the technical benefits:

* ahead-of-time compilation and optimization instead of heuristic JIT compilation

* heap access can be made extremely efficient, close to native pointer accesses on most (all? still experimenting) major platforms

* integers and doubles are represented completely unboxed -- no dynamic type guards

* absolutely no GC interaction or pauses

* no JIT guards, bailouts, deoptimizations, etc. necessary

But the bottom line here is: asm.js can be implemented massively faster than anything existing JS engines can do, and it's closing the gap to native more than ever. Not only that, it's significantly easier to implement in an existing JS engine than from-scratch technologies like NaCl/PNaCl. Luke Wagner has implemented our optimizing asm.js engine entirely by himself in the matter of a few months.

As the site says, the spec is a work in progress but it's nearly done. Our prototype implementation in Firefox is almost done and will hopefully land in the coming few months. Alon Zakai is presenting some of the ideas at http://mloc-js.com tomorrow, including an overview of the ideas and some preliminary benchmarks. We'll post his slides afterwards.

[+] kevingadd|13 years ago|reply
It lets a cross-compiler like Emscripten (and theoretically more weird ones like JSIL, GWT, etc) generate JavaScript that can be jitted into code that has performance (and semantics) closer to native code.

So, for example, you can provide clear hints that value x should be an int32, value y should be a float, etc. And if you create a virtual heap out of a Typed Array, asm.js lets you ensure that the JIT maps uses of that array to direct memory accesses where possible (instead of bounds-checked array lookups).

[+] pcwalton|13 years ago|reply
It's basically a way to ship native code on the Web that's compatible with existing browsers, with the same performance as native code in engines with full support for it. (Getting to complete parity with native code will take time, but the language has been carefully designed to allow that — and we're close already.)
[+] JoshTriplett|13 years ago|reply
I like the idea of this, but it bugs me that it still uses doubles and only simulates integers via optimizations in the JavaScript compiler. Why has no JavaScript extension arisen to supply real integers?

One notable side effect of this: asm.js only seems to support 32-bit integers, not 64-bit or larger integers.

[+] kevingadd|13 years ago|reply
JS has 'real integers', they're not being simulated. If you put the appropriate hints in your JS the JITted output will never use a float anywhere. Your complaint is more that all the operators (with the exception of the bitwise ones) operate on floats, and yes, that is kind of a problem.

64 bit integer support is being worked on for JS elsewhere; asm.js probably doesn't offer it yet since you can't use 64 bit ints in any browser yet.

[+] msvan|13 years ago|reply
This seems more realistic than Google's grand plans of displacing JavaScript with Dart. Let's hope it gains traction!
[+] spankalee|13 years ago|reply
This would probably help the Dart effort a lot by providing a sane compilation target.
[+] edtechdev|13 years ago|reply
It's still a bit surprising that types were never (and still haven't been) added to javascript, as proposed for javascript 2.0 back in 1999: http://web.archive.org/web/20000817085058/http://www.mozilla...

Now we have Google's Dart & Closure compilers, Microsoft's TypeScript, and Mozilla's asm, all of which essentially add types back to javascript, not to mention about two dozen other statically typed javascript compilers: https://github.com/jashkenas/coffee-script/wiki/List-of-lang...

If types were approved 13 years ago, javascript apps could have been made to run much faster (fast enough for games even), perhaps negating a need for 'native' mobile apps that we have today, and either hastening the demise or spurring the optimization of competitors like Flash and Java and .NET/Silverlight.

(I'm already aware of arguments against static typing, and against having a VM in the browser or treating javascript like one.)

[+] dherman|13 years ago|reply
It's a lot harder to add types to a general purpose programming language. Your types have to match actual programming idioms, and if you care about them being safe (which, to be fair, recent languages like Dart and TypeScript don't), you have to consider every possible loophole that could lead to a dynamic error -- and the legacy language fights you, because all of its dynamism was designed back when nobody was thinking about respecting some not-yet-existent type system.

The type system for asm.js is a far more restricted problem, which is why we were able to come up with a solution so quickly (we only started this project in late 2012). The type hierarchy is extremely spartan, and it's just designed to map a low-level language (intended for compilers to be writing in) to the low-level machine model of modern computers.

[+] kevingadd|13 years ago|reply
Adding a static type system to JavaScript would have, IMO, been a losing effort. You can take a look at ActionScript 3 for an example of the challenges that come up.

Once you've got a static type system, you need a GOOD static type system, and imposing one of those on JS without breaking backwards compatibility would have been an ordeal and complicated code tremendously.

[+] rntz|13 years ago|reply
The fact that you can't represent function pointers (not closures, just plain old C-style function pointers) in asm.js severely limits its usability as a target language for even C-like languages.
[+] marijn|13 years ago|reply
Can you explain the use case where you need need function pointers but closure pointers won't do?
[+] likeclockwork|13 years ago|reply
Wow. The devs at Mozilla are really working it.

I mean, with empscripten, Firefox OS, Firefox browser, and now asm.js... they're about to force everyone onto their own terms.

This is clearly a big move and the beginning of a major victory for Mozilla and all users and developers.

Serving native apps, in the browser, with JS. And everyone is going to have no choice but to follow them, because all other browsers will fallback to their regular JS interpretter/JIT if they don't optimize on asm.js.

We're talking games and applications that will run an order of magnitude faster on Firefox than in other platforms out of the gate. But they'll still run everywhere, just very slowly.

[+] AshleysBrain|13 years ago|reply
Any thoughts on whether there could be a JS -> Asm.JS compiler? Might be a handy way to get rid of GC pauses - and maybe even improve performance - for existing JS code.

Or even a JS -> Asm.JS compiler written in JS... so you can feature-detect and enable on demand :)

[+] btipling|13 years ago|reply
Can it still be considered a 'subset' if it adds new traits like 'int' and 'intish'? If it both adds and remove things wouldn't that make it more of a variant like scheme is to lisp?
[+] dherman|13 years ago|reply
Yes, the observable semantics is 100% identical to running the same code in an existing JS engine. That's the genius behind Emscripten (i.e., the genius of Alon Zakai) -- he figured out that you can effectively simulate the semantics of machine integer operations by exploiting the various parts of the JS semantics that internally does ToInt32/ToUint32 on its arguments.

What asm.js is simply formalize those tricks in order to guarantee that you're only using those parts of the JS semantics, so that it's sound for an optimizing engine to directly implement them with machine arithmetic and unboxed integers. But the behavior is absolutely identical to a standard JS interpreter/JIT/etc.

[+] evanprodromou|13 years ago|reply
@dherman The document says, "extraordinarily optimizable". Do you have any numbers on that?

I'd love to see benchmarks from running asm.js-compatible JavaScript on e.g. SpiderMonkey versus an asm.js-optimizing SpiderMonkey.

Are we talking about incremental differences of 5%, 25%, even 50%, or orders-of-magnitude improvement?

[+] jlebar|13 years ago|reply
Have you guys thought about memory management in the ArrayBuffer "heap"? One can decommit pages from a real heap, which can be a pretty important optimization.
[+] ksec|13 years ago|reply
Would asm.js reduce JS memory usage?

Would non computational scripts benefits from this? Things that are widely used like jQuery.