-
Notifications
You must be signed in to change notification settings - Fork 843
Description
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)
)
)