WASM is not quite a stack machine

(purplesyringa.moe)

45 points | by signa11 6 hours ago

6 comments

  • asibahi 13 minutes ago
    I dont really disagree with the main premise of the article, which is that WASM is not really a stack language, but this part just gave me pause:

    > In textual Wasm, for example, they are instead represented in a LISP-like notation – not any less or more efficient

    The Text format, at least when it comes to instructions, it 1 to 1 with the binary format. The LISP-like syntax is mainly just syntax sugar[1].

        ‘(’ plaininstr  instrs ‘)’ ≡  instrs plaininstr
    
    So (in theory, as far as I understand it) you can just do `(local.get 2 local.get 0 local.get 1)` to mean `local.get 0 local.get 1 local.get 2`, and it works for (almost) any instruction.

    Unfortunately, in my limited testing, tools like `wat2wasm` and Binaryen's `wasm-as` don't seem to adhere to (my perhaps faulty understanding of) the spec, and demand all instructions in a folded block be folded and have the "correct" amount of arguments, which makes Binaryen do weird things like

        (return
          (tuple.make     ;; Binaryen only pseudoinstruction
            (local.get 0) ;; or w/e expression
            (local.get 1) ;; or w/e expression
          )
        )
    
    
    when this is perfectly valid

        local.get 0
        local.get 1
        return
    
    
    tl;dr: the LISP syntax is just syntax sugar. The textual format is as "stack-like" as the binary format.

    [1]: https://webassembly.github.io/spec/core/text/instructions.ht...

    • soegaard 0 minutes ago
      FWIW if you are looking for examples of WebAssembly written in the textual format, take a look at:

      https://raw.githubusercontent.com/soegaard/webracket/refs/he...

      As a small example, here is a definition of `$car` which extracts the first value from a pair.

          (func $car (type $Prim1) 
                     (param $v (ref eq)) 
                     (result (ref eq))
            (if (result (ref eq)) (ref.test (ref $Pair) (local.get $v))
                (then (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $v))))
                (else (call $raise-pair-expected (local.get $v))
                      (unreachable))))
  • ufo 53 minutes ago
    The author seems to complain about a lack of stack manip expressions like dup and rot, but at least for me that's what I would expect from an average programming language stack machine. Even Java, which does have those instructions, doesn't use them --- reuse happens via local variables.

    The way I see it, the difference between register and stack vms is all about the instruction encoding. Register VMs have fatter instructions in exchange for needing fewer LOAD and STORE operations. Despite the name, register VMs also have a stack.

    • pjjpo 6 minutes ago
      > Despite the name, register VMs also have a stack.

      Out of curiosity what do you think about this - in spite of the name, stack machines also have yet another stack. Ok I don't like that wording, but locals are basically the stack frames people know of from their computer arch class I think.

      It doesn't change the fact that Wasm operations have to have the execution stack as one or more of the operands. Seems like a stack machines to me too, though I don't know more details on why the specific design of Wasm would make optimizing compilers harder to write than JVM as the article suggests (I think?).

    • U1F984 9 minutes ago
      Java does use dup in some cases, e.g.

         public static void test() { 
            new Object();
         }
      
               0: new           #2                  // class java/lang/Object
               3: dup
               4: invokespecial #1                  // Method java/lang/Object."<init>":()V
               7: pop
               8: return
  • stevefan1999 2 hours ago
    I'm trying to implement a WASM to C compiler, and because of that not-quite-so-stack behavior, I can actually guarantee that it will always build an expression and I don't have to discard or reset stack value! Everything stays within that function, which is very neat, and I think it is one of the reason WAT, the textual format is so neat, that you can represent it with a S-Expression.
  • Hendrikto 43 minutes ago
    The series of articles linked at the end (troubles.md/posts/wasm-is-not-a-stack-machine/) is even more interesting, imo.

    Very well articulated and concise critique by somebody who seems to have a great amount of knowledge and experience with the topics.

  • shevy-java 4 minutes ago
    I am sad about WASM. It was a promise for epic greatness.

    It has failed to deliver that - so much is clear now. You rarely see any awesome success story shown with regard to WASM nowadays. What happened to the old promises? "Electron will be SUPER fast thanks to WASM" or "use any language, WASM unifies it all for the larger browser ecosystem".

    It feels as if WASM is on a step towards exctinction. Sure, it is mentioned, it is used, but let's be honest - only few people really use it. And that won't change either.

  • kg 1 hour ago
    The lack of a dup opcode in Wasm as mentioned in the post is quite annoying when trying to generate compact code. I wish something like it had made it into the spec.
    • thomasmg 32 minutes ago
      You could use "local.tee". I kind of is "store" + "duplicate".
      • asibahi 22 minutes ago
        `local.tee` doesn't duplicate. it just doesn't remove the value from the stack. (so it is "just" `local.set` followed by `local.get`)