> I feel like PNaCl is the technically superior approach - define a stable set of LLVM bytecode and build an interface to run it in the browser.
I don't agree. I think that LLVM bitcode was not really designed for this either [1]. Google has not succeeded in eliminating all of the undefined behavior from LLVM [2]. At least asm.js has a fully defined semantics as specified by ECMA-262. For any hope of interoperability between browsers, that's critical. Things like order of iteration of properties were once left undefined by ECMA-262, but pages began to rely on Netscape's behavior and other browsers had to reverse engineer what Netscape did. Eventually they were added to the standard. Likewise, without all the undefined behavior removed from PNaCl, anyone who wanted a separate implementation of PNaCl would have to reverse engineer what Chrome did.
There is also the issue of compilation speed: asm.js was designed to compile quickly by a backend that doesn't do much optimization. LLVM, on the other hand, was optimized for -O2, which compiles much less quickly. PNaCl at -O0 is far worse than either V8 or OdinMonkey. On the Web, compilation speed matters a lot.
I think an actually technically superior approach, if backwards compatibility were not a concern, would involve:
1. A from-scratch, untyped, non-SSA bytecode, basically an isomorphism to asm.js. All operations would have fully defined semantics. This would yield fast compilation without the JavaScript syntax and would be amenable to multiple backends, like LLVM or V8.
2. A mechanism for providing a C binding to WebIDL-based Web APIs. This would eliminate the necessity of Pepper and would mean that any new Web APIs would instantly be available to native code.
PNaCl, unfortunately, is neither of these. asm.js isn't either, but I think it's closer to this ideal than PNaCl is.
I think quoting Dan's email repeatedly is entirely unhelpful, I'd appreciate it if you wouldn't to that. We've actually addressed every single point in Dan's email, talked to Dan repeatedly, and gone beyond what he originally wrote. Sure it has a catchy title, but if you actually read it (and the replies it generated back then), and then compare to what PNaCl does (including discussions on the LLVM mailing list) you'll see that I'm not making things up here. Our IR subset was designed to be portable and stable.
Undefined behavior: it's mostly gone by the time the pexe is created, and our intent is to remove all that's left (e.g. shift by larger than bit width is trivial to fix). I think what's left is mostly non-issues that can't be easily fixed without breakage, so honestly I don't think undefined behavior is any kind of a deal breaker at this point. asm.js still doesn't have canonical NaNs so it's not fully defined ;-)
Note for other readers here: yes C/C++ have undefined behavior, but both PNaCl and asm.js settle on actual behavior before something gets shipped to the browser.
On compile speed: agreed, but I'll take the "it's getting better" approach here (hey, asm.js can do it for runtime!).
I'm not quite sure why having a non-SSA representation would be a requirement. I agree that there are advantages, but I think that's true of both approaches.
> I think quoting Dan's email repeatedly is entirely unhelpful, I'd appreciate it if you wouldn't to that. We've actually addressed every single point in Dan's email, talked to Dan repeatedly, and gone beyond what he originally wrote. Sure it has a catchy title, but if you actually read it (and the replies it generated back then), and then compare to what PNaCl does (including discussions on the LLVM mailing list) you'll see that I'm not making things up here. Our IR subset was designed to be portable and stable.
My point wasn't to imply that PNaCl has done a bad job moving mountains to make LLVM into something stable and portable—in fact, the project has done an incredible job with it. I'm just saying that LLVM is not really any more of a natural fit than JS is. JS starts with compatibility and defined semantics and needs to add a low-level type system to become a portable native runtime. LLVM starts with a low-level type system and needs to add compatibility and a defined semantics to become a portable native runtime. In neither case was the system designed for it, as Dan's email shows.
> Undefined behavior: it's mostly gone by the time the pexe is created, and our intent is to remove all that's left (e.g. shift by larger than bit width is trivial to fix). I think what's left is mostly non-issues that can't be easily fixed without breakage, so honestly I don't think undefined behavior is any kind of a deal breaker at this point.
I just wish PNaCl hadn't shipped with any known undefined behavior. (I don't believe NaN canonicalization problems were known at the time asm.js shipped.)
Does PNaCl still have undefined behavior if you access outside a valid allocated region, as that document states ("accessing undefined/freed memory")? It is defined in asm.js, as the application ships its own malloc and there is no type system to tell the runtime what kind of pointer something is. Supposedly Microsoft got into trouble with their libc's malloc because they couldn't change it without breaking apps that relied on accessing freed memory in certain ways. I can see that being true on the Web as well…
Oh I see your point on being a proper fit. It's not much of an argument then, since you're just saying that making it work when it wasn't quite designed for that use is hard, but we did make it work. So I agree :-)
I also wish there weren't any UD, but we did try to do a good list and I think we chose which to fix and which to punt on purpose.
I see memory allocation (both for code and data) as well-defined randomness: saying that the allocated base addresses are random allows many interesting types of sandboxing to be used which couldn't be used otherwise (more on that in the future). I think this will lead to interesting perf gain in some cases, and interesting security gains in others. More pragmatically it currently allows ASLR while still providing memory access without indirection (i.e. reg-reg addressing) on x86-32 and ARM.
SSA tends to result in a larger on-the-wire format since you have more values and phi nodes. Interpretation is slower because you end up with a lot of values unless you do some sort of register allocation, and you have to interpret phi nodes. If and when you need SSA, the dominance frontiers algorithm is fast in practice (as IonMonkey/Crankshaft show).
While you may be right with regards to the file size (although most SSA-representations are not built for small size, there is quite some potential to reduce it, imho), you'll have to do register allocation/spilling/etc. in your VM/compiler anyway and that is easier and faster on SSA[1, and more]. The dominance frontiers algorithm is actually not that good imho, there are better options[2], especially considering that one may not want to construct an unnecessary dominance tree in an Interpreter/VM.
Now, I have no experience how much overhead SSA-deconstruction inflicts when lowering to machine code.
AFAIK V8 does not use dominance frontier based algorithm for SSA construction. It just inserts phis eagerly at loops headers and on forward edges it inserts phis when merge is needed.
I don't agree. I think that LLVM bitcode was not really designed for this either [1]. Google has not succeeded in eliminating all of the undefined behavior from LLVM [2]. At least asm.js has a fully defined semantics as specified by ECMA-262. For any hope of interoperability between browsers, that's critical. Things like order of iteration of properties were once left undefined by ECMA-262, but pages began to rely on Netscape's behavior and other browsers had to reverse engineer what Netscape did. Eventually they were added to the standard. Likewise, without all the undefined behavior removed from PNaCl, anyone who wanted a separate implementation of PNaCl would have to reverse engineer what Chrome did.
There is also the issue of compilation speed: asm.js was designed to compile quickly by a backend that doesn't do much optimization. LLVM, on the other hand, was optimized for -O2, which compiles much less quickly. PNaCl at -O0 is far worse than either V8 or OdinMonkey. On the Web, compilation speed matters a lot.
I think an actually technically superior approach, if backwards compatibility were not a concern, would involve:
1. A from-scratch, untyped, non-SSA bytecode, basically an isomorphism to asm.js. All operations would have fully defined semantics. This would yield fast compilation without the JavaScript syntax and would be amenable to multiple backends, like LLVM or V8.
2. A mechanism for providing a C binding to WebIDL-based Web APIs. This would eliminate the necessity of Pepper and would mean that any new Web APIs would instantly be available to native code.
PNaCl, unfortunately, is neither of these. asm.js isn't either, but I think it's closer to this ideal than PNaCl is.
[1]: http://lists.cs.uiuc.edu/pipermail/llvmdev/2011-October/0437...
[2]: http://www.chromium.org/nativeclient/pnacl/stability-of-the-...