From 45b454d5b71524f2bb0cc6f00855af6b85ee55c4 Mon Sep 17 00:00:00 2001 From: Thibaud Michaud Date: Sat, 24 Apr 2021 21:33:34 +0200 Subject: [PATCH] [interpreter] Parse and convert EH opcodes Add support for EH opcodes in the parser, AST, encoder, decoder and formatter, and add spec tests. This can already be used to convert the tests to JS, but not run them with the interpreter yet since validation and execution are still missing. --- interpreter/binary/decode.ml | 40 ++++++- interpreter/binary/encode.ml | 17 ++- interpreter/script/js.ml | 7 ++ interpreter/script/run.ml | 2 + interpreter/script/script.ml | 1 + interpreter/syntax/ast.ml | 7 ++ interpreter/syntax/free.ml | 9 ++ interpreter/syntax/operators.ml | 4 + interpreter/text/arrange.ml | 14 +++ interpreter/text/lexer.mll | 9 ++ interpreter/text/parser.mly | 91 +++++++++++++- interpreter/valid/valid.ml | 5 + test/core/exports.wast | 4 + test/core/rethrow.wast | 83 +++++++++++++ test/core/throw.wast | 45 +++++++ test/core/try_catch.wast | 206 ++++++++++++++++++++++++++++++++ test/core/try_delegate.wast | 115 ++++++++++++++++++ 17 files changed, 652 insertions(+), 7 deletions(-) create mode 100644 test/core/rethrow.wast create mode 100644 test/core/throw.wast create mode 100644 test/core/try_catch.wast create mode 100644 test/core/try_delegate.wast diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index 02d092f8..facbcee4 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -246,7 +246,30 @@ let rec instr s = end | 0x05 -> error s pos "misplaced ELSE opcode" - | 0x06| 0x07 | 0x08 | 0x09 | 0x0a as b -> illegal s pos b + | 0x06 -> + let bt = block_type s in + let es = instr_block s in + let ct = catch_list s in + let ca = + if peek s = Some 0x19 then begin + ignore (u8 s); + Some (instr_block s) + end else + None + in + if ct <> [] || ca <> None then begin + end_ s; + try_catch bt es ct ca + end else begin + match op s with + | 0x0b -> try_catch bt es [] None + | 0x18 -> try_delegate bt es (at var s) + | b -> illegal s pos b + end + | 0x07 -> error s pos "misplaced CATCH opcode" + | 0x08 -> throw (at var s) + | 0x09 -> rethrow (at var s) + | 0x0a as b -> illegal s pos b | 0x0b -> error s pos "misplaced END opcode" | 0x0c -> br (at var s) @@ -263,7 +286,10 @@ let rec instr s = let x = at var s in call_indirect x y - | 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b + | 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 as b -> illegal s pos b + + | 0x18 -> error s pos "misplaced DELEGATE opcode" + | 0x19 -> error s pos "misplaced CATCH_ALL opcode" | 0x1a -> drop | 0x1b -> select None @@ -499,11 +525,19 @@ let rec instr s = and instr_block s = List.rev (instr_block' s []) and instr_block' s es = match peek s with - | None | Some (0x05 | 0x0b) -> es + | None | Some (0x05 | 0x07 | 0x0a | 0x0b | 0x18 | 0x19) -> es | _ -> let pos = pos s in let e' = instr s in instr_block' s (Source.(e' @@ region s pos pos) :: es) +and catch_list s = + if peek s = Some 0x07 then begin + ignore (u8 s); + let tag = at var s in + let instrs = instr_block s in + (tag, instrs) :: catch_list s + end else + [] let const s = let c = at instr_block s in diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index a848a871..ef938a9f 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -156,13 +156,28 @@ let encode m = op 0x04; block_type bt; list instr es1; if es2 <> [] then op 0x05; list instr es2; end_ () - + | TryCatch (bt, es, ct, ca) -> + op 0x06; block_type bt; list instr es; + let catch (tag, es) = + op 0x07; var tag; list instr es + in + list catch ct; + begin match ca with + | None -> () + | Some es -> op 0x19; list instr es + end; + end_ () + | TryDelegate (bt, es, x) -> + op 0x06; block_type bt; list instr es; + op 0x18; var x | Br x -> op 0x0c; var x | BrIf x -> op 0x0d; var x | BrTable (xs, x) -> op 0x0e; vec var xs; var x | Return -> op 0x0f | Call x -> op 0x10; var x | CallIndirect (x, y) -> op 0x11; var y; var x + | Throw x -> op 0x08; var x + | Rethrow x -> op 0x09; var x | Drop -> op 0x1a | Select None -> op 0x1b diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index 024886e3..434cfbfb 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -135,6 +135,11 @@ function assert_trap(action) { throw new Error("Wasm trap expected"); } +function assert_exception(action) { + try { action() } catch (e) { return; } + throw new Error("exception expected"); +} + let StackOverflow; try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor } @@ -508,6 +513,8 @@ let of_assertion mods ass = of_assertion' mods act "assert_trap" [] None | AssertExhaustion (act, _) -> of_assertion' mods act "assert_exhaustion" [] None + | AssertUncaughtException act -> + of_assertion' mods act "assert_exception" [] None let of_command mods cmd = "\n// " ^ Filename.basename cmd.at.left.file ^ diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index de589970..81d22dc2 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -458,6 +458,8 @@ let run_assertion ass = | _ -> Assert.error ass.at "expected runtime error" ) + | AssertUncaughtException act -> () (* TODO *) + | AssertExhaustion (act, re) -> trace ("Asserting exhaustion..."); (match run_action act with diff --git a/interpreter/script/script.ml b/interpreter/script/script.ml index 6fe11950..282cfc61 100644 --- a/interpreter/script/script.ml +++ b/interpreter/script/script.ml @@ -32,6 +32,7 @@ and assertion' = | AssertUninstantiable of definition * string | AssertReturn of action * result list | AssertTrap of action * string + | AssertUncaughtException of action | AssertExhaustion of action * string type command = command' Source.phrase diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index 45dbd938..3520a028 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -114,6 +114,13 @@ and instr' = | Unary of unop (* unary numeric operator *) | Binary of binop (* binary numeric operator *) | Convert of cvtop (* conversion *) + | TryCatch of block_type * instr list * (* try *) + (var * instr list) list * (* catch exception with tag *) + instr list option (* catch_all *) + | TryDelegate of block_type * instr list * (* try *) + var (* delegate to outer handler *) + | Throw of var (* throw exception *) + | Rethrow of var (* rethrow exception *) (* Globals & Functions *) diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index fa381d8d..bc60edf7 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -87,6 +87,15 @@ let rec instr (e : instr) = memories zero | MemoryInit x -> memories zero ++ datas (var x) | DataDrop x -> datas (var x) + | TryCatch (bt, es, ct, ca) -> + let catch (tag, es) = events (var tag) ++ block es in + let catch_all = function + | None -> empty + | Some es -> block es in + block es ++ (list catch ct) ++ catch_all ca + | TryDelegate (bt, es, x) -> block es ++ events (var x) + | Throw x -> events (var x) + | Rethrow x -> labels (var x) and block (es : instr list) = let free = list instr es in {free with labels = shift free.labels} diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index 2b80b627..8f7a6983 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -19,6 +19,8 @@ let select t = Select t let block bt es = Block (bt, es) let loop bt es = Loop (bt, es) let if_ bt es1 es2 = If (bt, es1, es2) +let try_catch bt es ct ca = TryCatch (bt, es, ct, ca) +let try_delegate bt es x = TryDelegate (bt, es, x) let br x = Br x let br_if x = BrIf x let br_table xs x = BrTable (xs, x) @@ -26,6 +28,8 @@ let br_table xs x = BrTable (xs, x) let return = Return let call x = Call x let call_indirect x y = CallIndirect (x, y) +let throw x = Throw x +let rethrow x = Rethrow x let local_get x = LocalGet x let local_set x = LocalSet x diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index e421bec2..f07da0fe 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -279,6 +279,18 @@ let rec instr e = | Unary op -> unop op, [] | Binary op -> binop op, [] | Convert op -> cvtop op, [] + | TryCatch (bt, es, ct, ca) -> + let catch (tag, es) = Node ("catch " ^ var tag, list instr es) in + let catch_all = match ca with + | Some es -> [Node ("catch_all", list instr es)] + | None -> [] in + let handler = list catch ct @ catch_all in + "try", block_type bt @ [Node ("do", list instr es)] @ handler + | TryDelegate (bt, es, x) -> + let delegate = [Node ("delegate " ^ var x, [])] in + "try", block_type bt @ [Node ("do", list instr es)] @ delegate + | Throw x -> "throw " ^ var x, [] + | Rethrow x -> "rethrow " ^ var x, [] in Node (head, inner) let const head c = @@ -538,6 +550,8 @@ let assertion mode ass = [Node ("assert_return", action mode act :: List.map (result mode) results)] | AssertTrap (act, re) -> [Node ("assert_trap", [action mode act; Atom (string re)])] + | AssertUncaughtException act -> + [Node ("assert_exception", [action mode act])] | AssertExhaustion (act, re) -> [Node ("assert_exhaustion", [action mode act; Atom (string re)])] diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 02376521..aeea7442 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -348,6 +348,14 @@ rule token = parse | "i32.reinterpret_f32" { CONVERT i32_reinterpret_f32 } | "i64.reinterpret_f64" { CONVERT i64_reinterpret_f64 } + | "try" { TRY } + | "do" { DO } + | "catch" { CATCH } + | "catch_all" { CATCH_ALL } + | "delegate" { DELEGATE } + | "throw" { THROW } + | "rethrow" { RETHROW } + | "type" { TYPE } | "func" { FUNC } | "start" { START } @@ -379,6 +387,7 @@ rule token = parse | "assert_unlinkable" { ASSERT_UNLINKABLE } | "assert_return" { ASSERT_RETURN } | "assert_trap" { ASSERT_TRAP } + | "assert_exception" { ASSERT_EXCEPTION } | "assert_exhaustion" { ASSERT_EXHAUSTION } | "nan:canonical" { NAN Script.CanonicalNan } | "nan:arithmetic" { NAN Script.ArithmeticNan } diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 8ed472e3..08a9f88c 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -112,6 +112,8 @@ let func_type (c : context) x = try (Lib.List32.nth c.types.list x.it).it with Failure _ -> error x.at ("unknown type " ^ Int32.to_string x.it) +let handlers (c : context) h = + List.map (fun (l, i) -> (l c event, i c)) h let anon category space n = let i = space.count in @@ -179,7 +181,8 @@ let inline_type_explicit (c : context) x ft at = %token NAT INT FLOAT STRING VAR %token NUM_TYPE FUNCREF EXTERNREF EXTERN MUT %token UNREACHABLE NOP DROP SELECT -%token BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE +%token BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE TRY DO CATCH CATCH_ALL +%token DELEGATE %token CALL CALL_INDIRECT RETURN %token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET %token TABLE_GET TABLE_SET @@ -188,12 +191,13 @@ let inline_type_explicit (c : context) x ft at = %token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT %token CONST UNARY BINARY TEST COMPARE CONVERT %token REF_NULL REF_FUNC REF_EXTERN REF_IS_NULL +%token THROW RETHROW %token FUNC START TYPE PARAM RESULT LOCAL GLOBAL %token TABLE ELEM MEMORY EVENT DATA DECLARE OFFSET ITEM IMPORT EXPORT %token MODULE BIN QUOTE %token SCRIPT REGISTER INVOKE GET %token ASSERT_MALFORMED ASSERT_INVALID ASSERT_SOFT_INVALID ASSERT_UNLINKABLE -%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXHAUSTION +%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_EXHAUSTION %token NAN %token INPUT OUTPUT %token EOF @@ -358,6 +362,8 @@ plain_instr : br_table xs x } | RETURN { fun c -> return } | CALL var { fun c -> call ($2 c func) } + | THROW var { fun c -> throw ($2 c event) } + | RETHROW var { fun c -> rethrow ($2 c label) } | LOCAL_GET var { fun c -> local_get ($2 c local) } | LOCAL_SET var { fun c -> local_set ($2 c local) } | LOCAL_TEE var { fun c -> local_tee ($2 c local) } @@ -398,7 +404,6 @@ plain_instr : | BINARY { fun c -> $1 } | CONVERT { fun c -> $1 } - select_instr : | SELECT select_instr_results { let at = at () in fun c -> let b, ts = $2 in @@ -495,6 +500,12 @@ block_instr : | IF labeling_opt block ELSE labeling_end_opt instr_list END labeling_end_opt { fun c -> let c' = $2 c ($5 @ $8) in let ts, es1 = $3 c' in if_ ts es1 ($6 c') } + | TRY labeling_opt block handler_instr + { fun c -> let c' = $2 c [] in + let ts, es = $3 c' in $4 ts es c' } + | TRY labeling_opt block DELEGATE var + { fun c -> let c' = $2 c [] in + let ts, es = $3 c' in try_delegate ts es ($5 c label) } block : | type_use block_param_body @@ -524,6 +535,44 @@ block_result_body : { let FuncType (ins, out) = fst $5 in FuncType (ins, $3 @ out), snd $5 } +handler_instr : + | catch_list_instr END + { fun bt es c -> try_catch bt es (handlers c $1) None } + | catch_list_instr catch_all END + { fun bt es c -> try_catch bt es (handlers c $1) (Some ($2 c)) } + | catch_all END + { fun bt es c -> try_catch bt es [] (Some ($1 c)) } + | END { fun bt es c -> try_catch bt es [] None } + +catch_list_instr : + | catch catch_list_instr { $1 :: $2 } + | catch { [$1] } + +handler : + | catch_list + { fun bt es _ c' -> + let cs = (List.map (fun (l, i) -> (l c' event, i c')) $1) in + try_catch bt es cs None } + | catch_list LPAR catch_all RPAR + { fun bt es _ c' -> + let cs = (List.map (fun (l, i) -> (l c' event, i c')) $1) in + try_catch bt es cs (Some ($3 c')) } + | LPAR catch_all RPAR + { fun bt es _ c' -> try_catch bt es [] (Some ($2 c')) } + | LPAR DELEGATE var RPAR + { fun bt es c _ -> try_delegate bt es ($3 c label) } + | /* empty */ { fun bt es c _ -> try_catch bt es [] None } + +catch_list : + | catch_list LPAR catch RPAR { $1 @ [$3] } + | LPAR catch RPAR { [$2] } + +catch : + | CATCH var instr_list { ($2, $3) } + +catch_all : + | CATCH_ALL instr_list { $2 } + expr : /* Sugar */ | LPAR expr1 RPAR @@ -545,6 +594,8 @@ expr1 : /* Sugar */ | IF labeling_opt if_block { fun c -> let c' = $2 c [] in let bt, (es, es1, es2) = $3 c c' in es, if_ bt es1 es2 } + | TRY labeling_opt try_block + { fun c -> let c' = $2 c [] in [], $3 c c' } select_expr_results : | LPAR RESULT value_type_list RPAR select_expr_results @@ -614,6 +665,38 @@ if_ : | LPAR THEN instr_list RPAR /* Sugar */ { fun c c' -> [], $3 c', [] } +try_block : + | type_use try_block_param_body + { let at = at () in + fun c c' -> + let bt = VarBlockType (inline_type_explicit c' ($1 c' type_) (fst $2) at) in + snd $2 bt c c' } + | try_block_param_body /* Sugar */ + { let at = at () in + fun c c' -> + let bt = + match fst $1 with + | FuncType ([], []) -> ValBlockType None + | FuncType ([], [t]) -> ValBlockType (Some t) + | ft -> VarBlockType (inline_type c' ft at) + in snd $1 bt c c' } + +try_block_param_body : + | try_block_result_body { $1 } + | LPAR PARAM value_type_list RPAR try_block_param_body + { let FuncType (ins, out) = fst $5 in + FuncType ($3 @ ins, out), snd $5 } + +try_block_result_body : + | try_ { FuncType ([], []), $1 } + | LPAR RESULT value_type_list RPAR try_block_result_body + { let FuncType (ins, out) = fst $5 in + FuncType (ins, $3 @ out), snd $5 } + +try_ : + | LPAR DO instr_list RPAR handler + { fun bt c c' -> $5 bt ($3 c') c c' } + instr_list : | /* empty */ { fun c -> [] } | select_instr { fun c -> [$1 c] } @@ -1085,6 +1168,8 @@ assertion : { AssertUninstantiable (snd $3, $4) @@ at () } | LPAR ASSERT_RETURN action result_list RPAR { AssertReturn ($3, $4) @@ at () } | LPAR ASSERT_TRAP action STRING RPAR { AssertTrap ($3, $4) @@ at () } + | LPAR ASSERT_EXCEPTION action RPAR + { AssertUncaughtException $3 @@ at () } | LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ at () } cmd : diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index 57a4a8fe..d2dfcfb0 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -402,6 +402,11 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = let t1, t2 = type_cvtop e.at cvtop in [NumType t1] --> [NumType t2] + | TryCatch _ -> [] --> [] (* TODO *) + | TryDelegate _ -> [] --> [] (* TODO *) + | Throw _ -> [] --> [] (* TODO *) + | Rethrow _ -> [] --> [] (* TODO *) + and check_seq (c : context) (s : infer_stack_type) (es : instr list) : infer_stack_type = match es with diff --git a/test/core/exports.wast b/test/core/exports.wast index 8d535571..d6de8126 100644 --- a/test/core/exports.wast +++ b/test/core/exports.wast @@ -57,6 +57,10 @@ (module (func) (memory 0) (export "a" (func 0)) (export "a" (memory 0))) "duplicate export name" ) +(assert_invalid + (module (event $e0 (export "e0")) (event $e1 (export "e0"))) + "duplicate export name" +) ;; Globals diff --git a/test/core/rethrow.wast b/test/core/rethrow.wast new file mode 100644 index 00000000..eac15a50 --- /dev/null +++ b/test/core/rethrow.wast @@ -0,0 +1,83 @@ +;; Test rethrow instruction. + +(module + (event $e0) + (event $e1) + + (func (export "catch-rethrow-0") + (try + (do (throw $e0)) + (catch $e0 (rethrow 0)) + ) + ) + + (func (export "catch-rethrow-1") (param i32) (result i32) + (try (result i32) + (do (throw $e0)) + (catch $e0 + (if (i32.eqz (local.get 0)) (then (rethrow 1))) (i32.const 23) + ) + ) + ) + + (func (export "catchall-rethrow-0") + (try + (do (throw $e0)) + (catch_all (rethrow 0)) + ) + ) + + (func (export "catchall-rethrow-1") (param i32) (result i32) + (try (result i32) + (do (throw $e0)) + (catch_all + (if (i32.eqz (local.get 0)) (then (rethrow 1))) (i32.const 23) + ) + ) + ) + + (func (export "rethrow-nested") (param i32) (result i32) + (try (result i32) + (do (throw $e1)) + (catch $e1 + (try (result i32) + (do (throw $e0)) + (catch $e0 + (if (i32.eq (local.get 0) (i32.const 0)) (then (rethrow 1))) + (if (i32.eq (local.get 0) (i32.const 1)) (then (rethrow 2))) + (i32.const 23) + ) + ) + ) + ) + ) + + (func (export "rethrow-recatch") (param i32) (result i32) + (try (result i32) + (do (throw $e0)) + (catch $e0 + (try (result i32) + (do (if (i32.eqz (local.get 0)) (then (rethrow 2))) (i32.const 42)) + (catch $e0 (i32.const 23)) + ) + ) + ) + ) +) + +(assert_exception (invoke "catch-rethrow-0")) + +(assert_exception (invoke "catch-rethrow-1" (i32.const 0))) +(assert_return (invoke "catch-rethrow-1" (i32.const 1)) (i32.const 23)) + +(assert_exception (invoke "catchall-rethrow-0")) + +(assert_exception (invoke "catchall-rethrow-1" (i32.const 0))) +(assert_return (invoke "catchall-rethrow-1" (i32.const 1)) (i32.const 23)) +(assert_exception (invoke "rethrow-nested" (i32.const 0))) +(assert_exception (invoke "rethrow-nested" (i32.const 1))) +(assert_return (invoke "rethrow-nested" (i32.const 2)) (i32.const 23)) + +(assert_return (invoke "rethrow-recatch" (i32.const 0)) (i32.const 23)) +(assert_return (invoke "rethrow-recatch" (i32.const 1)) (i32.const 42)) + diff --git a/test/core/throw.wast b/test/core/throw.wast new file mode 100644 index 00000000..6564cc27 --- /dev/null +++ b/test/core/throw.wast @@ -0,0 +1,45 @@ +;; Test throw instruction. + +(module + (event $e0) + (event $e-i32 (param i32)) + (event $e-f32 (param f32)) + (event $e-i64 (param i64)) + (event $e-f64 (param f64)) + (event $e-i32-i32 (param i32 i32)) + + (func $throw-if (export "throw-if") (param i32) (result i32) + (local.get 0) + (i32.const 0) (if (i32.ne) (then (throw $e0))) + (i32.const 0) + ) + + (func (export "throw-param-f32") (param f32) (local.get 0) (throw $e-f32)) + + (func (export "throw-param-i64") (param i64) (local.get 0) (throw $e-i64)) + + (func (export "throw-param-f64") (param f64) (local.get 0) (throw $e-f64)) + + (func $throw-1-2 (i32.const 1) (i32.const 2) (throw $e-i32-i32)) + (func (export "test-throw-1-2") + (try + (do (call $throw-1-2)) + (catch $e-i32-i32 + (i32.const 2) + (if (i32.ne) (then (unreachable))) + (i32.const 1) + (if (i32.ne) (then (unreachable))) + ) + ) + ) +) + +(assert_return (invoke "throw-if" (i32.const 0)) (i32.const 0)) +(assert_exception (invoke "throw-if" (i32.const 10))) +(assert_exception (invoke "throw-if" (i32.const -1))) + +(assert_exception (invoke "throw-param-f32" (f32.const 5.0))) +(assert_exception (invoke "throw-param-i64" (i64.const 5))) +(assert_exception (invoke "throw-param-f64" (f64.const 5.0))) + +(assert_return (invoke "test-throw-1-2")) diff --git a/test/core/try_catch.wast b/test/core/try_catch.wast new file mode 100644 index 00000000..4d92fb1b --- /dev/null +++ b/test/core/try_catch.wast @@ -0,0 +1,206 @@ +;; Test try-catch blocks. + +(module + (event $e0 (export "e0")) + (func (export "throw") (throw $e0)) +) + +(register "test") + +(module + (event $imported-e0 (import "test" "e0")) + (func $imported-throw (import "test" "throw")) + (event $e0) + (event $e1) + (event $e2) + (event $e-i32 (param i32)) + (event $e-f32 (param f32)) + (event $e-i64 (param i64)) + (event $e-f64 (param f64)) + + (func $throw-if (param i32) (result i32) + (local.get 0) + (i32.const 0) (if (i32.ne) (then (throw $e0))) + (i32.const 0) + ) + + (func (export "empty-catch") (try (do) (catch $e0))) + + (func (export "simple-throw-catch") (param i32) (result i32) + (try (result i32) + (do (local.get 0) (i32.eqz) (if (then (throw $e0)) (else)) (i32.const 42)) + (catch $e0 (i32.const 23)) + ) + ) + + (func (export "unreachable-not-caught") (try (do (unreachable)) (catch_all))) + + (func $div (param i32 i32) (result i32) + (local.get 0) (local.get 1) (i32.div_u) + ) + (func (export "trap-in-callee") (param i32 i32) (result i32) + (try (result i32) + (do (local.get 0) (local.get 1) (call $div)) + (catch_all (i32.const 11)) + ) + ) + + (func (export "catch-complex-1") (param i32) (result i32) + (try (result i32) + (do + (try (result i32) + (do + (local.get 0) + (i32.eqz) + (if + (then (throw $e0)) + (else + (local.get 0) + (i32.const 1) + (i32.eq) + (if (then (throw $e1)) (else (throw $e2))) + ) + ) + (i32.const 2) + ) + (catch $e0 (i32.const 3)) + ) + ) + (catch $e1 (i32.const 4)) + ) + ) + + (func (export "catch-complex-2") (param i32) (result i32) + (try (result i32) + (do + (local.get 0) + (i32.eqz) + (if + (then (throw $e0)) + (else + (local.get 0) + (i32.const 1) + (i32.eq) + (if (then (throw $e1)) (else (throw $e2))) + ) + ) + (i32.const 2) + ) + (catch $e0 (i32.const 3)) + (catch $e1 (i32.const 4)) + ) + ) + + (func (export "throw-catch-param-i32") (param i32) (result i32) + (try (result i32) + (do (local.get 0) (throw $e-i32) (i32.const 2)) + (catch $e-i32 (return)) + ) + ) + + (func (export "throw-catch-param-f32") (param f32) (result f32) + (try (result f32) + (do (local.get 0) (throw $e-f32) (f32.const 0)) + (catch $e-f32 (return)) + ) + ) + + (func (export "throw-catch-param-i64") (param i64) (result i64) + (try (result i64) + (do (local.get 0) (throw $e-i64) (i64.const 2)) + (catch $e-i64 (return)) + ) + ) + + (func (export "throw-catch-param-f64") (param f64) (result f64) + (try (result f64) + (do (local.get 0) (throw $e-f64) (f64.const 0)) + (catch $e-f64 (return)) + ) + ) + + (func $throw-param-i32 (param i32) (local.get 0) (throw $e-i32)) + (func (export "catch-param-i32") (param i32) (result i32) + (try (result i32) + (do (i32.const 0) (local.get 0) (call $throw-param-i32)) + (catch $e-i32) + ) + ) + + (func (export "catch-imported") (result i32) + (try (result i32) + (do + (i32.const 1) + (call $imported-throw) + ) + (catch $imported-e0 (i32.const 2)) + ) + ) + + (func (export "catchless-try") (param i32) (result i32) + (try (result i32) + (do + (try (result i32) + (do (local.get 0) (call $throw-if)) + ) + ) + (catch $e0 (i32.const 1)) + ) + ) +) + +(assert_return (invoke "empty-catch")) + +(assert_return (invoke "simple-throw-catch" (i32.const 0)) (i32.const 23)) +(assert_return (invoke "simple-throw-catch" (i32.const 1)) (i32.const 42)) + +(assert_exception (invoke "unreachable-not-caught")) + +(assert_return (invoke "trap-in-callee" (i32.const 7) (i32.const 2)) (i32.const 3)) +(assert_exception (invoke "trap-in-callee" (i32.const 1) (i32.const 0))) + +(assert_return (invoke "catch-complex-1" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "catch-complex-1" (i32.const 1)) (i32.const 4)) +(assert_exception (invoke "catch-complex-1" (i32.const 2))) + +(assert_return (invoke "catch-complex-2" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "catch-complex-2" (i32.const 1)) (i32.const 4)) +(assert_exception (invoke "catch-complex-2" (i32.const 2))) + +(assert_return (invoke "throw-catch-param-i32" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "throw-catch-param-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "throw-catch-param-i32" (i32.const 10)) (i32.const 10)) + +(assert_return (invoke "throw-catch-param-f32" (f32.const 5.0)) (f32.const 5.0)) +(assert_return (invoke "throw-catch-param-f32" (f32.const 10.5)) (f32.const 10.5)) + +(assert_return (invoke "throw-catch-param-i64" (i64.const 5)) (i64.const 5)) +(assert_return (invoke "throw-catch-param-i64" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "throw-catch-param-i64" (i64.const -1)) (i64.const -1)) + +(assert_return (invoke "throw-catch-param-f64" (f64.const 5.0)) (f64.const 5.0)) +(assert_return (invoke "throw-catch-param-f64" (f64.const 10.5)) (f64.const 10.5)) + +(assert_return (invoke "catch-param-i32" (i32.const 5)) (i32.const 5)) + +(assert_return (invoke "catch-imported") (i32.const 2)) + +(assert_return (invoke "catchless-try" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "catchless-try" (i32.const 1)) (i32.const 1)) + +(assert_malformed + (module quote "(module (func (catch_all)))") + "unexpected token" +) + +(assert_malformed + (module quote "(module (event $e) (func (catch $e)))") + "unexpected token" +) + +(assert_malformed + (module quote + "(module (func (try (do) (catch_all) (catch_all))))" + ) + "unexpected token" +) diff --git a/test/core/try_delegate.wast b/test/core/try_delegate.wast new file mode 100644 index 00000000..b58abd64 --- /dev/null +++ b/test/core/try_delegate.wast @@ -0,0 +1,115 @@ +;; Test try-delegate blocks. + +(module + (event $e0) + (event $e1) + + (func (export "delegate-no-throw") (result i32) + (try $t (result i32) + (do (try (result i32) (do (i32.const 1)) (delegate $t))) + (catch $e0 (i32.const 2)) + ) + ) + + (func $throw-if (param i32) + (local.get 0) + (if (then (throw $e0)) (else)) + ) + + (func (export "delegate-throw") (param i32) (result i32) + (try $t (result i32) + (do + (try (result i32) + (do (local.get 0) (call $throw-if) (i32.const 1)) + (delegate $t) + ) + ) + (catch $e0 (i32.const 2)) + ) + ) + + (func (export "delegate-skip") (result i32) + (try $t (result i32) + (do + (try (result i32) + (do + (try (result i32) + (do (throw $e0) (i32.const 1)) + (delegate $t) + ) + ) + (catch $e0 (i32.const 2)) + ) + ) + (catch $e0 (i32.const 3)) + ) + ) + + (func (export "delegate-to-caller") + (try (do (try (do (throw $e0)) (delegate 1))) (catch_all)) + ) + + (func $select-event (param i32) + (block (block (block (local.get 0) (br_table 0 1 2)) (return)) (throw $e0)) + (throw $e1) + ) + + (func (export "delegate-merge") (param i32 i32) (result i32) + (try $t (result i32) + (do + (local.get 0) + (call $select-event) + (try + (result i32) + (do (local.get 1) (call $select-event) (i32.const 1)) + (delegate $t) + ) + ) + (catch $e0 (i32.const 2)) + ) + ) + + (func (export "delegate-throw-no-catch") (result i32) + (try (result i32) + (do (try (result i32) (do (throw $e0) (i32.const 1)) (delegate 0))) + (catch $e1 (i32.const 2)) + ) + ) +) + +(assert_return (invoke "delegate-no-throw") (i32.const 1)) + +(assert_return (invoke "delegate-throw" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "delegate-throw" (i32.const 1)) (i32.const 2)) + +(assert_exception (invoke "delegate-throw-no-catch")) + +(assert_return (invoke "delegate-merge" (i32.const 1) (i32.const 0)) (i32.const 2)) +(assert_exception (invoke "delegate-merge" (i32.const 2) (i32.const 0))) +(assert_return (invoke "delegate-merge" (i32.const 0) (i32.const 1)) (i32.const 2)) +(assert_exception (invoke "delegate-merge" (i32.const 0) (i32.const 2))) +(assert_return (invoke "delegate-merge" (i32.const 0) (i32.const 0)) (i32.const 1)) + +(assert_return (invoke "delegate-skip") (i32.const 3)) + +(assert_exception (invoke "delegate-to-caller")) + +(assert_malformed + (module quote "(module (func (delegate 0)))") + "unexpected token" +) + +(assert_malformed + (module quote "(module (event $e) (func (try (do) (catch $e) (delegate 0))))") + "unexpected token" +) + +(assert_malformed + (module quote "(module (func (try (do) (catch_all) (delegate 0))))") + "unexpected token" +) + +(assert_malformed + (module quote "(module (func (try (do) (delegate) (delegate 0))))") + "unexpected token" +)