Skip to content

wasm-opt --enable-tail-call causes 00006a3: error: unexpected opcode: 0x12 #8508

@BlackAsLight

Description

@BlackAsLight

I ran into an issue when using the -all flag. Specifically, enabling --enable-tail-call appears to produce an invalid WebAssembly binary for my code.

To isolate the problem, I created a minimal test case, but the issue does not reproduce there. So it seems to be something specific to my original codebase.

My code does not require the --enable-tail-call flag when running through wasm-opt. However, when I include either --enable-tail-call or -all and then pass the result to wasm-validate, validation fails with the following error:

00006a3: error: unexpected opcode: 0x12

The full command I’m using is:

wat2wasm src/mod.wat -o - | wasm-opt - --enable-multivalue --enable-tail-call -Oz -o - | wasm-validate -

Source

Wat Code

This code is a basic tokeniser I am experimenting with for a programming language. Do not ask me why I am writing it in wat. I also use empty block comments as markers for where I believe values are being placed on and taken off the stack for my own readability.

(module
  (memory (export "memory") 1)
  (func $scan (export "scan") (param i32 i32) (result i32 i32)
    ;; (has_error, addr)

    (;;) (i32.const 0)
    (;;) (local.get 1)
    (;;) (local.get 0)
    (block $end (param i32 i32) (result i32 i32)
    (loop $next (param i32 i32) (result i32 i32)
      (;;) (local.tee 0 (;;))
      (br_if $end (;;) (;;) (i32.eq (local.get 0) (local.get 1)))
      (;;) (local.tee 0 (i32.load8_u (local.get 0)))
      (;;) (i32.eq (;;) (i32.const 9)) ;; horizontal tab
      (;;) (i32.eq (local.get 0) (i32.const 10)) ;; line feed
      (;;) (i32.eq (local.get 0) (i32.const 13)) ;; carriage return
      (;;) (i32.eq (local.get 0) (i32.const 32)) ;; space
      (if (param i32 i32) (result i32 i32)
        (i32.or (i32.or (;;) (;;)) (i32.or (;;) (;;)))
      (then
        (;;) (i32.add (;;) (i32.const 1))
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.le_u (local.get 0) (i32.const 32)) ;; unallocated
      (then
        (local.set 0 (;;))
        (return (i32.const 1) (local.get 0)) ;; unexpected byte
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 33)) ;; exclamation mark
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 61)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x20)) ;; "!="
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x1)) ;; "!"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 34)) ;; double quotes
      (then
        (;;) (;;) (call $string_token (;;) (;;) (local.get 1))
        (;;) (local.tee 0 (;;))
        (if (i32.eqz (local.get 0)) (then
          (return (i32.const 2) (local.get 1))
        ))
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 35)) ;; hash
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x2)) ;; "#"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 36)) ;; dollar sign
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x3)) ;; "$"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 37)) ;; percent sign
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x4)) ;; "%"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 38)) ;; ampersand
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 38)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x21)) ;; "&&"
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x5)) ;; "&"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 39)) ;; single quote
      (then
        (;;) (;;) (call $char_token (;;) (;;) (local.get 1))
        (;;) (local.tee 0 (;;))
        (if (param i32) (result i32 i32) (i32.lt_u (;;) (i32.const 3)) (then
          (local.set 1 (;;))
          (return (local.get 0) (local.get 1))
        ) (else
          (;;) (local.get 0)
        ))
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 40)) ;; open parenthesis
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x6)) ;; "("
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 41)) ;; close parenthesis
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x7)) ;; ")"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 42)) ;; asterisk
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x8)) ;; "*"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 43)) ;; plus
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x9)) ;; "+"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 44)) ;; comma
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0xA)) ;; ","
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 45)) ;; minus
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 62)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x22)) ;; "->"
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0xB)) ;; "-"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 46)) ;; period
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0xC)) ;; "."
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 47)) ;; slash
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (;;) (local.tee 0 (i32.load8_u (local.get 0)))
          (if (param i32 i32) (result i32 i32)
            (i32.eq (;;) (i32.const 47))
          (then
            (;;) (;;) (call $line_comment (;;) (;;) (local.get 1))
          ) (else (if (param i32 i32) (result i32 i32)
            (i32.eq (local.get 0) (i32.const 42))
          (then
            (;;) (;;) (call $block_comment (;;) (;;) (local.get 1))
            (;;) (local.tee 1 (;;))
            (if (i32.eqz (local.get 1)) (then
              (return (i32.const 2) (local.get 1))
            ))
          ) (else
            (br $else (;;) (;;))
          ))))
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0xD)) ;; "/"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.le_u (local.get 0) (i32.const 57)) ;; 0-9
      (then
        (;;) (;;) (call $digit_token (;;) (;;) (local.get 1))
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 58)) ;; colon
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0xE)) ;; ":"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 59)) ;; semicolon
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0xF)) ;; ";"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 60)) ;; less than
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 61)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x23)) ;; "<="
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x10)) ;; "<"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 61)) ;; equal
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 61)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x24)) ;; "=="
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x11)) ;; "="
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 62)) ;; greater than
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 61)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x25)) ;; ">="
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x12)) ;; ">"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 63)) ;; question mark
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x13)) ;; "?"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 64)) ;; at sign
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x14)) ;; "@"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.le_u (local.get 0) (i32.const 90)) ;; A-Z
      (then
        (;;) (;;) (call $identifier_token (;;) (;;) (local.get 1))
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 91)) ;; opening bracket
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x15)) ;; "["
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 92)) ;; backslash
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x16)) ;; "\"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 93)) ;; closing bracket
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x17)) ;; "]"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 94)) ;; caret
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x18)) ;; "^"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 95)) ;; underscore
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x19)) ;; "_"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 96)) ;; backtick
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x1A)) ;; "`"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.le_u (local.get 0) (i32.const 122)) ;; a-z
      (then
        (;;) (;;) (call $identifier_token (;;) (;;) (local.get 1))
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 123)) ;; opening brace
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x1B)) ;; "{"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 124)) ;; vertical bar
      (then (block $then (param i32 i32) (result i32 i32)
        (block $else (param i32 i32) (result i32 i32)
          (;;) (local.tee 0 (;;))
          (;;) (local.tee 0 (i32.add (local.get 0) (i32.const 1)))
          (br_if $else (;;) (;;) (i32.eq (;;) (local.get 1)))
          (br_if $else (;;) (;;) (i32.ne
            (i32.load8_u (local.get 0))
            (i32.const 124)
          ))
          (;;) (;;) (call $double_token (;;) (;;) (i32.const 0x26)) ;; "||"
          (br $then (;;) (;;))
        )
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x1C)) ;; "|"
      )) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 125)) ;; closing brace
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x1D)) ;; "}"
      ) (else (if (param i32 i32) (result i32 i32)
        (i32.eq (local.get 0) (i32.const 126)) ;; tilde
      (then
        (;;) (;;) (call $single_token (;;) (;;) (i32.const 0x1E)) ;; "~"
      ) (else ;; unallocated
        (local.set 0 (;;))
        (return (i32.const 1) (local.get 0)) ;; unexpected byte
      ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
      (br $next (;;) (;;))
    ))
    (drop (;;))
  )

  (func $single_token (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (local.get 2)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (i32.store
      (i32.add (local.get 0) (i32.const 5))
      (local.tee 1 (i32.add (local.get 1) (i32.const 1)))
    ) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $double_token (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (local.get 2)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (i32.store
      (i32.add (local.get 0) (i32.const 5))
      (local.tee 1 (i32.add (local.get 1) (i32.const 2)))
    ) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $char_token (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (i32.const 0x30)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (;;) (local.tee 1 (i32.add (local.get 1) (i32.const 2)))
    (if (i32.ge_u (;;) (local.get 2)) (then
      (return (local.get 2) (i32.const 2)) ;; unexpected EOF
    ))
    (if (i32.ne (i32.load8_u (local.get 1)) (i32.const 39)) (then
      (return (local.get 1) (i32.const 1)) ;; unexpected byte
    ))
    (i32.store
      (i32.add (local.get 0) (i32.const 5))
      (local.tee 1 (i32.add (local.get 1) (i32.const 1)))
    ) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $string_token (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (i32.const 0x31)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (;;) (i32.add (local.get 1) (i32.const 1))
    (block $end (param i32) (result i32)
    (loop $next (param i32) (result i32)
      (;;) (local.tee 1 (;;))
      (if (i32.eq (local.get 1) (local.get 2)) (then
        (return (local.get 2) (i32.const 0)) ;; unexpected EOF
      ))
      (br_if $end (;;) (i32.eq (i32.load8_u (local.get 1)) (i32.const 34)))
      (;;) (i32.add (;;) (local.get 1))
      (br $next (;;))
    ))
    (local.set 1 (;;))
    (i32.store
      (i32.add (local.get 0) (i32.const 5))
      (local.tee 1 (i32.add (local.get 1) (i32.const 1)))
    ) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $digit_token (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (i32.const 0x32)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (;;) (i32.add (local.get 1) (i32.const 1))
    (block $end (param i32) (result i32)
    (loop $next (param i32) (result i32)
      (;;) (local.tee 1 (;;))
      (br_if $end (;;) (i32.eq (local.get 1) (local.get 2)))
      (;;) (local.tee 1 (i32.load8_u (local.get 1)))
      (br_if $end (;;) (i32.lt_u (;;) (i32.const 48)))
      (br_if $end (;;) (i32.gt_u (local.get 1) (i32.const 57)))
      (;;) (i32.add (;;) (i32.const 1))
      (br $next (;;))
    ))
    (local.set 1 (;;))
    (i32.store (i32.add (local.get 0) (i32.const 5)) (local.get 1)) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $identifier_token (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (i32.const 0x33)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (;;) (i32.add (local.get 1) (i32.const 1))
    (block $end (param i32) (result i32)
    (loop $next (param i32) (result i32)
      (;;) (local.tee 1 (;;))
      (br_if $end (;;) (i32.eq (local.get 1) (local.get 2)))
      (;;) (local.tee 1 (i32.load8_u (local.get 1)))
      (br_if $end (;;) (i32.lt_u (;;) (i32.const 65)))
      (br_if $end (;;) (i32.and
        (i32.gt_u (local.get 1) (i32.const 90))
        (i32.lt_u (local.get 1) (i32.const 97))
      ))
      (br_if $end (;;) (i32.gt_u (local.get 1) (i32.const 122)))
      (;;) (i32.add (local.get 1) (i32.const 1))
      (br $next (;;))
    ))
    (local.set 1 (;;))
    (i32.store (i32.add (local.get 0) (i32.const 5)) (local.get 1)) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $line_comment (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (i32.const 0x34)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (;;) (i32.add (local.get 1) (i32.const 1))
    (block $end (param i32) (result i32)
    (loop $next (param i32) (result i32)
      (;;) (local.tee 1 (;;))
      (br_if $end (;;) (i32.eq (local.get 1) (local.get 2)))
      (br_if $end (;;) (i32.eq (i32.load8_u (local.get 1)) (i32.const 10)))
      (;;) (i32.add (;;) (i32.const 1))
      (br $next (;;))
    ))
    (local.set 1 (;;))
    (i32.store (i32.add (local.get 0) (i32.const 5)) (local.get 1)) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )

  (func $block_comment (param i32 i32 i32) (result i32 i32)
    (i32.store8 (local.get 0) (i32.const 0x35)) ;; id
    (i32.store (i32.add (local.get 0) (i32.const 1)) (local.get 1))
      ;; start_addr
    (;;) (i32.add (local.get 1) (i32.const 1))
    (block $end (param i32) (result i32)
    (loop $next (param i32) (result i32)
      (;;) (local.tee 1 (;;))
      (if (i32.eq (local.get 1) (local.get 2)) (then
        (return (local.get 2) (i32.const 0)) ;; unexpected EOF
      ))
      (if (param i32) (result i32)
        (i32.eq (i32.load8_u (local.get 1)) (i32.const 42))
      (then
        (;;) (local.tee 1 (i32.add (;;) (i32.const 1)))
        (if (i32.eq (local.get 1) (local.get 2)) (then
          (return (local.get 2) (i32.const 0)) ;; unexpected EOF
        ))
        (br_if $end (;;) (i32.eq (i32.load8_u (local.get 1)) (i32.const 41)))
      ))
      (;;) (i32.add (;;) (i32.const 1))
      (br $next (;;))
    ))
    (local.set 1 (;;))
    (i32.store
      (i32.add (local.get 0) (i32.const 5))
      (local.tee 1 (i32.add (local.get 1) (i32.const 1)))
    ) ;; end_addr
    (;;) (i32.add (local.get 0) (i32.const 9))
    (;;) (local.get 1)
  )
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions