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" +)