From ce612e89ef4dec223721bb0299ec23897fa49c6f Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Wed, 17 Feb 2021 18:25:37 +0100 Subject: [PATCH] Implement events --- interpreter/binary/decode.ml | 47 +++++++++++-- interpreter/binary/encode.ml | 32 +++++++-- interpreter/exec/eval.ml | 115 ++++++++++++++++++-------------- interpreter/exec/eval.mli | 4 +- interpreter/host/spectest.ml | 6 ++ interpreter/runtime/event.ml | 10 +++ interpreter/runtime/event.mli | 7 ++ interpreter/runtime/instance.ml | 6 +- interpreter/script/js.ml | 18 ++--- interpreter/script/run.ml | 16 ++--- interpreter/script/script.ml | 2 +- interpreter/syntax/ast.ml | 21 +++++- interpreter/syntax/free.ml | 15 ++++- interpreter/syntax/free.mli | 2 + interpreter/syntax/operators.ml | 6 +- interpreter/syntax/types.ml | 15 ++++- interpreter/text/arrange.ml | 37 +++++++--- interpreter/text/lexer.mll | 15 +++-- interpreter/text/parser.mly | 89 ++++++++++++++++++++---- interpreter/valid/valid.ml | 56 ++++++++++++---- test/core/binary.wast | 2 +- test/core/catch.wast | 86 ++++++++++++++++++++++++ test/core/tinyexn.wast | 49 -------------- 23 files changed, 472 insertions(+), 184 deletions(-) create mode 100644 interpreter/runtime/event.ml create mode 100644 interpreter/runtime/event.mli create mode 100644 test/core/catch.wast delete mode 100644 test/core/tinyexn.wast diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index 62aca4fc7..fc1f85cad 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -189,6 +189,17 @@ let memory_type s = let lim = limits vu32 s in MemoryType lim +let resumability s = + match u8 s with + | 0 -> Terminal + | 1 -> Resumable + | _ -> error s (pos s - 1) "malformed resumability" + +let event_type s = + let res = resumability s in + let ft = func_type s in (* TODO *) + EventType (ft, res) + let mutability s = match u8 s with | 0 -> Immutable @@ -274,12 +285,21 @@ let rec instr s = | 0x06 -> let bt = block_type s in let es1 = instr_block s in - expect 0x07 s "CATCH opcode expected"; + let xo = + if peek s = Some 0x07 then begin + expect 0x07 s "CATCH or CATCH_ALL opcode expected"; + Some (at var s) + end + else begin + expect 0x19 s "CATCH or CATCH_ALL opcode expected"; + None + end + in let es2 = instr_block s in end_ s; - try_ bt es1 es2 + try_ bt es1 xo es2 | 0x07 -> error s pos "misplaced CATCH opcode" - | 0x08 -> throw + | 0x08 -> throw (at var s) | 0x09 | 0x0a as b -> illegal s pos b | 0x0b -> error s pos "misplaced END opcode" @@ -311,7 +331,9 @@ let rec instr s = end_ s; let_ bt locs es - | 0x18 | 0x19 as b -> illegal s pos b + | 0x18 as b -> illegal s pos b + + | 0x19 -> error s pos "misplaced CATCH_ALL opcode" | 0x1a -> drop | 0x1b -> select None @@ -549,7 +571,7 @@ let rec instr s = and instr_block s = List.rev (instr_block' s []) and instr_block' s es = match peek s with - | None | Some (0x05 | 0x07 | 0x0b) -> es + | None | Some (0x05 | 0x07 | 0x0b | 0x19) -> es | _ -> let pos = pos s in let e' = instr s in @@ -580,6 +602,7 @@ let id s = | 10 -> `CodeSection | 11 -> `DataSection | 12 -> `DataCountSection + | 13 -> `EventSection | _ -> error s (pos s) "malformed section id" ) bo @@ -646,6 +669,16 @@ let memory_section s = section `MemorySection (vec (at memory)) [] s +(* Event section *) + +let event s = + let evtype = event_type s in + {evtype} + +let event_section s = + section `EventSection (vec (at event)) [] s + + (* Global section *) let global s = @@ -830,6 +863,8 @@ let module_ s = iterate custom_section s; let memories = memory_section s in iterate custom_section s; + let events = event_section s in + iterate custom_section s; let globals = global_section s in iterate custom_section s; let exports = export_section s in @@ -855,7 +890,7 @@ let module_ s = let funcs = List.map2 Source.(fun t f -> {f.it with ftype = t} @@ f.at) func_types func_bodies - in {types; tables; memories; globals; funcs; imports; exports; elems; datas; start} + in {types; tables; memories; events; globals; funcs; imports; exports; elems; datas; start} let decode name bs = at module_ (stream name bs) diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index 6017cf2ff..0a9c2773c 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -128,6 +128,13 @@ let encode m = let memory_type = function | MemoryType lim -> limits vu32 lim + let resumability = function + | Terminal -> u8 0 + | Resumable -> u8 1 + + let event_type = function + | EventType (ft, res) -> resumability res; func_type ft (* TODO *) + let mutability = function | Immutable -> u8 0 | Mutable -> u8 1 @@ -179,6 +186,15 @@ let encode m = | Let (bt, locs, es) -> op 0x17; block_type bt; locals locs; list instr es; end_ () + | Try (bt, es1, xo, es2) -> + op 0x06; block_type bt; list instr es1; + (match xo with + | Some x -> op 0x07; var x + | None -> op 0x19 + ); + list instr es2; end_ () + | Throw x -> op 0x08; var x + | Br x -> op 0x0c; var x | BrIf x -> op 0x0d; var x | BrTable (xs, x) -> op 0x0e; vec var xs; var x @@ -428,11 +444,6 @@ let encode m = | Convert (F64 F64Op.DemoteF64) -> assert false | Convert (F64 F64Op.ReinterpretInt) -> op 0xbf - | Try (bt, es1, es2) -> - op 0x06; block_type bt; list instr es1; - op 0x07; list instr es2; end_ () - | Throw -> op 0x08 - let const c = list instr c.it; end_ () @@ -460,6 +471,7 @@ let encode m = | TableImport t -> u8 0x01; table_type t | MemoryImport t -> u8 0x02; memory_type t | GlobalImport t -> u8 0x03; global_type t + | EventImport t -> u8 0x04; event_type t let import im = let {module_name; item_name; idesc} = im.it in @@ -498,6 +510,14 @@ let encode m = let global_section gs = section 6 (vec global) gs (gs <> []) + (* Event section *) + let event evt = + let {evtype} = evt.it in + event_type evtype + + let event_section es = + section 13 (vec event) es (es <> []) + (* Export section *) let export_desc d = match d.it with @@ -505,6 +525,7 @@ let encode m = | TableExport x -> u8 1; var x | MemoryExport x -> u8 2; var x | GlobalExport x -> u8 3; var x + | EventExport x -> u8 4; var x let export ex = let {name = n; edesc} = ex.it in @@ -605,6 +626,7 @@ let encode m = func_section m.it.funcs; table_section m.it.tables; memory_section m.it.memories; + event_section m.it.events; global_section m.it.globals; export_section m.it.exports; start_section m.it.start; diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index c44dd72de..491ef4072 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -9,15 +9,15 @@ open Source module Link = Error.Make () module Trap = Error.Make () -module Crash = Error.Make () +module Exception = Error.Make () module Exhaustion = Error.Make () -module Uncaught = Error.Make () +module Crash = Error.Make () exception Link = Link.Error exception Trap = Trap.Error -exception Crash = Crash.Error (* failure that cannot happen in valid code *) +exception Exception = Exception.Error exception Exhaustion = Exhaustion.Error -exception Uncaught = Uncaught.Error +exception Crash = Crash.Error (* failure that cannot happen in valid code *) let table_error at = function | Table.Bounds -> "out of bounds table access" @@ -62,14 +62,14 @@ and admin_instr' = | Refer of ref_ | Invoke of func_inst | Trapping of string + | Throwing of event_inst * value stack | 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 * instr list * code - | Throwing + | Catch of int * event_inst option * instr list * code type config = { @@ -85,7 +85,8 @@ let plain e = Plain e.it @@ e.at let is_jumping e = match e.it with - | Trapping _ | Returning _ | ReturningInvoke _ | Breaking _ -> true + | Trapping _ | Throwing _ | Returning _ | ReturningInvoke _ | Breaking _ -> + true | _ -> false let lookup category list x = @@ -97,6 +98,7 @@ let func (inst : module_inst) x = lookup "function" inst.funcs x let table (inst : module_inst) x = lookup "table" inst.tables x let memory (inst : module_inst) x = lookup "memory" inst.memories x let global (inst : module_inst) x = lookup "global" inst.globals x +let event (inst : module_inst) x = lookup "event" inst.events x let elem (inst : module_inst) x = lookup "element segment" inst.elems x let data (inst : module_inst) x = lookup "data segment" inst.datas x let local (frame : frame) x = lookup "local" frame.locals x @@ -198,6 +200,17 @@ let rec step (c : config) : config = ) @@ e.at ] + | Try (bt, es1, xo, es2), vs -> + let FuncType (ts1, ts2) = block_type c.frame.inst bt e.at in + let n1 = List.length ts1 in + let n2 = List.length ts2 in + let args, vs' = split n1 vs e.at in + let exno = Option.map (event c.frame.inst) xo in + 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] + | Br x, vs -> [], [Breaking (x.it, vs) @@ e.at] @@ -531,16 +544,6 @@ let rec step (c : config) : config = (try Num (Eval_numeric.eval_cvtop cvtop n) :: vs', [] with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at]) - | Try (bt, es1, es2), vs -> - let FuncType (ts1, ts2) = block_type c.frame.inst bt e.at in - let n1 = List.length ts1 in - let n2 = List.length ts2 in - let args, vs' = split n1 vs e.at in - vs', [Catch (n2, es2, ([], [Label (n2, [], (args, List.map plain es1)) @@ e.at])) @@ e.at] - - | Throw, vs -> - vs, [Throwing @@ e.at] - | _ -> let s1 = string_of_values (List.rev vs) in let s2 = string_of_stack_type (List.map type_of_value (List.rev vs)) in @@ -551,16 +554,6 @@ let rec step (c : config) : config = | Refer r, vs -> Ref r :: vs, [] - | Trapping msg, vs -> - assert false - - | Returning _, vs - | ReturningInvoke _, vs -> - Crash.error e.at "undefined frame" - - | Breaking (k, vs'), vs -> - Crash.error e.at "undefined label" - | Label (n, es0, (vs', [])), vs -> vs' @ vs, [] @@ -568,10 +561,7 @@ let rec step (c : config) : config = take n vs0 e.at @ vs, List.map plain es0 | Label (n, es0, (vs', {it = Breaking (k, vs0); at} :: es')), vs -> - vs, [Breaking (Int32.sub k 1l, vs0) @@ at] - - | Label (n, es0, (vs', {it = Throwing; at} :: _)), vs -> - vs, [Throwing @@ at] + vs, [Breaking (Int32.sub k 1l, vs0) @@ at] | Label (n, es0, (vs', e' :: es')), vs when is_jumping e' -> vs, [e'] @@ -584,7 +574,7 @@ let rec step (c : config) : config = vs' @ vs, [] | Local (n, vs0, (vs', e' :: es')), vs when is_jumping e' -> - vs' @ vs, [e'] + vs, [e'] | Local (n, vs0, code'), vs -> let frame' = {c.frame with locals = List.map ref vs0 @ c.frame.locals} in @@ -595,19 +585,19 @@ let rec step (c : config) : config = | Frame (n, frame', (vs', [])), vs -> vs' @ vs, [] - | Frame (n, frame', (vs', {it = Trapping msg; at} :: es')), vs -> - vs, [Trapping msg @@ at] - | Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs -> - take n vs0 e.at @ vs, [] - - | Frame (n, frame', (vs', {it = Throwing; at} :: _)), vs -> - vs, [Throwing @@ at] + take n vs0 e.at @ vs, [] | Frame (n, frame', (vs', {it = ReturningInvoke (vs0, f); at} :: es')), vs -> let FuncType (ts1, _) = Func.type_of f in take (List.length ts1) vs0 e.at @ vs, [Invoke f @@ at] + | Frame (n, fame', (vs', {it = Breaking _; at} :: es')), vs -> + Crash.error at "undefined label" + + | Frame (n, frame', (vs', e' :: es')), vs when is_jumping e' -> + vs, [e'] + | Frame (n, frame', code'), vs -> let c' = step {frame = frame'; code = code'; budget = c.budget - 1} in vs, [Frame (n, frame', c'.code) @@ e.at] @@ -637,21 +627,36 @@ let rec step (c : config) : config = args @ args' @ vs', [Invoke f' @@ e.at] ) - | Throwing, _ -> - Uncaught.error e.at "uncaught exception" + | Catch (n, exno, es0, (vs', [])), vs -> + vs' @ vs, [] - | Catch (_, _, (_, ({it = Trapping _ | Breaking _ | Returning _; _} as e) :: _)), vs -> - vs, [e] + | Catch (n, None, es0, (vs', {it = Throwing (exn, vs0); at} :: _)), vs -> + vs, [Label (n, [], ([], List.map plain es0)) @@ e.at] - | Catch (n, es1, (_, {it = Throwing; _} :: _)), vs -> - vs, [Label (n, [], ([], List.map plain es1)) @@ 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] - | Catch (_, _, (vs', [])), vs -> - vs' @ vs, [] + | Catch (n, exno, es0, (vs', e' :: es')), vs when is_jumping e' -> + vs, [e'] - | Catch (n, es', code'), vs -> + | Catch (n, exno, es0, code'), vs -> let c' = step {c with code = code'} in - vs, [Catch (n, es', c'.code) @@ e.at] + vs, [Catch (n, exno, es0, 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 _, vs + | Throwing _, vs -> + assert false + in {c with code = vs', es' @ List.tl es} @@ -663,6 +668,9 @@ let rec eval (c : config) : value stack = | vs, {it = Trapping msg; at} :: _ -> Trap.error at msg + | vs, {it = Throwing _; at} :: _ -> + Exception.error at "uncaught exception" + | vs, es -> eval (step c) @@ -714,6 +722,10 @@ let create_global (inst : module_inst) (glob : global) : global_inst = let v = eval_const inst ginit in Global.alloc (Types.sem_global_type inst.types gtype) v +let create_event (inst : module_inst) (evt : event) : event_inst = + let {evtype} = evt.it in + Event.alloc (Types.sem_event_type inst.types evtype) + let create_export (inst : module_inst) (ex : export) : export_inst = let {name; edesc} = ex.it in let ext = @@ -722,6 +734,7 @@ let create_export (inst : module_inst) (ex : export) : export_inst = | TableExport x -> ExternTable (table inst x) | MemoryExport x -> ExternMemory (memory inst x) | GlobalExport x -> ExternGlobal (global inst x) + | EventExport x -> ExternEvent (event inst x) in (name, ext) let create_elem (inst : module_inst) (seg : elem_segment) : elem_inst = @@ -745,6 +758,7 @@ let add_import (m : module_) (ext : extern) (im : import) (inst : module_inst) | ExternTable tab -> {inst with tables = tab :: inst.tables} | ExternMemory mem -> {inst with memories = mem :: inst.memories} | ExternGlobal glob -> {inst with globals = glob :: inst.globals} + | ExternEvent evt -> {inst with events = evt :: inst.events} let init_type (inst : module_inst) (type_ : type_) (x : type_inst) = @@ -790,7 +804,7 @@ let run_start start = let init (m : module_) (exts : extern list) : module_inst = let - { imports; tables; memories; globals; funcs; types; + { types; imports; tables; memories; globals; funcs; events; exports; elems; datas; start } = m.it in @@ -806,6 +820,7 @@ let init (m : module_) (exts : extern list) : module_inst = tables = inst2.tables @ List.map (create_table inst2) tables; memories = inst2.memories @ List.map (create_memory inst2) memories; globals = inst2.globals @ List.map (create_global inst2) globals; + events = inst2.events @ List.map (create_event inst2) events; } in let inst = diff --git a/interpreter/exec/eval.mli b/interpreter/exec/eval.mli index 250576801..05617e098 100644 --- a/interpreter/exec/eval.mli +++ b/interpreter/exec/eval.mli @@ -3,9 +3,9 @@ open Instance exception Link of Source.region * string exception Trap of Source.region * string -exception Crash of Source.region * string +exception Exception of Source.region * string exception Exhaustion of Source.region * string -exception Uncaught of Source.region * string +exception Crash of Source.region * string val init : Ast.module_ -> extern list -> module_inst (* raises Link, Trap *) val invoke : func_inst -> value list -> value list (* raises Trap *) diff --git a/interpreter/host/spectest.ml b/interpreter/host/spectest.ml index 168fb9ff3..162734d4b 100644 --- a/interpreter/host/spectest.ml +++ b/interpreter/host/spectest.ml @@ -21,9 +21,13 @@ let global (GlobalType (t, _) as gt) = let table = Table.alloc (TableType ({min = 10l; max = Some 20l}, (Nullable, FuncHeapType))) (NullRef FuncHeapType) + let memory = Memory.alloc (MemoryType {min = 1l; max = Some 2l}) let func f ft = Func.alloc_host (Types.alloc (FuncDefType ft)) (f ft) +let event = Event.alloc (EventType (FuncType ([NumType I32Type], [NumType I32Type]), Resumable)) +let except = Event.alloc (EventType (FuncType ([NumType I32Type], []), Terminal)) + let print_value v = Printf.printf "%s : %s\n" (string_of_value v) (string_of_value_type (type_of_value v)) @@ -51,4 +55,6 @@ let lookup name t = | "global_f64", _ -> ExternGlobal (global (GlobalType (NumType F64Type, Immutable))) | "table", _ -> ExternTable table | "memory", _ -> ExternMemory memory + | "event", _ -> ExternEvent event + | "exception", _ -> ExternEvent except | _ -> raise Not_found diff --git a/interpreter/runtime/event.ml b/interpreter/runtime/event.ml new file mode 100644 index 000000000..852261579 --- /dev/null +++ b/interpreter/runtime/event.ml @@ -0,0 +1,10 @@ +open Types + +type event = {ty : event_type} +type t = event + +let alloc ty = + {ty} + +let type_of evt = + evt.ty diff --git a/interpreter/runtime/event.mli b/interpreter/runtime/event.mli new file mode 100644 index 000000000..a1aa74541 --- /dev/null +++ b/interpreter/runtime/event.mli @@ -0,0 +1,7 @@ +open Types + +type event +type t = event + +val alloc : event_type -> event +val type_of : event -> event_type diff --git a/interpreter/runtime/instance.ml b/interpreter/runtime/instance.ml index 30727c260..8899aa45d 100644 --- a/interpreter/runtime/instance.ml +++ b/interpreter/runtime/instance.ml @@ -7,6 +7,7 @@ type module_inst = tables : table_inst list; memories : memory_inst list; globals : global_inst list; + events : event_inst list; exports : export_inst list; elems : elem_inst list; datas : data_inst list; @@ -17,6 +18,7 @@ and func_inst = module_inst Lib.Promise.t Func.t and table_inst = Table.t and memory_inst = Memory.t and global_inst = Global.t +and event_inst = Event.t and export_inst = Ast.name * extern and elem_inst = Value.ref_ list ref and data_inst = string ref @@ -26,6 +28,7 @@ and extern = | ExternTable of table_inst | ExternMemory of memory_inst | ExternGlobal of global_inst + | ExternEvent of event_inst (* Reference types *) @@ -48,7 +51,7 @@ let () = (* Auxiliary functions *) let empty_module_inst = - { types = []; funcs = []; tables = []; memories = []; globals = []; + { types = []; funcs = []; tables = []; memories = []; globals = []; events = []; exports = []; elems = []; datas = [] } let extern_type_of c = function @@ -56,6 +59,7 @@ let extern_type_of c = function | ExternTable tab -> ExternTableType (Table.type_of tab) | ExternMemory mem -> ExternMemoryType (Memory.type_of mem) | ExternGlobal glob -> ExternGlobalType (Global.type_of glob) + | ExternEvent evt -> ExternEventType (Event.type_of evt) let export inst name = try Some (List.assoc name inst.exports) with Not_found -> None diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index 00e2c2d36..f9ca7f41c 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -136,6 +136,13 @@ function assert_trap(action) { throw new Error("Wasm trap expected"); } +function assert_exception(action) { + try { action() } catch (e) { + if (!(e instanceof WebAssembly.RuntimeError)) return; + } + throw new Error("Wasm exception expected"); +} + let StackOverflow; try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor } @@ -189,13 +196,6 @@ function assert_return(action, ...expected) { } } } - -function assert_uncaught(action) { - try { action() } catch (e) { - if (!(e instanceof WebAssembly.RuntimeError)) return; - } - throw new Error("Wasm uncaught exception expected"); -} |} @@ -534,10 +534,10 @@ let of_assertion mods ass = (Some (assert_return ress)) | AssertTrap (act, _) -> of_assertion' mods act "assert_trap" [] None + | AssertException (act, _) -> + of_assertion' mods act "assert_exception" [] None | AssertExhaustion (act, _) -> of_assertion' mods act "assert_exhaustion" [] None - | AssertUncaught (act, _) -> - of_assertion' mods act "assert_uncaught" [] None let of_command mods cmd = "\n// " ^ Filename.basename cmd.at.left.file ^ diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index 77c42a9d5..fde16d882 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -110,9 +110,9 @@ let input_from get_script run = | Import.Unknown (at, msg) -> error at "link failure" msg | Eval.Link (at, msg) -> error at "link failure" msg | Eval.Trap (at, msg) -> error at "runtime trap" msg + | Eval.Exception (at, msg) -> error at "runtime exception" msg | Eval.Exhaustion (at, msg) -> error at "resource exhaustion" msg | Eval.Crash (at, msg) -> error at "runtime crash" msg - | Eval.Uncaught (at, msg) -> error at "runtime uncaught" msg | Encode.Code (at, msg) -> error at "encoding error" msg | Script.Error (at, msg) -> error at "script error" msg | IO (at, msg) -> error at "i/o error" msg @@ -439,6 +439,13 @@ let run_assertion ass = | _ -> Assert.error ass.at "expected runtime error" ) + | AssertException (act, re) -> + trace ("Asserting exception..."); + (match run_action act with + | exception Eval.Exception (_, msg) -> assert_message ass.at "runtime" msg re + | _ -> Assert.error ass.at "expected exception" + ) + | AssertExhaustion (act, re) -> trace ("Asserting exhaustion..."); (match run_action act with @@ -447,13 +454,6 @@ let run_assertion ass = | _ -> Assert.error ass.at "expected exhaustion error" ) - | AssertUncaught (act, re) -> - trace ("Asserting exception..."); - (match run_action act with - | exception Eval.Uncaught (_, msg) -> assert_message ass.at "runtime" msg re - | _ -> Assert.error ass.at "expected exception" - ) - let rec run_command cmd = match cmd.it with | Module (x_opt, def) -> diff --git a/interpreter/script/script.ml b/interpreter/script/script.ml index d95e5b7a7..baff09e0e 100644 --- a/interpreter/script/script.ml +++ b/interpreter/script/script.ml @@ -33,8 +33,8 @@ and assertion' = | AssertUninstantiable of definition * string | AssertReturn of action * result list | AssertTrap of action * string + | AssertException of action * string | AssertExhaustion of action * string - | AssertUncaught of action * string type command = command' Source.phrase and command' = diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index d501efc89..951ac5670 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -84,6 +84,8 @@ and instr' = | Loop of block_type * instr list (* loop header *) | If of block_type * instr list * instr list (* conditional *) | Let of block_type * local list * instr list (* local bindings *) + | Try of block_type * instr list * idx option * instr list (* handle exception *) + | Throw of idx (* throw exception *) | Br of idx (* break to n-th surrounding label *) | BrIf of idx (* conditional break *) | BrTable of idx list * idx (* indexed break *) @@ -125,8 +127,6 @@ and instr' = | Unary of unop (* unary numeric operator *) | Binary of binop (* binary numeric operator *) | Convert of cvtop (* conversion *) - | Try of block_type * instr list * instr list - | Throw (* Globals & Functions *) @@ -149,6 +149,15 @@ and func' = } +(* Events *) + +type event = event' Source.phrase +and event' = +{ + evtype : event_type; +} + + (* Tables & Memories *) type table = table' Source.phrase @@ -195,6 +204,7 @@ and export_desc' = | TableExport of idx | MemoryExport of idx | GlobalExport of idx + | EventExport of idx type export = export' Source.phrase and export' = @@ -209,6 +219,7 @@ and import_desc' = | TableImport of table_type | MemoryImport of memory_type | GlobalImport of global_type + | EventImport of event_type type import = import' Source.phrase and import' = @@ -225,6 +236,7 @@ and module_' = globals : global list; tables : table list; memories : memory list; + events : event list; funcs : func list; start : idx option; elems : elem_segment list; @@ -242,6 +254,7 @@ let empty_module = globals = []; tables = []; memories = []; + events = []; funcs = []; start = None; elems = []; @@ -263,6 +276,7 @@ let import_type_of (m : module_) (im : import) : import_type = | TableImport t -> ExternTableType t | MemoryImport t -> ExternMemoryType t | GlobalImport t -> ExternGlobalType t + | EventImport t -> ExternEventType t in ImportType (et, module_name, item_name) let export_type_of (m : module_) (ex : export) : export_type = @@ -285,6 +299,9 @@ let export_type_of (m : module_) (ex : export) : export_type = | GlobalExport x -> let gts = globals ets @ List.map (fun g -> g.it.gtype) m.it.globals in ExternGlobalType (nth gts x.it) + | EventExport x -> + let evts = events ets @ List.map (fun e -> e.it.evtype) m.it.events in + ExternEventType (nth evts x.it) in ExportType (et, name) let module_type_of (m : module_) : module_type = diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index ceeedec24..669236cda 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -10,6 +10,7 @@ type t = globals : Set.t; tables : Set.t; memories : Set.t; + events : Set.t; funcs : Set.t; elems : Set.t; datas : Set.t; @@ -23,6 +24,7 @@ let empty : t = globals = Set.empty; tables = Set.empty; memories = Set.empty; + events = Set.empty; funcs = Set.empty; elems = Set.empty; datas = Set.empty; @@ -36,6 +38,7 @@ let union (s1 : t) (s2 : t) : t = globals = Set.union s1.globals s2.globals; tables = Set.union s1.tables s2.tables; memories = Set.union s1.memories s2.memories; + events = Set.union s1.events s2.events; funcs = Set.union s1.funcs s2.funcs; elems = Set.union s1.elems s2.elems; datas = Set.union s1.datas s2.datas; @@ -47,6 +50,7 @@ let types s = {empty with types = s} let globals s = {empty with globals = s} let tables s = {empty with tables = s} let memories s = {empty with memories = s} +let events s = {empty with events = s} let funcs s = {empty with funcs = s} let elems s = {empty with elems = s} let datas s = {empty with datas = s} @@ -59,6 +63,7 @@ let zero = Set.singleton 0l let shift s = Set.map (Int32.add (-1l)) (Set.remove 0l s) let (++) = union +let opt free xo = Lib.Option.get (Option.map free xo) empty let list free xs = List.fold_left union empty (List.map free xs) let var_type = function @@ -89,6 +94,7 @@ let func_type (FuncType (ins, out)) = let global_type (GlobalType (t, _mut)) = value_type t let table_type (TableType (_lim, t)) = ref_type t let memory_type (MemoryType (_lim)) = empty +let event_type (EventType (ft, _res)) = func_type ft let def_type = function | FuncDefType ft -> func_type ft @@ -106,6 +112,9 @@ let rec instr (e : instr) = | 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 -> 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 @@ -123,8 +132,6 @@ let rec instr (e : instr) = memories zero | MemoryInit x -> memories zero ++ datas (idx x) | DataDrop x -> datas (idx x) - | Try (bt, es1, es2) -> block_type bt ++ block es1 ++ block es2 - | Throw -> empty and block (es : instr list) = let free = list instr es in {free with labels = shift free.labels} @@ -136,6 +143,7 @@ let func (f : func) = {(types (idx f.it.ftype) ++ block f.it.body) with locals = Set.empty} let table (t : table) = table_type t.it.ttype let memory (m : memory) = memory_type m.it.mtype +let event (e : event) = event_type e.it.evtype let segment_mode f (m : segment_mode) = match m.it with @@ -156,6 +164,7 @@ let export_desc (d : export_desc) = | TableExport x -> tables (idx x) | MemoryExport x -> memories (idx x) | GlobalExport x -> globals (idx x) + | EventExport x -> events (idx x) let import_desc (d : import_desc) = match d.it with @@ -163,6 +172,7 @@ let import_desc (d : import_desc) = | TableImport tt -> table_type tt | MemoryImport mt -> memory_type mt | GlobalImport gt -> global_type gt + | EventImport et -> event_type et let export (e : export) = export_desc e.it.edesc let import (i : import) = import_desc i.it.idesc @@ -175,6 +185,7 @@ let module_ (m : module_) = list global m.it.globals ++ list table m.it.tables ++ list memory m.it.memories ++ + list event m.it.events ++ list func m.it.funcs ++ start m.it.start ++ list elem m.it.elems ++ diff --git a/interpreter/syntax/free.mli b/interpreter/syntax/free.mli index 71456c8d4..870056731 100644 --- a/interpreter/syntax/free.mli +++ b/interpreter/syntax/free.mli @@ -6,6 +6,7 @@ type t = globals : Set.t; tables : Set.t; memories : Set.t; + events : Set.t; funcs : Set.t; elems : Set.t; datas : Set.t; @@ -25,6 +26,7 @@ val global : Ast.global -> t val func : Ast.func -> t val table : Ast.table -> t val memory : Ast.memory -> t +val event : Ast.event -> t val elem : Ast.elem_segment -> t val data : Ast.data_segment -> t val export : Ast.export -> t diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index 26844f9e4..61c41a358 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -23,6 +23,9 @@ let loop bt es = Loop (bt, es) let if_ bt es1 es2 = If (bt, es1, es2) let let_ bt ts es = Let (bt, ts, es) +let try_ bt es1 xo es2 = Try (bt, es1, xo, es2) +let throw x = Throw x + let br x = Br x let br_if x = BrIf x let br_table xs x = BrTable (xs, x) @@ -238,6 +241,3 @@ let i32_reinterpret_f32 = Convert (I32 I32Op.ReinterpretFloat) let i64_reinterpret_f64 = Convert (I64 I64Op.ReinterpretFloat) let f32_reinterpret_i32 = Convert (F32 F32Op.ReinterpretInt) let f64_reinterpret_i64 = Convert (F64 F64Op.ReinterpretInt) - -let try_ bt es1 es2 = Try (bt, es1, es2) -let throw = Throw diff --git a/interpreter/syntax/types.ml b/interpreter/syntax/types.ml index 23d139c5a..0510553e0 100644 --- a/interpreter/syntax/types.ml +++ b/interpreter/syntax/types.ml @@ -19,14 +19,17 @@ and def_type = FuncDefType of func_type type 'a limits = {min : 'a; max : 'a option} type mutability = Immutable | Mutable +type resumability = Terminal | Resumable type table_type = TableType of Int32.t limits * ref_type type memory_type = MemoryType of Int32.t limits type global_type = GlobalType of value_type * mutability +type event_type = EventType of func_type * resumability (* TODO: use index *) type extern_type = | ExternFuncType of func_type | ExternTableType of table_type | ExternMemoryType of memory_type | ExternGlobalType of global_type + | ExternEventType of event_type type export_type = ExportType of extern_type * name type import_type = ImportType of extern_type * name * name @@ -91,6 +94,8 @@ let memories = Lib.List.map_filter (function ExternMemoryType t -> Some t | _ -> None) let globals = Lib.List.map_filter (function ExternGlobalType t -> Some t | _ -> None) +let events = + Lib.List.map_filter (function ExternEventType t -> Some t | _ -> None) (* Allocation *) @@ -140,11 +145,15 @@ let sem_global_type c (GlobalType (t, mut)) = let sem_func_type c (FuncType (ins, out)) = FuncType (sem_stack_type c ins, sem_stack_type c out) +let sem_event_type c (EventType (ft, res)) = + EventType (sem_func_type c ft, res) + let sem_extern_type c = function | ExternFuncType ft -> ExternFuncType (sem_func_type c ft) | ExternTableType tt -> ExternTableType (sem_table_type c tt) | ExternMemoryType mt -> ExternMemoryType (sem_memory_type c mt) | ExternGlobalType gt -> ExternGlobalType (sem_global_type c gt) + | ExternEventType et -> ExternEventType (sem_event_type c et) let sem_def_type c = function @@ -246,12 +255,16 @@ let string_of_global_type = function | GlobalType (t, Immutable) -> string_of_value_type t | GlobalType (t, Mutable) -> "(mut " ^ string_of_value_type t ^ ")" +let string_of_event_type = function + | EventType (ft, Terminal) -> "exception " ^ string_of_func_type ft + | EventType (ft, Resumable) -> string_of_func_type ft + let string_of_extern_type = function | ExternFuncType ft -> "func " ^ string_of_func_type ft | ExternTableType tt -> "table " ^ string_of_table_type tt | ExternMemoryType mt -> "memory " ^ string_of_memory_type mt | ExternGlobalType gt -> "global " ^ string_of_global_type gt - + | ExternEventType et -> "event " ^ string_of_event_type et let string_of_export_type (ExportType (et, name)) = "\"" ^ string_of_name name ^ "\" : " ^ string_of_extern_type et diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index b0849000e..fb0f53ade 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -69,6 +69,10 @@ let def_type dt = match dt with | FuncDefType ft -> func_type ft +let resumability = function + | Terminal -> " exception" + | Resumable -> "" + let limits nat {min; max} = String.concat " " (nat min :: opt nat max) @@ -245,6 +249,12 @@ let rec instr e = | Let (bt, locals, es) -> "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 + "try", block_type bt @ + [Node ("do", list instr es1); Node (catch, list instr es2)] + | Throw x -> "throw " ^ var x, [] | Br x -> "br " ^ var x, [] | BrIf x -> "br_if " ^ var x, [] | BrTable (xs, x) -> @@ -288,10 +298,6 @@ let rec instr e = | Unary op -> unop op, [] | Binary op -> binop op, [] | Convert op -> cvtop op, [] - | Try (bt, es1, es2) -> - "try", block_type bt @ - [Node ("do", list instr es1); Node ("catch", list instr es2)] - | Throw -> "throw", [] in Node (head, inner) let const head c = @@ -331,6 +337,12 @@ let memory off i mem = let {mtype = MemoryType lim} = mem.it in Node ("memory $" ^ nat (off + i) ^ " " ^ limits nat32 lim, []) +let event off i evt = + let {evtype = EventType (FuncType (ins, out), res)} = evt.it in + Node ("event $" ^ nat (off + i) ^ resumability res, + decls "param" ins @ decls "result" out + ) + let is_elem_kind = function | (NonNullable, FuncHeapType) -> true | _ -> false @@ -377,7 +389,7 @@ let data i seg = let type_ i ty = Node ("type $" ^ nat i, [def_type ty.it]) -let import_desc fx tx mx gx d = +let import_desc fx tx mx ex gx d = match d.it with | FuncImport x -> incr fx; Node ("func $" ^ nat (!fx - 1), [Node ("type", [atom var x])]) @@ -385,13 +397,15 @@ let import_desc fx tx mx gx d = incr tx; table 0 (!tx - 1) ({ttype = t} @@ d.at) | MemoryImport t -> incr mx; memory 0 (!mx - 1) ({mtype = t} @@ d.at) + | EventImport t -> + incr ex; event 0 (!ex - 1) ({evtype = t} @@ d.at) | GlobalImport t -> incr gx; Node ("global $" ^ nat (!gx - 1), [global_type t]) -let import fx tx mx gx im = +let import fx tx mx ex gx im = let {module_name; item_name; idesc} = im.it in Node ("import", - [atom name module_name; atom name item_name; import_desc fx tx mx gx idesc] + [atom name module_name; atom name item_name; import_desc fx tx mx ex gx idesc] ) let export_desc d = @@ -400,6 +414,7 @@ let export_desc d = | TableExport x -> Node ("table", [atom var x]) | MemoryExport x -> Node ("memory", [atom var x]) | GlobalExport x -> Node ("global", [atom var x]) + | EventExport x -> Node ("event", [atom var x]) let export ex = let {name = n; edesc} = ex.it in @@ -420,13 +435,15 @@ let module_with_var_opt x_opt m = let fx = ref 0 in let tx = ref 0 in let mx = ref 0 in + let ex = ref 0 in let gx = ref 0 in - let imports = list (import fx tx mx gx) m.it.imports in + let imports = list (import fx tx mx ex gx) m.it.imports in Node ("module" ^ var_opt x_opt, listi type_ m.it.types @ imports @ listi (table !tx) m.it.tables @ listi (memory !mx) m.it.memories @ + listi (event !ex) m.it.events @ listi (global !gx) m.it.globals @ listi (func_with_index !fx) m.it.funcs @ list export m.it.exports @ @@ -538,8 +555,8 @@ let assertion mode ass = [Node ("assert_trap", [action mode act; Atom (string re)])] | AssertExhaustion (act, re) -> [Node ("assert_exhaustion", [action mode act; Atom (string re)])] - | AssertUncaught (act, re) -> - [Node ("assert_uncaught", [action mode act; Atom (string re)])] + | AssertException (act, re) -> + [Node ("assert_exception", [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 e0fa22a77..9d0a4e75b 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -208,6 +208,12 @@ rule token = parse | "return_call_ref" { RETURN_CALL_REF } | "func.bind" { FUNC_BIND } + | "throw" { THROW } + | "try" { TRY } + | "do" { DO } + | "catch" { CATCH } + | "catch_all" { CATCH_ALL } + | "local.get" { LOCAL_GET } | "local.set" { LOCAL_SET } | "local.tee" { LOCAL_TEE } @@ -365,6 +371,8 @@ rule token = parse | "global" { GLOBAL } | "table" { TABLE } | "memory" { MEMORY } + | "event" { EVENT } + | "exception" { EXCEPTION } | "elem" { ELEM } | "data" { DATA } | "declare" { DECLARE } @@ -386,18 +394,13 @@ rule token = parse | "assert_unlinkable" { ASSERT_UNLINKABLE } | "assert_return" { ASSERT_RETURN } | "assert_trap" { ASSERT_TRAP } + | "assert_exception" { ASSERT_EXCEPTION } | "assert_exhaustion" { ASSERT_EXHAUSTION } - | "assert_uncaught" { ASSERT_UNCAUGHT } | "nan:canonical" { NAN Script.CanonicalNan } | "nan:arithmetic" { NAN Script.ArithmeticNan } | "input" { INPUT } | "output" { OUTPUT } - | "try" { TRY } - | "catch" { CATCH } - | "do" { DO } - | "throw" { THROW } - | name as s { VAR s } | ";;"utf8_no_nl*eof { EOF } diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 5dcd290de..2d2b62993 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -86,14 +86,16 @@ type types = {space : space; mutable list : type_ list} let empty_types () = {space = empty (); list = []} type context = - { types : types; tables : space; memories : space; + { types : types; + tables : space; memories : space; events : space; funcs : space; locals : space; globals : space; datas : space; elems : space; labels : space; deferred_locals : (unit -> unit) list ref } let empty_context () = - { types = empty_types (); tables = empty (); memories = empty (); + { types = empty_types (); + tables = empty (); memories = empty (); events = empty (); funcs = empty (); locals = empty (); globals = empty (); datas = empty (); elems = empty (); labels = empty (); deferred_locals = ref [] @@ -134,6 +136,7 @@ let local (c : context) x = lookup "local" c.locals x let global (c : context) x = lookup "global" c.globals x let table (c : context) x = lookup "table" c.tables x let memory (c : context) x = lookup "memory" c.memories x +let event (c : context) x = lookup "event" c.events x let elem (c : context) x = lookup "elem segment" c.elems x let data (c : context) x = lookup "data segment" c.datas x let label (c : context) x = lookup "label " c.labels x @@ -162,6 +165,7 @@ let bind_local (c : context) x = force_locals c; bind_abs "local" c.locals x let bind_global (c : context) x = bind_abs "global" c.globals x let bind_table (c : context) x = bind_abs "table" c.tables x let bind_memory (c : context) x = bind_abs "memory" c.memories x +let bind_event (c : context) x = bind_abs "event" c.events x let bind_elem (c : context) x = bind_abs "elem segment" c.elems x let bind_data (c : context) x = bind_abs "data segment" c.datas x let bind_label (c : context) x = bind_rel "label" c.labels x @@ -178,6 +182,7 @@ let anon_locals (c : context) n at = let anon_global (c : context) at = bind "global" c.globals 1l at let anon_table (c : context) at = bind "table" c.tables 1l at let anon_memory (c : context) at = bind "memory" c.memories 1l at +let anon_event (c : context) at = bind "event" c.events 1l at let anon_elem (c : context) at = bind "elem segment" c.elems 1l at let anon_data (c : context) at = bind "data segment" c.datas 1l at let anon_label (c : context) at = bind "label" c.labels 1l at @@ -210,6 +215,7 @@ let inline_func_type_explicit (c : context) x ft at = %token NUM_TYPE FUNCREF EXTERNREF REF EXTERN NULL MUT %token UNREACHABLE NOP DROP SELECT %token BLOCK END IF THEN ELSE LOOP LET +%token THROW TRY DO CATCH CATCH_ALL %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 @@ -219,16 +225,15 @@ let inline_func_type_explicit (c : context) x ft at = %token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT %token CONST UNARY BINARY TEST COMPARE CONVERT %token REF_NULL REF_FUNC REF_EXTERN REF_IS_NULL REF_AS_NON_NULL -%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL +%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL EVENT EXCEPTION %token TABLE ELEM MEMORY DATA DECLARE OFFSET ITEM IMPORT EXPORT %token MODULE BIN QUOTE %token SCRIPT REGISTER INVOKE GET %token ASSERT_MALFORMED ASSERT_INVALID ASSERT_SOFT_INVALID ASSERT_UNLINKABLE -%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXHAUSTION ASSERT_UNCAUGHT +%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_EXHAUSTION %token NAN %token INPUT OUTPUT %token EOF -%token TRY CATCH DO THROW %token NAT %token INT @@ -314,6 +319,12 @@ func_type : { fun c -> let FuncType (ins, out) = $6 c in FuncType ($4 c :: ins, out) } +event_type : + | func_type + { fun c -> EventType ($1 c, Resumable) } + | EXCEPTION func_type + { fun c -> EventType ($2 c, Terminal) } + table_type : | limits ref_type { fun c -> TableType ($1, $2 c) } @@ -393,6 +404,7 @@ plain_instr : | UNREACHABLE { fun c -> unreachable } | NOP { fun c -> nop } | DROP { fun c -> drop } + | THROW var { fun c -> throw ($2 c event) } | BR var { fun c -> br ($2 c label) } | BR_IF var { fun c -> br_if ($2 c label) } | BR_TABLE var var_list @@ -443,7 +455,6 @@ plain_instr : | UNARY { fun c -> $1 } | BINARY { fun c -> $1 } | CONVERT { fun c -> $1 } - | THROW { fun c -> throw } select_instr : @@ -664,8 +675,8 @@ expr1 : /* Sugar */ let bt, ls, es = $3 c c' in [], let_ bt ls es } | TRY try_block { fun c -> - let bt, (es1, es2) = $2 c in - [], try_ bt es1 es2 } + let bt, (es1, xo, es2) = $2 c in + [], try_ bt es1 xo es2 } select_expr_results : | LPAR RESULT value_type_list RPAR select_expr_results @@ -730,10 +741,10 @@ try_block_result_body : let out' = snd $3 c in FuncType (ins, out' @ out), es } try_ : - | LPAR DO instr_list RPAR LPAR CATCH instr_list RPAR - { fun c -> - let es1, es2 = $3 c, $7 c in - (es1, es2) } + | 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_ALL instr_list RPAR + { fun c -> $3 c, None, $7 c } if_block : | type_use if_block_param_body @@ -867,7 +878,7 @@ func_body : {f with locals = $4 c :: f.locals} } -/* Tables, Memories & Globals */ +/* Tables, Memories, Globals, Events */ table_use : | LPAR TABLE var RPAR { fun c -> $3 c } @@ -1029,6 +1040,40 @@ global_fields : { fun c x at -> let globs, ims, exs = $2 c x at in globs, ims, $1 (GlobalExport x) c :: exs } +event : + | LPAR EVENT bind_var_opt event_fields RPAR + { let at = at () in + fun c -> let x = $3 c anon_event bind_event @@ at in + fun () -> $4 c x at } + | LPAR EXCEPTION bind_var_opt exception_fields RPAR /* Sugar */ + { let at = at () in + fun c -> let x = $3 c anon_event bind_event @@ at in + fun () -> $4 c x at } + +event_fields : + | event_type + { fun c x at -> [{evtype = $1 c} @@ at], [], [] } + | inline_import event_type /* Sugar */ + { fun c x at -> + [], + [{ module_name = fst $1; item_name = snd $1; + idesc = EventImport ($2 c) @@ at } @@ at], [] } + | inline_export event_fields /* Sugar */ + { fun c x at -> let evts, ims, exs = $2 c x at in + evts, ims, $1 (EventExport x) c :: exs } + +exception_fields : /* Sugar */ + | func_type + { fun c x at -> [{evtype = EventType ($1 c, Terminal)} @@ at], [], [] } + | inline_import func_type + { fun c x at -> + [], + [{ module_name = fst $1; item_name = snd $1; + idesc = EventImport (EventType ($2 c, Terminal)) @@ at } @@ at], [] } + | inline_export exception_fields + { fun c x at -> let evts, ims, exs = $2 c x at in + evts, ims, $1 (EventExport x) c :: exs } + /* Imports & Exports */ @@ -1049,6 +1094,12 @@ import_desc : | LPAR GLOBAL bind_var_opt global_type RPAR { fun c -> ignore ($3 c anon_global bind_global); fun () -> GlobalImport ($4 c) } + | LPAR EVENT bind_var_opt event_type RPAR + { fun c -> ignore ($3 c anon_event bind_event); + fun () -> EventImport ($4 c) } + | LPAR EXCEPTION bind_var_opt func_type RPAR /* Sugar */ + { fun c -> ignore ($3 c anon_event bind_event); + fun () -> EventImport (EventType ($4 c, Terminal)) } import : | LPAR IMPORT name name import_desc RPAR @@ -1064,6 +1115,8 @@ export_desc : | LPAR TABLE var RPAR { fun c -> TableExport ($3 c table) } | LPAR MEMORY var RPAR { fun c -> MemoryExport ($3 c memory) } | LPAR GLOBAL var RPAR { fun c -> GlobalExport ($3 c global) } + | LPAR EVENT var RPAR { fun c -> EventExport ($3 c event) } + | LPAR EXCEPTION var RPAR { fun c -> EventExport ($3 c event) } /* Sugar */ export : | LPAR EXPORT name export_desc RPAR @@ -1124,6 +1177,14 @@ module_fields1 : error (List.hd m.imports).at "import after memory definition"; { m with memories = mems @ m.memories; datas = data @ m.datas; imports = ims @ m.imports; exports = exs @ m.exports } } + | event module_fields + { fun c -> let ef = $1 c in let mff = $2 c in + fun () -> let mf = mff () in + fun () -> let evts, ims, exs = ef () in let m = mf () in + if evts <> [] && m.imports <> [] then + error (List.hd m.imports).at "import after event definition"; + { m with events = evts @ m.events; + imports = ims @ m.imports; exports = exs @ m.exports } } | func module_fields { fun c -> let ff = $1 c in let mff = $2 c in fun () -> let mf = mff () in @@ -1206,8 +1267,8 @@ assertion : { AssertUninstantiable (snd $3, $4) @@ at () } | LPAR ASSERT_RETURN action result_list RPAR { AssertReturn ($3, $4) @@ at () } | LPAR ASSERT_TRAP action STRING RPAR { AssertTrap ($3, $4) @@ at () } + | LPAR ASSERT_EXCEPTION action STRING RPAR { AssertException ($3, $4) @@ at () } | LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ at () } - | LPAR ASSERT_UNCAUGHT action STRING RPAR { AssertUncaught ($3, $4) @@ at () } cmd : | action { Action $1 @@ at () } diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index d804ae3fa..87b77e2c7 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -22,6 +22,7 @@ type context = tables : table_type list; memories : memory_type list; globals : global_type list; + events : event_type list; elems : ref_type list; datas : unit list; locals : value_type list; @@ -32,7 +33,7 @@ type context = let empty_context = { types = []; funcs = []; tables = []; memories = []; - globals = []; elems = []; datas = []; + globals = []; events = []; elems = []; datas = []; locals = []; results = []; labels = []; refs = Free.empty } @@ -46,6 +47,7 @@ let func_var (c : context) x = lookup "function" c.funcs x let table (c : context) x = lookup "table" c.tables x let memory (c : context) x = lookup "memory" c.memories x let global (c : context) x = lookup "global" c.globals x +let event (c : context) x = lookup "event" c.events x let elem (c : context) x = lookup "elem segment" c.elems x let data (c : context) x = lookup "data segment" c.datas x let local (c : context) x = lookup "local" c.locals x @@ -111,6 +113,12 @@ let check_memory_type (c : context) (mt : memory_type) at = check_limits lim 0x1_0000l at "memory size must be at most 65536 pages (4GiB)" +let check_event_type (c : context) (et : event_type) at = + let EventType (ft, res) = et in + let FuncType (_, ts2) = ft in + check_func_type c ft at; + require (res = Resumable || ts2 = []) at "exception type must not have results" + let check_global_type (c : context) (gt : global_type) at = let GlobalType (t, mut) = gt in check_value_type c t at @@ -331,6 +339,26 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = in check_block c' es ft e.at; (ts1 @ List.map Source.it locals) --> ts2 + | Try (bt, es1, xo, es2) -> + let FuncType (ts1, ts2) as ft1 = check_block_type c bt e.at in + check_block {c with labels = ts2 :: c.labels} es1 ft1 e.at; + let ts1' = + match xo with + | None -> [] + | Some x -> + let EventType (FuncType (ts1', _), res) = event c x in + require (res = Terminal) e.at "catching a non-exception event"; + ts1' + in + let ft2 = FuncType (ts1', ts2) in + check_block {c with labels = ts2 :: c.labels} es2 ft2 e.at; + ts1 --> ts2 + + | Throw x -> + let EventType (FuncType (ts1, ts2), res) = event c x in + require (res = Terminal) e.at "throwing a non-exception event"; + ts1 -->... ts2 + | Br x -> label c x -->... [] @@ -538,16 +566,6 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = let t1, t2 = type_cvtop e.at cvtop in [NumType t1] --> [NumType t2] - | Try (bt, es1, es2) -> - let FuncType (ts1, ts2) as ft1 = check_block_type c bt e.at in - check_block {c with labels = ts2 :: c.labels} es1 ft1 e.at; - let ft2 = FuncType ([], ts2) in - check_block {c with labels = ts2 :: c.labels} es2 ft2 e.at; - ts1 --> ts2 - - | Throw -> - [] -->... [] - and check_seq (c : context) (s : infer_stack_type) (es : instr list) : infer_stack_type = match es with @@ -610,7 +628,7 @@ let check_const (c : context) (const : const) (t : value_type) = check_block c const.it (FuncType ([], [t])) const.at -(* Tables, Memories, & Globals *) +(* Tables, Memories, Globals, Events *) let check_table (c : context) (tab : table) = let {ttype} = tab.it in @@ -655,6 +673,10 @@ let check_global (c : context) (glob : global) = let GlobalType (t, mut) = gtype in check_const c ginit t +let check_event (c : context) (evt : event) = + let {evtype} = evt.it in + check_event_type c evtype evt.at + (* Modules *) @@ -679,6 +701,9 @@ let check_import (im : import) (c : context) : context = | GlobalImport gt -> check_global_type c gt idesc.at; {c with globals = gt :: c.globals} + | EventImport et -> + check_event_type c et idesc.at; + {c with events = et :: c.events} module NameSet = Set.Make(struct type t = Ast.name let compare = compare end) @@ -689,6 +714,7 @@ let check_export (c : context) (set : NameSet.t) (ex : export) : NameSet.t = | TableExport x -> ignore (table c x) | MemoryExport x -> ignore (memory c x) | GlobalExport x -> ignore (global c x) + | EventExport x -> ignore (event c x) ); require (not (NameSet.mem name set)) ex.at "duplicate export name"; NameSet.add name set @@ -696,8 +722,8 @@ let check_export (c : context) (set : NameSet.t) (ex : export) : NameSet.t = let check_module (m : module_) = let - { types; imports; tables; memories; globals; funcs; start; elems; datas; - exports } = m.it + { types; imports; tables; memories; globals; events; funcs; + start; elems; datas; exports } = m.it in let c0 = List.fold_right check_import imports @@ -711,6 +737,7 @@ let check_module (m : module_) = funcs = c0.funcs @ List.map (fun f -> ignore (func_type c0 f.it.ftype); f.it.ftype.it) funcs; tables = c0.tables @ List.map (fun tab -> tab.it.ttype) tables; memories = c0.memories @ List.map (fun mem -> mem.it.mtype) memories; + events = c0.events @ List.map (fun evt -> evt.it.evtype) events; elems = List.map (fun elem -> elem.it.etype) elems; datas = List.map (fun _data -> ()) datas; } @@ -722,6 +749,7 @@ let check_module (m : module_) = List.iter (check_global c1) globals; List.iter (check_table c1) tables; List.iter (check_memory c1) memories; + List.iter (check_event c1) events; List.iter (check_elem c1) elems; List.iter (check_data c1) datas; List.iter (check_func c) funcs; diff --git a/test/core/binary.wast b/test/core/binary.wast index 7e6c3e76b..d20d25f01 100644 --- a/test/core/binary.wast +++ b/test/core/binary.wast @@ -45,7 +45,7 @@ (assert_malformed (module binary "\00asm\00\00\00\01") "unknown binary version") ;; Invalid section id. -(assert_malformed (module binary "\00asm" "\01\00\00\00" "\0d\00") "malformed section id") +(assert_malformed (module binary "\00asm" "\01\00\00\00" "\0e\00") "malformed section id") (assert_malformed (module binary "\00asm" "\01\00\00\00" "\7f\00") "malformed section id") (assert_malformed (module binary "\00asm" "\01\00\00\00" "\80\00\01\00") "malformed section id") (assert_malformed (module binary "\00asm" "\01\00\00\00" "\81\00\01\00") "malformed section id") diff --git a/test/core/catch.wast b/test/core/catch.wast new file mode 100644 index 000000000..74b269376 --- /dev/null +++ b/test/core/catch.wast @@ -0,0 +1,86 @@ +;; Test the minimal "exception handling" extension + +(module + (exception $e0) + (exception $e1 (param i32)) + + (func (export "catch-1") (result i32) + (try (result i32) + (do (i32.const -1) (throw $e0) (i32.const 0)) + (catch_all (i32.const 1)) + ) + ) + + (func (export "catch-2") (result i32) + (try (result i32) + (do + (try (result i32) + (do + (throw $e0) + (i32.const 0) + ) + (catch_all + (throw $e0) + (i32.const 1) + ) + ) + ) + (catch_all + (i32.const 2) + ) + ) + ) + + (func (export "catch-3") (result i32) + (try (result i32) + (do (throw $e1 (i32.const 66)) (i32.const 0)) + (catch_all (i32.const 1)) + ) + ) + + (func (export "catch-4") (result i32) + (try (result i32) + (do (throw $e1 (i32.const 66)) (i32.const 0)) + (catch $e1) + ) + ) + + (func (export "success-0") (result i32) + (try (result i32) + (do (i32.const 0)) + (catch_all (i32.const 1)) + ) + ) + + (func (export "success-1") (result i32) + (try (result i32) + (do + (try (result i32) + (do (throw $e0) (i32.const 0)) + (catch_all (i32.const 1)) + ) + ) + (catch_all (i32.const 2)) + ) + ) + + (func (export "uncaught-1") + (throw $e0) + ) + + (func (export "uncaught-2") (result i32) + (try (result i32) + (do (throw $e0) (i32.const 0)) + (catch $e1) + ) + ) +) + +(assert_return (invoke "catch-1") (i32.const 1)) +(assert_return (invoke "catch-2") (i32.const 2)) +(assert_return (invoke "catch-3") (i32.const 1)) +(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") diff --git a/test/core/tinyexn.wast b/test/core/tinyexn.wast deleted file mode 100644 index 278b834c4..000000000 --- a/test/core/tinyexn.wast +++ /dev/null @@ -1,49 +0,0 @@ -;; Test the minimal "exception handling" extension - -(module - (func (export "catch-1") (result i32) - (try (result i32) - (do - throw - i32.const 0) - (catch - i32.const 1))) - - (func (export "catch-2") (result i32) - (try (result i32) - (do - (try (result i32) - (do - throw - i32.const 0) - (catch - throw - i32.const 1))) - (catch - i32.const 2))) - - (func (export "success-0") (result i32) - (try (result i32) - (do i32.const 0) - (catch i32.const 1))) - - (func (export "success-1") (result i32) - (try (result i32) - (do - (try (result i32) - (do - throw - i32.const 0) - (catch - i32.const 1))) - (catch i32.const 2))) - - (func (export "uncaught") - (throw)) -) - -(assert_return (invoke "catch-1") (i32.const 1)) -(assert_return (invoke "catch-2") (i32.const 2)) -(assert_return (invoke "success-0") (i32.const 0)) -(assert_return (invoke "success-1") (i32.const 1)) -(assert_uncaught (invoke "uncaught") "uncaught exception")