From c9f369b6349fee623dc993e808729dc6b292a8c8 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Thu, 18 Feb 2021 19:30:39 +0100 Subject: [PATCH 01/15] First semantics --- interpreter/exec/eval.ml | 103 +++++++++++++++++++++++++++++++++--- interpreter/text/arrange.ml | 2 +- test/core/catch.wast | 4 +- 3 files changed, 99 insertions(+), 10 deletions(-) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 491ef40723..fad50f59a8 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -63,6 +63,7 @@ and admin_instr' = | Invoke of func_inst | Trapping of string | Throwing of event_inst * value stack + | Suspending of event_inst * value stack * admin_instr | Returning of value stack | ReturningInvoke of value stack * func_inst | Breaking of int32 * value stack @@ -70,6 +71,12 @@ and admin_instr' = | Local of int * value list * code | Frame of int * frame * code | Catch of int * event_inst option * instr list * code + | Resume of (event_inst * idx) list * code + | Hole + +type cont = int * code + +type ref_ += ContRef of cont type config = { @@ -130,6 +137,23 @@ let drop n (vs : 'a stack) at = let split n (vs : 'a stack) at = take n vs at, drop n vs at +let compose (vs1, es1) (vs2, es2) = vs1 @ vs2, es1 @ es2 +let rec plug c (vs, es) = + match es with + | {it = Label (n, es, c'); at} :: es' -> + vs, (Label (n, es, plug c c') @@ at) :: es' + | {it = Local (n, vs, c'); at} :: es' -> + vs, (Local (n, vs, plug c c') @@ at) :: es' + | {it = Frame (n, frame, c'); at} :: es' -> + vs, (Frame (n, frame, plug c c') @@ at) :: es' + | {it = Catch (n, evo, es, c'); at} :: es' -> + vs, (Catch (n, evo, es, plug c c') @@ at) :: es' + | {it = Resume (hs, c'); at} :: es' -> + vs, (Resume (hs, plug c c') @@ at) :: es' + | {it = Hole; at} :: es' -> + compose c (vs, es') + | _ -> assert false + (* Evaluation *) @@ -209,7 +233,10 @@ let rec step (c : config) : config = vs', [Catch (n2, exno, es2, ([], [Label (n2, [], (args, List.map plain es1)) @@ e.at])) @@ e.at] | Throw x, vs -> - [], [Throwing (event c.frame.inst x, vs) @@ e.at] + let evt = event c.frame.inst x in + let EventType (FuncType (ts, _), _) = Event.type_of evt in + let vs0, vs' = split (List.length ts) vs e.at in + vs', [Throwing (evt, vs0) @@ e.at] | Br x, vs -> [], [Breaking (x.it, vs) @@ e.at] @@ -278,6 +305,37 @@ let rec step (c : config) : config = let f' = Func.alloc_closure (type_ c.frame.inst x) f args in Ref (FuncRef f') :: vs', [] + | ContNew x, Ref (NullRef _) :: vs -> + vs, [Trapping "null function reference" @@ e.at] + + | ContNew x, Ref (FuncRef f) :: vs -> + let FuncType (ts, _) = Func.type_of f in + Ref (ContRef (List.length ts, ([], [Invoke f @@ e.at]))) :: vs, [] + + | ContSuspend x, vs -> + let evt = event c.frame.inst x in + let EventType (FuncType (ts, _), _) = Event.type_of evt in + let vs0, vs' = split (List.length ts) vs e.at in + vs', [Suspending (evt, vs0, Hole @@ e.at) @@ e.at] + + | ContThrow x, Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | ContThrow x, Ref (ContRef (n, code)) :: vs -> + let evt = event c.frame.inst x in + let EventType (FuncType (ts, _), _) = Event.type_of evt in + let vs0, vs' = split (List.length ts) vs e.at in + let vs1', es1' = plug (vs0, [Plain (Throw x) @@ e.at]) code in + vs1' @ vs', es1' + + | ContResume xls, Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | ContResume xls, Ref (ContRef (n, code)) :: vs -> + let hs = List.map (fun (x, l) -> event c.frame.inst x, l) xls in + let vs0, vs' = split n vs e.at in + vs', [Resume (hs, plug (vs0, []) code) @@ e.at] + | Drop, v :: vs' -> vs', [] @@ -557,6 +615,9 @@ let rec step (c : config) : config = | Label (n, es0, (vs', [])), vs -> vs' @ vs, [] + | Label (n, es0, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> + vs, [Suspending (evt, vs1, Label (n, es0, (vs', e1 :: es')) @@ e.at) @@ at] + | Label (n, es0, (vs', {it = Breaking (0l, vs0); at} :: es')), vs -> take n vs0 e.at @ vs, List.map plain es0 @@ -573,6 +634,9 @@ let rec step (c : config) : config = | Local (n, vs0, (vs', [])), vs -> vs' @ vs, [] + | Local (n, vs0, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> + vs, [Suspending (evt, vs1, Local (n, vs0, (vs', e1 :: es')) @@ e.at) @@ at] + | Local (n, vs0, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] @@ -585,6 +649,9 @@ let rec step (c : config) : config = | Frame (n, frame', (vs', [])), vs -> vs' @ vs, [] + | Frame (n, frame', (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> + vs, [Suspending (evt, vs1, Frame (n, frame', (vs', e1 :: es')) @@ e.at) @@ at] + | Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs -> take n vs0 e.at @ vs, [] @@ -630,14 +697,15 @@ let rec step (c : config) : config = | Catch (n, exno, es0, (vs', [])), vs -> vs' @ vs, [] + | Catch (n, exno, es0, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> + vs, [Suspending (evt, vs1, Catch (n, exno, es0, (vs', e1 :: es')) @@ e.at) @@ at] + | Catch (n, None, es0, (vs', {it = Throwing (exn, vs0); at} :: _)), vs -> vs, [Label (n, [], ([], List.map plain es0)) @@ e.at] | Catch (n, Some exn, es0, (vs', {it = Throwing (exn0, vs0); at} :: _)), vs when exn0 == exn -> - let EventType (FuncType (ts, _), _) = Event.type_of exn in - let n' = List.length ts in - vs, [Label (n, [], (take n' vs0 at, List.map plain es0)) @@ e.at] + vs, [Label (n, [], (vs0, List.map plain es0)) @@ e.at] | Catch (n, exno, es0, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] @@ -646,6 +714,22 @@ let rec step (c : config) : config = let c' = step {c with code = code'} in vs, [Catch (n, exno, es0, c'.code) @@ e.at] + | Resume (hs, (vs', [])), vs -> + vs' @ vs, [] + + | Resume (hs, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs + when List.mem_assq evt hs -> + let EventType (FuncType (_, ts), _) = Event.type_of evt in + [Ref (ContRef (List.length ts, (vs', e1 :: es')))] @ vs1 @ vs, + [Plain (Br (List.assq evt hs)) @@ e.at] + + | Resume (hs, (vs', e' :: es')), vs when is_jumping e' -> + vs, [e'] + + | Resume (hs, code'), vs -> + let c' = step {c with code = code'} in + vs, [Resume (hs, c'.code) @@ e.at] + | Returning _, vs | ReturningInvoke _, vs -> Crash.error e.at "undefined frame" @@ -653,8 +737,10 @@ let rec step (c : config) : config = | Breaking (k, vs'), vs -> Crash.error e.at "undefined label" - | Trapping _, vs - | Throwing _, vs -> + | Trapping _, _ + | Throwing _, _ + | Suspending _, _ + | Hole, _ -> assert false in {c with code = vs', es' @ List.tl es} @@ -669,7 +755,10 @@ let rec eval (c : config) : value stack = Trap.error at msg | vs, {it = Throwing _; at} :: _ -> - Exception.error at "uncaught exception" + Exception.error at "unhandled exception" + + | vs, {it = Suspending _; at} :: _ -> + Exception.error at "unhandled event" | vs, es -> eval (step c) diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index 12db2baf2e..acf63eecb8 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -275,7 +275,7 @@ let rec instr e = | ContNew x -> "cont.new", [Node ("type " ^ var x, [])] | ContResume xys -> "cont.resume", - List.map (fun (x, y) -> [Node ("event " ^ var x ^ " " ^ var y, [])]) xys + List.map (fun (x, y) -> Node ("event " ^ var x ^ " " ^ var y, [])) xys | ContSuspend x -> "cont.suspend", [Node ("event" ^ var x, [])] | ContThrow x -> "cont.throw", [Node ("exception" ^ var x, [])] | LocalGet x -> "local.get " ^ var x, [] diff --git a/test/core/catch.wast b/test/core/catch.wast index 74b2693768..081614f510 100644 --- a/test/core/catch.wast +++ b/test/core/catch.wast @@ -82,5 +82,5 @@ (assert_return (invoke "catch-4") (i32.const 66)) (assert_return (invoke "success-0") (i32.const 0)) (assert_return (invoke "success-1") (i32.const 1)) -(assert_exception (invoke "uncaught-1") "uncaught exception") -(assert_exception (invoke "uncaught-2") "uncaught exception") +(assert_exception (invoke "uncaught-1") "unhandled") +(assert_exception (invoke "uncaught-2") "unhandled") From 7f092d05fd8db419e200614587e5491e88acb6f1 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Thu, 18 Feb 2021 23:39:21 +0100 Subject: [PATCH 02/15] Simplify --- interpreter/exec/eval.ml | 127 ++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 68 deletions(-) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index fad50f59a8..6e2d5d828b 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -44,7 +44,7 @@ let numeric_error at = function | exn -> raise exn -(* Administrative Expressions & Configurations *) +(* Administrative Expressions & Continuations *) type 'a stack = 'a list @@ -61,23 +61,37 @@ and admin_instr' = | Plain of instr' | Refer of ref_ | Invoke of func_inst - | Trapping of string - | Throwing of event_inst * value stack - | Suspending of event_inst * value stack * admin_instr - | Returning of value stack - | ReturningInvoke of value stack * func_inst - | Breaking of int32 * value stack | Label of int * instr list * code | Local of int * value list * code | Frame of int * frame * code | Catch of int * event_inst option * instr list * code | Resume of (event_inst * idx) list * code - | Hole -type cont = int * code + | Trapping of string + | Throwing of event_inst * value stack + | Suspending of event_inst * value stack * ctxt + | Returning of value stack + | ReturningInvoke of value stack * func_inst + | Breaking of int32 * value stack + +and ctxt = code -> code +type cont = int * ctxt type ref_ += ContRef of cont +let plain e = Plain e.it @@ e.at + +let is_jumping e = + match e.it with + | Trapping _ | Throwing _ | Returning _ | ReturningInvoke _ | Breaking _ -> + true + | _ -> false + +let compose (vs1, es1) (vs2, es2) = vs1 @ vs2, es1 @ es2 + + +(* Configurations *) + type config = { frame : frame; @@ -88,14 +102,6 @@ type config = let frame inst = {inst; locals = []} let config inst vs es = {frame = frame inst; code = vs, es; budget = 300} -let plain e = Plain e.it @@ e.at - -let is_jumping e = - match e.it with - | Trapping _ | Throwing _ | Returning _ | ReturningInvoke _ | Breaking _ -> - true - | _ -> false - let lookup category list x = try Lib.List32.nth list x.it with Failure _ -> Crash.error x.at ("undefined " ^ category ^ " " ^ Int32.to_string x.it) @@ -137,23 +143,6 @@ let drop n (vs : 'a stack) at = let split n (vs : 'a stack) at = take n vs at, drop n vs at -let compose (vs1, es1) (vs2, es2) = vs1 @ vs2, es1 @ es2 -let rec plug c (vs, es) = - match es with - | {it = Label (n, es, c'); at} :: es' -> - vs, (Label (n, es, plug c c') @@ at) :: es' - | {it = Local (n, vs, c'); at} :: es' -> - vs, (Local (n, vs, plug c c') @@ at) :: es' - | {it = Frame (n, frame, c'); at} :: es' -> - vs, (Frame (n, frame, plug c c') @@ at) :: es' - | {it = Catch (n, evo, es, c'); at} :: es' -> - vs, (Catch (n, evo, es, plug c c') @@ at) :: es' - | {it = Resume (hs, c'); at} :: es' -> - vs, (Resume (hs, plug c c') @@ at) :: es' - | {it = Hole; at} :: es' -> - compose c (vs, es') - | _ -> assert false - (* Evaluation *) @@ -310,31 +299,32 @@ let rec step (c : config) : config = | ContNew x, Ref (FuncRef f) :: vs -> let FuncType (ts, _) = Func.type_of f in - Ref (ContRef (List.length ts, ([], [Invoke f @@ e.at]))) :: vs, [] + let ctxt code = compose code ([], [Invoke f @@ e.at]) in + Ref (ContRef (List.length ts, ctxt)) :: vs, [] | ContSuspend x, vs -> let evt = event c.frame.inst x in let EventType (FuncType (ts, _), _) = Event.type_of evt in let vs0, vs' = split (List.length ts) vs e.at in - vs', [Suspending (evt, vs0, Hole @@ e.at) @@ e.at] + vs', [Suspending (evt, vs0, fun code -> code) @@ e.at] | ContThrow x, Ref (NullRef _) :: vs -> vs, [Trapping "null continuation reference" @@ e.at] - | ContThrow x, Ref (ContRef (n, code)) :: vs -> + | ContThrow x, Ref (ContRef (n, ctxt)) :: vs -> let evt = event c.frame.inst x in let EventType (FuncType (ts, _), _) = Event.type_of evt in let vs0, vs' = split (List.length ts) vs e.at in - let vs1', es1' = plug (vs0, [Plain (Throw x) @@ e.at]) code in + let vs1', es1' = ctxt (vs0, [Plain (Throw x) @@ e.at]) in vs1' @ vs', es1' | ContResume xls, Ref (NullRef _) :: vs -> vs, [Trapping "null continuation reference" @@ e.at] - | ContResume xls, Ref (ContRef (n, code)) :: vs -> + | ContResume xls, Ref (ContRef (n, ctxt)) :: vs -> let hs = List.map (fun (x, l) -> event c.frame.inst x, l) xls in let vs0, vs' = split n vs e.at in - vs', [Resume (hs, plug (vs0, []) code) @@ e.at] + vs', [Resume (hs, ctxt (vs0, [])) @@ e.at] | Drop, v :: vs' -> vs', [] @@ -615,8 +605,9 @@ let rec step (c : config) : config = | Label (n, es0, (vs', [])), vs -> vs' @ vs, [] - | Label (n, es0, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> - vs, [Suspending (evt, vs1, Label (n, es0, (vs', e1 :: es')) @@ e.at) @@ at] + | Label (n, es0, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Label (n, es0, compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (evt, vs1, ctxt') @@ at] | Label (n, es0, (vs', {it = Breaking (0l, vs0); at} :: es')), vs -> take n vs0 e.at @ vs, List.map plain es0 @@ -634,8 +625,9 @@ let rec step (c : config) : config = | Local (n, vs0, (vs', [])), vs -> vs' @ vs, [] - | Local (n, vs0, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> - vs, [Suspending (evt, vs1, Local (n, vs0, (vs', e1 :: es')) @@ e.at) @@ at] + | Local (n, vs0, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Local (n, vs0, compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (evt, vs1, ctxt') @@ at] | Local (n, vs0, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] @@ -649,8 +641,9 @@ let rec step (c : config) : config = | Frame (n, frame', (vs', [])), vs -> vs' @ vs, [] - | Frame (n, frame', (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> - vs, [Suspending (evt, vs1, Frame (n, frame', (vs', e1 :: es')) @@ e.at) @@ at] + | Frame (n, frame', (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Frame (n, frame', compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (evt, vs1, ctxt') @@ at] | Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs -> take n vs0 e.at @ vs, [] @@ -697,8 +690,9 @@ let rec step (c : config) : config = | Catch (n, exno, es0, (vs', [])), vs -> vs' @ vs, [] - | Catch (n, exno, es0, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs -> - vs, [Suspending (evt, vs1, Catch (n, exno, es0, (vs', e1 :: es')) @@ e.at) @@ at] + | Catch (n, exno, es0, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Catch (n, exno, es0, compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (evt, vs1, ctxt') @@ at] | Catch (n, None, es0, (vs', {it = Throwing (exn, vs0); at} :: _)), vs -> vs, [Label (n, [], ([], List.map plain es0)) @@ e.at] @@ -717,10 +711,11 @@ let rec step (c : config) : config = | Resume (hs, (vs', [])), vs -> vs' @ vs, [] - | Resume (hs, (vs', {it = Suspending (evt, vs1, e1); at} :: es')), vs + | Resume (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs when List.mem_assq evt hs -> let EventType (FuncType (_, ts), _) = Event.type_of evt in - [Ref (ContRef (List.length ts, (vs', e1 :: es')))] @ vs1 @ vs, + let ctxt' code = compose (ctxt code) (vs', es') in + [Ref (ContRef (List.length ts, ctxt'))] @ vs1 @ vs, [Plain (Br (List.assq evt hs)) @@ e.at] | Resume (hs, (vs', e' :: es')), vs when is_jumping e' -> @@ -730,17 +725,12 @@ let rec step (c : config) : config = let c' = step {c with code = code'} in vs, [Resume (hs, c'.code) @@ e.at] - | Returning _, vs - | ReturningInvoke _, vs -> - Crash.error e.at "undefined frame" - - | Breaking (k, vs'), vs -> - Crash.error e.at "undefined label" - | Trapping _, _ | Throwing _, _ | Suspending _, _ - | Hole, _ -> + | Returning _, _ + | ReturningInvoke _, _ + | Breaking _, _ -> assert false in {c with code = vs', es' @ List.tl es} @@ -751,16 +741,17 @@ let rec eval (c : config) : value stack = | vs, [] -> vs - | vs, {it = Trapping msg; at} :: _ -> - Trap.error at msg - - | vs, {it = Throwing _; at} :: _ -> - Exception.error at "unhandled exception" - - | vs, {it = Suspending _; at} :: _ -> - Exception.error at "unhandled event" - - | vs, es -> + | vs, e::_ when is_jumping e -> + (match e.it with + | Trapping msg -> Trap.error e.at msg + | Throwing _ -> Exception.error e.at "unhandled exception" + | Suspending _ -> Exception.error e.at "unhandled event" + | Returning _ | ReturningInvoke _ -> Crash.error e.at "undefined frame" + | Breaking _ -> Crash.error e.at "undefined label" + | _ -> assert false + ) + + | _ -> eval (step c) From 5e510eefde10b2ef4caedb18f8fc39ef57839f27 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 09:29:55 +0100 Subject: [PATCH 03/15] Text, simple test --- interpreter/exec/eval.ml | 8 ++-- interpreter/exec/eval.mli | 1 + interpreter/script/js.ml | 10 +++++ interpreter/script/run.ml | 7 +++ interpreter/script/script.ml | 1 + interpreter/text/arrange.ml | 6 ++- interpreter/text/lexer.mll | 7 +++ interpreter/text/parser.mly | 69 +++++++++++++++++++++++++++++- test/core/cont.wast | 82 ++++++++++++++++++++++++++++++++++++ 9 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 test/core/cont.wast diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 6e2d5d828b..d0a521e74e 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -10,12 +10,14 @@ open Source module Link = Error.Make () module Trap = Error.Make () module Exception = Error.Make () +module Suspension = Error.Make () module Exhaustion = Error.Make () module Crash = Error.Make () exception Link = Link.Error exception Trap = Trap.Error exception Exception = Exception.Error +exception Suspension = Suspension.Error exception Exhaustion = Exhaustion.Error exception Crash = Crash.Error (* failure that cannot happen in valid code *) @@ -66,7 +68,6 @@ and admin_instr' = | Frame of int * frame * code | Catch of int * event_inst option * instr list * code | Resume of (event_inst * idx) list * code - | Trapping of string | Throwing of event_inst * value stack | Suspending of event_inst * value stack * ctxt @@ -83,7 +84,8 @@ let plain e = Plain e.it @@ e.at let is_jumping e = match e.it with - | Trapping _ | Throwing _ | Returning _ | ReturningInvoke _ | Breaking _ -> + | Trapping _ | Throwing _ | Suspending _ + | Returning _ | ReturningInvoke _ | Breaking _ -> true | _ -> false @@ -745,7 +747,7 @@ let rec eval (c : config) : value stack = (match e.it with | Trapping msg -> Trap.error e.at msg | Throwing _ -> Exception.error e.at "unhandled exception" - | Suspending _ -> Exception.error e.at "unhandled event" + | Suspending _ -> Suspension.error e.at "unhandled event" | Returning _ | ReturningInvoke _ -> Crash.error e.at "undefined frame" | Breaking _ -> Crash.error e.at "undefined label" | _ -> assert false diff --git a/interpreter/exec/eval.mli b/interpreter/exec/eval.mli index 05617e0980..089aaeca45 100644 --- a/interpreter/exec/eval.mli +++ b/interpreter/exec/eval.mli @@ -4,6 +4,7 @@ open Instance exception Link of Source.region * string exception Trap of Source.region * string exception Exception of Source.region * string +exception Suspension of Source.region * string exception Exhaustion of Source.region * string exception Crash of Source.region * string diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index f9ca7f41cb..4ee89a264a 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -143,6 +143,14 @@ function assert_exception(action) { throw new Error("Wasm exception expected"); } +function assert_suspension(action) { + try { action() } catch (e) { + /* TODO: Not clear how to observe form JS */ + return; + } + throw new Error("Wasm exception expected"); +} + let StackOverflow; try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor } @@ -536,6 +544,8 @@ let of_assertion mods ass = of_assertion' mods act "assert_trap" [] None | AssertException (act, _) -> of_assertion' mods act "assert_exception" [] None + | AssertSuspension (act, _) -> + of_assertion' mods act "assert_suspension" [] None | AssertExhaustion (act, _) -> of_assertion' mods act "assert_exhaustion" [] None diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index fde16d882e..cc6f0d3a18 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -446,6 +446,13 @@ let run_assertion ass = | _ -> Assert.error ass.at "expected exception" ) + | AssertSuspension (act, re) -> + trace ("Asserting suspension..."); + (match run_action act with + | exception Eval.Suspension (_, msg) -> assert_message ass.at "runtime" msg re + | _ -> Assert.error ass.at "expected suspension" + ) + | AssertExhaustion (act, re) -> trace ("Asserting exhaustion..."); (match run_action act with diff --git a/interpreter/script/script.ml b/interpreter/script/script.ml index baff09e0e6..d0abe6be20 100644 --- a/interpreter/script/script.ml +++ b/interpreter/script/script.ml @@ -34,6 +34,7 @@ and assertion' = | AssertReturn of action * result list | AssertTrap of action * string | AssertException of action * string + | AssertSuspension of action * string | AssertExhaustion of action * string type command = command' Source.phrase diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index acf63eecb8..e6a8fe1cef 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -564,10 +564,12 @@ 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)])] - | AssertExhaustion (act, re) -> - [Node ("assert_exhaustion", [action mode act; Atom (string re)])] | AssertException (act, re) -> [Node ("assert_exception", [action mode act; Atom (string re)])] + | AssertSuspension (act, re) -> + [Node ("assert_suspension", [action mode act; Atom (string re)])] + | AssertExhaustion (act, re) -> + [Node ("assert_exhaustion", [action mode act; Atom (string re)])] let command mode cmd = match cmd.it with diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 9d0a4e75ba..4bec43b3da 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -167,6 +167,7 @@ rule token = parse | "funcref" { FUNCREF } | (nxx as t) { NUM_TYPE (num_type t) } | "mut" { MUT } + | "cont" { CONT } | (nxx as t)".const" { let open Source in @@ -214,6 +215,11 @@ rule token = parse | "catch" { CATCH } | "catch_all" { CATCH_ALL } + | "cont.new" { CONT_NEW } + | "cont.suspend" { CONT_SUSPEND } + | "cont.throw" { CONT_THROW } + | "cont.resume" { CONT_RESUME } + | "local.get" { LOCAL_GET } | "local.set" { LOCAL_SET } | "local.tee" { LOCAL_TEE } @@ -395,6 +401,7 @@ rule token = parse | "assert_return" { ASSERT_RETURN } | "assert_trap" { ASSERT_TRAP } | "assert_exception" { ASSERT_EXCEPTION } + | "assert_suspension" { ASSERT_SUSPENSION } | "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 f008517931..a97439c229 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -213,10 +213,11 @@ let inline_func_type_explicit (c : context) x ft at = %token LPAR RPAR %token NAT INT FLOAT STRING VAR -%token NUM_TYPE FUNCREF EXTERNREF REF EXTERN NULL MUT +%token NUM_TYPE FUNCREF EXTERNREF REF EXTERN NULL MUT CONT %token UNREACHABLE NOP DROP SELECT %token BLOCK END IF THEN ELSE LOOP LET %token THROW TRY DO CATCH CATCH_ALL +%token CONT_NEW CONT_SUSPEND CONT_THROW CONT_RESUME %token BR BR_IF BR_TABLE BR_ON_NULL %token CALL CALL_REF CALL_INDIRECT RETURN RETURN_CALL_REF FUNC_BIND %token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET @@ -231,7 +232,7 @@ let inline_func_type_explicit (c : context) x ft at = %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_EXCEPTION ASSERT_EXHAUSTION +%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_SUSPENSION ASSERT_EXHAUSTION %token NAN %token INPUT OUTPUT %token EOF @@ -305,6 +306,32 @@ global_type : def_type : | LPAR FUNC func_type RPAR { fun c -> FuncDefType ($3 c) } + | LPAR CONT cont_type RPAR { fun c -> ContDefType (ContType (SynVar ($3 c).it)) } + +cont_type : + | type_use cont_type_params + { let at1 = ati 1 in + fun c -> + match $2 c with + | FuncType ([], []) -> $1 c type_ + | ft -> inline_func_type_explicit c ($1 c type_) ft at1 } + | cont_type_params + /* TODO: the inline type is broken for now */ + { let at = at () in fun c -> inline_func_type c ($1 c) at } + +cont_type_params : + | LPAR PARAM value_type_list RPAR cont_type_params + { fun c -> let FuncType (ts1, ts2) = $5 c in + FuncType (snd $3 c @ ts1, ts2) } + | cont_type_results + { fun c -> FuncType ([], $1 c) } + +cont_type_results : + | LPAR RESULT value_type_list RPAR cont_type_results + { fun c -> snd $3 c @ $5 c } + | /* empty */ + { fun c -> [] } + func_type : | /* empty */ @@ -398,6 +425,7 @@ instr : | plain_instr { let at = at () in fun c -> [$1 c @@ at] } | select_instr_instr { fun c -> let e, es = $1 c in e :: es } | call_instr_instr { fun c -> let e, es = $1 c in e :: es } + | resume_instr_instr { fun c -> let e, es = $1 c in e :: es } | block_instr { let at = at () in fun c -> [$1 c @@ at] } | expr { $1 } /* Sugar */ @@ -416,6 +444,9 @@ plain_instr : | CALL var { fun c -> call ($2 c func) } | CALL_REF { fun c -> call_ref } | RETURN_CALL_REF { fun c -> return_call_ref } + | CONT_NEW LPAR TYPE var RPAR { fun c -> cont_new ($4 c type_) } + | CONT_SUSPEND var { fun c -> cont_suspend ($2 c event) } + | CONT_THROW var { fun c -> cont_throw ($2 c event) } | 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) } @@ -550,6 +581,29 @@ call_instr_results_instr : { fun c -> [], $1 c } +resume_instr : + | CONT_RESUME resume_instr_handler + { let at = at () in fun c -> cont_resume ($2 c) @@ at } + +resume_instr_handler : + | LPAR EVENT var var RPAR resume_instr_handler + { fun c -> ($3 c event, $4 c label) :: $6 c } + | /* empty */ + { fun c -> [] } + + +resume_instr_instr : + | CONT_RESUME resume_instr_handler_instr + { let at1 = ati 1 in + fun c -> let hs, es = $2 c in cont_resume hs @@ at1, es } + +resume_instr_handler_instr : + | LPAR EVENT var var RPAR resume_instr_handler_instr + { fun c -> let hs, es = $6 c in ($3 c event, $4 c label) :: hs, es } + | instr + { fun c -> [], $1 c } + + block_instr : | BLOCK labeling_opt block END labeling_end_opt { fun c -> let c' = $2 c $5 in let bt, es = $3 c' in block bt es } @@ -663,6 +717,8 @@ expr1 : /* Sugar */ fun c -> let x, es = $2 c in es, call_indirect (0l @@ at1) x } | FUNC_BIND call_expr_type { fun c -> let x, es = $2 c in es, func_bind x } + | CONT_RESUME resume_expr_handler + { fun c -> let hs, es = $2 c in es, cont_resume 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 @@ -709,6 +765,13 @@ call_expr_results : | expr_list { fun c -> [], $1 c } +resume_expr_handler : + | LPAR EVENT var var RPAR resume_expr_handler + { fun c -> let hs, es = $6 c in ($3 c event, $4 c label) :: hs, es } + | expr_list + { fun c -> [], $1 c } + + try_block : | type_use try_block_param_body @@ -790,6 +853,7 @@ instr_list : | /* empty */ { fun c -> [] } | select_instr { fun c -> [$1 c] } | call_instr { fun c -> [$1 c] } + | resume_instr { fun c -> [$1 c] } | instr instr_list { fun c -> $1 c @ $2 c } expr_list : @@ -1269,6 +1333,7 @@ assertion : | 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 STRING RPAR { AssertException ($3, $4) @@ at () } + | LPAR ASSERT_SUSPENSION action STRING RPAR { AssertSuspension ($3, $4) @@ at () } | LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ at () } cmd : diff --git a/test/core/cont.wast b/test/core/cont.wast new file mode 100644 index 0000000000..e816e3a46b --- /dev/null +++ b/test/core/cont.wast @@ -0,0 +1,82 @@ +(module + (event $e1) + (event $e2) + + (type $f1 (func)) + (type $k1 (cont (type $f1))) + + (func $f1 (export "unhandled-1") + (cont.suspend $e1) + ) + + (func (export "unhandled-2") + (block $h (result (ref $k1)) + (cont.resume (event $e2 $h) (cont.new (type $k1) (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (func (export "handled") + (block $h (result (ref $k1)) + (cont.resume (event $e1 $h) (cont.new (type $k1) (ref.func $f1))) + (unreachable) + ) + (drop) + ) +) + +(assert_suspension (invoke "unhandled-1") "unhandled") +(assert_suspension (invoke "unhandled-2") "unhandled") +(assert_return (invoke "handled")) + + +(module $state + (event $get (result i32)) + (event $set (param i32) (result i32)) + + (type $f (func (param i32) (result i32))) + (type $k (cont (type $f))) + + (func $runner (param $s i32) (param $k (ref $k)) (result i32) + (loop $loop + (block $on_get (result (ref $k)) + (block $on_set (result i32 (ref $k)) + (cont.resume (event $get $on_get) (event $set $on_set) + (local.get $s) (local.get $k) + ) + (return) + ) + ;; on set + (local.set $k) + (local.set $s) + (br $loop) + ) + ;; on get + (local.set $k) + (br $loop) + ) + (unreachable) + ) + + (func $f (param i32) (result i32) + (drop (cont.suspend $set (i32.const 7))) + (i32.add + (cont.suspend $get) + (i32.mul + (i32.const 2) + (i32.add + (cont.suspend $set (i32.const 3)) + (cont.suspend $get) + ) + ) + ) + ) + + (elem declare func $f) + (func (export "run") (result i32) + (call $runner (i32.const 0) (cont.new (type $k) (ref.func $f))) + ) +) + +(assert_return (invoke "run") (i32.const 19)) From a44c070d029f57d9b1639e077019462d10a14314 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 09:37:50 +0100 Subject: [PATCH 04/15] Missing rule --- interpreter/exec/eval.ml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index d0a521e74e..9594fdf1b1 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -720,6 +720,10 @@ let rec step (c : config) : config = [Ref (ContRef (List.length ts, ctxt'))] @ vs1 @ vs, [Plain (Br (List.assq evt hs)) @@ e.at] + | Resume (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Resume (hs, compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (evt, vs1, ctxt') @@ at] + | Resume (hs, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] From e10edf6424788d27fbdd49e3581e1b731d81bba1 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 09:56:44 +0100 Subject: [PATCH 05/15] Minor grammar tweak --- interpreter/text/parser.mly | 2 ++ test/core/cont.wast | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index a97439c229..3fda8e1321 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -318,6 +318,8 @@ cont_type : | cont_type_params /* TODO: the inline type is broken for now */ { let at = at () in fun c -> inline_func_type c ($1 c) at } + | var /* Sugar */ + { fun c -> $1 c type_ } cont_type_params : | LPAR PARAM value_type_list RPAR cont_type_params diff --git a/test/core/cont.wast b/test/core/cont.wast index e816e3a46b..d089bd7bfa 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -3,7 +3,7 @@ (event $e2) (type $f1 (func)) - (type $k1 (cont (type $f1))) + (type $k1 (cont $f1)) (func $f1 (export "unhandled-1") (cont.suspend $e1) @@ -36,7 +36,7 @@ (event $set (param i32) (result i32)) (type $f (func (param i32) (result i32))) - (type $k (cont (type $f))) + (type $k (cont $f)) (func $runner (param $s i32) (param $k (ref $k)) (result i32) (loop $loop From e235dac90130c2293ad75f4d0c52c06290acb53b Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 10:49:24 +0100 Subject: [PATCH 06/15] Test exns --- test/core/cont.wast | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/core/cont.wast b/test/core/cont.wast index d089bd7bfa..c22d326a4e 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -1,4 +1,5 @@ (module + (exception $exn) (event $e1) (event $e2) @@ -24,11 +25,34 @@ ) (drop) ) + + (elem declare func $f2) + (func $f2 + (throw $exn) + ) + + (func (export "uncaught-1") + (block $h (result (ref $k1)) + (cont.resume (event $e1 $h) (cont.new (type $k1) (ref.func $f2))) + (unreachable) + ) + (drop) + ) + + (func (export "uncaught-2") + (block $h (result (ref $k1)) + (cont.resume (event $e1 $h) (cont.new (type $k1) (ref.func $f1))) + (unreachable) + ) + (cont.throw $exn) + ) ) (assert_suspension (invoke "unhandled-1") "unhandled") (assert_suspension (invoke "unhandled-2") "unhandled") (assert_return (invoke "handled")) +(assert_exception (invoke "uncaught-1") "unhandled") +(assert_exception (invoke "uncaught-2") "unhandled") (module $state From 1b478efc53784780334859bd983195b4fdee0a16 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 13:14:32 +0100 Subject: [PATCH 07/15] Scheduler example --- interpreter/exec/eval.ml | 14 +++- interpreter/valid/match.ml | 11 +++ test/core/cont.wast | 148 +++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 9594fdf1b1..cb6c5094a6 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -77,9 +77,21 @@ and admin_instr' = and ctxt = code -> code -type cont = int * ctxt +type cont = int * ctxt (* TODO: represent type properly *) type ref_ += ContRef of cont +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | ContRef _ -> BotHeapType (* TODO *) + | r -> type_of_ref' r + +let () = + let string_of_ref' = !Value.string_of_ref' in + Value.string_of_ref' := function + | ContRef _ -> "cont" + | r -> string_of_ref' r + let plain e = Plain e.it @@ e.at let is_jumping e = diff --git a/interpreter/valid/match.ml b/interpreter/valid/match.ml index 013afb1121..9e6d9d8471 100644 --- a/interpreter/valid/match.ml +++ b/interpreter/valid/match.ml @@ -26,6 +26,9 @@ let eq_nullability c a nul1 nul2 = let eq_mutability c a mut1 mut2 = mut1 = mut2 +let eq_resumability c a res1 res2 = + res1 = res2 + let eq_limits c a lim1 lim2 = lim1.min = lim2.min && lim1.max = lim2.max @@ -78,12 +81,16 @@ and eq_memory_type c a (MemoryType lim1) (MemoryType lim2) = and eq_global_type c a (GlobalType (t1, mut1)) (GlobalType (t2, mut2)) = eq_mutability c a mut1 mut2 && eq_value_type c a t1 t2 +and eq_event_type c a (EventType (ft1, res1)) (EventType (ft2, res2)) = + eq_resumability c a res1 res2 && eq_func_type c [] ft1 ft2 + and eq_extern_type c a et1 et2 = match et1, et2 with | ExternFuncType ft1, ExternFuncType ft2 -> eq_func_type c a ft1 ft2 | ExternTableType tt1, ExternTableType tt2 -> eq_table_type c a tt1 tt2 | ExternMemoryType mt1, ExternMemoryType mt2 -> eq_memory_type c a mt1 mt2 | ExternGlobalType gt1, ExternGlobalType gt2 -> eq_global_type c a gt1 gt2 + | ExternEventType et1, ExternEventType et2 -> eq_event_type c a et1 et2 | _, _ -> false @@ -146,12 +153,16 @@ and match_global_type c a (GlobalType (t1, mut1)) (GlobalType (t2, mut2)) = | Immutable -> match_value_type c a t1 t2 | Mutable -> eq_value_type c [] t1 t2 +and match_event_type c a (EventType (ft1, res1)) (EventType (ft2, res2)) = + eq_resumability c [] res1 res2 && match_func_type c [] ft1 ft2 + and match_extern_type c a et1 et2 = match et1, et2 with | ExternFuncType ft1, ExternFuncType ft2 -> match_func_type c a ft1 ft2 | ExternTableType tt1, ExternTableType tt2 -> match_table_type c a tt1 tt2 | ExternMemoryType mt1, ExternMemoryType mt2 -> match_memory_type c a mt1 mt2 | ExternGlobalType gt1, ExternGlobalType gt2 -> match_global_type c a gt1 gt2 + | ExternEventType et1, ExternEventType et2 -> match_event_type c a et1 et2 | _, _ -> false and match_def_type c a dt1 dt2 = diff --git a/test/core/cont.wast b/test/core/cont.wast index c22d326a4e..d115aa195c 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -1,3 +1,5 @@ +;; Unhandled events + (module (exception $exn) (event $e1) @@ -55,6 +57,8 @@ (assert_exception (invoke "uncaught-2") "unhandled") +;; Simple state example + (module $state (event $get (result i32)) (event $set (param i32) (result i32)) @@ -104,3 +108,147 @@ ) (assert_return (invoke "run") (i32.const 19)) + + +;; Simple scheduler example + +(module $scheduler + (type $proc (func)) + (type $cont (cont $proc)) + + (event $yield (export "yield")) + (event $spawn (export "spawn") (param (ref $proc))) + + (table $queue 0 (ref null $cont)) + (global $qdelta i32 (i32.const 10)) + (global $qback (mut i32) (i32.const 0)) + (global $qfront (mut i32) (i32.const 0)) + + (func $queue-empty (result i32) + (i32.eq (global.get $qfront) (global.get $qback)) + ) + + (func $dequeue (result (ref null $cont)) + (local $k (ref null $cont)) + ;; Check if queue is empty + (if (call $queue-empty) + (then (return (ref.null $cont))) + ) + (local.set $k (table.get $queue (global.get $qfront))) + (global.set $qfront (i32.add (global.get $qfront) (i32.const 1))) + (local.get $k) + ) + + (func $enqueue (param $k (ref $cont)) + (local $qlen i32) + ;; Check if queue is full + (if (i32.eq (global.get $qback) (table.size $queue)) + (then + ;; Check if there is enough space in the front to compact + (if (i32.lt_u (global.get $qfront) (global.get $qdelta)) + (then + ;; Not enough room, grow table + (drop (table.grow $queue (ref.null $cont) (global.get $qdelta))) + ) + (else + ;; Enough room, move entries down + (local.set $qlen (i32.sub (global.get $qback) (global.get $qfront))) + (table.copy $queue $queue + (i32.const 0) + (global.get $qfront) + (local.get $qlen) + ) + (table.fill $queue + (local.get $qlen) + (ref.null $cont) + (global.get $qfront) + ) + (global.set $qfront (i32.const 0)) + (global.set $qback (local.get $qlen)) + ) + ) + ) + ) + (table.set $queue (global.get $qback) (local.get $k)) + (global.set $qback (i32.add (global.get $qback) (i32.const 1))) + ) + + (func $scheduler (export "scheduler") (param $main (ref $proc)) + (call $enqueue (cont.new (type $cont) (local.get $main))) + (loop $l + (if (call $queue-empty) (then (return))) + (block $on_yield (result (ref $cont)) + (block $on_spawn (result (ref $proc) (ref $cont)) + (cont.resume (event $yield $on_yield) (event $spawn $on_spawn) + (call $dequeue) + ) + (br $l) ;; thread terminated + ) + ;; on $spawn, proc and cont on stack + (call $enqueue) ;; continuation of old thread + (cont.new (type $cont)) + (call $enqueue) ;; new thread + (br $l) + ) + ;; on $yield, cont on stack + (call $enqueue) + (br $l) + ) + ) +) + +(register "scheduler") + +(module + (type $proc (func)) + (type $cont (cont $proc)) + (event $yield (import "scheduler" "yield")) + (event $spawn (import "scheduler" "spawn") (param (ref $proc))) + (func $scheduler (import "scheduler" "scheduler") (param $main (ref $proc))) + + (func $log (import "spectest" "print_i32") (param i32)) + + (elem declare func $main $thread1 $thread2 $thread3) + + (func $main + (call $log (i32.const 0)) + (cont.suspend $spawn (ref.func $thread1)) + (call $log (i32.const 1)) + (cont.suspend $spawn (ref.func $thread2)) + (call $log (i32.const 2)) + (cont.suspend $spawn (ref.func $thread3)) + (call $log (i32.const 3)) + ) + + (func $thread1 + (call $log (i32.const 10)) + (cont.suspend $yield) + (call $log (i32.const 11)) + (cont.suspend $yield) + (call $log (i32.const 12)) + (cont.suspend $yield) + (call $log (i32.const 13)) + ) + + (func $thread2 + (call $log (i32.const 20)) + (cont.suspend $yield) + (call $log (i32.const 21)) + ) + + (func $thread3 + (call $log (i32.const 30)) + (cont.suspend $yield) + (call $log (i32.const 31)) + (cont.suspend $yield) + (call $log (i32.const 32)) + ) + + (func (export "run") + (call $log (i32.const -1)) + (call $scheduler (ref.func $main)) + (call $log (i32.const -2)) + ) +) + +(assert_return (invoke "run")) From 8349beeae60522ba935e0af52bada4c4714daa26 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 15:14:31 +0100 Subject: [PATCH 08/15] Generator example; make threads more interesting --- test/core/cont.wast | 86 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/test/core/cont.wast b/test/core/cont.wast index d115aa195c..544a2d28ed 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -110,6 +110,52 @@ (assert_return (invoke "run") (i32.const 19)) +;; Simple generator example + +(module $generator + (type $gen (func (param i64))) + (type $geny (func (param i32))) + (type $cont0 (cont $gen)) + (type $cont (cont $geny)) + + (event $yield (param i64) (result i32)) + + (elem declare func $gen) + (func $gen (param $i i64) + (loop $l + (br_if 1 (cont.suspend $yield (local.get $i))) + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br $l) + ) + ) + + (func (export "sum") (param $i i64) (param $j i64) (result i64) + (local $sum i64) + (local.get $i) + (cont.new (type $cont0) (ref.func $gen)) + (block $on_first_yield (param i64 (ref $cont0)) (result i64 (ref $cont)) + (cont.resume (event $yield $on_first_yield)) + (unreachable) + ) + (loop $on_yield (param i64) (param (ref $cont)) + (let (result i32 (ref $cont)) + (local $n i64) (local $k (ref $cont)) + (local.set $sum (i64.add (local.get $sum) (local.get $n))) + (i64.eq (local.get $n) (local.get $j)) (local.get $k) + ) + (cont.resume (event $yield $on_yield)) + ) + (return (local.get $sum)) + ) +) + +(assert_return (invoke "sum" (i64.const 0) (i64.const 0)) (i64.const 0)) +(assert_return (invoke "sum" (i64.const 2) (i64.const 2)) (i64.const 2)) +(assert_return (invoke "sum" (i64.const 0) (i64.const 3)) (i64.const 6)) +(assert_return (invoke "sum" (i64.const 1) (i64.const 10)) (i64.const 55)) +(assert_return (invoke "sum" (i64.const 100) (i64.const 2000)) (i64.const 1_996_050)) + + ;; Simple scheduler example (module $scheduler @@ -208,13 +254,16 @@ (func $log (import "spectest" "print_i32") (param i32)) + (global $width (mut i32) (i32.const 0)) + (global $depth (mut i32) (i32.const 0)) + (elem declare func $main $thread1 $thread2 $thread3) (func $main (call $log (i32.const 0)) (cont.suspend $spawn (ref.func $thread1)) (call $log (i32.const 1)) - (cont.suspend $spawn (ref.func $thread2)) + (cont.suspend $spawn (func.bind (type $proc) (global.get $depth) (ref.func $thread2))) (call $log (i32.const 2)) (cont.suspend $spawn (ref.func $thread3)) (call $log (i32.const 3)) @@ -230,10 +279,31 @@ (call $log (i32.const 13)) ) - (func $thread2 + (func $thread2 (param $d i32) + (local $w i32) + (local.set $w (global.get $width)) (call $log (i32.const 20)) - (cont.suspend $yield) + (br_if 0 (i32.eqz (local.get $d))) (call $log (i32.const 21)) + (loop $l + (if (local.get $w) + (then + (call $log (i32.const 22)) + (cont.suspend $yield) + (call $log (i32.const 23)) + (cont.suspend $spawn + (func.bind (type $proc) + (i32.sub (local.get $d) (i32.const 1)) + (ref.func $thread2) + ) + ) + (call $log (i32.const 24)) + (local.set $w (i32.sub (local.get $w) (i32.const 1))) + (br $l) + ) + ) + ) + (call $log (i32.const 25)) ) (func $thread3 @@ -244,11 +314,17 @@ (call $log (i32.const 32)) ) - (func (export "run") + (func (export "run") (param $width i32) (param $depth i32) + (global.set $depth (local.get $depth)) + (global.set $width (local.get $width)) (call $log (i32.const -1)) (call $scheduler (ref.func $main)) (call $log (i32.const -2)) ) ) -(assert_return (invoke "run")) +(assert_return (invoke "run" (i32.const 0) (i32.const 0))) +(assert_return (invoke "run" (i32.const 0) (i32.const 1))) +(assert_return (invoke "run" (i32.const 1) (i32.const 0))) +(assert_return (invoke "run" (i32.const 1) (i32.const 1))) +(assert_return (invoke "run" (i32.const 3) (i32.const 4))) From 34ebc1902d1dc34afb8efba17979ccb821a58729 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 21:49:41 +0100 Subject: [PATCH 09/15] Avoid code duplication --- test/core/cont.wast | 98 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/test/core/cont.wast b/test/core/cont.wast index 544a2d28ed..df9818388e 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -120,15 +120,21 @@ (event $yield (param i64) (result i32)) - (elem declare func $gen) - (func $gen (param $i i64) + ;; Hook for logging purposes + (global $hook (export "hook") (mut (ref $gen)) (ref.func $dummy)) + (func $dummy (param i64)) + + (func $gen (export "start") (param $i i64) (loop $l (br_if 1 (cont.suspend $yield (local.get $i))) + (call_ref (local.get $i) (global.get $hook)) (local.set $i (i64.add (local.get $i) (i64.const 1))) (br $l) ) ) + (elem declare func $gen) + (func (export "sum") (param $i i64) (param $j i64) (result i64) (local $sum i64) (local.get $i) @@ -149,6 +155,8 @@ ) ) +(register "generator") + (assert_return (invoke "sum" (i64.const 0) (i64.const 0)) (i64.const 0)) (assert_return (invoke "sum" (i64.const 2) (i64.const 2)) (i64.const 2)) (assert_return (invoke "sum" (i64.const 0) (i64.const 3)) (i64.const 6)) @@ -165,6 +173,7 @@ (event $yield (export "yield")) (event $spawn (export "spawn") (param (ref $proc))) + ;; Table as simple queue (keeping it simple, no ring buffer) (table $queue 0 (ref null $cont)) (global $qdelta i32 (i32.const 10)) (global $qback (mut i32) (i32.const 0)) @@ -175,42 +184,39 @@ ) (func $dequeue (result (ref null $cont)) - (local $k (ref null $cont)) - ;; Check if queue is empty + (local $i i32) (if (call $queue-empty) (then (return (ref.null $cont))) ) - (local.set $k (table.get $queue (global.get $qfront))) - (global.set $qfront (i32.add (global.get $qfront) (i32.const 1))) - (local.get $k) + (local.set $i (global.get $qfront)) + (global.set $qfront (i32.add (local.get $i) (i32.const 1))) + (table.get $queue (local.get $i)) ) (func $enqueue (param $k (ref $cont)) - (local $qlen i32) ;; Check if queue is full (if (i32.eq (global.get $qback) (table.size $queue)) (then ;; Check if there is enough space in the front to compact (if (i32.lt_u (global.get $qfront) (global.get $qdelta)) (then - ;; Not enough room, grow table + ;; Space is below threshold, grow table instead (drop (table.grow $queue (ref.null $cont) (global.get $qdelta))) ) (else - ;; Enough room, move entries down - (local.set $qlen (i32.sub (global.get $qback) (global.get $qfront))) + ;; Enough space, move entries up to head of table + (global.set $qback (i32.sub (global.get $qback) (global.get $qfront))) (table.copy $queue $queue - (i32.const 0) - (global.get $qfront) - (local.get $qlen) + (i32.const 0) ;; dest = new front = 0 + (global.get $qfront) ;; src = old front + (global.get $qback) ;; len = new back = old back - old front ) - (table.fill $queue - (local.get $qlen) - (ref.null $cont) - (global.get $qfront) + (table.fill $queue ;; null out old entries to avoid leaks + (global.get $qback) ;; start = new back + (ref.null $cont) ;; init value + (global.get $qfront) ;; len = old front = old front - new front ) (global.set $qfront (i32.const 0)) - (global.set $qback (local.get $qlen)) ) ) ) @@ -328,3 +334,57 @@ (assert_return (invoke "run" (i32.const 1) (i32.const 0))) (assert_return (invoke "run" (i32.const 1) (i32.const 1))) (assert_return (invoke "run" (i32.const 3) (i32.const 4))) + + +;; Nested example: generator in a thread + +(module $concurrent-generator + (func $log (import "spectest" "print_i64") (param i64)) + + (event $syield (import "scheduler" "yield")) + (event $spawn (import "scheduler" "spawn") (param (ref $proc))) + (func $scheduler (import "scheduler" "scheduler") (param $main (ref $proc))) + + (type $hook (func (param i64))) + (func $sum (import "generator" "sum") (param i64 i64) (result i64)) + (global $hook (import "generator" "hook") (mut (ref $hook))) + + (global $result (mut i64) (i64.const 0)) + (global $done (mut i32) (i32.const 0)) + + (elem declare func $main $bg-thread $syield) + + (func $syield (param $i i64) + (call $log (local.get $i)) + (cont.suspend $syield) + ) + + (func $bg-thread + (call $log (i64.const -10)) + (loop $l + (call $log (i64.const -11)) + (cont.suspend $syield) + (br_if $l (i32.eqz (global.get $done))) + ) + (call $log (i64.const -12)) + ) + + (func $main (param $i i64) (param $j i64) + (cont.suspend $spawn (ref.func $bg-thread)) + (global.set $hook (ref.func $syield)) + (global.set $result (call $sum (local.get $i) (local.get $j))) + (global.set $done (i32.const 1)) + ) + + (type $proc (func)) + (func (export "sum") (param $i i64) (param $j i64) (result i64) + (call $log (i64.const -1)) + (call $scheduler + (func.bind (type $proc) (local.get $i) (local.get $j) (ref.func $main)) + ) + (call $log (i64.const -2)) + (global.get $result) + ) +) + +(assert_return (invoke "sum" (i64.const 10) (i64.const 20)) (i64.const 165)) From 39398284a04ca9d2fbccc322335615b9abbb5343 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 19 Feb 2021 21:52:41 +0100 Subject: [PATCH 10/15] Eps --- test/core/cont.wast | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/core/cont.wast b/test/core/cont.wast index df9818388e..22134eadff 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -345,9 +345,9 @@ (event $spawn (import "scheduler" "spawn") (param (ref $proc))) (func $scheduler (import "scheduler" "scheduler") (param $main (ref $proc))) - (type $hook (func (param i64))) - (func $sum (import "generator" "sum") (param i64 i64) (result i64)) - (global $hook (import "generator" "hook") (mut (ref $hook))) + (type $ghook (func (param i64))) + (func $gsum (import "generator" "sum") (param i64 i64) (result i64)) + (global $ghook (import "generator" "hook") (mut (ref $ghook))) (global $result (mut i64) (i64.const 0)) (global $done (mut i32) (i32.const 0)) @@ -371,8 +371,8 @@ (func $main (param $i i64) (param $j i64) (cont.suspend $spawn (ref.func $bg-thread)) - (global.set $hook (ref.func $syield)) - (global.set $result (call $sum (local.get $i) (local.get $j))) + (global.set $ghook (ref.func $syield)) + (global.set $result (call $gsum (local.get $i) (local.get $j))) (global.set $done (i32.const 1)) ) From 92fd88f166697f127691de1268dccec9f677b51c Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Sat, 20 Feb 2021 08:42:54 +0100 Subject: [PATCH 11/15] Renames; fixes; add guard instruction --- interpreter/binary/decode.ml | 15 +++++-- interpreter/binary/encode.ml | 7 +-- interpreter/exec/eval.ml | 77 +++++++++++++++++++++------------ interpreter/syntax/ast.ml | 7 +-- interpreter/syntax/free.ml | 6 +-- interpreter/syntax/operators.ml | 7 +-- interpreter/text/arrange.ml | 18 +++++--- interpreter/text/lexer.mll | 7 +-- interpreter/text/parser.mly | 32 +++++++++----- interpreter/valid/valid.ml | 33 ++++++++------ test/core/binary.wast | 4 +- test/core/catch.wast | 4 +- test/core/cont.wast | 74 ++++++++++++++++++------------- 13 files changed, 180 insertions(+), 111 deletions(-) diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index e7a0cc0079..6cdb0b3ee9 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -225,7 +225,7 @@ let global_type s = let def_type s = match peek s with | Some 0x60 -> FuncDefType (func_type s) - | Some 0x61 -> ContDefType (cont_type s) + | Some 0x5f -> ContDefType (cont_type s) | None -> ignore (vs7 s); assert false (* force error *) | _ -> error s (pos s) "malformed type definition" @@ -553,9 +553,14 @@ let rec instr s = | 0xd4 -> br_on_null (at var s) | 0xe0 -> cont_new (at var s) - | 0xe1 -> cont_suspend (at var s) - | 0xe2 -> cont_throw (at var s) - | 0xe3 -> cont_resume (vec var_pair s) + | 0xe1 -> suspend (at var s) + | 0xe2 -> resume (vec var_pair s) + | 0xe3 -> resume_throw (at var s) + | 0xe4 -> + let bt = block_type s in + let es' = instr_block s in + end_ s; + guard bt es' | 0xfc as b -> (match vu32 s with @@ -656,6 +661,7 @@ let import_desc s = | 0x01 -> TableImport (table_type s) | 0x02 -> MemoryImport (memory_type s) | 0x03 -> GlobalImport (global_type s) + | 0x04 -> EventImport (event_type s) | _ -> error s (pos s - 1) "malformed import kind" let import s = @@ -723,6 +729,7 @@ let export_desc s = | 0x01 -> TableExport (at var s) | 0x02 -> MemoryExport (at var s) | 0x03 -> GlobalExport (at var s) + | 0x04 -> EventExport (at var s) | _ -> error s (pos s - 1) "malformed export kind" let export s = diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index 44ee669e20..009b63f432 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -215,9 +215,10 @@ let encode m = | FuncBind x -> op 0x16; var x | ContNew x -> op 0xe0; var x - | ContSuspend x -> op 0xe1; var x - | ContThrow x -> op 0xe2; var x - | ContResume xls -> op 0xe3; vec var_pair xls + | Suspend x -> op 0xe1; var x + | Resume xls -> op 0xe2; vec var_pair xls + | ResumeThrow x -> op 0xe3; var x + | Guard (bt, es) -> op 0xe4; block_type bt; list instr es; end_ () | Drop -> op 0x1a | Select None -> op 0x1b diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index cb6c5094a6..834762ca6c 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -67,7 +67,8 @@ and admin_instr' = | Local of int * value list * code | Frame of int * frame * code | Catch of int * event_inst option * instr list * code - | Resume of (event_inst * idx) list * code + | Handle of (event_inst * idx) list * code + | Guarded of int * code | Trapping of string | Throwing of event_inst * value stack | Suspending of event_inst * value stack * ctxt @@ -218,12 +219,12 @@ let rec step (c : config) : config = vs', [Plain (Block (bt, es1)) @@ e.at] | Let (bt, locals, es'), vs -> - let vs0, vs' = split (List.length locals) vs e.at in + let locs, vs' = split (List.length locals) vs e.at in let FuncType (ts1, ts2) = block_type c.frame.inst bt e.at in - let vs1, vs2 = split (List.length ts1) vs' e.at in - vs2, [ - Local (List.length ts2, List.rev vs0, - (vs1, [Plain (Block (bt, es')) @@ e.at]) + let args, vs'' = split (List.length ts1) vs' e.at in + vs'', [ + Local (List.length ts2, List.rev locs, + (args, [Plain (Block (bt, es')) @@ e.at]) ) @@ e.at ] @@ -316,29 +317,38 @@ let rec step (c : config) : config = let ctxt code = compose code ([], [Invoke f @@ e.at]) in Ref (ContRef (List.length ts, ctxt)) :: vs, [] - | ContSuspend x, vs -> + | Suspend x, vs -> let evt = event c.frame.inst x in let EventType (FuncType (ts, _), _) = Event.type_of evt in - let vs0, vs' = split (List.length ts) vs e.at in - vs', [Suspending (evt, vs0, fun code -> code) @@ e.at] + let args, vs' = split (List.length ts) vs e.at in + vs', [Suspending (evt, args, fun code -> code) @@ e.at] + + | Resume xls, Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | Resume xls, Ref (ContRef (n, ctxt)) :: vs -> + let hs = List.map (fun (x, l) -> event c.frame.inst x, l) xls in + let args, vs' = split n vs e.at in + vs', [Handle (hs, ctxt (args, [])) @@ e.at] - | ContThrow x, Ref (NullRef _) :: vs -> + | ResumeThrow x, Ref (NullRef _) :: vs -> vs, [Trapping "null continuation reference" @@ e.at] - | ContThrow x, Ref (ContRef (n, ctxt)) :: vs -> + | ResumeThrow x, Ref (ContRef (n, ctxt)) :: vs -> let evt = event c.frame.inst x in let EventType (FuncType (ts, _), _) = Event.type_of evt in - let vs0, vs' = split (List.length ts) vs e.at in - let vs1', es1' = ctxt (vs0, [Plain (Throw x) @@ e.at]) in + let args, vs' = split (List.length ts) vs e.at in + let vs1', es1' = ctxt (args, [Plain (Throw x) @@ e.at]) in vs1' @ vs', es1' - | ContResume xls, Ref (NullRef _) :: vs -> - vs, [Trapping "null continuation reference" @@ e.at] - - | ContResume xls, Ref (ContRef (n, ctxt)) :: vs -> - let hs = List.map (fun (x, l) -> event c.frame.inst x, l) xls in - let vs0, vs' = split n vs e.at in - vs', [Resume (hs, ctxt (vs0, [])) @@ e.at] + | Guard (bt, es'), vs -> + let FuncType (ts1, ts2) = block_type c.frame.inst bt e.at in + let args, vs' = split (List.length ts1) vs e.at in + vs', [ + Guarded (List.length ts2, + (args, [Plain (Block (bt, es')) @@ e.at]) + ) @@ e.at + ] | Drop, v :: vs' -> vs', [] @@ -722,26 +732,39 @@ let rec step (c : config) : config = let c' = step {c with code = code'} in vs, [Catch (n, exno, es0, c'.code) @@ e.at] - | Resume (hs, (vs', [])), vs -> + | Handle (hs, (vs', [])), vs -> vs' @ vs, [] - | Resume (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs + | Handle (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs when List.mem_assq evt hs -> let EventType (FuncType (_, ts), _) = Event.type_of evt in let ctxt' code = compose (ctxt code) (vs', es') in [Ref (ContRef (List.length ts, ctxt'))] @ vs1 @ vs, [Plain (Br (List.assq evt hs)) @@ e.at] - | Resume (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> - let ctxt' code = [], [Resume (hs, compose (ctxt code) (vs', es')) @@ e.at] in + | Handle (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Handle (hs, compose (ctxt code) (vs', es')) @@ e.at] in vs, [Suspending (evt, vs1, ctxt') @@ at] - | Resume (hs, (vs', e' :: es')), vs when is_jumping e' -> + | Handle (hs, (vs', e' :: es')), vs when is_jumping e' -> + vs, [e'] + + | Handle (hs, code'), vs -> + let c' = step {c with code = code'} in + vs, [Handle (hs, c'.code) @@ e.at] + + | Guarded (n, (vs', [])), vs -> + vs' @ vs, [] + + | Guarded (n, (vs', {it = Suspending _; at} :: es')), vs -> + vs, [Trapping "guard suspended" @@ at] + + | Guarded (n, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] - | Resume (hs, code'), vs -> + | Guarded (n, code'), vs -> let c' = step {c with code = code'} in - vs, [Resume (hs, c'.code) @@ e.at] + vs, [Guarded (n, c'.code) @@ e.at] | Trapping _, _ | Throwing _, _ diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index 26685a2f3b..33f9721308 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -97,9 +97,10 @@ and instr' = | ReturnCallRef (* tail call through reference *) | FuncBind of idx (* create closure *) | ContNew of idx (* create continuation *) - | ContSuspend of idx (* suspend continuation *) - | ContThrow of idx (* abort continuation *) - | ContResume of (idx * idx) list (* resume continuation *) + | Suspend of idx (* suspend continuation *) + | Resume of (idx * idx) list (* resume continuation *) + | ResumeThrow of idx (* abort continuation *) + | Guard of block_type * instr list (* guard against suspension *) | LocalGet of idx (* read local idxiable *) | LocalSet of idx (* write local idxiable *) | LocalTee of idx (* write local idxiable and keep value *) diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index 17a7d408cd..f0c5aca94a 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -109,21 +109,21 @@ let rec instr (e : instr) = | RefNull t -> heap_type t | RefFunc x -> funcs (idx x) | Const _ | Test _ | Compare _ | Unary _ | Binary _ | Convert _ -> empty - | Block (bt, es) | Loop (bt, es) -> block_type bt ++ block es + | Block (bt, es) | Loop (bt, es) | Guard (bt, es) -> block_type bt ++ block es | If (bt, es1, es2) -> block_type bt ++ block es1 ++ block es2 | Let (bt, ts, es) -> let free = block_type bt ++ block es in {free with locals = Lib.Fun.repeat (List.length ts) shift free.locals} | Try (bt, es1, xo, es2) -> block_type bt ++ block es1 ++ opt (fun x -> events (idx x)) xo ++ block es2 - | Throw x | ContThrow x | ContSuspend x -> events (idx x) + | Throw x | ResumeThrow x | Suspend x -> events (idx x) | Br x | BrIf x | BrOnNull x -> labels (idx x) | BrTable (xs, x) -> list (fun x -> labels (idx x)) (x::xs) | Return | CallRef | ReturnCallRef -> empty | Call x -> funcs (idx x) | CallIndirect (x, y) -> tables (idx x) ++ types (idx y) | FuncBind x | ContNew x -> types (idx x) - | ContResume xys -> list (fun (x, y) -> events (idx x) ++ labels (idx y)) xys + | Resume xys -> list (fun (x, y) -> events (idx x) ++ labels (idx y)) xys | LocalGet x | LocalSet x | LocalTee x -> locals (idx x) | GlobalGet x | GlobalSet x -> globals (idx x) | TableGet x | TableSet x | TableSize x | TableGrow x | TableFill x -> diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index 66454eddf5..56c99c7ff7 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -39,9 +39,10 @@ let return_call_ref = ReturnCallRef let func_bind x = FuncBind x let cont_new x = ContNew x -let cont_suspend x = ContSuspend x -let cont_throw x = ContThrow x -let cont_resume xys = ContResume xys +let suspend x = Suspend x +let resume xys = Resume xys +let resume_throw x = ResumeThrow x +let guard bt es = Guard (bt, es) 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 e6a8fe1cef..c7d767fc9b 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -255,10 +255,13 @@ let rec instr e = "let", block_type bt @ decls "local" (List.map Source.it locals) @ list instr es | Try (bt, es1, xo, es2) -> - let catch = - match xo with Some x -> "catch " ^ var x | None -> "catch_all" in + let catch, exn = + match xo with + | Some x -> "catch", [Node ("exception " ^ var x, [])] + | None -> "catch_all", [] + in "try", block_type bt @ - [Node ("do", list instr es1); Node (catch, list instr es2)] + [Node ("do", list instr es1); Node (catch, exn @ list instr es2)] | Throw x -> "throw " ^ var x, [] | Br x -> "br " ^ var x, [] | BrIf x -> "br_if " ^ var x, [] @@ -273,11 +276,12 @@ let rec instr e = | ReturnCallRef -> "return_call_ref", [] | FuncBind x -> "func.bind", [Node ("type " ^ var x, [])] | ContNew x -> "cont.new", [Node ("type " ^ var x, [])] - | ContResume xys -> - "cont.resume", + | Suspend x -> "suspend " ^ var x, [] + | Resume xys -> + "resume", List.map (fun (x, y) -> Node ("event " ^ var x ^ " " ^ var y, [])) xys - | ContSuspend x -> "cont.suspend", [Node ("event" ^ var x, [])] - | ContThrow x -> "cont.throw", [Node ("exception" ^ var x, [])] + | ResumeThrow x -> "resume_throw " ^ var x, [] + | Guard (bt, es) -> "guard", block_type bt @ list instr es | LocalGet x -> "local.get " ^ var x, [] | LocalSet x -> "local.set " ^ var x, [] | LocalTee x -> "local.tee " ^ var x, [] diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 4bec43b3da..5ce6528e82 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -216,9 +216,10 @@ rule token = parse | "catch_all" { CATCH_ALL } | "cont.new" { CONT_NEW } - | "cont.suspend" { CONT_SUSPEND } - | "cont.throw" { CONT_THROW } - | "cont.resume" { CONT_RESUME } + | "suspend" { SUSPEND } + | "resume" { RESUME } + | "resume_throw" { RESUME_THROW } + | "guard" { GUARD } | "local.get" { LOCAL_GET } | "local.set" { LOCAL_SET } diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 3fda8e1321..efed764349 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -217,7 +217,7 @@ let inline_func_type_explicit (c : context) x ft at = %token UNREACHABLE NOP DROP SELECT %token BLOCK END IF THEN ELSE LOOP LET %token THROW TRY DO CATCH CATCH_ALL -%token CONT_NEW CONT_SUSPEND CONT_THROW CONT_RESUME +%token CONT_NEW SUSPEND RESUME RESUME_THROW GUARD %token BR BR_IF BR_TABLE BR_ON_NULL %token CALL CALL_REF CALL_INDIRECT RETURN RETURN_CALL_REF FUNC_BIND %token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET @@ -447,8 +447,8 @@ plain_instr : | CALL_REF { fun c -> call_ref } | RETURN_CALL_REF { fun c -> return_call_ref } | CONT_NEW LPAR TYPE var RPAR { fun c -> cont_new ($4 c type_) } - | CONT_SUSPEND var { fun c -> cont_suspend ($2 c event) } - | CONT_THROW var { fun c -> cont_throw ($2 c event) } + | SUSPEND var { fun c -> suspend ($2 c event) } + | RESUME_THROW var { fun c -> resume_throw ($2 c event) } | 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) } @@ -584,8 +584,8 @@ call_instr_results_instr : resume_instr : - | CONT_RESUME resume_instr_handler - { let at = at () in fun c -> cont_resume ($2 c) @@ at } + | RESUME resume_instr_handler + { let at = at () in fun c -> resume ($2 c) @@ at } resume_instr_handler : | LPAR EVENT var var RPAR resume_instr_handler @@ -595,9 +595,9 @@ resume_instr_handler : resume_instr_instr : - | CONT_RESUME resume_instr_handler_instr + | RESUME resume_instr_handler_instr { let at1 = ati 1 in - fun c -> let hs, es = $2 c in cont_resume hs @@ at1, es } + fun c -> let hs, es = $2 c in resume hs @@ at1, es } resume_instr_handler_instr : | LPAR EVENT var var RPAR resume_instr_handler_instr @@ -620,6 +620,14 @@ block_instr : { let at = at () in fun c -> let c' = enter_let ($2 c $5) at in let ts, ls, es = $3 c c' in let_ ts ls es } + | TRY labeling_opt block CATCH_ALL labeling_end_opt instr_list END labeling_end_opt + { fun c -> let c' = $2 c ($5 @ $8) in + let ts, es1 = $3 c' in try_ ts es1 None ($6 c') } + | TRY labeling_opt block CATCH labeling_end_opt LPAR EXCEPTION var RPAR instr_list END labeling_end_opt + { fun c -> let c' = $2 c ($5 @ $12) in + let ts, es1 = $3 c' in try_ ts es1 (Some ($8 c' event)) ($10 c') } + | GUARD labeling_opt block END labeling_end_opt + { fun c -> let c' = $2 c $5 in let bt, es = $3 c' in guard bt es } block : | type_use block_param_body @@ -719,8 +727,8 @@ expr1 : /* Sugar */ fun c -> let x, es = $2 c in es, call_indirect (0l @@ at1) x } | FUNC_BIND call_expr_type { fun c -> let x, es = $2 c in es, func_bind x } - | CONT_RESUME resume_expr_handler - { fun c -> let hs, es = $2 c in es, cont_resume hs } + | RESUME resume_expr_handler + { fun c -> let hs, es = $2 c in es, resume 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 @@ -736,6 +744,8 @@ expr1 : /* Sugar */ { fun c -> let bt, (es1, xo, es2) = $2 c in [], try_ bt es1 xo es2 } + | GUARD labeling_opt block + { fun c -> let c' = $2 c [] in let bt, es = $3 c' in [], guard bt es } select_expr_results : | LPAR RESULT value_type_list RPAR select_expr_results @@ -807,8 +817,8 @@ try_block_result_body : let out' = snd $3 c in FuncType (ins, out' @ out), es } try_ : - | LPAR DO instr_list RPAR LPAR CATCH var instr_list RPAR - { fun c -> $3 c, Some ($7 c event), $8 c } + | LPAR DO instr_list RPAR LPAR CATCH LPAR EXCEPTION var RPAR instr_list RPAR + { fun c -> $3 c, Some ($9 c event), $11 c } | LPAR DO instr_list RPAR LPAR CATCH_ALL instr_list RPAR { fun c -> $3 c, None, $7 c } diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index 820fbb786a..fe2aefcec9 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -450,42 +450,47 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = [RefType (NonNullable, DefHeapType y)] --> [RefType (NonNullable, DefHeapType (SynVar x.it))] - | ContSuspend x -> + | Suspend x -> let EventType (FuncType (ts1, ts2), res) = event c x in require (res = Resumable) e.at "suspending with a non-resumable event"; ts1 --> ts2 - | ContThrow x -> - let EventType (FuncType (ts0, _), res) = event c x in - require (res = Terminal) e.at "throwing a non-exception event"; + | Resume xys -> (match peek_ref 0 s e.at with | nul, DefHeapType (SynVar y) -> let ContType z = cont_type c (y @@ e.at) in let FuncType (ts1, ts2) = func_type c (as_syn_var z @@ e.at) in - (ts0 @ [RefType (nul, DefHeapType (SynVar y))]) --> ts2 + List.iter (fun (x1, x2) -> + let EventType (FuncType (ts3, ts4), res) = event c x1 in + require (res = Resumable) x1.at "handling a non-resumable event"; + (* TODO: check label; problem: we don't have a type idx to produce here + check_stack c (ts3 @ [RefType (NonNullable, DefHeapType (SynVar ?))]) (label c x2) x2.at + *) + ) xys; + (ts1 @ [RefType (nul, DefHeapType (SynVar y))]) --> ts2 | _, BotHeapType -> [] -->... [] | _ -> assert false ) - | ContResume xys -> + | ResumeThrow x -> + let EventType (FuncType (ts0, _), res) = event c x in + require (res = Terminal) e.at "throwing a non-exception event"; (match peek_ref 0 s e.at with | nul, DefHeapType (SynVar y) -> let ContType z = cont_type c (y @@ e.at) in let FuncType (ts1, ts2) = func_type c (as_syn_var z @@ e.at) in - List.iter (fun (x1, x2) -> - let EventType (FuncType (ts3, ts4), res) = event c x1 in - require (res = Resumable) x1.at "handling a non-resumable event"; - (* TODO: check label; problem: we don't have a type idx to produce here - check_stack c (ts3 @ [RefType (NonNullable, DefHeapType (SynVar ?))]) (label c x2) x2.at - *) - ) xys; - (ts1 @ [RefType (nul, DefHeapType (SynVar y))]) --> ts2 + (ts0 @ [RefType (nul, DefHeapType (SynVar y))]) --> ts2 | _, BotHeapType -> [] -->... [] | _ -> assert false ) + | Guard (bt, es) -> + let FuncType (ts1, ts2) as ft = check_block_type c bt e.at in + check_block {c with labels = ts2 :: c.labels} es ft e.at; + ts1 --> ts2 + | LocalGet x -> [] --> [local c x] diff --git a/test/core/binary.wast b/test/core/binary.wast index ca80f2933d..77ae2a5068 100644 --- a/test/core/binary.wast +++ b/test/core/binary.wast @@ -1284,7 +1284,7 @@ "\02\04\01" ;; import section with single entry "\00" ;; string length 0 "\00" ;; string length 0 - "\04" ;; malformed import kind + "\05" ;; malformed import kind ) "malformed import kind" ) @@ -1294,7 +1294,7 @@ "\02\05\01" ;; import section with single entry "\00" ;; string length 0 "\00" ;; string length 0 - "\04" ;; malformed import kind + "\05" ;; malformed import kind "\00" ;; dummy byte ) "malformed import kind" diff --git a/test/core/catch.wast b/test/core/catch.wast index 081614f510..34815415a6 100644 --- a/test/core/catch.wast +++ b/test/core/catch.wast @@ -41,7 +41,7 @@ (func (export "catch-4") (result i32) (try (result i32) (do (throw $e1 (i32.const 66)) (i32.const 0)) - (catch $e1) + (catch (exception $e1)) ) ) @@ -71,7 +71,7 @@ (func (export "uncaught-2") (result i32) (try (result i32) (do (throw $e0) (i32.const 0)) - (catch $e1) + (catch (exception $e1)) ) ) ) diff --git a/test/core/cont.wast b/test/core/cont.wast index 22134eadff..b758ed8a96 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -1,4 +1,4 @@ -;; Unhandled events +;; Unhandled events & guards (module (exception $exn) @@ -9,12 +9,12 @@ (type $k1 (cont $f1)) (func $f1 (export "unhandled-1") - (cont.suspend $e1) + (suspend $e1) ) (func (export "unhandled-2") (block $h (result (ref $k1)) - (cont.resume (event $e2 $h) (cont.new (type $k1) (ref.func $f1))) + (resume (event $e2 $h) (cont.new (type $k1) (ref.func $f1))) (unreachable) ) (drop) @@ -22,7 +22,7 @@ (func (export "handled") (block $h (result (ref $k1)) - (cont.resume (event $e1 $h) (cont.new (type $k1) (ref.func $f1))) + (resume (event $e1 $h) (cont.new (type $k1) (ref.func $f1))) (unreachable) ) (drop) @@ -35,7 +35,7 @@ (func (export "uncaught-1") (block $h (result (ref $k1)) - (cont.resume (event $e1 $h) (cont.new (type $k1) (ref.func $f2))) + (resume (event $e1 $h) (cont.new (type $k1) (ref.func $f2))) (unreachable) ) (drop) @@ -43,10 +43,26 @@ (func (export "uncaught-2") (block $h (result (ref $k1)) - (cont.resume (event $e1 $h) (cont.new (type $k1) (ref.func $f1))) + (resume (event $e1 $h) (cont.new (type $k1) (ref.func $f1))) (unreachable) ) - (cont.throw $exn) + (resume_throw $exn) + ) + + (elem declare func $f3) + (func $f3 + (guard (call $f4)) + ) + (func $f4 + (suspend $e1) + ) + + (func (export "guarded") + (block $h (result (ref $k1)) + (resume (event $e1 $h) (cont.new (type $k1) (ref.func $f3))) + (unreachable) + ) + (resume_throw $exn) ) ) @@ -70,7 +86,7 @@ (loop $loop (block $on_get (result (ref $k)) (block $on_set (result i32 (ref $k)) - (cont.resume (event $get $on_get) (event $set $on_set) + (resume (event $get $on_get) (event $set $on_set) (local.get $s) (local.get $k) ) (return) @@ -88,14 +104,14 @@ ) (func $f (param i32) (result i32) - (drop (cont.suspend $set (i32.const 7))) + (drop (suspend $set (i32.const 7))) (i32.add - (cont.suspend $get) + (suspend $get) (i32.mul (i32.const 2) (i32.add - (cont.suspend $set (i32.const 3)) - (cont.suspend $get) + (suspend $set (i32.const 3)) + (suspend $get) ) ) ) @@ -126,7 +142,7 @@ (func $gen (export "start") (param $i i64) (loop $l - (br_if 1 (cont.suspend $yield (local.get $i))) + (br_if 1 (suspend $yield (local.get $i))) (call_ref (local.get $i) (global.get $hook)) (local.set $i (i64.add (local.get $i) (i64.const 1))) (br $l) @@ -140,7 +156,7 @@ (local.get $i) (cont.new (type $cont0) (ref.func $gen)) (block $on_first_yield (param i64 (ref $cont0)) (result i64 (ref $cont)) - (cont.resume (event $yield $on_first_yield)) + (resume (event $yield $on_first_yield)) (unreachable) ) (loop $on_yield (param i64) (param (ref $cont)) @@ -149,7 +165,7 @@ (local.set $sum (i64.add (local.get $sum) (local.get $n))) (i64.eq (local.get $n) (local.get $j)) (local.get $k) ) - (cont.resume (event $yield $on_yield)) + (resume (event $yield $on_yield)) ) (return (local.get $sum)) ) @@ -231,7 +247,7 @@ (if (call $queue-empty) (then (return))) (block $on_yield (result (ref $cont)) (block $on_spawn (result (ref $proc) (ref $cont)) - (cont.resume (event $yield $on_yield) (event $spawn $on_spawn) + (resume (event $yield $on_yield) (event $spawn $on_spawn) (call $dequeue) ) (br $l) ;; thread terminated @@ -267,21 +283,21 @@ (func $main (call $log (i32.const 0)) - (cont.suspend $spawn (ref.func $thread1)) + (suspend $spawn (ref.func $thread1)) (call $log (i32.const 1)) - (cont.suspend $spawn (func.bind (type $proc) (global.get $depth) (ref.func $thread2))) + (suspend $spawn (func.bind (type $proc) (global.get $depth) (ref.func $thread2))) (call $log (i32.const 2)) - (cont.suspend $spawn (ref.func $thread3)) + (suspend $spawn (ref.func $thread3)) (call $log (i32.const 3)) ) (func $thread1 (call $log (i32.const 10)) - (cont.suspend $yield) + (suspend $yield) (call $log (i32.const 11)) - (cont.suspend $yield) + (suspend $yield) (call $log (i32.const 12)) - (cont.suspend $yield) + (suspend $yield) (call $log (i32.const 13)) ) @@ -295,9 +311,9 @@ (if (local.get $w) (then (call $log (i32.const 22)) - (cont.suspend $yield) + (suspend $yield) (call $log (i32.const 23)) - (cont.suspend $spawn + (suspend $spawn (func.bind (type $proc) (i32.sub (local.get $d) (i32.const 1)) (ref.func $thread2) @@ -314,9 +330,9 @@ (func $thread3 (call $log (i32.const 30)) - (cont.suspend $yield) + (suspend $yield) (call $log (i32.const 31)) - (cont.suspend $yield) + (suspend $yield) (call $log (i32.const 32)) ) @@ -356,21 +372,21 @@ (func $syield (param $i i64) (call $log (local.get $i)) - (cont.suspend $syield) + (suspend $syield) ) (func $bg-thread (call $log (i64.const -10)) (loop $l (call $log (i64.const -11)) - (cont.suspend $syield) + (suspend $syield) (br_if $l (i32.eqz (global.get $done))) ) (call $log (i64.const -12)) ) (func $main (param $i i64) (param $j i64) - (cont.suspend $spawn (ref.func $bg-thread)) + (suspend $spawn (ref.func $bg-thread)) (global.set $ghook (ref.func $syield)) (global.set $result (call $gsum (local.get $i) (local.get $j))) (global.set $done (i32.const 1)) From 6f1aed80212956f96a8c70f5a7897bafc5eaa70a Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Sat, 20 Feb 2021 08:46:07 +0100 Subject: [PATCH 12/15] Actually invoke test --- test/core/cont.wast | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/core/cont.wast b/test/core/cont.wast index b758ed8a96..c047e15cce 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -69,9 +69,12 @@ (assert_suspension (invoke "unhandled-1") "unhandled") (assert_suspension (invoke "unhandled-2") "unhandled") (assert_return (invoke "handled")) + (assert_exception (invoke "uncaught-1") "unhandled") (assert_exception (invoke "uncaught-2") "unhandled") +(assert_trap (invoke "guarded") "guard suspended") + ;; Simple state example From c3a3a3d66ee3bad9dec55f5748783d1b9bf73928 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Mon, 22 Feb 2021 10:43:34 +0100 Subject: [PATCH 13/15] Unify Guarded admin instr with Handle --- interpreter/exec/eval.ml | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 834762ca6c..7ae637a420 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -67,8 +67,7 @@ and admin_instr' = | Local of int * value list * code | Frame of int * frame * code | Catch of int * event_inst option * instr list * code - | Handle of (event_inst * idx) list * code - | Guarded of int * code + | Handle of (event_inst * idx) list option * code | Trapping of string | Throwing of event_inst * value stack | Suspending of event_inst * value stack * ctxt @@ -329,7 +328,7 @@ let rec step (c : config) : config = | Resume xls, Ref (ContRef (n, ctxt)) :: vs -> let hs = List.map (fun (x, l) -> event c.frame.inst x, l) xls in let args, vs' = split n vs e.at in - vs', [Handle (hs, ctxt (args, [])) @@ e.at] + vs', [Handle (Some hs, ctxt (args, [])) @@ e.at] | ResumeThrow x, Ref (NullRef _) :: vs -> vs, [Trapping "null continuation reference" @@ e.at] @@ -342,10 +341,10 @@ let rec step (c : config) : config = vs1' @ vs', es1' | Guard (bt, es'), vs -> - let FuncType (ts1, ts2) = block_type c.frame.inst bt e.at in + let FuncType (ts1, _) = block_type c.frame.inst bt e.at in let args, vs' = split (List.length ts1) vs e.at in vs', [ - Guarded (List.length ts2, + Handle (None, (args, [Plain (Block (bt, es')) @@ e.at]) ) @@ e.at ] @@ -732,39 +731,29 @@ let rec step (c : config) : config = let c' = step {c with code = code'} in vs, [Catch (n, exno, es0, c'.code) @@ e.at] - | Handle (hs, (vs', [])), vs -> + | Handle (hso, (vs', [])), vs -> vs' @ vs, [] - | Handle (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs + | Handle (None, (vs', {it = Suspending _; at} :: es')), vs -> + vs, [Trapping "guard suspended" @@ at] + + | Handle (Some hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs when List.mem_assq evt hs -> let EventType (FuncType (_, ts), _) = Event.type_of evt in let ctxt' code = compose (ctxt code) (vs', es') in [Ref (ContRef (List.length ts, ctxt'))] @ vs1 @ vs, [Plain (Br (List.assq evt hs)) @@ e.at] - | Handle (hs, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> - let ctxt' code = [], [Handle (hs, compose (ctxt code) (vs', es')) @@ e.at] in + | Handle (hso, (vs', {it = Suspending (evt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Handle (hso, compose (ctxt code) (vs', es')) @@ e.at] in vs, [Suspending (evt, vs1, ctxt') @@ at] - | Handle (hs, (vs', e' :: es')), vs when is_jumping e' -> - vs, [e'] - - | Handle (hs, code'), vs -> - let c' = step {c with code = code'} in - vs, [Handle (hs, c'.code) @@ e.at] - - | Guarded (n, (vs', [])), vs -> - vs' @ vs, [] - - | Guarded (n, (vs', {it = Suspending _; at} :: es')), vs -> - vs, [Trapping "guard suspended" @@ at] - - | Guarded (n, (vs', e' :: es')), vs when is_jumping e' -> + | Handle (hso, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] - | Guarded (n, code'), vs -> + | Handle (hso, code'), vs -> let c' = step {c with code = code'} in - vs, [Guarded (n, c'.code) @@ e.at] + vs, [Handle (hso, c'.code) @@ e.at] | Trapping _, _ | Throwing _, _ From be2c1c04ae926b677b92c00607ffce996cc5558e Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Mon, 22 Feb 2021 11:21:51 +0100 Subject: [PATCH 14/15] Test empty handler --- test/core/cont.wast | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/core/cont.wast b/test/core/cont.wast index c047e15cce..4871822ec1 100644 --- a/test/core/cont.wast +++ b/test/core/cont.wast @@ -13,6 +13,10 @@ ) (func (export "unhandled-2") + (resume (cont.new (type $k1) (ref.func $f1))) + ) + + (func (export "unhandled-3") (block $h (result (ref $k1)) (resume (event $e2 $h) (cont.new (type $k1) (ref.func $f1))) (unreachable) @@ -68,6 +72,7 @@ (assert_suspension (invoke "unhandled-1") "unhandled") (assert_suspension (invoke "unhandled-2") "unhandled") +(assert_suspension (invoke "unhandled-3") "unhandled") (assert_return (invoke "handled")) (assert_exception (invoke "uncaught-1") "unhandled") From dfb8dadf9550b1378034cb12bc00b42486204b48 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Mon, 22 Feb 2021 14:37:15 +0100 Subject: [PATCH 15/15] Comment --- interpreter/valid/match.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpreter/valid/match.ml b/interpreter/valid/match.ml index 9e6d9d8471..cd7c605e70 100644 --- a/interpreter/valid/match.ml +++ b/interpreter/valid/match.ml @@ -154,7 +154,7 @@ and match_global_type c a (GlobalType (t1, mut1)) (GlobalType (t2, mut2)) = | Mutable -> eq_value_type c [] t1 t2 and match_event_type c a (EventType (ft1, res1)) (EventType (ft2, res2)) = - eq_resumability c [] res1 res2 && match_func_type c [] ft1 ft2 + eq_resumability c [] res1 res2 && match_func_type c a ft1 ft2 and match_extern_type c a et1 et2 = match et1, et2 with