There were 3 systems, all with interesting differences.
The original NaCl was a 'validated subset' of native CPU machine code (e.g. actual x86 machine code with some instructions and instruction sequences disallowed which would allow to escape the sandbox).
The next iteration was P(ortable)-NaCl which replaced the native machine code with a subset of LLVM bitcode, which was then compiled at load time. Unfortunately with this step NaCl lost most of its advantages. Startup time was atrocious because it was basically the second half of the LLVM compilation pipeline (from LLVM-IR to machine code). LLVM-IR also isn't actually great as CPU-agnostic bytecode.
WASM was designed from the ground up as CPU agnostic bytecode that's also much easier and faster to validate.
The only major advantage of PNaCl vs early WASM was that PNaCl supported shared-memory threading right from the start (this is still knee-capped in WASM because of the COOP/COEP response header requirement).
...apart from Emscripten => asm.js => WASM, and Google's NaCl/PNaCl there was also a system by Adobe (Flascc/Alchemy(?) I forgot all the names this went through) to compile C and C++ code into Adobe Flash bytecode.
I have an ancient blogpost from 2012 which compares the three (and where I have been flabbergasted by how well Emscripten actually worked - and this was even before asm.js - the linked demo is unfortunately no longer up):
> The only major advantage of PNaCl vs early WASM was that PNaCl supported shared-memory threading right from the start (this is still knee-capped in WASM because of the COOP/COEP response header requirement).
Presumably that is because PNaCl predated spectre (?)
> (Flascc/Alchemy(?) I forgot all the names this went through)
Crossbridge was its third and most recent name.
Pepper.js is another interesting project from around this time that didn't pan out.
> The original NaCl was a 'validated subset' of native CPU machine code (e.g. actual x86 machine code with some instructions and instruction sequences disallowed which would allow to escape the sandbox).
Out of curiosity, does that mean that NaCL (without P) only ran on x86? Or were there different subsets for different architectures?
Heh, emscripten. I remember running the Unreal engine in the browser with that. Quite impressive indeed.
> Alchemy
Yep, that's the name. There was a brief period in late 2000s when Adobe was pushing hard to make Flash embedded into web ecosystem. They made Air as a way to package Web or Flash code into a desktop app. Essentially it was Electron-before-Electron. Alchemy was a part of this grand plan to be able to integrate existing native libraries with Flash code. The plan was like you said to compile to Flash bytecode, and AFAIK it never went further than a tech demo.
This whole ecosystem turned into a slow train-wreck over approximately 5-year period. Adobe really saw themselves as future stewards of web technology. They donated their ActionScript VM and a JIT to Mozilla and hoped that Firefox would become the first browser with fast JavaScript engine. Google developed Chrome and V8 in secret and managed to release their fast browser early. Microsoft and Yahoo sabotaged adoption of ActionScript dialect as at the next JavaScript. And at the same time Apple went fully anti-plugins, and with the rise of iPhone both Flash and silverlight died off.
Years later Java folks tried to build their own version of Alchemy as part of GraalVM. The project was called Sulong and was using Graal to execute and JIT LLMV bitcode. TruffleRuby was supposed to be a primary early beneficiary to be able to compile and run Ruby native extensions. This was during the period of a race between several JIT solutions in hopes to become "the next Ruby", and Truffle team (along with IBM's OMR) lost the race first to MJIT and then to YJIT. Graal itself seems to loosing steam, because their multi-language VM never got enough adoption among Java, Node, or Ruby people, and the VM itself tended to use too much RAM in era when RAM became premium in the cloud.