What’s in that .wasm? Introducing wasm-decompile
v8.dev
v8.dev
(module
(type (;0;) (func (param i32 i32) (result f32)))
(import "env" "__linear_memory" (memory (;0;) 0))
(import "env" "__indirect_function_table" (table (;0;) 0 funcref))
(func (;0;) (type 0) (param i32 i32) (result f32)
(f32.add
(f32.add
(f32.mul
(f32.load
(local.get 0))
(f32.load
(local.get 1)))
(f32.mul
(f32.load offset=4
(local.get 0))
(f32.load offset=4
(local.get 1))))
(f32.mul
(f32.load offset=8
(local.get 0))
(f32.load offset=8
(local.get 1))))))
wasm2c (also from WABT) returns this thing: https://paste.linux.community/view/7877995fSee: https://en.wikipedia.org/wiki/Basic_block
WASM instead provides traditional control structures. So the compiler either has to preserve control structures through to the IR, or has to work backwards from basic blocks to control structures. Both options are undesirable, from the perspective of compiler writers, and would be unnecessary if the VM were a greenfield project.
It was built to be a better target than asm.js for compiled languages to run in JavaScript VMs and it seems to have succeeded on that front. That it's not a perfect fully-general bytecode format doesn't seem like a real knock against it, that's a much harder problem to solve. In any event it seems to be getting a lot more traction than any previous entries in the space, which is exciting!
Liveness information simply doesn’t belong in the bytecode. SSA is trivial to recreate from local mutable variables (it would make a good homework assignment for someone in an undergrad “intro to compilers” class). WebAssembly is obviously a register machine.
> With this, it becomes possible to get rid of locals entirely.
Both factually incorrect and pointless. There is no tangible benefit in getting rid of locals entirely. You are merely changing out one representation (register machine) for a different one (stack machine).
There are valid criticisms of WASM, but the linked article doesn’t have any.
The weird part of WASM is the control structures. The rest of it is a fairly sensible, actually rather nice register machine. You can see that older bytecode systems like the JVM are stack machines, but newer ones tend to be register machines. This isn’t because people are getting stupid, it’s because there are legitimate reasons to prefer register machines, and on the balance of things, my observations are that people with experience in the field tend to prefer register machines.
> You can see that older bytecode systems like the JVM are stack machines, but newer ones tend to be register machines.
Like LLVM. Is there a good reason for WebAssembly not being SSA?
The purpose of SSA is to make it easier to write optimizations. However, you wouldn’t be optimizing WASM anyway, you would necessarily translate it first into some kind of IR suitable for optimization passes. Might as well convert to SSA at that point, rather than bloat the bytecode by exposing implementation details of the target.
Remember that WASM’s purpose is to be portable and safe. Making it more complicated just in order to make sophisticated back-ends slightly faster is a net loss. Using SSA would also make naïve/simple backends slower.
Out of curiosity, what do you find weird about the control structure portion?
I just wrote a basic language that compiles to wasm and found the built in control structures made my life easier.
Overall the main negative point seems to be that compilers toward wasm are more complex, but under the constraints of the web moving complexity from the client looks like a valuable positive point.
Two other negative points would possibly be performance and code size (which is the reason for a if-then-else specific instruction for example) about which I know very little. Do you think it would have made a difference here?
> my observations are that people with experience in the field tend to prefer register machines
That's actually the opposite of my observation, they seem to prefer stack machines.
To me, the distinction here is that the stack machine in WASM is restricted to the point that it corresponds 1:1 with an expression tree—not even a graph, just a tree. This means that every function in Web Assembly can be thought of as a collections of statements and expressions, and the stack machine abstraction is nothing more than a serialization format for the expressions.
Maybe dial it back a bit with the challenge to point at literature. The literature has not really caught up with the existence of WASM yet.
A defining feature of a register machine is that the actual instruction encoding has direct references to source and destination registers in it. Wasm doesn't have those, it has explicit get_local instructions instead.
That said, if you turn off LLVM's WebAssemblyRegStackify pass, all LLVM IR's values will end up in locals, with little to no stack usage. Still no register machine, but a bit more of a grey area :)
It wasn't me who claimed that WASM is "obviously" a register machine, despite the inventors saying otherwise. They even explicitly state that they decided against a register machine. I guess it's then reasonable for me to ask on what definition of stack vs register you are basing this opinion on. Let me be clear: I was not asking here for literature about WASM specifically but a definition of register/stack machines that supports your claim.
WASM's instruction encoding is very much based on a stack machine. Even with the initial limitations you mentioned I don't think it qualifies as "obviously a register machine". As already mentioned in multiple comments those restrictions were already lifted with the multi value proposal.
I understand that there is a grey area, but simply claiming "obviously a register machine" doesn't seem right to me. Implementation-wise WASM is a stack machine even if it needs/needed locals to be turing-complete.
(f32.add
(f32.add
(f32.mul
(f32.load
(local.get 0))
(f32.load
(local.get 1)))
(f32.mul
(f32.load offset=4
(local.get 0))
(f32.load offset=4
(local.get 1))))
(f32.mul
(f32.load offset=8
(local.get 0))
(f32.load offset=8
(local.get 1))))
The outer f32.add could translate into a byte code instruction that finds its two operands on a stack, or to one which gets them from registers.The code only says that there is a f32.add call which has two operands that are the result of a f32.add and f32.mul and so on.
The implementations will agree in their treatment of locals: that there are two locals 0 and 1, which support loading at offsets and such.
Both stack and register machines can support locals.
Just read the standard, it's quite clear from the semantics that it's a stack machine: https://webassembly.github.io/spec/core/exec/runtime.html#st... https://webassembly.github.io/spec/core/exec/instructions.ht...
In fact, the construction of a basic block from flat code is a form of recovery of control structure. If the original code had nested loops, nesting will emerge in the basic block graph.
A recursive traversal of the original structure could produce that graph more directly; it doesn't have to walk a flat list of instructions asking, "does this have a label on it which is the target of a branch". without having to walk a flat list of instructions asking questions like "is this the target of a branch".
For instance, if we walk an if/then/else AST node, we can recursively get the basic block graph for the test, the then and else part, and then integrate that into a larger basic block graph according to a rigid pattern.
> `wasm-decompile` produces output that tries to look like a "very average programming language" while still staying close to the Wasm it represents.
> #1 goal is readability
> #2 goal is to still represent Wasm as 1:1 as possible
It seems AssemblyScript would do the job
One more question please - does this tool support naming of global variables? The official wasm documented 'name' section only supports local variable names I think?
There was a project I saw too that intended to visualize WebAssembly's execution. That'd be extremely helpful too
Do you know about `wasm2wat` (from the WebAssembly binary toolkit, "WABT")? It produces a 1-to-1 text representation of the bytecode and is meant to always roundtrip via `wat2wasm` back to the same bytecode.
https://developer.mozilla.org/en-US/docs/WebAssembly/Concept...
> Its #1 goal is readability: help guide readers understand what is in a .wasm with as easy to follow code as possible. Its #2 goal is to still represent Wasm as 1:1 as possible, to not lose its utility as a disassembler. Obviously these two goals are not always unifiable.
> This output is not meant to be an actual programming language and there is currently no way to compile it back into Wasm.
If you're already developing in TypeScript, WebAssembly is a good way to generate WASM code that interoperates nicely with it, which you can't do with plain old JavaScript.
https://github.com/appcypher/awesome-wasm-langs
I've implemented one but still need to get back to it to finish it... WASM is one of the easiest targets out there, hence so many languages already targeting it. But that will change once some of the WASM proposals become a standard, especially things like GC support and WASI, which will take a team (and long term investments) rather than a lone dev to implement:
AssemblyScript: A Subset of TypeScript That Compiles to WebAssembly
https://news.ycombinator.com/item?id=15187961
https://github.com/AssemblyScript