diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index 68bddd9b..749007f3 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -646,7 +646,10 @@ let rec instr s = let tag = at var s in let xls = vec on_clause s in resume_throw x tag xls - (* TODO: resume_throw_ref *) + | 0xe5 -> + let x = at var s in + let xls = vec on_clause s in + resume_throw_ref x xls | 0xe6 -> let x = at var s in let y = at var s in diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index f4942331..ca32b89b 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -303,7 +303,7 @@ struct | Suspend x -> op 0xe2; var x | Resume (x, xls) -> op 0xe3; var x; resumetable xls | ResumeThrow (x, y, xls) -> op 0xe4; var x; var y; resumetable xls - (* TOOD: resume_throw_ref *) + | ResumeThrowRef (x, xls) -> op 0xe5; var x; resumetable xls | Switch (x, y) -> op 0xe6; var x; var y | Throw x -> op 0x08; var x diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 3fb2176b..32b80606 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -407,6 +407,22 @@ let rec step (c : config) : config = cont := None; vs', [Prompt (hs, ctxt ([], [Throwing (tagt, args) @@ e.at])) @@ e.at] + | ResumeThrowRef (x, xls), Ref _ :: Ref (NullRef _) :: vs -> + vs, [Trapping "null exception reference" @@ e.at] + + | ResumeThrowRef (x, xls), Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | ResumeThrowRef (x, xls), Ref (ContRef {contents = None}) :: Ref _ :: vs -> + vs, [Trapping "continuation already consumed" @@ e.at] + + | ResumeThrowRef (x, xls), + Ref (ContRef ({contents = Some (n, ctxt)} as cont)) :: + v :: vs -> + let hs = handle_table c xls in + cont := None; + vs, [Prompt (hs, ctxt ([v], [Plain ThrowRef @@ e.at])) @@ e.at] + | Switch (x, y), Ref (NullRef _) :: vs -> vs, [Trapping "null continuation reference" @@ e.at] diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index 5ee382fd..30ea07dc 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -177,6 +177,7 @@ and instr' = | Suspend of idx (* suspend continuation *) | Resume of idx * (idx * hdl) list (* resume continuation *) | ResumeThrow of idx * idx * (idx * hdl) list (* abort continuation *) + | ResumeThrowRef of idx * (idx * hdl) list (* abort continuation *) | Switch of idx * idx (* direct switch continuation *) | Throw of idx (* throw exception *) | ThrowRef (* rethrow exception *) diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index 8bfb0d40..ef6a181f 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -181,6 +181,7 @@ let rec instr (e : instr) = | ContNew x -> types (idx x) | ContBind (x, y) -> types (idx x) ++ types (idx y) | ResumeThrow (x, y, xys) -> types (idx x) ++ tags (idx y) ++ list (fun (x, y) -> tags (idx x) ++ hdl y) xys + | ResumeThrowRef (x, xys) -> types (idx x) ++ list (fun (x, y) -> tags (idx x) ++ hdl y) xys | Resume (x, xys) -> types (idx x) ++ list (fun (x, y) -> tags (idx x) ++ hdl y) xys | Suspend x -> tags (idx x) | Switch (x, z) -> types (idx x) ++ tags (idx z) diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index e6b39aa3..42fa82cc 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -54,6 +54,7 @@ let cont_bind x y = ContBind (x, y) let suspend x = Suspend x let resume x xys = Resume (x, xys) let resume_throw x y xys = ResumeThrow (x, y, xys) +let resume_throw_ref x xys = ResumeThrowRef (x, xys) let switch x y = Switch (x, y) let throw x = Throw x let throw_ref = ThrowRef diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index 9599265d..d369554c 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -561,6 +561,8 @@ let rec instr e = "resume " ^ var x, resumetable xys | ResumeThrow (x, y, xys) -> "resume_throw " ^ var x ^ " " ^ var y, resumetable xys + | ResumeThrowRef (x, xys) -> + "resume_throw_ref " ^ var x, resumetable xys | Switch (x, z) -> "switch " ^ var x ^ " " ^ var z, [] | Throw x -> "throw " ^ var x, [] diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index cb124af8..001b4294 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -230,6 +230,7 @@ rule token = parse | "suspend" -> SUSPEND | "resume" -> RESUME | "resume_throw" -> RESUME_THROW + | "resume_throw_ref" -> RESUME_THROW_REF | "switch" -> SWITCH diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index c7f400e2..617b299b 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -304,7 +304,7 @@ let parse_annots (m : module_) : Custom.section list = %token MUT FIELD STRUCT ARRAY SUB FINAL REC %token UNREACHABLE NOP DROP SELECT %token BLOCK END IF THEN ELSE LOOP -%token CONT_NEW CONT_BIND SUSPEND RESUME RESUME_THROW SWITCH +%token CONT_NEW CONT_BIND SUSPEND RESUME RESUME_THROW RESUME_THROW_REF SWITCH %token BR BR_IF BR_TABLE BR_ON_NON_NULL %token Ast.instr'> BR_ON_NULL %token Types.ref_type -> Types.ref_type -> Ast.instr'> BR_ON_CAST @@ -789,6 +789,11 @@ resume_instr_instr_list : let x = $2 c type_ in let tag = $3 c tag in let hs, es = $4 c in (resume_throw x tag hs @@ loc1) :: es } + | RESUME_THROW_REF var resume_instr_handler_instr + { let loc1 = $loc($1) in + fun c -> + let x = $2 c type_ in + let hs, es = $3 c in (resume_throw_ref x hs @@ loc1) :: es } resume_instr_handler_instr : | LPAR ON var var RPAR resume_instr_handler_instr @@ -907,6 +912,11 @@ expr1 : /* Sugar */ let tag = $3 c tag in let hs, es = $4 c in es, resume_throw x tag hs } + | RESUME_THROW_REF var resume_expr_handler + { fun c -> + let x = $2 c type_ in + let hs, es = $3 c in + es, resume_throw_ref x hs } | BLOCK labeling_opt block { fun c -> let c' = $2 c [] in let bt, es = $3 c' in [], block bt es } | LOOP labeling_opt block diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index 9173e3c6..7754c7bb 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -658,6 +658,12 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in check_resume_table c ts2 xys e.at; (ts0 @ [RefT (Null, VarHT (StatX x.it))]) --> ts2, [] + | ResumeThrowRef (x, xys) -> + let ct = cont_type c x in + let FuncT (_ts1, ts2) = func_type_of_cont_type c ct x.at in + check_resume_table c ts2 xys e.at; + ([RefT (Null, ExnHT); RefT (Null, VarHT (StatX x.it))]) --> ts2, [] + | Switch (x, y) -> let ct1 = cont_type c x in let FuncT (ts11, ts12) = func_type_of_cont_type c ct1 x.at in diff --git a/test/core/stack-switching/resume_throw.wast b/test/core/stack-switching/resume_throw.wast new file mode 100644 index 00000000..2b19f367 --- /dev/null +++ b/test/core/stack-switching/resume_throw.wast @@ -0,0 +1,312 @@ +;; Tests for resume_throw + +;; Test resume_throw on a continuation that is never resumed. +(module + (tag $exn) + + (type $f (func)) + (type $k (cont $f)) + + (func $never (unreachable)) + (elem declare func $never) + + (func (export "throw_never") + (block $h + (try_table (catch $exn $h) + (cont.new $k (ref.func $never)) + (resume_throw $k $exn) + (unreachable) + ) + ) + ) +) +(assert_return (invoke "throw_never")) + +;; Test resume_throw with a value type argument. +(module + (tag $exn_i32 (param i32)) + + (type $f (func)) + (type $k (cont $f)) + + (func $never (unreachable)) + (elem declare func $never) + + (func (export "throw_never_i32") (result i32) + (block $h (result i32) + (try_table (result i32) (catch $exn_i32 $h) + (i32.const 42) + (cont.new $k (ref.func $never)) + (resume_throw $k $exn_i32) + (unreachable) + ) + ) + ) +) +(assert_return (invoke "throw_never_i32") (i32.const 42)) + +;; Test resume_throw with a reference type argument. +(module + (tag $exn_ref (param externref)) + + (type $f (func)) + (type $k (cont $f)) + + (func $never (unreachable)) + (elem declare func $never) + + (func (export "throw_never_ref") (param $val externref) (result externref) + (block $h (result externref) + (try_table (result externref) (catch $exn_ref $h) + (local.get $val) + (cont.new $k (ref.func $never)) + (resume_throw $k $exn_ref) + (unreachable) + ) + ) + ) +) +(assert_return (invoke "throw_never_ref" (ref.extern 1)) (ref.extern 1)) + +;; Test resume_throw where the continuation handles the exception. +(module + (tag $exn) + (tag $e1) + + (type $f (func)) + (type $k (cont $f)) + + (func $handler + (block $h + (try_table (catch $exn $h) + (suspend $e1) + ) + ) + ) + (elem declare func $handler) + + (func (export "throw_handled") + (block $h (result (ref $k)) + (resume $k (on $e1 $h) (cont.new $k (ref.func $handler))) + (unreachable) + ) + (resume_throw $k $exn) + ) +) +(assert_return (invoke "throw_handled")) + +;; Test resume_throw where the continuation does not handle the exception. +(module + (tag $exn) + (tag $e1) + + (type $f (func)) + (type $k (cont $f)) + + (func $no_handler + (suspend $e1) + ) + (elem declare func $no_handler) + + (func (export "throw_unhandled") + (block $h (result (ref $k)) + (resume $k (on $e1 $h) (cont.new $k (ref.func $no_handler))) + (unreachable) + ) + (resume_throw $k $exn) + ) +) +(assert_exception (invoke "throw_unhandled")) + +;; Test resume_throw on a consumed continuation. +(module + (tag $exn) + + (type $f (func)) + (type $k (cont $f)) + + (func $f1) + (elem declare func $f1) + + (func (export "throw_consumed") + (local $k_ref (ref $k)) + (local.set $k_ref (cont.new $k (ref.func $f1))) + (resume $k (local.get $k_ref)) ;; consume it + (resume_throw $k $exn (local.get $k_ref)) ;; should trap + ) +) +(assert_trap (invoke "throw_consumed") "continuation already consumed") + +;; Test resume_throw on a null continuation reference. +(module + (tag $exn) + (type $f (func)) + (type $k (cont $f)) + (func (export "throw_null") + (resume_throw $k $exn (ref.null $k)) + ) +) +(assert_trap (invoke "throw_null") "null continuation reference") + +;; Test resume_throw_ref where the continuation handles the exception. +(module + (tag $e0 (param i32)) + (tag $yield) + + (type $f (func (result i32))) + (type $k (cont $f)) + + (func (export "throw_handled_ref") (result i32) + (local $k_ref (ref $k)) + (local.set $k_ref (cont.new $k (ref.func $yield42))) + + (block $y (result (ref $k)) + (resume $k (on $yield $y) + (local.get $k_ref)) + (unreachable)) + (local.set $k_ref) + + (block $h (result i32 exnref) + (try_table (catch_ref $e0 $h) + (i32.const 42) + (throw $e0)) + (unreachable) + ) + + (resume_throw_ref $k (local.get $k_ref)) + (return) + ) + + (func $yield42 (result i32) + (block $h (result i32) + (try_table (result i32) (catch $e0 $h) + (suspend $yield) + (unreachable) + ) + ) + ) + (elem declare func $yield42) +) +(assert_return (invoke "throw_handled_ref") (i32.const 42)) + + +;; Test resume_throw_ref where the continuation does not handle the exception. +(module + (tag $e0) + + (type $f (func)) + (type $k (cont $f)) + + (func $no_handler + (unreachable) ;; We only throw into this function + ) + (elem declare func $no_handler) + + (func (export "throw_unhandled_ref") + (block $h (result exnref) + (try_table (catch_ref $e0 $h) (throw $e0)) + (unreachable) + ) + (resume_throw_ref $k (cont.new $k (ref.func $no_handler))) + ) +) +(assert_exception (invoke "throw_unhandled_ref")) + +;; Test resume_throw_ref on a consumed continuation. +(module + (tag $e0) + + (type $f (func)) + (type $k (cont $f)) + + (func $f1) + (elem declare func $f1) + + (func (export "throw_consumed_ref") + (local $k_ref (ref $k)) + (local.set $k_ref (cont.new $k (ref.func $f1))) + (resume $k (local.get $k_ref)) ;; consume it + + (block $h (result exnref) + (try_table (result exnref) (catch_ref $e0 $h) + (throw $e0) + ) + ) + (local.get $k_ref) + + (resume_throw_ref $k) ;; should trap + ) +) +(assert_trap (invoke "throw_consumed_ref") "continuation already consumed") + +;; Test resume_throw_ref on a null continuation reference. +(module + (tag $e0) + (type $f (func)) + (type $k (cont $f)) + (func (export "throw_null_ref") + (block $h (result exnref) + (try_table (catch_ref $e0 $h) + (throw $e0)) + (unreachable) + ) + (resume_throw_ref $k (ref.null $k)) + ) +) +(assert_trap (invoke "throw_null_ref") "null continuation reference") + +;; ---- Validation ---- + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $exn (param i32)) + (func + (i64.const 0) + (resume_throw $ct $exn (ref.null $ct)) ;; null continuation + (unreachable))) + "type mismatch") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $exn) + (func + (ref.null $ct) + (i32.const 0) + (resume_throw $ct $exn) ;; exception tag does not take paramter + (unreachable))) + "type mismatch") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $exn (param i32)) + (func + (resume_throw $ct $exn (ref.null $ct)) ;; missing exception payload + (unreachable))) + "type mismatch") + + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $exn (param externref)) + (func + (i64.const 0) + (resume_throw_ref $ct (ref.null $ct)) ;; expecting an exception ref + (unreachable))) + "type mismatch") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (resume_throw_ref $ct (ref.null $ct)) ;; expecting an exception ref + (unreachable))) + "type mismatch") diff --git a/test/core/throw_ref.wast b/test/core/throw_ref.wast index f59710a1..a920ff1d 100644 --- a/test/core/throw_ref.wast +++ b/test/core/throw_ref.wast @@ -17,7 +17,8 @@ (try_table (result i32) (catch_ref $e0 $h) (throw $e0)) (return) ) - (if (param exnref) (i32.eqz (local.get 0)) + (if (param exnref) + (i32.eqz (local.get 0)) (then (throw_ref)) (else (drop)) )