> 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.
[1]: http://lists.cs.uiuc.edu/pipermail/llvmdev/2011-October/0437...
[2]: http://www.chromium.org/nativeclient/pnacl/stability-of-the-...