diff --git a/ml-proto/README.md b/ml-proto/README.md index 7640a4731a..fd476b02bc 100644 --- a/ml-proto/README.md +++ b/ml-proto/README.md @@ -96,6 +96,7 @@ wasm -d script.wast -o script.js The first creates a new test scripts where all embedded modules are converted to binary, the second one where all are converted to textual. The last invocation produces an equivalent, self-contained JavaScript test file. +By default, the generated script will require `assert_soft_invalid` (see below) to detect validation failures. Use the `-us` flag ("unchecked soft") to deactivate these assertions to run on implementations that do not validate dead code. #### Command Line Expressions @@ -274,6 +275,7 @@ assertion: ( assert_trap ) ;; assert action traps with given failure string ( assert_malformed ) ;; assert module cannot be decoded with given failure string ( assert_invalid ) ;; assert module is invalid with given failure string + ( assert_soft_invalid ) ;; assert module is for cases that are not required to be checked ( assert_unlinkable ) ;; assert module fails to link ( assert_trap ) ;; assert module traps on instantiation @@ -294,6 +296,8 @@ The `input` and `output` meta commands determine the requested file format from The interpreter supports a "dry" mode (flag `-d`), in which modules are only validated. In this mode, all actions and assertions are ignored. It also supports an "unchecked" mode (flag `-u`), in which module definitions are not validated before use. +Finally, "unchecked soft" mode (flag `-us`), will not require `assert_soft_valid` assertions to succeed. When outputing JavaScript scripts, this flag also controls how the created script implements this assertions. + ## Abstract Syntax The abstract WebAssembly syntax, as described above and in the [design doc](https://github.com/WebAssembly/design/blob/master/AstSemantics.md), is defined in [ast.ml](spec/ast.ml). diff --git a/ml-proto/host/arrange.ml b/ml-proto/host/arrange.ml index 3d73870616..32ca5e6d94 100644 --- a/ml-proto/host/arrange.ml +++ b/ml-proto/host/arrange.ml @@ -411,6 +411,8 @@ let assertion mode ass = Node ("assert_malformed", [definition `Original None def; Atom (string re)]) | AssertInvalid (def, re) -> Node ("assert_invalid", [definition mode None def; Atom (string re)]) + | AssertSoftInvalid (def, re) -> + Node ("assert_soft_invalid", [definition mode None def; Atom (string re)]) | AssertUnlinkable (def, re) -> Node ("assert_unlinkable", [definition mode None def; Atom (string re)]) | AssertUninstantiable (def, re) -> diff --git a/ml-proto/host/flags.ml b/ml-proto/host/flags.ml index 2554fba0f9..00b8e6fde3 100644 --- a/ml-proto/host/flags.ml +++ b/ml-proto/host/flags.ml @@ -1,6 +1,7 @@ let interactive = ref false let trace = ref false let unchecked = ref false +let unchecked_soft = ref false let print_sig = ref false let dry = ref false let width = ref 80 diff --git a/ml-proto/host/js.ml b/ml-proto/host/js.ml index a385f890a8..5a85204cb6 100644 --- a/ml-proto/host/js.ml +++ b/ml-proto/host/js.ml @@ -9,6 +9,8 @@ open Source let prefix = "'use strict';\n" ^ "\n" ^ + "let soft_validate = " ^ string_of_bool (not !Flags.unchecked_soft) ^ ";\n" ^ + "\n" ^ "let spectest = {\n" ^ " print: print || ((...xs) => console.log(...xs)),\n" ^ " global: 666,\n" ^ @@ -51,6 +53,15 @@ let prefix = " throw new Error(\"Wasm validation failure expected\");\n" ^ "}\n" ^ "\n" ^ + "function assert_soft_invalid(bytes) {\n" ^ + " try { module(bytes) } catch (e) {\n" ^ + " if (e instanceof WebAssembly.CompileError) return;\n" ^ + " throw new Error(\"Wasm validation failure expected\");\n" ^ + " }\n" ^ + " if (soft_validate)\n" ^ + " throw new Error(\"Wasm validation failure expected\");\n" ^ + "}\n" ^ + "\n" ^ "function assert_unlinkable(bytes) {\n" ^ " let mod = module(bytes);\n" ^ " try { new WebAssembly.Instance(mod, registry) } catch (e) {\n" ^ @@ -273,6 +284,8 @@ let of_assertion mods ass = "assert_malformed(" ^ of_definition def ^ ");" | AssertInvalid (def, _) -> "assert_invalid(" ^ of_definition def ^ ");" + | AssertSoftInvalid (def, _) -> + "assert_soft_invalid(" ^ of_definition def ^ ");" | AssertUnlinkable (def, _) -> "assert_unlinkable(" ^ of_definition def ^ ");" | AssertUninstantiable (def, _) -> diff --git a/ml-proto/host/lexer.mll b/ml-proto/host/lexer.mll index 1e71ba7010..e0e06a5341 100644 --- a/ml-proto/host/lexer.mll +++ b/ml-proto/host/lexer.mll @@ -306,6 +306,7 @@ rule token = parse | "get" { GET } | "assert_malformed" { ASSERT_MALFORMED } | "assert_invalid" { ASSERT_INVALID } + | "assert_soft_invalid" { ASSERT_SOFT_INVALID } | "assert_unlinkable" { ASSERT_UNLINKABLE } | "assert_return" { ASSERT_RETURN } | "assert_return_nan" { ASSERT_RETURN_NAN } diff --git a/ml-proto/host/main.ml b/ml-proto/host/main.ml index 8be920a066..665df8e51c 100644 --- a/ml-proto/host/main.ml +++ b/ml-proto/host/main.ml @@ -28,6 +28,7 @@ let argspec = Arg.align " configure output width (default is 80)"; "-s", Arg.Set Flags.print_sig, " show module signatures"; "-u", Arg.Set Flags.unchecked, " unchecked, do not perform validation"; + "-us", Arg.Set Flags.unchecked_soft, " do not perform soft validation checks"; "-d", Arg.Set Flags.dry, " dry, do not run program"; "-t", Arg.Set Flags.trace, " trace execution"; "-v", Arg.Unit banner, " show version" diff --git a/ml-proto/host/parser.mly b/ml-proto/host/parser.mly index 24e9a7a29f..e6ca907df2 100644 --- a/ml-proto/host/parser.mly +++ b/ml-proto/host/parser.mly @@ -167,7 +167,7 @@ let inline_type c t at = %token FUNC START TYPE PARAM RESULT LOCAL GLOBAL %token MODULE TABLE ELEM MEMORY DATA OFFSET IMPORT EXPORT TABLE %token SCRIPT REGISTER INVOKE GET -%token ASSERT_MALFORMED ASSERT_INVALID ASSERT_UNLINKABLE +%token ASSERT_MALFORMED ASSERT_INVALID ASSERT_SOFT_INVALID ASSERT_UNLINKABLE %token ASSERT_RETURN ASSERT_RETURN_NAN ASSERT_TRAP %token INPUT OUTPUT %token EOF @@ -691,6 +691,8 @@ assertion : { AssertMalformed (snd $3, $4) @@ at () } | LPAR ASSERT_INVALID module_ TEXT RPAR { AssertInvalid (snd $3, $4) @@ at () } + | LPAR ASSERT_SOFT_INVALID module_ TEXT RPAR + { AssertSoftInvalid (snd $3, $4) @@ at () } | LPAR ASSERT_UNLINKABLE module_ TEXT RPAR { AssertUnlinkable (snd $3, $4) @@ at () } | LPAR ASSERT_TRAP module_ TEXT RPAR diff --git a/ml-proto/host/run.ml b/ml-proto/host/run.ml index d9b6ca8efc..6f39dc20d1 100644 --- a/ml-proto/host/run.ml +++ b/ml-proto/host/run.ml @@ -271,8 +271,14 @@ let run_assertion ass = Assert.error ass.at "expected decoding error" ) - | AssertInvalid (def, re) -> - trace "Asserting invalid..."; + | AssertInvalid (def, re) + | AssertSoftInvalid (def, re) -> + let active = + match ass.it with + | AssertSoftInvalid _ -> not !Flags.unchecked_soft + | _ -> true + in + trace ("Asserting " ^ (if active then "" else "soft ") ^ "invalid..."); (match let m = run_definition def in Check.check_module m @@ -284,7 +290,7 @@ let run_assertion ass = Assert.error ass.at "wrong validation error" end | _ -> - Assert.error ass.at "expected validation error" + if active then Assert.error ass.at "expected validation error" ) | AssertUnlinkable (def, re) -> diff --git a/ml-proto/host/script.ml b/ml-proto/host/script.ml index ccdf4bdb4b..e2e8717c89 100644 --- a/ml-proto/host/script.ml +++ b/ml-proto/host/script.ml @@ -14,6 +14,7 @@ type assertion = assertion' Source.phrase and assertion' = | AssertMalformed of definition * string | AssertInvalid of definition * string + | AssertSoftInvalid of definition * string | AssertUnlinkable of definition * string | AssertUninstantiable of definition * string | AssertReturn of action * Ast.literal list diff --git a/ml-proto/spec/check.ml b/ml-proto/spec/check.ml index fe819eba87..f3c69b2ad5 100644 --- a/ml-proto/spec/check.ml +++ b/ml-proto/spec/check.ml @@ -11,16 +11,9 @@ exception Invalid = Invalid.Error let error = Invalid.error let require b at s = if not b then error at s -let result_error at r1 r2 = - error at - ("type mismatch: operator requires " ^ string_of_result_type r1 ^ - " but stack has " ^ string_of_result_type r2) - (* Context *) -type op_type = stack_type * result_type - type context = { module_ : module_; @@ -52,12 +45,44 @@ let table c x = lookup "table" c.tables x let memory c x = lookup "memory" c.memories x -(* Join *) +(* Stack typing *) + +type ellipses = NoEllipses | Ellipses +type infer_stack_type = ellipses * value_type option list +type op_type = {ins : infer_stack_type; outs : infer_stack_type} + +let known = List.map (fun t -> Some t) +let stack ts = (NoEllipses, known ts) +let (-~>) ts1 ts2 = {ins = NoEllipses, ts1; outs = NoEllipses, ts2} +let (-->) ts1 ts2 = {ins = NoEllipses, known ts1; outs = NoEllipses, known ts2} +let (-->...) ts1 ts2 = {ins = Ellipses, known ts1; outs = Ellipses, known ts2} + +let string_of_infer_type t = + Lib.Option.get (Lib.Option.map string_of_value_type t) "_" +let string_of_infer_types ts = + "[" ^ String.concat " " (List.map string_of_infer_type ts) ^ "]" + +let eq_ty t1 t2 = (t1 = t2 || t1 = None || t2 = None) +let check_stack ts1 ts2 at = + require (List.length ts1 = List.length ts2 && List.for_all2 eq_ty ts1 ts2) at + ("type mismatch: operator requires " ^ string_of_infer_types ts1 ^ + " but stack has " ^ string_of_infer_types ts2) + +let pop (ell1, ts1) (ell2, ts2) at = + let n1 = List.length ts1 in + let n2 = List.length ts2 in + let n = min n1 n2 in + let n3 = if ell2 = Ellipses then (n1 - n) else 0 in + check_stack ts1 (Lib.List.make n3 None @ Lib.List.drop (n2 - n) ts2) at; + (ell2, if ell1 = Ellipses then [] else Lib.List.take (n2 - n) ts2) -let check_join ts r at = - match r with - | Bot -> () - | Stack ts' -> if ts <> ts' then result_error at (Stack ts) r +let push (ell1, ts1) (ell2, ts2) = + assert (ell1 = NoEllipses || ts2 = []); + (if ell1 = Ellipses || ell2 = Ellipses then Ellipses else NoEllipses), + ts2 @ ts1 + +let peek i (ell, ts) = + try List.nth ts i with Failure _ -> None (* Type Synthesis *) @@ -122,14 +147,7 @@ let check_memop (c : context) (memop : 'a memop) get_sz at = let check_arity n at = require (n <= 1) at "invalid result arity, larger than 1 is not (yet) allowed" -let check_result_arity r at = - match r with - | Stack ts -> check_arity (List.length ts) at - | Bot -> () - (* - * check_instr : context -> instr -> stack_type -> unit - * * Conventions: * c : context * e : instr @@ -137,154 +155,141 @@ let check_result_arity r at = * v : value * t : value_type var * ts : stack_type + * + * Note: To deal with the non-determinism in some of the declarative rules, + * the function takes the current stack `s` as an additional argument, allowing + * it to "peek" when it would otherwise have to guess an input type. *) -let (-->) ts r = ts, r - -let peek i ts = - try List.nth ts i with Failure _ -> I32Type - -let peek_n n ts = - let m = min n (List.length ts) in - Lib.List.take m ts @ Lib.List.make (n - m) I32Type - -let rec check_instr (c : context) (e : instr) (stack : stack_type) : op_type = +let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = match e.it with | Unreachable -> - [] --> Bot + [] -->... [] | Nop -> - [] --> Stack [] + [] --> [] | Drop -> - [peek 0 stack] --> Stack [] + [peek 0 s] -~> [] | Block (ts, es) -> check_arity (List.length ts) e.at; - let c' = {c with labels = ts :: c.labels} in - check_block c' es ts e.at; - [] --> Stack ts + check_block {c with labels = ts :: c.labels} es ts e.at; + [] --> ts | Loop (ts, es) -> check_arity (List.length ts) e.at; - let c' = {c with labels = [] :: c.labels} in - check_block c' es ts e.at; - [] --> Stack ts + check_block {c with labels = [] :: c.labels} es ts e.at; + [] --> ts | Br x -> - label c x --> Bot + label c x -->... [] | BrIf x -> - (label c x @ [I32Type]) --> Stack (label c x) + (label c x @ [I32Type]) --> label c x | BrTable (xs, x) -> let ts = label c x in - List.iter (fun x' -> check_join ts (Stack (label c x')) x'.at) xs; - (ts @ [I32Type]) --> Bot + List.iter (fun x' -> check_stack (known ts) (known (label c x')) x'.at) xs; + (label c x @ [I32Type]) -->... [] | Return -> - c.results --> Bot + c.results -->... [] | If (ts, es1, es2) -> check_arity (List.length ts) e.at; - let c' = {c with labels = ts :: c.labels} in - check_block c' es1 ts e.at; - check_block c' es2 ts e.at; - [I32Type] --> Stack ts + check_block {c with labels = ts :: c.labels} es1 ts e.at; + check_block {c with labels = ts :: c.labels} es2 ts e.at; + [I32Type] --> ts | Select -> - let t = peek 1 stack in - [t; t; I32Type] --> Stack [t] + let t = peek 1 s in + [t; t; Some I32Type] -~> [t] | Call x -> let FuncType (ins, out) = func c x in - ins --> Stack out + ins --> out | CallIndirect x -> ignore (table c (0l @@ e.at)); let FuncType (ins, out) = type_ c x in - (ins @ [I32Type]) --> Stack out + (ins @ [I32Type]) --> out | GetLocal x -> - [] --> Stack [local c x] + [] --> [local c x] | SetLocal x -> - [local c x] --> Stack [] + [local c x] --> [] | TeeLocal x -> - [local c x] --> Stack [local c x] + [local c x] --> [local c x] | GetGlobal x -> let GlobalType (t, mut) = global c x in - [] --> Stack [t] + [] --> [t] | SetGlobal x -> let GlobalType (t, mut) = global c x in require (mut = Mutable) x.at "global is immutable"; - [t] --> Stack [] + [t] --> [] | Load memop -> check_memop c memop (Lib.Option.map fst) e.at; - [I32Type] --> Stack [memop.ty] + [I32Type] --> [memop.ty] | Store memop -> check_memop c memop (fun sz -> sz) e.at; - [I32Type; memop.ty] --> Stack [] + [I32Type; memop.ty] --> [] | Const v -> let t = type_value v.it in - [] --> Stack [t] + [] --> [t] | Unary unop -> let t = type_unop unop in - [t] --> Stack [t] + [t] --> [t] | Binary binop -> let t = type_binop binop in - [t; t] --> Stack [t] + [t; t] --> [t] | Test testop -> let t = type_testop testop in - [t] --> Stack [I32Type] + [t] --> [I32Type] | Compare relop -> let t = type_relop relop in - [t; t] --> Stack [I32Type] + [t; t] --> [I32Type] | Convert cvtop -> let t1, t2 = type_cvtop e.at cvtop in - [t1] --> Stack [t2] + [t1] --> [t2] | CurrentMemory -> ignore (memory c (0l @@ e.at)); - [] --> Stack [I32Type] + [] --> [I32Type] | GrowMemory -> ignore (memory c (0l @@ e.at)); - [I32Type] --> Stack [I32Type] + [I32Type] --> [I32Type] -and check_seq (c : context) (es : instr list) : result_type = +and check_seq (c : context) (es : instr list) : infer_stack_type = match es with | [] -> - Stack [] + stack [] | _ -> let es', e = Lib.List.split_last es in - let r1 = check_seq c es' in - match r1 with - | Bot -> Bot - | Stack ts0 -> - let ts2, r2 = check_instr c e (List.rev ts0) in - let n1 = max (List.length ts0 - List.length ts2) 0 in - let ts1 = Lib.List.take n1 ts0 in - let ts2' = Lib.List.drop n1 ts0 in - if ts2 <> ts2' then result_error e.at (Stack ts2) (Stack ts2'); - match r2 with - | Bot -> Bot - | Stack ts3 -> Stack (ts1 @ ts3) + let s = check_seq c es' in + let {ins; outs} = check_instr c e s in + push outs (pop ins s e.at) and check_block (c : context) (es : instr list) (ts : stack_type) at = - check_join ts (check_seq c es) at + let s = check_seq c es in + let s' = pop (stack ts) s at in + require (snd s' = []) at + ("type mismatch: operator requires " ^ string_of_stack_type ts ^ + " but stack has " ^ string_of_infer_types (snd s')) (* Functions & Constants *) diff --git a/ml-proto/spec/types.ml b/ml-proto/spec/types.ml index 49982347bc..4cf0df046e 100644 --- a/ml-proto/spec/types.ml +++ b/ml-proto/spec/types.ml @@ -3,7 +3,6 @@ type value_type = I32Type | I64Type | F32Type | F64Type type elem_type = AnyFuncType type stack_type = value_type list -type result_type = Stack of stack_type | Bot type func_type = FuncType of stack_type * stack_type type 'a limits = {min : 'a; max : 'a option} @@ -55,11 +54,7 @@ let string_of_global_type = function | GlobalType (t, Mutable) -> "(mut " ^ string_of_value_type t ^ ")" let string_of_stack_type ts = - "(" ^ String.concat " " (List.map string_of_value_type ts) ^ ")" - -let string_of_result_type = function - | Stack ts -> string_of_stack_type ts - | Bot -> "_|_" + "[" ^ String.concat " " (List.map string_of_value_type ts) ^ "]" let string_of_func_type (FuncType (ins, out)) = string_of_stack_type ins ^ " -> " ^ string_of_stack_type out diff --git a/ml-proto/test/block.wast b/ml-proto/test/block.wast index 389a71a376..3108f8cbf6 100644 --- a/ml-proto/test/block.wast +++ b/ml-proto/test/block.wast @@ -165,39 +165,6 @@ "type mismatch" ) -(; TODO(stack): soft failure -(assert_invalid - (module (func $type-value-num-vs-void-after-break - (block (br 0) (i32.const 1)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-value-void-vs-num-after-break (result i32) - (block (i32.const 1) (br 0) (nop)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-value-num-vs-num-after-break (result i32) - (block (i32.const 1) (br 0) (f32.const 0)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-break-second-void-vs-num (result i32) - (block i32 (br 0 (i32.const 1)) (br 0 (nop))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-break-second-num-vs-num (result i32) - (block i32 (br 0 (i32.const 1)) (br 0 (f64.const 1))) - )) - "type mismatch" -) -;) - (assert_invalid (module (func $type-break-last-void-vs-num (result i32) (block i32 (br 0)) diff --git a/ml-proto/test/br.wast b/ml-proto/test/br.wast index 034e6ff391..829ff45595 100644 --- a/ml-proto/test/br.wast +++ b/ml-proto/test/br.wast @@ -53,10 +53,14 @@ (block (br_if 0 (br 0))) ) (func (export "as-br_if-value") (result i32) - (block i32 (br_if 0 (br 0 (i32.const 8)) (i32.const 1)) (i32.const 7)) + (block i32 + (drop (br_if 0 (br 0 (i32.const 8)) (i32.const 1))) (i32.const 7) + ) ) (func (export "as-br_if-value-cond") (result i32) - (block i32 (drop (br_if 0 (i32.const 6) (br 0 (i32.const 9)))) (i32.const 7)) + (block i32 + (drop (br_if 0 (i32.const 6) (br 0 (i32.const 9)))) (i32.const 7) + ) ) (func (export "as-br_table-index") @@ -233,7 +237,7 @@ (drop (block i32 (drop (i32.const 4)) - (br_if 0 (br 1 (i32.const 8)) (i32.const 1)) + (drop (br_if 0 (br 1 (i32.const 8)) (i32.const 1))) (i32.const 32) ) ) @@ -371,20 +375,6 @@ "type mismatch" ) -;; TODO(stack): move this elsewhere -(module (func $type-arg-num-vs-void - (block (drop (i32.const 0)) (br 0)) -)) - -(; TODO(stack): soft failure -(assert_invalid - (module (func $type-arg-poly-vs-empty - (block (br 0 (unreachable))) - )) - "type mismatch" -) -;) - (assert_invalid (module (func $type-arg-void-vs-num (result i32) (block (br 0 (nop)) (i32.const 1)) diff --git a/ml-proto/test/br_if.wast b/ml-proto/test/br_if.wast index d115fcbc50..e6a2c79de0 100644 --- a/ml-proto/test/br_if.wast +++ b/ml-proto/test/br_if.wast @@ -226,21 +226,6 @@ "type mismatch" ) -(; TODO(stack): soft failure -(assert_invalid - (module (func $type-false-arg-poly-vs-empty - (block (br_if 0 (unreachable) (i32.const 0))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-true-arg-poly-vs-empty - (block (br_if 0 (unreachable) (i32.const 1))) - )) - "type mismatch" -) -;) - (assert_invalid (module (func $type-false-arg-void-vs-num (result i32) (block i32 (br_if 0 (nop) (i32.const 0)) (i32.const 1)) diff --git a/ml-proto/test/br_table.wast b/ml-proto/test/br_table.wast index 04d565e047..24f6ed2ebf 100644 --- a/ml-proto/test/br_table.wast +++ b/ml-proto/test/br_table.wast @@ -875,7 +875,7 @@ ) (func (export "as-br_if-value") (result i32) (block i32 - (br_if 0 (br_table 0 (i32.const 8) (i32.const 0)) (i32.const 1)) + (drop (br_if 0 (br_table 0 (i32.const 8) (i32.const 0)) (i32.const 1))) (i32.const 7) ) ) @@ -1142,7 +1142,12 @@ (drop (block i32 (drop (i32.const 4)) - (br_if 0 (br_table 0 1 2 (i32.const 8) (get_local 0)) (i32.const 1)) + (drop + (br_if 0 + (br_table 0 1 2 (i32.const 8) (get_local 0)) + (i32.const 1) + ) + ) (i32.const 32) ) ) @@ -1158,7 +1163,9 @@ (i32.const 1) (block i32 (drop (i32.const 2)) - (drop (br_if 0 (i32.const 4) (br_table 0 1 0 (i32.const 8) (get_local 0)))) + (drop + (br_if 0 (i32.const 4) (br_table 0 1 0 (i32.const 8) (get_local 0))) + ) (i32.const 16) ) ) @@ -1382,15 +1389,6 @@ "type mismatch" ) -(; TODO(stack): soft failure -(assert_invalid - (module (func $type-arg-poly-vs-empty - (block (br_table 0 (unreachable) (i32.const 1))) - )) - "type mismatch" -) -;) - (assert_invalid (module (func $type-arg-void-vs-num (result i32) (block i32 (br_table 0 (nop) (i32.const 1)) (i32.const 1)) diff --git a/ml-proto/test/func.wast b/ml-proto/test/func.wast index ee35ca8552..a9c71db480 100644 --- a/ml-proto/test/func.wast +++ b/ml-proto/test/func.wast @@ -392,37 +392,6 @@ "type mismatch" ) -(; TODO(stack): Should these become legal? -(assert_invalid - (module (func $type-value-void-vs-num-after-return (result i32) - (return (i32.const 1)) (nop) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-value-num-vs-num-after-return (result i32) - (return (i32.const 1)) (f32.const 0) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-value-void-vs-num-after-break (result i32) - (br 0 (i32.const 1)) (nop) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-value-num-vs-num-after-break (result i32) - (br 0 (i32.const 1)) (f32.const 0) - )) - "arity mismatch" -) -;) - -;; TODO(stack): move these somewhere else -(module (func $type-return-void-vs-enpty (return (nop)))) -(module (func $type-return-num-vs-enpty (return (i32.const 0)))) - (assert_invalid (module (func $type-return-last-empty-vs-num (result i32) (return) @@ -466,14 +435,6 @@ )) "type mismatch" ) -(; TODO(stack): Should this become legal? -(assert_invalid - (module (func $type-return-second-num-vs-num (result i32) - (return (i32.const 1)) (return (f64.const 1)) - )) - "type mismatch" -) -;) (assert_invalid (module (func $type-break-last-void-vs-num (result i32) @@ -506,15 +467,6 @@ "type mismatch" ) -(; TODO(stack): soft failure -(assert_invalid - (module (func $type-break-second-num-vs-num (result i32) - (br 0 (i32.const 1)) (br 0 (f64.const 1)) - )) - "type mismatch" -) -;) - (assert_invalid (module (func $type-break-nested-empty-vs-num (result i32) (block (br 1)) (br 0 (i32.const 1)) diff --git a/ml-proto/test/loop.wast b/ml-proto/test/loop.wast index 0187115bfd..209b1a9189 100644 --- a/ml-proto/test/loop.wast +++ b/ml-proto/test/loop.wast @@ -252,21 +252,6 @@ "type mismatch" ) -(; TODO(stack): soft failure -(assert_invalid - (module (func $type-value-void-vs-num-after-break (result i32) - (loop (br 1 (i32.const 1)) (nop)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-value-num-vs-num-after-break (result i32) - (loop (br 1 (i32.const 1)) (f32.const 0)) - )) - "type mismatch" -) -;) - (assert_invalid (module (func $type-cont-last-void-vs-empty (result i32) (loop (br 0 (nop))) diff --git a/ml-proto/test/return.wast b/ml-proto/test/return.wast index e5a87c58cb..60db41c1f1 100644 --- a/ml-proto/test/return.wast +++ b/ml-proto/test/return.wast @@ -56,10 +56,14 @@ (block (br_if 0 (return))) ) (func (export "as-br_if-value") (result i32) - (block i32 (br_if 0 (return (i32.const 8)) (i32.const 1)) (i32.const 7)) + (block i32 + (drop (br_if 0 (return (i32.const 8)) (i32.const 1))) (i32.const 7) + ) ) (func (export "as-br_if-value-cond") (result i32) - (block i32 (drop (br_if 0 (i32.const 6) (return (i32.const 9)))) (i32.const 7)) + (block i32 + (drop (br_if 0 (i32.const 6) (return (i32.const 9)))) (i32.const 7) + ) ) (func (export "as-br_table-index") (result i64) @@ -81,7 +85,7 @@ ) (func (export "as-if-cond") (result i32) - (if (return (i32.const 2)) (i32.const 0) (i32.const 1)) + (if i32 (return (i32.const 2)) (i32.const 0) (i32.const 1)) ) (func (export "as-if-then") (param i32 i32) (result i32) (if i32 (get_local 0) (return (i32.const 3)) (get_local 1)) @@ -262,10 +266,6 @@ (assert_return (invoke "as-grow_memory-size") (i32.const 40)) -;; TODO(stack): move these somewhere else -(module (func $type-value-void-vs-empty (return (nop)))) -(module (func $type-value-num-vs-empty (return (i32.const 0)))) - (assert_invalid (module (func $type-value-empty-vs-num (result f64) (return))) "type mismatch" diff --git a/ml-proto/test/soft-fail.wast b/ml-proto/test/soft-fail.wast new file mode 100644 index 0000000000..7750ddd1a2 --- /dev/null +++ b/ml-proto/test/soft-fail.wast @@ -0,0 +1,564 @@ +;; Test soft failures +;; These are invalid Wasm, but the failure is in dead code, which +;; implementations are not required to validate. If they do, they shall +;; diagnose the correct error. + +(assert_soft_invalid + (module (func $type-num-vs-num + (unreachable) (drop (i64.eqz (i32.const 0)))) + ) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-poly-num-vs-num (result i32) + (unreachable) (i64.const 0) (i32.const 0) (select) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-poly-transitive-num-vs-num (result i32) + (unreachable) + (i64.const 0) (i32.const 0) (select) + (i32.const 0) (i32.const 0) (select) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unconsumed-const (unreachable) (i32.const 0))) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unconsumed-result (unreachable) (i32.eqz))) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unconsumed-result2 + (unreachable) (i32.const 0) (i32.add) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unconsumed-poly0 (unreachable) (select))) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unconsumed-poly1 (unreachable) (i32.const 0) (select))) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unconsumed-poly2 + (unreachable) (i32.const 0) (i32.const 0) (select) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unary-num-vs-void-after-break + (block (br 0) (block (drop (i32.eqz (nop))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unary-num-vs-num-after-break + (block (br 0) (drop (i32.eqz (f32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-void-after-break + (block (br 0) (block (drop (f32.eq (i32.const 1))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-num-after-break + (block (br 0) (drop (f32.eq (i32.const 1) (f32.const 0)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-after-break + (block (br 0) (i32.const 1)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-after-break (result i32) + (block i32 (i32.const 1) (br 0) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-void-after-break + (block (loop (br 1) (i32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-num-after-break (result i32) + (loop i32 (br 1 (i32.const 1)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-void-after-break + (br 0) (i32.const 1) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-num-after-break (result i32) + (br 0 (i32.const 1)) (f32.const 0) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unary-num-vs-void-after-return + (return) (block (drop (i32.eqz (nop)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unary-num-vs-num-after-return + (return) (drop (i32.eqz (f32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-void-after-return + (return) (block (drop (f32.eq (i32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-num-after-return + (return) (drop (f32.eq (i32.const 1) (f32.const 0))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-after-return + (block (return) (i32.const 1)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-after-return (result i32) + (block i32 (i32.const 1) (return (i32.const 0)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-void-after-return + (block (loop (return) (i32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-num-after-return (result i32) + (loop i32 (return (i32.const 1)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-void-after-return + (return) (i32.const 1) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-num-after-return (result i32) + (return (i32.const 1)) (f32.const 0) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unary-num-vs-void-after-unreachable + (unreachable) (block (drop (i32.eqz (nop)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unary-num-vs-num-after-unreachable + (unreachable) (drop (i32.eqz (f32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-void-after-unreachable + (unreachable) (block (drop (f32.eq (i32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-num-after-unreachable + (unreachable) (drop (f32.eq (i32.const 1) (f32.const 0))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-after-unreachable + (block (unreachable) (i32.const 1)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-after-unreachable (result i32) + (block i32 (i32.const 1) (unreachable) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-void-after-unreachable + (block (loop (unreachable) (i32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-num-after-unreachable (result i32) + (loop i32 (unreachable) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-void-after-unreachable + (unreachable) (i32.const 1) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-num-after-unreachable (result i32) + (unreachable) (f32.const 0) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unary-num-vs-void-after-nested-unreachable + (block (unreachable)) (block (drop (i32.eqz (nop)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unary-num-vs-num-after-nested-unreachable + (block (unreachable)) (drop (i32.eqz (f32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-void-after-nested-unreachable + (block (unreachable)) (block (drop (f32.eq (i32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-num-after-nested-unreachable + (block (unreachable)) (drop (f32.eq (i32.const 1) (f32.const 0))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-after-nested-unreachable + (block (block (unreachable)) (i32.const 1)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-after-nested-unreachable + (result i32) + (block i32 (i32.const 1) (block (unreachable)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-void-after-nested-unreachable + (block (loop (block (unreachable)) (i32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-num-after-nested-unreachable + (result i32) + (loop i32 (block (unreachable)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-void-after-nested-unreachable + (block (unreachable)) (i32.const 1) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-num-after-nested-unreachable + (result i32) + (block (unreachable)) (f32.const 0) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unary-num-vs-void-after-infinite-loop + (loop (br 0)) (block (drop (i32.eqz (nop)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unary-num-vs-num-after-infinite-loop + (loop (br 0)) (drop (i32.eqz (f32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-void-after-infinite-loop + (loop (br 0)) (block (drop (f32.eq (i32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-num-after-infinite-loop + (loop (br 0)) (drop (f32.eq (i32.const 1) (f32.const 0))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-after-infinite-loop + (block (loop (br 0)) (i32.const 1)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-after-infinite-loop (result i32) + (block i32 (i32.const 1) (loop (br 0)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-void-after-infinite-loop + (block (loop (loop (br 0)) (i32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-num-vs-num-after-infinite-loop (result i32) + (loop i32 (loop (br 0)) (f32.const 0)) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-void-after-infinite-loop + (loop (br 0)) (i32.const 1) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-func-value-num-vs-num-after-infinite-loop (result i32) + (loop (br 0)) (f32.const 0) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-unary-num-vs-void-in-dead-body + (if (i32.const 0) (then (drop (i32.eqz (nop))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-unary-num-vs-num-in-dead-body + (if (i32.const 0) (then (drop (i32.eqz (f32.const 1))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-void-in-dead-body + (if (i32.const 0) (then (drop (f32.eq (i32.const 1))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-binary-num-vs-num-in-dead-body + (if (i32.const 0) (then (drop (f32.eq (i32.const 1) (f32.const 0))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-if-value-num-vs-void-in-dead-body + (if (i32.const 0) (then (i32.const 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-if-value-num-vs-num-in-dead-body (result i32) + (if i32 (i32.const 0) (then (f32.const 0))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-in-dead-body + (if (i32.const 0) (then (block (i32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-in-dead-body (result i32) + (if i32 (i32.const 0) (then (block i32 (f32.const 0)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-void-in-dead-body + (if (i32.const 0) (then (loop (i32.const 1)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-num-vs-num-in-dead-body (result i32) + (if i32 (i32.const 0) (then (loop i32 (f32.const 0)))) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-return-second-num-vs-num (result i32) + (return (i32.const 1)) (return (f64.const 1)) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-br-second-num-vs-num (result i32) + (block i32 (br 0 (i32.const 1)) (br 0 (f64.const 1))) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-br_if-cond-num-vs-num-after-unreachable + (block (br_if 0 (unreachable) (f32.const 0))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-br_table-num-vs-num-after-unreachable + (block (br_table 0 (unreachable) (f32.const 1))) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-block-value-nested-unreachable-num-vs-void + (block (i32.const 3) (block (unreachable))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-unreachable-void-vs-num (result i32) + (block (block (unreachable))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-unreachable-num-vs-num (result i32) + (block i64 (i64.const 0) (block (unreachable))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-unreachable-num2-vs-void (result i32) + (block (i32.const 3) (block (i64.const 1) (unreachable))) (i32.const 9) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-block-value-nested-br-num-vs-void + (block (i32.const 3) (block (br 1))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-br-void-vs-num (result i32) + (block i32 (block (br 1 (i32.const 0)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-br-num-vs-num (result i32) + (block i32 (i64.const 0) (block (br 1 (i32.const 0)))) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-block-value-nested2-br-num-vs-void + (block (block (i32.const 3) (block (br 2)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested2-br-void-vs-num (result i32) + (block i32 (block (block (br 2 (i32.const 0))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested2-br-num-vs-num (result i32) + (block i32 (block i64 (i64.const 0) (block (br 2 (i32.const 0))))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested2-br-num2-vs-void (result i32) + (block (i32.const 3) (block (i64.const 1) (br 1))) (i32.const 9) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-block-value-nested-return-num-vs-void + (block (i32.const 3) (block (return))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-return-void-vs-num (result i32) + (block (block (return (i32.const 0)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-return-num-vs-num (result i32) + (block i64 (i64.const 0) (block (return (i32.const 0)))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-block-value-nested-return-num2-vs-void (result i32) + (block (i32.const 3) (block (i64.const 1) (return (i32.const 0)))) + (i32.const 9) + )) + "type mismatch" +) + +(assert_soft_invalid + (module (func $type-loop-value-nested-unreachable-num-vs-void + (loop (i32.const 3) (block (unreachable))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-nested-unreachable-void-vs-num (result i32) + (loop (block (unreachable))) + )) + "type mismatch" +) +(assert_soft_invalid + (module (func $type-loop-value-nested-unreachable-num-vs-num (result i32) + (loop i64 (i64.const 0) (block (unreachable))) + )) + "type mismatch" +) diff --git a/ml-proto/test/stack.wast b/ml-proto/test/stack.wast index a0559f5616..e6492679ee 100644 --- a/ml-proto/test/stack.wast +++ b/ml-proto/test/stack.wast @@ -130,9 +130,3 @@ (assert_return (invoke "fac-expr" (i64.const 25)) (i64.const 7034535277573963776)) (assert_return (invoke "fac-stack" (i64.const 25)) (i64.const 7034535277573963776)) (assert_return (invoke "fac-mixed" (i64.const 25)) (i64.const 7034535277573963776)) - -;; from br_table.wast -(module (func $type-arg-num-vs-void - (block (br_table 0 (i32.const 0) (i32.const 1))) -)) - diff --git a/ml-proto/test/typecheck.wast b/ml-proto/test/typecheck.wast index 444e0556df..1d15cc5675 100644 --- a/ml-proto/test/typecheck.wast +++ b/ml-proto/test/typecheck.wast @@ -157,14 +157,6 @@ )) "type mismatch" ) -(assert_invalid - (module (func $type-br-operand-missing-in-loop - (i32.const 0) - (loop i32 (br 0)) - (i32.eqz) (drop) - )) - "type mismatch" -) (assert_invalid (module (func $type-br-operand-missing-in-if (block diff --git a/ml-proto/test/unreachable.wast b/ml-proto/test/unreachable.wast index 389dc8aec2..82efa8fad7 100644 --- a/ml-proto/test/unreachable.wast +++ b/ml-proto/test/unreachable.wast @@ -60,7 +60,7 @@ (block (br_if 0 (unreachable))) ) (func (export "as-br_if-value") (result i32) - (block i32 (br_if 0 (unreachable) (i32.const 1)) (i32.const 7)) + (block i32 (drop (br_if 0 (unreachable) (i32.const 1))) (i32.const 7)) ) (func (export "as-br_if-value-cond") (result i32) (block i32 (drop (br_if 0 (i32.const 6) (unreachable))) (i32.const 7)) @@ -81,7 +81,7 @@ ) (func (export "as-if-cond") (result i32) - (if (unreachable) (i32.const 0) (i32.const 1)) + (if i32 (unreachable) (i32.const 0) (i32.const 1)) ) (func (export "as-if-then") (param i32 i32) (result i32) (if i32 (get_local 0) (unreachable) (get_local 1)) diff --git a/ml-proto/test/unwind.wast b/ml-proto/test/unwind.wast index 8db16c75bd..b993f2a873 100644 --- a/ml-proto/test/unwind.wast +++ b/ml-proto/test/unwind.wast @@ -1,6 +1,25 @@ ;; Test that control-flow transfer unwinds stack and it can be anything after. (module + (func (export "func-unwind-by-unreachable") + (i32.const 3) (i64.const 1) (unreachable) + ) + (func (export "func-unwind-by-br") + (i32.const 3) (i64.const 1) (br 0) + ) + (func (export "func-unwind-by-br-value") (result i32) + (i32.const 3) (i64.const 1) (br 0 (i32.const 9)) + ) + (func (export "func-unwind-by-br_table") + (i32.const 3) (i64.const 1) (br_table 0 (i32.const 0)) + ) + (func (export "func-unwind-by-br_table-value") (result i32) + (i32.const 3) (i64.const 1) (br_table 0 (i32.const 9) (i32.const 0)) + ) + (func (export "func-unwind-by-return") (result i32) + (i32.const 3) (i64.const 1) (return (i32.const 9)) + ) + (func (export "block-unwind-by-unreachable") (block (i32.const 3) (i64.const 1) (unreachable)) ) @@ -127,6 +146,13 @@ ) ) +(assert_trap (invoke "func-unwind-by-unreachable") "unreachable") +(assert_return (invoke "func-unwind-by-br")) +(assert_return (invoke "func-unwind-by-br-value") (i32.const 9)) +(assert_return (invoke "func-unwind-by-br_table")) +(assert_return (invoke "func-unwind-by-br_table-value") (i32.const 9)) +(assert_return (invoke "func-unwind-by-return") (i32.const 9)) + (assert_trap (invoke "block-unwind-by-unreachable") "unreachable") (assert_return (invoke "block-unwind-by-br") (i32.const 9)) (assert_return (invoke "block-unwind-by-br-value") (i32.const 9))