diff --git a/.github/workflows/ci-interpreter.yml b/.github/workflows/ci-interpreter.yml index 91a5fc423..63afd8c81 100644 --- a/.github/workflows/ci-interpreter.yml +++ b/.github/workflows/ci-interpreter.yml @@ -33,4 +33,4 @@ jobs: - name: Run tests # TODO: reactiate node once it supports all of Wasm 3.0 # run: cd interpreter && opam exec make JS=node ci - run: cd interpreter && opam exec make ci + run: cd interpreter && opam exec make test diff --git a/document/core/appendix/embedding.rst b/document/core/appendix/embedding.rst index 4bf979dbf..2afc574e8 100644 --- a/document/core/appendix/embedding.rst +++ b/document/core/appendix/embedding.rst @@ -696,7 +696,7 @@ Matching .. math:: \begin{array}{lclll} - \F{match\_reftype}(t_1, t_2) &=& \TRUE && (\iff \vdashvaltypematch t_1 \matchesvaltype t_2) \\ + \F{match\_reftype}(t_1, t_2) &=& \TRUE && (\iff {} \vdashvaltypematch t_1 \matchesvaltype t_2) \\ \F{match\_reftype}(t_1, t_2) &=& \FALSE && (\otherwise) \\ \end{array} @@ -712,6 +712,6 @@ Matching .. math:: \begin{array}{lclll} - \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \TRUE && (\iff \vdashexterntypematch \X{et}_1 \matchesexterntype \X{et}_2) \\ + \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \TRUE && (\iff {} \vdashexterntypematch \X{et}_1 \matchesexterntype \X{et}_2) \\ \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \FALSE && (\otherwise) \\ \end{array} diff --git a/document/core/valid/instructions.rst b/document/core/valid/instructions.rst index 69ab85d05..83e74e96b 100644 --- a/document/core/valid/instructions.rst +++ b/document/core/valid/instructions.rst @@ -2112,10 +2112,15 @@ Control Instructions * There must be a sequence :math:`t^\ast` of :ref:`value types `, such that: - * The result type :math:`[t^\ast]` :ref:`matches ` :math:`C.\CLABELS[l_N]`. + * The length of the sequence :math:`t^\ast` is the same as the length of the sequence :math:`C.\CLABELS[l_N]`. - * For all :math:`l_i` in :math:`l^\ast`, - the result type :math:`[t^\ast]` :ref:`matches ` :math:`C.\CLABELS[l_i]`. + * For each :ref:`operand type ` :math:`t_j` in :math:`t^\ast` and corresponding type :math:`t'_{Nj}` in :math:`C.\CLABELS[l_N]`, :math:`t_j` :ref:`matches ` :math:`t'_{Nj}`. + + * For each :ref:`label ` :math:`l_i` in :math:`l^\ast`: + + * The length of the sequence :math:`t^\ast` is the same as the length of the sequence :math:`C.\CLABELS[l_i]`. + + * For each :ref:`operand type ` :math:`t_j` in :math:`t^\ast` and corresponding type :math:`t'_{ij}` in :math:`C.\CLABELS[l_i]`, :math:`t_j` :ref:`matches ` :math:`t'_{ij}`. * Then the instruction is valid with any :ref:`valid ` type of the form :math:`[t_1^\ast~t^\ast~\I32] \to [t_2^\ast]`. diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 5ecdd3e50..99e162d21 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -1389,7 +1389,7 @@ The internal methods of an [=Exported GC Object=] use the following implementati 1. Return keys. -
+
To create a new Exported GC Object from a WebAssembly [=object address=] |objectaddr| and a string |objectkind|, perform the following steps: 1. Assert: |objectkind| is either "array" or "struct". diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index ae08c3f2b..f9858f446 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -180,6 +180,7 @@ let heap_type s = (fun s -> VarHT (var_type s33 s)); (fun s -> match s7 s with + | -0x0b -> NoContHT | -0x0c -> NoExnHT | -0x0d -> NoFuncHT | -0x0e -> NoExternHT @@ -192,6 +193,7 @@ let heap_type s = | -0x15 -> StructHT | -0x16 -> ArrayHT | -0x17 -> ExnHT + | -0x18 -> ContHT | _ -> error s pos "malformed heap type" ) ] s @@ -199,6 +201,7 @@ let heap_type s = let ref_type s = let pos = pos s in match s7 s with + | -0x0b -> (Null, NoContHT) | -0x0c -> (Null, NoExnHT) | -0x0d -> (Null, NoFuncHT) | -0x0e -> (Null, NoExternHT) @@ -211,6 +214,7 @@ let ref_type s = | -0x15 -> (Null, StructHT) | -0x16 -> (Null, ArrayHT) | -0x17 -> (Null, ExnHT) + | -0x18 -> (Null, ContHT) | -0x1c -> (NoNull, heap_type s) | -0x1d -> (Null, heap_type s) | _ -> error s pos "malformed reference type" @@ -253,11 +257,15 @@ let func_type s = let ts2 = result_type s in FuncT (ts1, ts2) +let cont_type s = + ContT (heap_type s) + let str_type s = match s7 s with | -0x20 -> DefFuncT (func_type s) | -0x21 -> DefStructT (struct_type s) | -0x22 -> DefArrayT (array_type s) + | -0x23 -> DefContT (cont_type s) (* TODO(dhil): See comment in encode.ml *) | _ -> error s (pos s - 1) "malformed definition type" let sub_type s = @@ -293,14 +301,16 @@ let memory_type s = let lim = limits u32 s in MemoryT lim +let tag_type s = + zero s; + let et = heap_type s in + TagT et + let global_type s = let t = val_type s in let mut = mutability s in GlobalT (mut, t) -let tag_type s = - zero s; at var s - (* Instructions *) @@ -327,6 +337,11 @@ let block_type s = (fun s -> ValBlockType (Some (val_type s))); ] s +let var_pair s = + let x = at var s in + let y = at var s in + x, y + let local s = let n = u32 s in let t = at val_type s in @@ -399,7 +414,10 @@ let rec instr s = | 0x14 -> call_ref (at var s) | 0x15 -> return_call_ref (at var s) - | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b + | (0x16 | 0x17) as b -> illegal s pos b + + | 0x18 -> error s pos "misplaced DELEGATE opcode" + | 0x19 -> error s pos "misplaced CATCH_ALL opcode" | 0x1a -> drop | 0x1b -> select None @@ -607,6 +625,27 @@ let rec instr s = | 0xd5 -> br_on_null (at var s) | 0xd6 -> br_on_non_null (at var s) + | 0xe0 -> cont_new (at var s) + | 0xe1 -> + let x = at var s in + let y = at var s in + cont_bind x y + | 0xe2 -> suspend (at var s) + | 0xe3 -> + let x = at var s in + let xls = vec var_pair s in + resume x xls + | 0xe4 -> + let x = at var s in + let tag = at var s in + let xls = vec var_pair s in + resume_throw x tag xls + | 0xe5 -> + let bt = block_type s in + let es' = instr_block s in + end_ s; + barrier bt es' + | 0xfb as b -> (match u32 s with | 0x00l -> struct_new (at var s) @@ -971,11 +1010,11 @@ let rec instr s = and instr_block s = List.rev (instr_block' s []) and instr_block' s es = match peek s with - | None | Some (0x05 | 0x0b) -> es + | None | Some (0x05 | 0x07 | 0x0b | 0x19) -> es | _ -> let pos = pos s in let e' = instr s in - instr_block' s ((e' @@ region s pos pos) :: es) + instr_block' s (Source.(e' @@ region s pos pos) :: es) and catch s = match byte s with @@ -1045,7 +1084,7 @@ let import_desc s = | 0x01 -> TableImport (table_type s) | 0x02 -> MemoryImport (memory_type s) | 0x03 -> GlobalImport (global_type s) - | 0x04 -> TagImport (tag_type s) + | 0x04 -> TagImport (at var s) | _ -> error s (pos s - 1) "malformed import kind" let import s = @@ -1098,12 +1137,13 @@ let memory_section s = (* Tag section *) let tag s = - let tgtype = tag_type s in - {tgtype} + let tagtype = tag_type s in + {tagtype} let tag_section s = section `TagSection (vec (at tag)) [] s + (* Global section *) let global s = @@ -1329,7 +1369,6 @@ let module_ s = in { types; tables; memories; globals; tags; funcs; imports; exports; elems; datas; start } - let decode name bs = at module_ (stream name bs) let all_custom tag s = diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index a370c113a..04492bf1a 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -98,8 +98,6 @@ struct open Types open Source - let var x = u32 x.it - let mutability = function | Cons -> byte 0 | Var -> byte 1 @@ -130,6 +128,8 @@ struct | NoExnHT -> s7 (-0x0c) | ExternHT -> s7 (-0x11) | NoExternHT -> s7 (-0x0e) + | ContHT -> s7 (-0x18) + | NoContHT -> s7 (-0x0b) | VarHT x -> var_type s33 x | DefHT _ | BotHT -> assert false @@ -150,6 +150,8 @@ struct | (Null, NoExnHT) -> s7 (-0x0c) | (Null, ExternHT) -> s7 (-0x11) | (Null, NoExternHT) -> s7 (-0x0e) + | (Null, ContHT) -> s7 (-0x18) + | (Null, NoContHT) -> s7 (-0x0b) | (Null, t) -> s7 (-0x1d); heap_type t | (NoNull, t) -> s7 (-0x1c); heap_type t @@ -180,10 +182,17 @@ struct let func_type = function | FuncT (ts1, ts2) -> vec val_type ts1; vec val_type ts2 + let cont_type = function + | ContT ht -> heap_type ht + let str_type = function | DefStructT st -> s7 (-0x21); struct_type st | DefArrayT at -> s7 (-0x22); array_type at | DefFuncT ft -> s7 (-0x20); func_type ft + | DefContT ct -> s7 (-0x23); cont_type ct + (* TODO(dhil): This might need to change again in the future as a + different proposal might claim this opcode! GC proposal claimed + the previous opcode we were using. *) let sub_type = function | SubT (Final, [], st) -> str_type st @@ -206,9 +215,8 @@ struct let global_type = function | GlobalT (mut, t) -> val_type t; mutability mut - let tag_type x = - u32 0x00l; var x - + let tag_type = function + | TagT ht -> byte 0x00; heap_type ht (* Expressions *) @@ -222,6 +230,7 @@ struct let end_ () = op 0x0b let var x = u32 x.it + let var_pair (x, y) = var x; var y let memop x {align; offset; _} = let has_var = x.it <> 0l in @@ -278,6 +287,13 @@ struct | ReturnCallRef x -> op 0x15; var x | ReturnCallIndirect (x, y) -> op 0x13; var y; var x + | ContNew x -> op 0xe0; var x + | ContBind (x, y) -> op 0xe1; var x; var y + | Suspend x -> op 0xe2; var x + | Resume (x, xls) -> op 0xe3; var x; vec var_pair xls + | ResumeThrow (x, y, xls) -> op 0xe4; var x; var y; vec var_pair xls + | Barrier (bt, es) -> op 0xe5; block_type bt; list instr es; end_ () + | Throw x -> op 0x08; var x | ThrowRef -> op 0x0a @@ -925,7 +941,7 @@ struct | TableImport t -> byte 0x01; table_type t | MemoryImport t -> byte 0x02; memory_type t | GlobalImport t -> byte 0x03; global_type t - | TagImport t -> byte 0x04; tag_type t + | TagImport t -> byte 0x04; var t let import im = let {module_name; item_name; idesc} = im.it in @@ -966,14 +982,6 @@ struct section 5 (vec memory) mems (mems <> []) - (* Tag section *) - - let tag (t : tag) = byte 0x00; var t.it.tgtype - - let tag_section ts = - section 13 (vec tag) ts (ts <> []) - - (* Global section *) let global g = @@ -983,6 +991,12 @@ struct let global_section gs = section 6 (vec global) gs (gs <> []) + (* Tag section *) + let tag tag = + tag_type tag.it.tagtype + + let tag_section ts = + section 13 (vec tag) ts (ts <> []) (* Export section *) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index f68e20703..c2b36563c 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -9,16 +9,18 @@ open Instance (* Errors *) module Link = Error.Make () -module Exception = Error.Make () module Trap = Error.Make () -module Crash = Error.Make () +module Exception = Error.Make () +module Suspension = Error.Make () module Exhaustion = Error.Make () +module Crash = Error.Make () exception Link = Link.Error -exception Exception = Exception.Error exception Trap = Trap.Error -exception Crash = Crash.Error (* failure that cannot happen in valid code *) +exception Exception = Exception.Error +exception Suspension = Suspension.Error exception Exhaustion = Exhaustion.Error +exception Crash = Crash.Error (* failure that cannot happen in valid code *) let table_error at = function | Table.Bounds -> "out of bounds table access" @@ -45,7 +47,7 @@ let numeric_error at = function | exn -> raise exn -(* Administrative Expressions & Configurations *) +(* Administrative Expressions & Continuations *) type 'a stack = 'a list @@ -70,6 +72,39 @@ and admin_instr' = | Label of int * instr list * code | Frame of int * frame * code | Handler of int * catch list * code + | Handle of (tag_inst * idx) list option * code + | Suspending of tag_inst * value stack * ctxt + +and ctxt = code -> code + +type cont = int32 * ctxt (* TODO: represent type properly *) +type ref_ += ContRef of cont option ref + +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | ContRef _ -> BotHT (* 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 = + match e.it with + | Returning _ | ReturningInvoke _ | Breaking _ + | Throwing _ | Trapping _ | Suspending _ -> true + | _ -> false + + +let compose (vs1, es1) (vs2, es2) = vs1 @ vs2, es1 @ es2 + + +(* Configurations *) type config = { @@ -82,20 +117,12 @@ let frame inst locals = {inst; locals} let config inst vs es = {frame = frame inst []; code = vs, es; budget = !Flags.budget} -let plain e = Plain e.it @@ e.at - let admin_instr_of_value (v : value) at : admin_instr' = match v with | Num n -> Plain (Const (n @@ at)) | Vec v -> Plain (VecConst (v @@ at)) | Ref r -> Refer r -let is_jumping e = - match e.it with - | Returning _ | ReturningInvoke _ | Breaking _ - | Throwing _ | Trapping _ -> 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) @@ -104,8 +131,8 @@ let type_ (inst : module_inst) x = lookup "type" inst.types x 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 tag (inst : module_inst) x = lookup "tag" inst.tags x let global (inst : module_inst) x = lookup "global" inst.globals x +let tag (inst : module_inst) x = lookup "tag" inst.tags 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 @@ -114,6 +141,7 @@ let str_type (inst : module_inst) x = expand_def_type (type_ inst x) let func_type (inst : module_inst) x = as_func_str_type (str_type inst x) let struct_type (inst : module_inst) x = as_struct_str_type (str_type inst x) let array_type (inst : module_inst) x = as_array_str_type (str_type inst x) +let cont_type (inst : module_inst) x = as_cont_str_type (str_type inst x) let subst_of (inst : module_inst) = function | StatX x when x < Lib.List32.length inst.types -> @@ -145,6 +173,24 @@ let drop n (vs : 'a stack) at = let split n (vs : 'a stack) at = take n vs at, drop n vs at +let i32_split n (vs : 'a stack) at = + try + Lib.List32.take n vs, Lib.List32.drop n vs + with + Failure _ -> Crash.error at "stack underflow" + +let str_type_of_heap_type (inst : module_inst) ht : str_type = + match ht with + | VarHT (StatX x) -> str_type inst (x @@ Source.no_region) + | DefHT dt -> expand_def_type dt + | _ -> assert false + +let func_type_of_cont_type (inst : module_inst) (ContT ht) : func_type = + as_func_str_type (str_type_of_heap_type inst ht) + +let func_type_of_tag_type (inst : module_inst) (TagT ht) : func_type = + as_func_str_type (str_type_of_heap_type inst ht) + (* Evaluation *) @@ -185,7 +231,7 @@ let rec step (c : config) : config = match e.it, vs with | Plain e', vs -> (match e', vs with - | Unreachable, vs -> + | Unreachable, vs -> vs, [Trapping "unreachable executed" @@ e.at] | Nop, vs -> @@ -272,12 +318,6 @@ let rec step (c : config) : config = string_of_def_type (type_ c.frame.inst y) ^ " but got " ^ string_of_def_type (Func.type_of f)) @@ e.at] - | ReturnCall x, vs -> - (match (step {c with code = (vs, [Plain (Call x) @@ e.at])}).code with - | vs', [{it = Invoke a; at}] -> vs', [ReturningInvoke (vs', a) @@ at] - | _ -> assert false - ) - | ReturnCallRef _x, Ref (NullRef _) :: vs -> vs, [Trapping "null function reference" @@ e.at] @@ -288,6 +328,79 @@ let rec step (c : config) : config = | _ -> assert false ) + | ContNew x, Ref (NullRef _) :: vs -> + vs, [Trapping "null function reference" @@ e.at] + + | ContNew x, Ref (FuncRef f) :: vs -> + let FuncT (ts, _) = as_func_str_type (expand_def_type (Func.type_of f)) in + let ctxt code = compose code ([], [Invoke f @@ e.at]) in + Ref (ContRef (ref (Some (Lib.List32.length ts, ctxt)))) :: vs, [] + + | ContBind (x, y), Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | ContBind (x, y), Ref (ContRef {contents = None}) :: vs -> + vs, [Trapping "continuation already consumed" @@ e.at] + + | ContBind (x, y), Ref (ContRef ({contents = Some (n, ctxt)} as cont)) :: vs -> + let ct = cont_type c.frame.inst y in + let ct = subst_cont_type (subst_of c.frame.inst) ct in + let FuncT (ts', _) = func_type_of_cont_type c.frame.inst ct in + let args, vs' = + try i32_split (I32.sub n (Lib.List32.length ts')) vs e.at + with Failure _ -> Crash.error e.at "type mismatch at continuation bind" + in + cont := None; + let ctxt' code = ctxt (compose code (args, [])) in + Ref (ContRef (ref (Some (I32.sub n (Lib.List32.length args), ctxt')))) :: vs', [] + + | Suspend x, vs -> + let tagt = tag c.frame.inst x in + let FuncT (ts, _) = func_type_of_tag_type c.frame.inst (Tag.type_of tagt) in + let args, vs' = i32_split (Lib.List32.length ts) vs e.at in + vs', [Suspending (tagt, args, fun code -> code) @@ e.at] + + | Resume (x, xls), Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | Resume (x, xls), Ref (ContRef {contents = None}) :: vs -> + vs, [Trapping "continuation already consumed" @@ e.at] + + | Resume (x, xls), Ref (ContRef ({contents = Some (n, ctxt)} as cont)) :: vs -> + let hs = List.map (fun (x, l) -> tag c.frame.inst x, l) xls in + let args, vs' = i32_split n vs e.at in + cont := None; + vs', [Handle (Some hs, ctxt (args, [])) @@ e.at] + + | ResumeThrow (x, y, xls), Ref (NullRef _) :: vs -> + vs, [Trapping "null continuation reference" @@ e.at] + + | ResumeThrow (x, y, xls), Ref (ContRef {contents = None}) :: vs -> + vs, [Trapping "continuation already consumed" @@ e.at] + + | ResumeThrow (x, y, xls), Ref (ContRef ({contents = Some (n, ctxt)} as cont)) :: vs -> + let tagt = tag c.frame.inst y in + let FuncT (ts, _) = func_type_of_tag_type c.frame.inst (Tag.type_of tagt) in + let hs = List.map (fun (x, l) -> tag c.frame.inst x, l) xls in + let args, vs' = split (List.length ts) vs e.at in + cont := None; + vs', [Handle (Some hs, ctxt (args, [Plain (Throw x) @@ e.at])) @@ e.at] + + | Barrier (bt, es'), vs -> + let InstrT (ts1, _, _xs) = block_type c.frame.inst bt e.at in + let args, vs' = i32_split (Lib.List32.length ts1) vs e.at in + vs', [ + Handle (None, + (args, [Plain (Block (bt, es')) @@ e.at]) + ) @@ e.at + ] + + | ReturnCall x, vs -> + (match (step {c with code = (vs, [Plain (Call x) @@ e.at])}).code with + | vs', [{it = Invoke a; at}] -> vs', [ReturningInvoke (vs', a) @@ at] + | _ -> assert false + ) + | ReturnCallIndirect (x, y), vs -> (match (step {c with code = (vs, [Plain (CallIndirect (x, y)) @@ e.at])}).code @@ -299,8 +412,7 @@ let rec step (c : config) : config = | Throw x, vs -> let t = tag c.frame.inst x in - let TagT dt = Tag.type_of t in - let FuncT (ts, _) = as_func_str_type (expand_def_type dt) in + let FuncT (ts, _) = func_type_of_tag_type c.frame.inst (Tag.type_of t) in let n = List.length ts in let args, vs' = split n vs e.at in vs', [Throwing (t, args) @@ e.at] @@ -653,7 +765,7 @@ let rec step (c : config) : config = let args, vs'' = match initop with | Explicit -> - let args, vs'' = split (List.length fts) vs' e.at in + let args, vs'' = i32_split (Lib.List32.length fts) vs' e.at in List.rev args, vs'' | Implicit -> let ts = List.map unpacked_field_type fts in @@ -702,7 +814,7 @@ let rec step (c : config) : config = in Ref (Aggr.ArrayRef array) :: vs'', [] | ArrayNewFixed (x, n), vs' -> - let args, vs'' = split (I32.to_int_u n) vs' e.at in + let args, vs'' = i32_split n vs' e.at in let array = try Aggr.alloc_array (type_ c.frame.inst x) (List.rev args) with Failure _ -> Crash.error e.at "type mismatch packing value" @@ -1014,6 +1126,13 @@ let rec step (c : config) : config = | Label (n, es0, (vs', [])), vs -> vs' @ vs, [] + | Label (n, es0, (vs', {it = Suspending (tagt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Label (n, es0, compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (tagt, vs1, ctxt') @@ at] + + | Label (n, es0, (vs', {it = ReturningInvoke (vs0, f); at} :: es')), vs -> + vs, [ReturningInvoke (vs0, f) @@ at] + | Label (n, es0, (vs', {it = Breaking (0l, vs0); at} :: es')), vs -> take n vs0 e.at @ vs, List.map plain es0 @@ -1030,6 +1149,16 @@ 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 = Throwing (a, vs0); at} :: es')), vs -> + vs, [Throwing (a, vs0) @@ at] + + | Frame (n, frame', (vs', {it = Suspending (tagt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Frame (n, frame', compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (tagt, vs1, ctxt') @@ at] + | Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs -> take n vs0 e.at @ vs, [] @@ -1083,7 +1212,7 @@ let rec step (c : config) : config = let n1, n2 = List.length ts1, List.length ts2 in let args, vs' = split n1 vs e.at in (match f with - | Func.AstFunc (_, inst', func) -> + | Func.AstFunc (_, inst', func) -> let {locals; body; _} = func.it in let m = Lib.Promise.value inst' in let s = subst_of m in @@ -1097,6 +1226,32 @@ let rec step (c : config) : config = (try List.rev (f (List.rev args)) @ vs', [] with Crash (_, msg) -> Crash.error e.at msg) ) + + | Handle (hso, (vs', [])), vs -> + vs' @ vs, [] + + | Handle (None, (vs', {it = Suspending _; at} :: es')), vs -> + vs, [Trapping "barrier hit by suspension" @@ at] + + | Handle (Some hs, (vs', {it = Suspending (tagt, vs1, ctxt); at} :: es')), vs + when List.mem_assq tagt hs -> + let FuncT (_, ts) = func_type_of_tag_type c.frame.inst (Tag.type_of tagt) in + let ctxt' code = compose (ctxt code) (vs', es') in + [Ref (ContRef (ref (Some (Lib.List32.length ts, ctxt'))))] @ vs1 @ vs, + [Plain (Br (List.assq tagt hs)) @@ e.at] + + | Handle (hso, (vs', {it = Suspending (tagt, vs1, ctxt); at} :: es')), vs -> + let ctxt' code = [], [Handle (hso, compose (ctxt code) (vs', es')) @@ e.at] in + vs, [Suspending (tagt, vs1, ctxt') @@ at] + + | Handle (hso, (vs', e' :: es')), vs when is_jumping e' -> + vs, [e'] + + | Handle (hso, code'), vs -> + let c' = step {c with code = code'} in + vs, [Handle (hso, c'.code) @@ e.at] + + | Suspending (_, _, _), _ -> assert false in {c with code = vs', es' @ List.tl es} @@ -1105,8 +1260,15 @@ let rec eval (c : config) : value stack = | vs, [] -> vs - | vs, {it = Trapping msg; at} :: _ -> - Trap.error at msg + | 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 _ -> Suspension.error e.at "unhandled tag" + | Returning _ | ReturningInvoke _ -> Crash.error e.at "undefined frame" + | Breaking _ -> Crash.error e.at "undefined label" + | _ -> assert false + ) | vs, {it = Throwing (a, args); at} :: _ -> let msg = "uncaught exception with args (" ^ string_of_values args ^ ")" in @@ -1155,7 +1317,7 @@ let init_import (inst : module_inst) (ex : extern) (im : import) : module_inst = | TableImport tt -> ExternTableT tt | MemoryImport mt -> ExternMemoryT mt | GlobalImport gt -> ExternGlobalT gt - | TagImport x -> ExternTagT (TagT (type_ inst x)) + | TagImport x -> ExternTagT (TagT (VarHT (StatX x.it))) in let et = subst_extern_type (subst_of inst) it in let et' = extern_type_of inst.types ex in @@ -1176,10 +1338,6 @@ let init_func (inst : module_inst) (f : func) : module_inst = let func = Func.alloc (type_ inst f.it.ftype) (Lib.Promise.make ()) f in {inst with funcs = inst.funcs @ [func]} -let init_tag (inst : module_inst) (t : tag) : module_inst = - let tag = Tag.alloc (TagT (type_ inst t.it.tgtype)) in - {inst with tags = inst.tags @ [tag]} - let init_global (inst : module_inst) (glob : global) : module_inst = let {gtype; ginit} = glob.it in let gt = subst_global_type (subst_of inst) gtype in @@ -1209,6 +1367,11 @@ let init_elem (inst : module_inst) (seg : elem_segment) : module_inst = let elem = Elem.alloc (List.map (fun c -> as_ref (eval_const inst c)) einit) in {inst with elems = inst.elems @ [elem]} +let init_tag (inst : module_inst) (tag : tag) : module_inst = + let {tagtype} = tag.it in + let tag = Tag.alloc (subst_tag_type (subst_of inst) tagtype) in + {inst with tags = inst.tags @ [tag]} + let init_data (inst : module_inst) (seg : data_segment) : module_inst = let {dinit; _} = seg.it in let data = Data.alloc dinit in @@ -1226,7 +1389,6 @@ let init_export (inst : module_inst) (ex : export) : module_inst = in {inst with exports = inst.exports @ [(name, ext)]} - let init_func_inst (inst : module_inst) (func : func_inst) = match func with | Func.AstFunc (_, prom, _) when Lib.Promise.value_opt prom = None -> @@ -1281,6 +1443,7 @@ let init (m : module_) (exts : extern list) : module_inst = |> init_list2 init_import exts m.it.imports |> init_list init_func m.it.funcs |> init_list init_global m.it.globals + |> init_list init_tag m.it.tags |> init_list init_table m.it.tables |> init_list init_memory m.it.memories |> init_list init_tag m.it.tags diff --git a/interpreter/exec/eval.mli b/interpreter/exec/eval.mli index 196f8997e..089aaeca4 100644 --- a/interpreter/exec/eval.mli +++ b/interpreter/exec/eval.mli @@ -4,8 +4,9 @@ open Instance exception Link of Source.region * string exception Trap of Source.region * string exception Exception of Source.region * string -exception Crash of Source.region * string +exception Suspension of Source.region * string exception Exhaustion 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/runtime/tag.ml b/interpreter/runtime/tag.ml index 3e0b9e499..cd6c93079 100644 --- a/interpreter/runtime/tag.ml +++ b/interpreter/runtime/tag.ml @@ -4,7 +4,7 @@ type tag = {ty : tag_type} type t = tag let alloc ty = - {ty} + {ty} -let type_of tg = - tg.ty +let type_of tag = + tag.ty diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index bd6a19e86..9c28daa21 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -131,6 +131,14 @@ function assert_exception(action) { throw new Error("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 } @@ -593,12 +601,14 @@ let of_assertion mods ass = | AssertReturn (act, ress) -> of_assertion' mods act "assert_return" (List.map of_result ress) (Some (assert_return ress)) + | AssertException act -> + of_assertion' mods act "assert_exception" [] None | AssertTrap (act, _) -> of_assertion' mods act "assert_trap" [] None + | AssertSuspension (act, _) -> + of_assertion' mods act "assert_suspension" [] None | AssertExhaustion (act, _) -> of_assertion' mods act "assert_exhaustion" [] None - | AssertException act -> - of_assertion' mods act "assert_exception" [] None let of_command mods cmd = "\n// " ^ Filename.basename cmd.at.left.file ^ diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index fe4bcec15..4d8a5f44a 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -493,6 +493,13 @@ let run_assertion ass = | _ -> Assert.error ass.at "expected runtime error" ) + | 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 e7c07144d..510bd852c 100644 --- a/interpreter/script/script.ml +++ b/interpreter/script/script.ml @@ -45,8 +45,9 @@ and assertion' = | AssertUnlinkable of definition * string | AssertUninstantiable of definition * string | AssertReturn of action * result list - | AssertException of action | AssertTrap of action * string + | AssertException of action + | AssertSuspension of action * string | AssertExhaustion of action * string type command = command' Source.phrase diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index 5d5ac6dc6..310ed49f5 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -163,6 +163,12 @@ and instr' = | ReturnCall of idx (* tail-call function *) | ReturnCallRef of idx (* tail call through reference *) | ReturnCallIndirect of idx * idx (* tail-call function through table *) + | ContNew of idx (* create continuation *) + | ContBind of idx * idx (* bind continuation arguments *) + | Suspend of idx (* suspend continuation *) + | Resume of idx * (idx * idx) list (* resume continuation *) + | ResumeThrow of idx * idx * (idx * idx) list (* abort continuation *) + | Barrier of block_type * instr list (* guard against suspension *) | Throw of idx (* throw exception *) | ThrowRef (* rethrow exception *) | TryTable of block_type * catch list * instr list (* handle exceptions *) @@ -271,6 +277,15 @@ and func' = } +(* Tags *) + +type tag = tag' Source.phrase +and tag' = +{ + tagtype : tag_type; +} + + (* Tables & Memories *) type table = table' Source.phrase @@ -286,13 +301,6 @@ and memory' = mtype : memory_type; } -type tag = tag' Source.phrase -and tag' = -{ - tgtype : idx; -} - - type segment_mode = segment_mode' Source.phrase and segment_mode' = | Passive @@ -399,6 +407,9 @@ let def_types_of (m : module_) : def_type list = dts @ List.map (subst_def_type (subst_of dts)) (roll_def_types x rt) ) [] rts +let ht (m : module_) (x : idx) : heap_type = + VarHT (StatX x.it) + let import_type_of (m : module_) (im : import) : import_type = let {idesc; module_name; item_name} = im.it in let dts = def_types_of m in @@ -408,7 +419,7 @@ let import_type_of (m : module_) (im : import) : import_type = | TableImport tt -> ExternTableT tt | MemoryImport mt -> ExternMemoryT mt | GlobalImport gt -> ExternGlobalT gt - | TagImport x -> ExternTagT (TagT (Lib.List32.nth dts x.it)) + | TagImport et -> ExternTagT (TagT (ht m et)) in ImportT (subst_extern_type (subst_of dts) et, module_name, item_name) let export_type_of (m : module_) (ex : export) : export_type = @@ -432,9 +443,8 @@ let export_type_of (m : module_) (ex : export) : export_type = let gts = globals ets @ List.map (fun g -> g.it.gtype) m.it.globals in ExternGlobalT (Lib.List32.nth gts x.it) | TagExport x -> - let tts = tags ets @ List.map (fun t -> - TagT (Lib.List32.nth dts t.it.tgtype.it)) m.it.tags in - ExternTagT (Lib.List32.nth tts x.it) + let tagts = tags ets @ List.map (fun t -> t.it.tagtype) m.it.tags in + ExternTagT (Lib.List32.nth tagts x.it) in ExportT (subst_extern_type (subst_of dts) et, name) let module_type_of (m : module_) : module_type = diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index 082c9d397..1a88e69de 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -81,6 +81,7 @@ let heap_type = function | FuncHT | NoFuncHT -> empty | ExnHT | NoExnHT -> empty | ExternHT | NoExternHT -> empty + | ContHT | NoContHT -> empty | VarHT x -> var_type x | DefHT _ct -> empty (* assume closed *) | BotHT -> empty @@ -94,6 +95,9 @@ let val_type = function | RefT t -> ref_type t | BotT -> empty +(* let func_type (FuncT (ins, out)) = list val_type ins ++ list val_type out *) +let cont_type (ContT ht) = heap_type ht + let pack_type t = empty let storage_type = function @@ -110,6 +114,7 @@ let str_type = function | DefStructT st -> struct_type st | DefArrayT at -> array_type at | DefFuncT ft -> func_type ft + | DefContT ct -> cont_type ct let sub_type = function | SubT (_fin, hts, st) -> list heap_type hts ++ str_type st @@ -123,14 +128,14 @@ let def_type = function let global_type (GlobalT (_mut, t)) = val_type t let table_type (TableT (_lim, t)) = ref_type t let memory_type (MemoryT (_lim)) = empty -let tag_type (TagT dt) = def_type dt +let tag_type (TagT ht) = heap_type ht let extern_type = function | ExternFuncT dt -> def_type dt | ExternTableT tt -> table_type tt | ExternMemoryT mt -> memory_type mt | ExternGlobalT gt -> global_type gt - | ExternTagT tt -> tag_type tt + | ExternTagT et -> tag_type et let block_type = function | VarBlockType x -> types (idx x) @@ -158,7 +163,7 @@ let rec instr (e : instr) = | ArrayInitElem (x, y) -> types (idx x) ++ elems (idx y) | ExternConvert _ -> empty | Const _ | Test _ | Compare _ | Unary _ | Binary _ | Convert _ -> empty - | Block (bt, es) | Loop (bt, es) -> block_type bt ++ block es + | Block (bt, es) | Loop (bt, es) | Barrier (bt, es) -> block_type bt ++ block es | If (bt, es1, es2) -> block_type bt ++ block es1 ++ block es2 | Br x | BrIf x | BrOnNull x | BrOnNonNull x -> labels (idx x) | BrOnCast (x, t1, t2) | BrOnCastFail (x, t1, t2) -> @@ -168,7 +173,12 @@ let rec instr (e : instr) = | Call x | ReturnCall x -> funcs (idx x) | CallRef x | ReturnCallRef x -> types (idx x) | CallIndirect (x, y) | ReturnCallIndirect (x, y) -> - tables (idx x) ++ types (idx y) + tables (idx x) ++ types (idx y) + | ContNew x -> types (idx x) + | ContBind (x, y) -> types (idx x) ++ types (idx y) + | ResumeThrow (x, y, xys) -> types (idx x) ++ tags (idx y) ++ list (fun (x, y) -> tags (idx x) ++ labels (idx y)) xys + | Resume (x, xys) -> types (idx x) ++ list (fun (x, y) -> tags (idx x) ++ labels (idx y)) xys + | Suspend x -> tags (idx x) | Throw x -> tags (idx x) | ThrowRef -> empty | TryTable (bt, cs, es) -> @@ -208,7 +218,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 ++ const t.it.tinit let memory (m : memory) = memory_type m.it.mtype -let tag (t : tag) = empty +let tag (e : tag) = tag_type e.it.tagtype let segment_mode f (m : segment_mode) = match m.it with @@ -237,7 +247,7 @@ let import_desc (d : import_desc) = | TableImport tt -> table_type tt | MemoryImport mt -> memory_type mt | GlobalImport gt -> global_type gt - | TagImport x -> types (idx x) + | TagImport et -> types (idx et) let export (e : export) = export_desc e.it.edesc let import (i : import) = import_desc i.it.idesc @@ -249,6 +259,7 @@ let module_ (m : module_) = list global m.it.globals ++ list table m.it.tables ++ list memory m.it.memories ++ + list tag m.it.tags ++ list func m.it.funcs ++ opt start m.it.start ++ list elem m.it.elems ++ diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index 9a467b1cb..8bbb839a8 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -45,6 +45,12 @@ let return_call x = ReturnCall x let return_call_ref x = ReturnCallRef x let return_call_indirect x y = ReturnCallIndirect (x, y) +let cont_new x = ContNew x +let cont_bind x y = ContBind (x, y) +let suspend x = Suspend x +let resume x xys = Resume (x, xys) +let resume_throw x y xys = ResumeThrow (x, y, xys) +let barrier bt es = Barrier (bt, es) let throw x = Throw x let throw_ref = ThrowRef let try_table bt cs es = TryTable (bt, cs, es) diff --git a/interpreter/syntax/types.ml b/interpreter/syntax/types.ml index dd233c9f7..a36191788 100644 --- a/interpreter/syntax/types.ml +++ b/interpreter/syntax/types.ml @@ -19,6 +19,7 @@ type heap_type = | FuncHT | NoFuncHT | ExnHT | NoExnHT | ExternHT | NoExternHT + | ContHT | NoContHT | VarHT of var | DefHT of def_type | BotHT @@ -34,11 +35,13 @@ and field_type = FieldT of mut * storage_type and struct_type = StructT of field_type list and array_type = ArrayT of field_type and func_type = FuncT of result_type * result_type +and cont_type = ContT of heap_type and str_type = | DefStructT of struct_type | DefArrayT of array_type | DefFuncT of func_type + | DefContT of cont_type and sub_type = SubT of final * heap_type list * str_type and rec_type = RecT of sub_type list @@ -47,8 +50,8 @@ and def_type = DefT of rec_type * int32 type table_type = TableT of Int32.t limits * ref_type type memory_type = MemoryT of Int32.t limits type global_type = GlobalT of mut * val_type -type tag_type = TagT of def_type type local_type = LocalT of init * val_type +type tag_type = TagT of heap_type type extern_type = | ExternFuncT of def_type | ExternTableT of table_type @@ -110,34 +113,6 @@ let defaultable = function | BotT -> assert false -(* Projections *) - -let unpacked_storage_type = function - | ValStorageT t -> t - | PackStorageT _ -> NumT I32T - -let unpacked_field_type (FieldT (_mut, t)) = unpacked_storage_type t - - -let as_func_str_type (st : str_type) : func_type = - match st with - | DefFuncT ft -> ft - | _ -> assert false - -let as_struct_str_type (st : str_type) : struct_type = - match st with - | DefStructT st -> st - | _ -> assert false - -let as_array_str_type (st : str_type) : array_type = - match st with - | DefArrayT at -> at - | _ -> assert false - -let extern_type_of_import_type (ImportT (et, _, _)) = et -let extern_type_of_export_type (ExportT (et, _)) = et - - (* Filters *) let funcs = List.filter_map (function ExternFuncT ft -> Some ft | _ -> None) @@ -173,6 +148,8 @@ let subst_heap_type s = function | NoExnHT -> NoExnHT | ExternHT -> ExternHT | NoExternHT -> NoExternHT + | ContHT -> ContHT + | NoContHT -> NoContHT | VarHT x -> s x | DefHT dt -> DefHT dt (* assume closed *) | BotHT -> BotHT @@ -206,10 +183,14 @@ let subst_array_type s = function let subst_func_type s = function | FuncT (ts1, ts2) -> FuncT (subst_result_type s ts1, subst_result_type s ts2) +let subst_cont_type s = function + | ContT ht -> ContT (subst_heap_type s ht) + let subst_str_type s = function | DefStructT st -> DefStructT (subst_struct_type s st) | DefArrayT at -> DefArrayT (subst_array_type s at) | DefFuncT ft -> DefFuncT (subst_func_type s ft) + | DefContT ct -> DefContT (subst_cont_type s ct) let subst_sub_type s = function | SubT (fin, hts, st) -> @@ -232,15 +213,14 @@ let subst_global_type s = function | GlobalT (mut, t) -> GlobalT (mut, subst_val_type s t) let subst_tag_type s = function - | TagT dt -> TagT (subst_def_type s dt) + | TagT ht -> TagT (subst_heap_type s ht) let subst_extern_type s = function | ExternFuncT dt -> ExternFuncT (subst_def_type s dt) | ExternTableT tt -> ExternTableT (subst_table_type s tt) | ExternMemoryT mt -> ExternMemoryT (subst_memory_type s mt) | ExternGlobalT gt -> ExternGlobalT (subst_global_type s gt) - | ExternTagT tt -> ExternTagT (subst_tag_type s tt) - + | ExternTagT et -> ExternTagT (subst_tag_type s et) let subst_export_type s = function | ExportT (et, name) -> ExportT (subst_extern_type s et, name) @@ -289,6 +269,37 @@ let expand_def_type (dt : def_type) : str_type = let SubT (_, _, st) = unroll_def_type dt in st +(* Projections *) + +let unpacked_storage_type = function + | ValStorageT t -> t + | PackStorageT _ -> NumT I32T + +let unpacked_field_type (FieldT (_mut, t)) = unpacked_storage_type t + +let as_func_str_type (st : str_type) : func_type = + match st with + | DefFuncT ft -> ft + | _ -> assert false + +let as_cont_str_type (dt : str_type) : cont_type = + match dt with + | DefContT ct -> ct + | _ -> assert false + +let as_struct_str_type (st : str_type) : struct_type = + match st with + | DefStructT st -> st + | _ -> assert false + +let as_array_str_type (st : str_type) : array_type = + match st with + | DefArrayT at -> at + | _ -> assert false + +let extern_type_of_import_type (ImportT (et, _, _)) = et +let extern_type_of_export_type (ExportT (et, _)) = et + (* String conversion *) @@ -348,6 +359,8 @@ let rec string_of_heap_type = function | NoExnHT -> "noexn" | ExternHT -> "extern" | NoExternHT -> "noextern" + | ContHT -> "cont" + | NoContHT -> "nocont" | VarHT x -> string_of_var x | DefHT dt -> "(" ^ string_of_def_type dt ^ ")" | BotHT -> "something" @@ -384,10 +397,18 @@ and string_of_func_type = function | FuncT (ts1, ts2) -> string_of_result_type ts1 ^ " -> " ^ string_of_result_type ts2 +and string_of_cont_type = function + | ContT ht -> string_of_heap_type ht + and string_of_str_type = function | DefStructT st -> "struct " ^ string_of_struct_type st | DefArrayT at -> "array " ^ string_of_array_type at | DefFuncT ft -> "func " ^ string_of_func_type ft + | DefContT ct -> "cont " ^ string_of_cont_type ct + + +and string_of_tag_type = function + | TagT ht -> string_of_heap_type ht and string_of_sub_type = function | SubT (Final, [], st) -> string_of_str_type st @@ -421,9 +442,6 @@ let string_of_table_type = function let string_of_global_type = function | GlobalT (mut, t) -> string_of_mut (string_of_val_type t) mut -let string_of_tag_type = function - | TagT dt -> string_of_def_type dt - let string_of_local_type = function | LocalT (Set, t) -> string_of_val_type t | LocalT (Unset, t) -> "(unset " ^ string_of_val_type t ^ ")" @@ -433,8 +451,7 @@ let string_of_extern_type = function | ExternTableT tt -> "table " ^ string_of_table_type tt | ExternMemoryT mt -> "memory " ^ string_of_memory_type mt | ExternGlobalT gt -> "global " ^ string_of_global_type gt - | ExternTagT tt -> "tag " ^ string_of_tag_type tt - + | ExternTagT t -> "tag " ^ string_of_tag_type t let string_of_export_type = function | ExportT (et, name) -> @@ -450,4 +467,4 @@ let string_of_module_type = function String.concat "" ( List.map (fun it -> "import " ^ string_of_import_type it ^ "\n") its @ List.map (fun et -> "export " ^ string_of_export_type et ^ "\n") ets - ) + ) diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index 58f6bf63f..bb07b64a6 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -96,11 +96,15 @@ let array_type (ArrayT ft) = let func_type (FuncT (ts1, ts2)) = Node ("func", decls "param" ts1 @ decls "result" ts2) +let cont_type (ContT ct) = + Node ("cont", [atom heap_type ct]) + let str_type st = match st with | DefStructT st -> struct_type st | DefArrayT at -> array_type at | DefFuncT ft -> func_type ft + | DefContT ct -> cont_type ct let sub_type = function | SubT (Final, [], st) -> str_type st @@ -516,6 +520,16 @@ let rec instr e = | ReturnCallRef x -> "return_call_ref " ^ var x, [] | ReturnCallIndirect (x, y) -> "return_call_indirect " ^ var x, [Node ("type " ^ var y, [])] + | ContNew x -> "cont.new " ^ var x, [] + | ContBind (x, y) -> "cont.bind " ^ var x ^ " " ^ var y, [] + | Suspend x -> "suspend " ^ var x, [] + | Resume (x, xys) -> + "resume " ^ var x, + List.map (fun (x, y) -> Node ("tag " ^ var x ^ " " ^ var y, [])) xys + | ResumeThrow (x, y, xys) -> + "resume_throw " ^ var x ^ " " ^ var y, + List.map (fun (x, y) -> Node ("tag " ^ var x ^ " " ^ var y, [])) xys + | Barrier (bt, es) -> "barrier", block_type bt @ list instr es | Throw x -> "throw " ^ var x, [] | ThrowRef -> "throw_ref", [] | TryTable (bt, cs, es) -> @@ -635,6 +649,12 @@ let memory off i mem = let {mtype = MemoryT lim} = mem.it in Node ("memory $" ^ nat (off + i) ^ " " ^ limits nat32 lim, []) +let tag off i tag = + let {tagtype = TagT et} = tag.it in + Node ("tag $" ^ nat (off + i), + [Node ("type", [atom heap_type et])] + ) + let is_elem_kind = function | (NoNull, FuncHT) -> true | _ -> false @@ -676,17 +696,6 @@ let data i seg = Node ("data $" ^ nat i, segment_mode "memory" dmode @ break_bytes dinit) -(* Tags *) - -let tag_with_name name (t : tag) = - Node ("tag" ^ name, - [Node ("type " ^ var (t.it.tgtype), [])] - ) - -let tag_with_index off i t = - tag_with_name (" $" ^ nat (off + i)) t - - (* Modules *) let type_ (ns, i) ty = @@ -720,8 +729,8 @@ let export_desc d = | FuncExport x -> Node ("func", [atom var x]) | TableExport x -> Node ("table", [atom var x]) | MemoryExport x -> Node ("memory", [atom var x]) - | TagExport x -> Node ("tag", [atom var x]) | GlobalExport x -> Node ("global", [atom var x]) + | TagExport x -> Node ("tag", [atom var x]) let export ex = let {name = n; edesc} = ex.it in @@ -753,7 +762,7 @@ let module_with_var_opt x_opt m = imports @ listi (table !tx) m.it.tables @ listi (memory !mx) m.it.memories @ - listi (tag_with_index !tgx) m.it.tags @ + listi (tag !tgx) m.it.tags @ listi (global !gx) m.it.globals @ listi (func_with_index !fx) m.it.funcs @ list export m.it.exports @ @@ -878,10 +887,12 @@ let assertion mode ass = [Node ("assert_trap", [definition mode None def; Atom (string re)])] | AssertReturn (act, results) -> [Node ("assert_return", action mode act :: List.map (result mode) results)] - | AssertTrap (act, re) -> - [Node ("assert_trap", [action mode act; Atom (string re)])] | AssertException act -> [Node ("assert_exception", [action mode act])] + | AssertTrap (act, re) -> + [Node ("assert_trap", [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)])] diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 3bd457ffd..24a7ac3f1 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -173,6 +173,9 @@ rule token = parse | "noextern" -> NOEXTERN | "externref" -> EXTERNREF | "nullexternref" -> NULLEXTERNREF + | "nocont" -> NOCONT + | "contref" -> CONTREF + | "nullcontref" -> NULLCONTREF | "ref" -> REF | "null" -> NULL @@ -180,6 +183,7 @@ rule token = parse | "struct" -> STRUCT | "field" -> FIELD | "mut" -> MUT + | "cont" -> CONT | "sub" -> SUB | "final" -> FINAL | "rec" -> REC @@ -216,6 +220,15 @@ rule token = parse | "catch_all" -> CATCH_ALL | "catch_all_ref" -> CATCH_ALL_REF + + | "cont.new" -> CONT_NEW + | "cont.bind" -> CONT_BIND + | "suspend" -> SUSPEND + | "resume" -> RESUME + | "resume_throw" -> RESUME_THROW + | "barrier" -> BARRIER + + | "local.get" -> LOCAL_GET | "local.set" -> LOCAL_SET | "local.tee" -> LOCAL_TEE @@ -323,7 +336,6 @@ rule token = parse | "ref.func" -> REF_FUNC | "ref.struct" -> REF_STRUCT | "ref.array" -> REF_ARRAY - | "ref.exn" -> REF_EXN | "ref.extern" -> REF_EXTERN | "ref.host" -> REF_HOST @@ -771,6 +783,7 @@ rule token = parse | "assert_trap" -> ASSERT_TRAP | "assert_exception" -> ASSERT_EXCEPTION | "assert_exhaustion" -> ASSERT_EXHAUSTION + | "assert_suspension" -> ASSERT_SUSPENSION | "nan:canonical" -> NAN Script.CanonicalNan | "nan:arithmetic" -> NAN Script.ArithmeticNan | "input" -> INPUT diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 966eaedf5..d16970357 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -167,7 +167,6 @@ let func_type (c : context) x = | _ -> error x.at ("non-function type " ^ Int32.to_string x.it) | exception Failure _ -> error x.at ("unknown type " ^ Int32.to_string x.it) - let bind_abs category space x = if VarMap.mem x.it space.map then error x.at ("duplicate " ^ category ^ " " ^ x.it); @@ -217,9 +216,8 @@ let anon_label (c : context) loc = bind "label" c.labels 1l (at loc) let anon_fields (c : context) x n loc = bind "field" (Lib.List32.nth c.types.fields x) n (at loc) - -let inline_func_type (c : context) ft loc = - let st = SubT (Final, [], DefFuncT ft) in +let find_type_index (c : context) dt loc = + let st = SubT (Final, [], dt) in match Lib.List.index_where (function | DefT (RecT [st'], 0l) -> st = st' @@ -233,6 +231,10 @@ let inline_func_type (c : context) ft loc = define_def_type c (DefT (RecT [st], 0l)); i @@ loc +let inline_func_type (c : context) ft loc = + let dt = DefFuncT ft in + find_type_index c dt loc + let inline_func_type_explicit (c : context) x ft loc = if ft = FuncT ([], []) then (* Deferring ensures that type lookup is only triggered when @@ -245,6 +247,12 @@ let inline_func_type_explicit (c : context) x ft loc = error (at loc) "inline function type does not match explicit type"; x +let inline_tag_type (c : context) (TagT ht) at = + match ht with + | VarHT (StatX x) -> x @@ at + | DefHT dt -> find_type_index c (expand_def_type dt) at + | _ -> assert false + %} %token LPAR RPAR @@ -255,11 +263,13 @@ let inline_func_type_explicit (c : context) x ft loc = %token VEC_SHAPE %token ANYREF NULLREF EQREF I31REF STRUCTREF ARRAYREF %token FUNCREF NULLFUNCREF EXNREF NULLEXNREF EXTERNREF NULLEXTERNREF +%token NOCONT CONTREF NULLCONTREF %token ANY NONE EQ I31 REF NOFUNC EXN NOEXN EXTERN NOEXTERN NULL %token MUT FIELD STRUCT ARRAY SUB FINAL REC %token UNREACHABLE NOP DROP SELECT %token BLOCK END IF THEN ELSE LOOP -%token BR BR_IF BR_TABLE +%token CONT_NEW CONT_BIND SUSPEND RESUME RESUME_THROW BARRIER +%token BR BR_IF BR_TABLE BR_ON_NON_NULL %token Ast.instr'> BR_ON_NULL %token Types.ref_type -> Types.ref_type -> Ast.instr'> BR_ON_CAST %token CALL CALL_REF CALL_INDIRECT @@ -273,7 +283,7 @@ let inline_func_type_explicit (c : context) x ft loc = %token OFFSET_EQ_NAT ALIGN_EQ_NAT %token Ast.instr' * Value.num> CONST %token UNARY BINARY TEST COMPARE CONVERT -%token REF_NULL REF_FUNC REF_I31 REF_STRUCT REF_ARRAY REF_EXN REF_EXTERN REF_HOST +%token REF_NULL REF_FUNC REF_I31 REF_STRUCT REF_ARRAY REF_EXTERN REF_HOST %token REF_EQ REF_IS_NULL REF_AS_NON_NULL REF_TEST REF_CAST %token I31_GET %token Ast.instr'> STRUCT_NEW ARRAY_NEW ARRAY_GET @@ -290,12 +300,12 @@ let inline_func_type_explicit (c : context) x ft loc = %token VEC_SHIFT VEC_BITMASK VEC_SPLAT %token VEC_SHUFFLE %token Ast.instr'> VEC_EXTRACT VEC_REPLACE -%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL +%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL CONT %token TABLE ELEM MEMORY TAG DATA DECLARE OFFSET ITEM IMPORT EXPORT %token MODULE BIN QUOTE %token SCRIPT REGISTER INVOKE GET %token ASSERT_MALFORMED ASSERT_INVALID ASSERT_UNLINKABLE -%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_EXHAUSTION +%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_EXHAUSTION ASSERT_SUSPENSION %token NAN %token INPUT OUTPUT %token EOF @@ -336,6 +346,8 @@ heap_type : | NOEXN { fun c -> NoExnHT } | EXTERN { fun c -> ExternHT } | NOEXTERN { fun c -> NoExternHT } + | CONT { fun c -> ContHT } + | NOCONT { fun c -> NoContHT } | var { fun c -> VarHT (StatX ($1 c type_).it) } ref_type : @@ -352,6 +364,8 @@ ref_type : | NULLEXNREF { fun c -> (Null, NoExnHT) } /* Sugar */ | EXTERNREF { fun c -> (Null, ExternHT) } /* Sugar */ | NULLEXTERNREF { fun c -> (Null, NoExternHT) } /* Sugar */ + | CONTREF { fun c -> (Null, ContHT) } /* Sugar */ + | NULLCONTREF { fun c -> (Null, NoContHT) } /* Sugar */ val_type : | NUM_TYPE { fun c -> NumT $1 } @@ -366,6 +380,34 @@ global_type : | val_type { fun c -> GlobalT (Cons, $1 c) } | LPAR MUT val_type RPAR { fun c -> GlobalT (Var, $3 c) } +cont_type : + | type_use cont_type_params + { let at1 = $loc($1) in + fun c -> + match $2 c with + | FuncT ([], []) -> ContT (VarHT (StatX ($1 c).it)) + | ft -> + let x = inline_func_type_explicit c ($1 c) ft at1 in + ContT (VarHT (StatX x.it)) } + | cont_type_params + /* TODO: the inline type is broken for now */ + { let at = $sloc in fun c -> ContT (VarHT (StatX (inline_func_type c ($1 c) at).it)) } + | var /* Sugar */ + { fun c -> ContT (VarHT (StatX ($1 c type_).it)) } + +cont_type_params : + | LPAR PARAM val_type_list RPAR cont_type_params + { fun c -> let FuncT (ts1, ts2) = $5 c in + FuncT (snd $3 c @ ts1, ts2) } + | cont_type_results + { fun c -> FuncT ([], $1 c) } + +cont_type_results : + | LPAR RESULT val_type_list RPAR cont_type_results + { fun c -> snd $3 c @ $5 c } + | /* empty */ + { fun c -> [] } + storage_type : | val_type { fun c -> ValStorageT ($1 c) } | PACK_TYPE { fun c -> PackStorageT $1 } @@ -408,10 +450,17 @@ func_type_result : | LPAR RESULT val_type_list RPAR func_type_result { fun c -> snd $3 c @ $5 c } +tag_type : + | type_use + { fun c -> TagT (VarHT (StatX ($1 c).it)) } + | func_type + { let at1 = $sloc in fun c -> TagT (VarHT (StatX (inline_func_type c ($1 c) at1).it)) } + str_type : | LPAR STRUCT struct_type RPAR { fun c x -> DefStructT ($3 c x) } | LPAR ARRAY array_type RPAR { fun c x -> DefArrayT ($3 c) } | LPAR FUNC func_type RPAR { fun c x -> DefFuncT ($3 c) } + | LPAR CONT cont_type RPAR { fun c x -> DefContT ($3 c) } sub_type : | str_type { fun c x -> SubT (Final, [], $1 c x) } @@ -510,6 +559,7 @@ instr_list : | instr1 instr_list { fun c -> $1 c @ $2 c } | select_instr_instr_list { $1 } | call_instr_instr_list { $1 } + | resume_instr_instr { fun c -> let e, es = $1 c in e :: es } instr1 : | plain_instr { fun c -> [$1 c @@ $sloc] } @@ -526,12 +576,16 @@ plain_instr : { fun c -> let xs, x = Lib.List.split_last ($2 c label :: $3 c label) in br_table xs x } | BR_ON_NULL var { fun c -> $1 ($2 c label) } + | BR_ON_NON_NULL var { fun c -> br_on_non_null ($2 c label) } | BR_ON_CAST var ref_type ref_type { fun c -> $1 ($2 c label) ($3 c) ($4 c) } | RETURN { fun c -> return } | CALL var { fun c -> call ($2 c func) } | CALL_REF var { fun c -> call_ref ($2 c type_) } | RETURN_CALL var { fun c -> return_call ($2 c func) } | RETURN_CALL_REF var { fun c -> return_call_ref ($2 c type_) } + | CONT_NEW var { fun c -> cont_new ($2 c type_) } + | CONT_BIND var var { fun c -> cont_bind ($2 c type_) ($3 c type_) } + | SUSPEND var { fun c -> suspend ($2 c tag) } | THROW var { fun c -> throw ($2 c tag) } | THROW_REF { fun c -> throw_ref } | LOCAL_GET var { fun c -> local_get ($2 c local) } @@ -682,6 +736,24 @@ call_instr_results_instr_list : | instr_list { fun c -> [], $1 c } +resume_instr_instr : + | RESUME var resume_instr_handler_instr + { let loc1 = $loc($1) in + fun c -> + let x = $2 c type_ in + let hs, es = $3 c in resume x hs @@ loc1, es } + | RESUME_THROW var var resume_instr_handler_instr + { let loc1 = $loc($1) in + fun c -> + let x = $2 c type_ in + let tag = $3 c tag in + let hs, es = $4 c in resume_throw x tag hs @@ loc1, es } + +resume_instr_handler_instr : + | LPAR TAG var var RPAR resume_instr_handler_instr + { fun c -> let hs, es = $6 c in ($3 c tag, $4 c label) :: hs, es } + | instr1 + { fun c -> [], $1 c } block_instr : | BLOCK labeling_opt block END labeling_end_opt @@ -693,6 +765,8 @@ block_instr : | IF labeling_opt block ELSE labeling_end_opt instr_list END labeling_end_opt { fun c -> let c' = $2 c ($5 @ $8) in let ts, es1 = $3 c' in if_ ts es1 ($6 c') } + | BARRIER labeling_opt block END labeling_end_opt + { fun c -> let c' = $2 c $5 in let bt, es = $3 c' in barrier bt es } | TRY_TABLE labeling_opt handler_block END labeling_end_opt { fun c -> let c' = $2 c $5 in let bt, (cs, es) = $3 c c' in try_table bt cs es } @@ -782,6 +856,16 @@ expr1 : /* Sugar */ { fun c -> let x, es = $3 c in es, return_call_indirect ($2 c table) x } | RETURN_CALL_INDIRECT call_expr_type /* Sugar */ { fun c -> let x, es = $2 c in es, return_call_indirect (0l @@ $loc($1)) x } + | RESUME var resume_expr_handler + { fun c -> + let x = $2 c type_ in + let hs, es = $3 c in es, resume x hs } + | RESUME_THROW var var resume_expr_handler + { fun c -> + let x = $2 c type_ in + let tag = $3 c tag in + let hs, es = $4 c in + es, resume_throw x tag 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 @@ -789,6 +873,8 @@ expr1 : /* Sugar */ | IF labeling_opt if_block { fun c -> let c' = $2 c [] in let bt, (es, es1, es2) = $3 c c' in es, if_ bt es1 es2 } + | BARRIER labeling_opt block + { fun c -> let c' = $2 c [] in let bt, es = $3 c' in [], barrier bt es } | TRY_TABLE labeling_opt try_block { fun c -> let c' = $2 c [] in let bt, (cs, es) = $3 c c' in [], try_table bt cs es } @@ -821,6 +907,11 @@ call_expr_results : | expr_list { fun c -> [], $1 c } +resume_expr_handler : + | LPAR TAG var var RPAR resume_expr_handler + { fun c -> let hs, es = $6 c in ($3 c tag, $4 c label) :: hs, es } + | expr_list + { fun c -> [], $1 c } if_block : | type_use if_block_param_body @@ -830,7 +921,7 @@ if_block : | if_block_param_body /* Sugar */ { fun c c' -> let ft, es = $1 c c' in let bt = - match ft with + match fst ($1 c c') with | FuncT ([], []) -> ValBlockType None | FuncT ([], [t]) -> ValBlockType (Some t) | ft -> VarBlockType (inline_func_type c ft $sloc) @@ -857,7 +948,6 @@ if_ : | LPAR THEN instr_list RPAR /* Sugar */ { fun c c' -> [], $3 c', [] } - try_block : | type_use try_block_param_body { fun c c' -> @@ -993,11 +1083,11 @@ local_type : | val_type { fun c -> {ltype = $1 c} @@ $sloc } local_type_list : - | list(local_type) - { Lib.List32.length $1, fun c -> List.map (fun f -> f c) $1 } + | /* empty */ { 0l, fun c -> [] } + | local_type local_type_list { I32.add (fst $2) 1l, fun c -> $1 c :: snd $2 c } -/* Tables, Memories & Globals */ +/* Tables, Memories, Globals, Tags */ table_use : | LPAR TABLE var RPAR { fun c -> $3 c } @@ -1136,50 +1226,6 @@ memory_fields : [{dinit = $3; dmode = Active {index = x; offset} @@ loc} @@ loc], [], [] } -tag : - | LPAR TAG bind_var_opt tag_fields RPAR - { fun c -> let x = $3 c anon_tag bind_tag @@ $sloc in fun () -> $4 c x $sloc } - -tag_fields : - | type_use func_type - { fun c x at -> - let tgtype = inline_func_type_explicit c ($1 c) ($2 c) at in - [{tgtype} @@ at], [], [] } - | func_type /* Sugar */ - { fun c x at -> - let tgtype = inline_func_type c ($1 c) at in - [{tgtype} @@ at], [], [] } - | inline_import type_use tag_fields_import /* Sugar */ - { fun c x at -> - let y = inline_func_type_explicit c ($2 c) ($3 c) at in - [], - [{ module_name = fst $1; item_name = snd $1; - idesc = TagImport y @@ at } @@ at ], [] } - | inline_import tag_fields_import /* Sugar */ - { fun c x at -> - let y = inline_func_type c ($2 c) at in - [], - [{ module_name = fst $1; item_name = snd $1; - idesc = TagImport y @@ at } @@ at ], [] } - | inline_export tag_fields /* Sugar */ - { fun c x at -> - let tgs, ims, exs = $2 c x at in tgs, ims, $1 (TagExport x) c :: exs } - -tag_fields_import : /* Sugar */ - | tag_fields_import_result { $1 } - | LPAR PARAM val_type_list RPAR tag_fields_import - { fun c -> let FuncT (ts1, ts2) = $5 c in - FuncT (snd $3 c @ ts1, ts2) } - | LPAR PARAM bind_var val_type RPAR tag_fields_import /* Sugar */ - { fun c -> let FuncT (ts1, ts2) = $6 c in - FuncT ($4 c :: ts1, ts2) } - -tag_fields_import_result : /* Sugar */ - | /* empty */ { fun c -> FuncT ([], []) } - | LPAR RESULT val_type_list RPAR tag_fields_import_result - { fun c -> - let FuncT (ts1, ts2) = $5 c in FuncT (ts1, snd $3 c @ ts2) } - global : | LPAR GLOBAL bind_var_opt global_fields RPAR { fun c -> let x = $3 c anon_global bind_global @@ $sloc in @@ -1197,6 +1243,23 @@ global_fields : { fun c x loc -> let globs, ims, exs = $2 c x loc in globs, ims, $1 (GlobalExport x) c :: exs } +tag : + | LPAR TAG bind_var_opt tag_fields RPAR + { let loc = $sloc in + fun c -> let x = $3 c anon_tag bind_tag @@ loc in + fun () -> $4 c x loc } + +tag_fields : + | tag_type + { fun c x at -> [{tagtype = $1 c} @@ at], [], [] } + | inline_import tag_type /* Sugar */ + { fun c x at -> + [], + [{ module_name = fst $1; item_name = snd $1; + idesc = TagImport (inline_tag_type c ($2 c) at) @@ at } @@ at], [] } + | inline_export tag_fields /* Sugar */ + { fun c x at -> let evts, ims, exs = $2 c x at in + evts, ims, $1 (TagExport x) c :: exs } /* Imports & Exports */ @@ -1216,12 +1279,13 @@ import_desc : | LPAR GLOBAL bind_var_opt global_type RPAR { fun c -> ignore ($3 c anon_global bind_global); fun () -> GlobalImport ($4 c) } - | LPAR TAG bind_var_opt type_use RPAR - { fun c -> ignore ($3 c anon_tag bind_tag); - fun () -> TagImport ($4 c) } - | LPAR TAG bind_var_opt func_type RPAR /* Sugar */ - { fun c -> ignore ($3 c anon_tag bind_tag); - fun () -> TagImport (inline_func_type c ($4 c) $loc($4)) } + | LPAR TAG bind_var_opt tag_type RPAR + { let at4 = $loc($4) in + fun c -> ignore ($3 c anon_tag bind_tag); + fun () -> TagImport (inline_tag_type c ($4 c) at4) } + /* | LPAR TAG bind_var_opt type_use RPAR */ + /* { fun c -> ignore ($3 c anon_tag bind_tag); */ + /* fun () -> TagImport ($4 c) } */ import : | LPAR IMPORT name name import_desc RPAR @@ -1235,8 +1299,8 @@ export_desc : | LPAR FUNC var RPAR { fun c -> FuncExport ($3 c func) } | LPAR TABLE var RPAR { fun c -> TableExport ($3 c table) } | LPAR MEMORY var RPAR { fun c -> MemoryExport ($3 c memory) } - | LPAR TAG var RPAR { fun c -> TagExport ($3 c tag) } | LPAR GLOBAL var RPAR { fun c -> GlobalExport ($3 c global) } + | LPAR TAG var RPAR { fun c -> TagExport ($3 c tag) } export : | LPAR EXPORT name export_desc RPAR @@ -1391,6 +1455,7 @@ action : | LPAR GET option(module_var) name RPAR { Get ($3, $4) @@ $sloc } + assertion : | LPAR ASSERT_MALFORMED script_module STRING RPAR { AssertMalformed (snd $3, $4) @@ $sloc } @@ -1401,9 +1466,9 @@ assertion : | LPAR ASSERT_TRAP script_module STRING RPAR { AssertUninstantiable (snd $3, $4) @@ $sloc } | LPAR ASSERT_RETURN action list(result) RPAR { AssertReturn ($3, $4) @@ $sloc } - | LPAR ASSERT_EXCEPTION action RPAR - { AssertException $3 @@ $sloc } + | LPAR ASSERT_EXCEPTION action RPAR { AssertException $3 @@ $sloc } | LPAR ASSERT_TRAP action STRING RPAR { AssertTrap ($3, $4) @@ $sloc } + | LPAR ASSERT_SUSPENSION action STRING RPAR { AssertSuspension ($3, $4) @@ $sloc } | LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ $sloc } cmd : @@ -1449,7 +1514,6 @@ result : | LPAR REF_STRUCT RPAR { RefResult (RefTypePat StructHT) @@ $sloc } | LPAR REF_ARRAY RPAR { RefResult (RefTypePat ArrayHT) @@ $sloc } | LPAR REF_FUNC RPAR { RefResult (RefTypePat FuncHT) @@ $sloc } -/*| LPAR REF_EXN RPAR { RefResult (RefTypePat ExnRefType) @@ $sloc }*/ | LPAR REF_EXTERN RPAR { RefResult (RefTypePat ExternHT) @@ $sloc } | LPAR REF_NULL RPAR { RefResult NullPat @@ $sloc } | LPAR VEC_CONST VEC_SHAPE list(numpat) RPAR diff --git a/interpreter/util/lib.ml b/interpreter/util/lib.ml index a8876b3ef..1cc4e40d2 100644 --- a/interpreter/util/lib.ml +++ b/interpreter/util/lib.ml @@ -83,6 +83,11 @@ struct | n, y::ys' when n > 0 -> split' (n - 1) (y::xs) ys' | _ -> failwith "split" + let rec last_opt = function + | x::[] -> Some x + | _::xs -> last_opt xs + | [] -> None + let rec lead = function | x::[] -> [] | x::xs -> x :: lead xs @@ -111,6 +116,13 @@ struct | [] -> [] | x1::x2::xs -> f x1 x2 :: pairwise f xs | _ -> failwith "pairwise" + + let rec map_filter f = function + | [] -> [] + | x::xs -> + match f x with + | None -> map_filter f xs + | Some y -> y :: map_filter f xs end module List32 = diff --git a/interpreter/util/lib.mli b/interpreter/util/lib.mli index bc7be3132..56923cabc 100644 --- a/interpreter/util/lib.mli +++ b/interpreter/util/lib.mli @@ -19,6 +19,7 @@ sig val drop : int -> 'a list -> 'a list (* raises Failure *) val split : int -> 'a list -> 'a list * 'a list (* raises Failure *) + val last_opt : 'a list -> 'a option val lead : 'a list -> 'a list (* raises Failure *) val last : 'a list -> 'a (* raises Failure *) val split_last : 'a list -> 'a list * 'a (* raises Failure *) @@ -26,6 +27,7 @@ sig val index_of : 'a -> 'a list -> int option val index_where : ('a -> bool) -> 'a list -> int option val pairwise : ('a -> 'a -> 'b) -> 'a list -> 'b list + val map_filter : ('a -> 'b option) -> 'a list -> 'b list end module List32 : diff --git a/interpreter/valid/match.ml b/interpreter/valid/match.ml index 70f8136c9..0f21d6221 100644 --- a/interpreter/valid/match.ml +++ b/interpreter/valid/match.ml @@ -13,6 +13,7 @@ let lookup c x = Lib.List32.nth c x let abs_of_str_type _c = function | DefStructT _ | DefArrayT _ -> StructHT | DefFuncT _ -> FuncHT + | DefContT _ -> ContHT let rec top_of_str_type c st = top_of_heap_type c (abs_of_str_type c st) @@ -22,6 +23,7 @@ and top_of_heap_type c = function | FuncHT | NoFuncHT -> FuncHT | ExnHT | NoExnHT -> ExnHT | ExternHT | NoExternHT -> ExternHT + | ContHT | NoContHT -> ContHT | DefHT dt -> top_of_str_type c (expand_def_type dt) | VarHT (StatX x) -> top_of_str_type c (expand_def_type (lookup c x)) | VarHT (RecX _) | BotHT -> assert false @@ -34,6 +36,7 @@ and bot_of_heap_type c = function | FuncHT | NoFuncHT -> NoFuncHT | ExnHT | NoExnHT -> NoExnHT | ExternHT | NoExternHT -> NoExternHT + | ContHT | NoContHT -> NoContHT | DefHT dt -> bot_of_str_type c (expand_def_type dt) | VarHT (StatX x) -> bot_of_str_type c (expand_def_type (lookup c x)) | VarHT (RecX _) | BotHT -> assert false @@ -73,6 +76,7 @@ let rec match_heap_type c t1 t2 = | NoFuncHT, t -> match_heap_type c t FuncHT | NoExnHT, t -> match_heap_type c t ExnHT | NoExternHT, t -> match_heap_type c t ExternHT + | NoContHT, t -> match_heap_type c t ContHT | VarHT (StatX x1), _ -> match_heap_type c (DefHT (lookup c x1)) t2 | _, VarHT (StatX x2) -> match_heap_type c t1 (DefHT (lookup c x2)) | DefHT dt1, DefHT dt2 -> match_def_type c dt1 dt2 @@ -85,6 +89,7 @@ let rec match_heap_type c t1 t2 = | DefArrayT _, EqHT -> true | DefArrayT _, ArrayHT -> true | DefFuncT _, FuncHT -> true + | DefContT _, ContHT -> true | _ -> false ) | BotHT, _ -> true @@ -107,7 +112,6 @@ and match_result_type c ts1 ts2 = List.length ts1 = List.length ts2 && List.for_all2 (match_val_type c) ts1 ts2 - and match_pack_type _c t1 t2 = t1 = t2 @@ -134,12 +138,15 @@ and match_array_type c (ArrayT ft1) (ArrayT ft2) = and match_func_type c (FuncT (ts11, ts12)) (FuncT (ts21, ts22)) = match_result_type c ts21 ts11 && match_result_type c ts12 ts22 +and match_cont_type c (ContT ht1) (ContT ht2) = + match_heap_type c ht1 ht2 and match_str_type c dt1 dt2 = match dt1, dt2 with | DefStructT st1, DefStructT st2 -> match_struct_type c st1 st2 | DefArrayT at1, DefArrayT at2 -> match_array_type c at1 at2 | DefFuncT ft1, DefFuncT ft2 -> match_func_type c ft1 ft2 + | DefContT ct1, DefContT ct2 -> match_cont_type c ct1 ct2 | _, _ -> false and match_def_type c dt1 dt2 = @@ -155,16 +162,15 @@ let match_global_type c (GlobalT (mut1, t1)) (GlobalT (mut2, t2)) = | Cons -> true | Var -> match_val_type c t2 t1 +let match_tag_type c (TagT ht1) (TagT ht2) = + match_heap_type c ht1 ht2 + let match_table_type c (TableT (lim1, t1)) (TableT (lim2, t2)) = match_limits c lim1 lim2 && match_ref_type c t1 t2 && match_ref_type c t2 t1 let match_memory_type c (MemoryT lim1) (MemoryT lim2) = match_limits c lim1 lim2 -let match_tag_type c (TagT dt1) (TagT dt2) = - match_def_type c dt1 dt2 && match_def_type c dt2 dt1 - - let match_extern_type c et1 et2 = match et1, et2 with | ExternFuncT dt1, ExternFuncT dt2 -> match_def_type c dt1 dt2 diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index cbe1d6de5..dfc38b027 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -46,8 +46,8 @@ let type_ (c : context) x = lookup "type" c.types x let func (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 tag (c : context) x = lookup "tag" c.tags x let global (c : context) x = lookup "global" c.globals x +let tag (c : context) x = lookup "tag" c.tags 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 @@ -67,7 +67,12 @@ let init_locals (c : context) xs = let func_type (c : context) x = match expand_def_type (type_ c x) with | DefFuncT ft -> ft - | _ -> error x.at ("non-function type " ^ I32.to_string_u x.it) + | _ -> error x.at ("non-function type " ^ Int32.to_string x.it) + +let cont_type (c : context) x = + match expand_def_type (type_ c x) with + | DefContT ct -> ct + | _ -> error x.at ("non-continuation type " ^ Int32.to_string x.it) let struct_type (c : context) x = match expand_def_type (type_ c x) with @@ -87,6 +92,30 @@ let refer category (s : Free.Set.t) x = let refer_func (c : context) x = refer "function" c.refs.Free.funcs x +(* Conversions *) + +let cont_type_of_heap_type (c : context) (ht : heap_type) at : cont_type = + match ht with + | DefHT dt -> assert false + | VarHT (StatX x) -> cont_type c (x @@ at) + | _ -> assert false + +let func_type_of_heap_type (c : context) (ht : heap_type) at : func_type = + match ht with + | DefHT dt -> assert false + | VarHT (StatX x) -> func_type c (x @@ at) + | _ -> assert false + +let func_type_of_cont_type (c : context) (ContT ht) at : func_type = + func_type_of_heap_type c ht at + +let func_type_of_tag_type (c : context) (TagT ht) at : func_type = + func_type_of_heap_type c ht at + +let heap_type_of_str_type (_c : context) (st : str_type) : heap_type = + DefHT (DefT (RecT [SubT (Final, [], st)], Int32.of_int 0)) + + (* Types *) let check_limits {min; max} range at msg = @@ -108,6 +137,7 @@ let check_heap_type (c : context) (t : heap_type) at = match t with | AnyHT | NoneHT | EqHT | I31HT | StructHT | ArrayHT | FuncHT | NoFuncHT + | ContHT | NoContHT | ExnHT | NoExnHT | ExternHT | NoExternHT -> () | VarHT (StatX x) -> let _dt = type_ c (x @@ at) in () @@ -150,6 +180,12 @@ let check_func_type (c : context) (ft : func_type) at = check_result_type c ts1 at; check_result_type c ts2 at +let check_cont_type (c : context) (ct : cont_type) at = + match ct with + | ContT (VarHT (StatX x)) -> + let _dt = func_type c (x @@ at) in () + | _ -> assert false + let check_table_type (c : context) (tt : table_type) at = let TableT (lim, t) = tt in check_limits lim 0xffff_ffffl at "table size must be at most 2^32-1"; @@ -160,16 +196,20 @@ 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_tag_type (c : context) (et : tag_type) at = + match et with + | TagT ht -> check_heap_type c ht at + let check_global_type (c : context) (gt : global_type) at = let GlobalT (_mut, t) = gt in check_val_type c t at - let check_str_type (c : context) (st : str_type) at = match st with | DefStructT st -> check_struct_type c st at | DefArrayT rt -> check_array_type c rt at | DefFuncT ft -> check_func_type c ft at + | DefContT ct -> check_cont_type c ct at let check_sub_type (c : context) (sut : sub_type) at = let SubT (_fin, hts, st) = sut in @@ -383,6 +423,23 @@ let check_memop (c : context) (memop : ('t, 's) memop) ty_size get_sz at = * declarative typing rules. *) +let check_resume_table (c : context) ts2 (xys : (idx * idx) list) at = + List.iter (fun (x1, x2) -> + let FuncT (ts3, ts4) = func_type_of_tag_type c (tag c x1) x1.at in + let ts' = label c x2 in + match Lib.List.last_opt ts' with + | Some (RefT (nul', ht)) -> + let ct = cont_type_of_heap_type c ht x2.at in + let ft' = func_type_of_cont_type c ct x2.at in + require (match_func_type c.types (FuncT (ts4, ts2)) ft') x2.at + "type mismatch in continuation type"; + match_stack c (ts3 @ [RefT (nul', ht)]) ts' x2.at + | _ -> + error at + ("type mismatch: instruction requires continuation reference type" ^ + " but label has " ^ string_of_result_type ts') + ) xys + let check_block_type (c : context) (bt : block_type) at : instr_type = match bt with | ValBlockType None -> InstrT ([], [], []) @@ -431,10 +488,12 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in (ts1 @ [NumT I32T]) --> ts2, List.map (fun x -> x @@ e.at) xs | Br x -> - label c x -->... [], [] + let ts = label c x in + ts -->... [], [] | BrIf x -> - (label c x @ [NumT I32T]) --> label c x, [] + let ts = label c x in + (ts @ [NumT I32T]) --> ts, [] | BrTable (xs, x) -> let n = List.length (label c x) in @@ -445,18 +504,20 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in | BrOnNull x -> let (_nul, ht) = peek_ref 0 s e.at in - (label c x @ [RefT (Null, ht)]) --> (label c x @ [RefT (NoNull, ht)]), [] + let ts = label c x in + (ts @ [RefT (Null, ht)]) --> (ts @ [RefT (NoNull, ht)]), [] | BrOnNonNull x -> let (_nul, ht) = peek_ref 0 s e.at in let t' = RefT (NoNull, ht) in - require (label c x <> []) e.at + let ts = label c x in + require (ts <> []) e.at ("type mismatch: instruction requires type " ^ string_of_val_type t' ^ " but label has " ^ string_of_result_type (label c x)); let ts0, t1 = Lib.List.split_last (label c x) in require (match_val_type c.types t' t1) e.at ("type mismatch: instruction requires type " ^ string_of_val_type t' ^ - " but label has " ^ string_of_result_type (label c x)); + " but label has " ^ string_of_result_type ts); (ts0 @ [RefT (Null, ht)]) --> ts0, [] | BrOnCast (x, rt1, rt2) -> @@ -536,9 +597,51 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in " but callee returns " ^ string_of_result_type ts2); (ts1 @ [NumT I32T]) -->... [], [] + | ContNew x -> + let ct = cont_type c x in + let ft = func_type_of_cont_type c ct x.at in + [RefT (Null, heap_type_of_str_type c (DefFuncT ft))] --> + [RefT (NoNull, DefHT (type_ c x))], [] + + | ContBind (x, y) -> + let ct = cont_type c x in + let FuncT (ts1, ts2) = func_type_of_cont_type c ct x.at in + let ct' = cont_type c y in + let FuncT (ts1', _) as ft' = func_type_of_cont_type c ct' y.at in + require (List.length ts1 >= List.length ts1') x.at + "type mismatch in continuation arguments"; + let ts11, ts12 = Lib.List.split (List.length ts1 - List.length ts1') ts1 in + require (match_func_type c.types (FuncT (ts12, ts2)) ft') e.at + "type mismatch in continuation types"; + (ts11 @ [RefT (Null, heap_type_of_str_type c (DefContT ct))]) --> + [RefT (NoNull, heap_type_of_str_type c (DefContT ct'))], [] + + | Suspend x -> + let tag = tag c x in + let FuncT (ts1, ts2) = func_type_of_tag_type c tag x.at in + ts1 --> ts2, [] + + | Resume (x, xys) -> + let ct = cont_type c x in + let FuncT (ts1, ts2) = func_type_of_cont_type c ct x.at in + check_resume_table c ts2 xys e.at; + (ts1 @ [RefT (Null, heap_type_of_str_type c (DefContT ct))]) --> ts2, [] + + | ResumeThrow (x, y, xys) -> + let ct = cont_type c x in + let FuncT (ts1, ts2) = func_type_of_cont_type c ct x.at in + let tag = tag c y in + let FuncT (ts0, _) = func_type_of_tag_type c tag y.at in + check_resume_table c ts2 xys e.at; + (ts0 @ [RefT (Null, heap_type_of_str_type c (DefContT ct))]) --> ts2, [] + + | Barrier (bt, es) -> + let InstrT (ts1, ts2, xs) as ft = check_block_type c bt e.at in + check_block {c with labels = ts2 :: c.labels} es ft e.at; + ts1 --> ts2, List.map (fun x -> x @@ e.at) xs + | Throw x -> - let TagT dt = tag c x in - let FuncT (ts1, ts2) = as_func_str_type (expand_def_type dt) in + let FuncT (ts1, ts2) = func_type_of_tag_type c (tag c x) x.at in ts1 -->... [], [] | ThrowRef -> @@ -933,12 +1036,10 @@ and check_catch (c : context) (cc : catch) (ts : val_type list) at = let match_target = match_resulttype "label" "catch handler" in match cc.it with | Catch (x1, x2) -> - let TagT dt = tag c x1 in - let FuncT (ts1, ts2) = as_func_str_type (expand_def_type dt) in + let FuncT (ts1, ts2) = func_type_of_tag_type c (tag c x1) x1.at in match_target c ts1 (label c x2) cc.at | CatchRef (x1, x2) -> - let TagT dt = tag c x1 in - let FuncT (ts1, ts2) = as_func_str_type (expand_def_type dt) in + let FuncT (ts1, ts2) = func_type_of_tag_type c (tag c x1) x1.at in match_target c (ts1 @ [RefT (Null, ExnHT)]) (label c x2) cc.at | CatchAll x -> match_target c [] (label c x) cc.at @@ -1019,11 +1120,6 @@ let check_memory (c : context) (mem : memory) : context = check_memory_type c mtype mem.at; {c with memories = c.memories @ [mtype]} -let check_tag (c : context) (t : tag) : context = - let FuncT (_, ts2) = func_type c t.it.tgtype in - require (ts2 = []) t.it.tgtype.at "non-empty tag result type"; - {c with tags = c.tags @ [TagT (type_ c t.it.tgtype)]} - let check_elem_mode (c : context) (t : ref_type) (mode : segment_mode) = match mode.it with | Passive -> () @@ -1055,6 +1151,12 @@ let check_data (c : context) (seg : data_segment) : context = check_data_mode c dmode; {c with datas = c.datas @ [()]} +let check_tag (c : context) (tag : tag) : context = + let {tagtype} = tag.it in + check_tag_type c tagtype tag.at; + {c with tags = c.tags @ [tagtype]} + + (* Modules *) @@ -1080,8 +1182,9 @@ let check_import (c : context) (im : import) : context = check_memory_type c mt idesc.at; {c with memories = c.memories @ [mt]} | TagImport x -> - let _ = func_type c x in - {c with tags = c.tags @ [TagT (type_ c x)]} + let tag = (TagT (VarHT (StatX x.it))) in + check_tag_type c tag idesc.at; + {c with tags = c.tags @ [tag]} module NameSet = Set.Make(struct type t = Ast.name let compare = compare end) @@ -1108,6 +1211,7 @@ let check_module (m : module_) = |> check_list check_type m.it.types |> check_list check_import m.it.imports |> check_list check_func m.it.funcs + |> check_list check_tag m.it.tags |> check_list check_table m.it.tables |> check_list check_memory m.it.memories |> check_list check_global m.it.globals diff --git a/test/core/cont.wast b/test/core/cont.wast new file mode 100644 index 000000000..cdcd7ce3d --- /dev/null +++ b/test/core/cont.wast @@ -0,0 +1,640 @@ +;; Unhandled tags & guards + +(module + (tag $exn) + (tag $e1) + (tag $e2) + + (type $f1 (func)) + (type $k1 (cont $f1)) + + (func $f1 (export "unhandled-1") + (suspend $e1) + ) + + (func (export "unhandled-2") + (resume $k1 (cont.new $k1 (ref.func $f1))) + ) + + (func (export "unhandled-3") + (block $h (result (ref $k1)) + (resume $k1 (tag $e2 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (func (export "handled") + (block $h (result (ref $k1)) + (resume $k1 (tag $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (drop) + ) + + (elem declare func $f2) + (func $f2 + (throw $exn) + ) + + (func (export "uncaught-1") + (block $h (result (ref $k1)) + (resume $k1 (tag $e1 $h) (cont.new $k1 (ref.func $f2))) + (unreachable) + ) + (drop) + ) + + (func (export "uncaught-2") + (block $h (result (ref $k1)) + (resume $k1 (tag $e1 $h) (cont.new $k1 (ref.func $f1))) + (unreachable) + ) + (resume_throw $k1 $exn) + ) + + (elem declare func $f3) + (func $f3 + (barrier (call $f4)) + ) + (func $f4 + (suspend $e1) + ) + + (func (export "barrier") + (block $h (result (ref $k1)) + (resume $k1 (tag $e1 $h) (cont.new $k1 (ref.func $f3))) + (unreachable) + ) + (resume_throw $k1 $exn) + ) + + (elem declare func $r0 $r1) + (func $r0) + (func $r1 (suspend $e1) (suspend $e1)) + + (func $nl1 (param $k (ref $k1)) + (resume $k1 (local.get $k)) + (resume $k1 (local.get $k)) + ) + (func $nl2 (param $k (ref $k1)) + (block $h (result (ref $k1)) + (resume $k1 (tag $e1 $h) (local.get $k)) + (unreachable) + ) + (resume $k1 (local.get $k)) + (unreachable) + ) + (func $nl3 (param $k (ref $k1)) + (local $k' (ref null $k1)) + (block $h1 (result (ref $k1)) + (resume $k1 (tag $e1 $h1) (local.get $k)) + (unreachable) + ) + (local.set $k') + (block $h2 (result (ref $k1)) + (resume $k1 (tag $e1 $h2) (local.get $k')) + (unreachable) + ) + (resume $k1 (local.get $k')) + (unreachable) + ) + (func $nl4 (param $k (ref $k1)) + (drop (cont.bind $k1 $k1 (local.get $k))) + (resume $k1 (local.get $k)) + ) + + (func (export "non-linear-1") + (call $nl1 (cont.new $k1 (ref.func $r0))) + ) + (func (export "non-linear-2") + (call $nl2 (cont.new $k1 (ref.func $r1))) + ) + (func (export "non-linear-3") + (call $nl3 (cont.new $k1 (ref.func $r1))) + ) + (func (export "non-linear-4") + (call $nl4 (cont.new $k1 (ref.func $r1))) + ) +) + +(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")) +(assert_exception (invoke "uncaught-2")) + +(assert_trap (invoke "barrier") "barrier") + +(assert_trap (invoke "non-linear-1") "continuation already consumed") +(assert_trap (invoke "non-linear-2") "continuation already consumed") +(assert_trap (invoke "non-linear-3") "continuation already consumed") +(assert_trap (invoke "non-linear-4") "continuation already consumed") + +(assert_invalid + (module + (type $ft (func)) + (func + (cont.new $ft (ref.null $ft)) + (drop))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (resume $ft (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $exn) + (func + (resume_throw $ft $exn (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (cont.bind $ft $ct (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (func + (cont.bind $ct $ft (ref.null $ct)) + (unreachable))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $foo) + (func + (block $on_foo (result (ref $ft)) + (resume $ct (tag $foo $on_foo) (ref.null $ct)) + (unreachable) + ) + (drop))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ft (func)) + (type $ct (cont $ft)) + (tag $foo) + (func + (block $on_foo (result (ref $ct) (ref $ft)) + (resume $ct (tag $foo $on_foo) (ref.null $ct)) + (unreachable) + ) + (drop) + (drop))) + "non-continuation type 0") + +(assert_invalid + (module + (type $ct (cont $ct))) + "non-function type 0") + +(assert_invalid + (module + (rec + (type $s0 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1)))) + (type $s1 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1)))) + ) + (type $ct (cont $s0))) + "non-function type 0") + +(module + (rec + (type $f1 (func (param (ref $f2)))) + (type $f2 (func (param (ref $f1)))) + ) + (type $c1 (cont $f1)) + (type $c2 (cont $f2)) +) + +;; Simple state example + +(module $state + (tag $get (result i32)) + (tag $set (param i32) (result i32)) + + (type $f (func (param i32) (result i32))) + (type $k (cont $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)) + (resume $k (tag $get $on_get) (tag $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 (suspend $set (i32.const 7))) + (i32.add + (suspend $get) + (i32.mul + (i32.const 2) + (i32.add + (suspend $set (i32.const 3)) + (suspend $get) + ) + ) + ) + ) + + (elem declare func $f) + (func (export "run") (result i32) + (call $runner (i32.const 0) (cont.new $k (ref.func $f))) + ) +) + +(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)) + + (tag $yield (param i64) (result i32)) + + ;; 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 (suspend $yield (local.get $i))) + (call_ref $gen (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 $n i64) + (local $k (ref null $cont)) + (local.get $i) + (cont.new $cont0 (ref.func $gen)) + (block $on_first_yield (param i64 (ref $cont0)) (result i64 (ref $cont)) + (resume $cont0 (tag $yield $on_first_yield)) + (unreachable) + ) + (loop $on_yield (param i64) (param (ref $cont)) + (local.set $k) + (local.set $n) + (local.set $sum (i64.add (local.get $sum) (local.get $n))) + (i64.eq (local.get $n) (local.get $j)) + (local.get $k) + (resume $cont (tag $yield $on_yield)) + ) + (return (local.get $sum)) + ) +) + +(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)) +(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 + (type $proc (func)) + (type $cont (cont $proc)) + + (tag $yield (export "yield")) + (tag $spawn (export "spawn") (param (ref $cont))) + + ;; 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)) + (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 $i i32) + (if (call $queue-empty) + (then (return (ref.null $cont))) + ) + (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)) + ;; 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 + ;; Space is below threshold, grow table instead + (drop (table.grow $queue (ref.null $cont) (global.get $qdelta))) + ) + (else + ;; 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) ;; dest = new front = 0 + (global.get $qfront) ;; src = old front + (global.get $qback) ;; len = new back = old back - old front + ) + (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)) + ) + ) + ) + ) + (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 $cont)) + (call $enqueue (local.get $main)) + (loop $l + (if (call $queue-empty) (then (return))) + (block $on_yield (result (ref $cont)) + (block $on_spawn (result (ref $cont) (ref $cont)) + (resume $cont (tag $yield $on_yield) (tag $spawn $on_spawn) + (call $dequeue) + ) + (br $l) ;; thread terminated + ) + ;; on $spawn, proc and cont on stack + (call $enqueue) ;; continuation of old thread + (call $enqueue) ;; new thread + (br $l) + ) + ;; on $yield, cont on stack + (call $enqueue) + (br $l) + ) + ) +) + +(register "scheduler") + +(module + (type $proc (func)) + (type $pproc (func (param i32))) ;; parameterised proc + (type $cont (cont $proc)) + (type $pcont (cont $pproc)) ;; parameterised continuation proc + (tag $yield (import "scheduler" "yield")) + (tag $spawn (import "scheduler" "spawn") (param (ref $cont))) + (func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont))) + + (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)) + (suspend $spawn (cont.new $cont (ref.func $thread1))) + (call $log (i32.const 1)) + (suspend $spawn (cont.bind $pcont $cont (global.get $depth) (cont.new $pcont (ref.func $thread2)))) + (call $log (i32.const 2)) + (suspend $spawn (cont.new $cont (ref.func $thread3))) + (call $log (i32.const 3)) + ) + + (func $thread1 + (call $log (i32.const 10)) + (suspend $yield) + (call $log (i32.const 11)) + (suspend $yield) + (call $log (i32.const 12)) + (suspend $yield) + (call $log (i32.const 13)) + ) + + (func $thread2 (param $d i32) + (local $w i32) + (local.set $w (global.get $width)) + (call $log (i32.const 20)) + (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)) + (suspend $yield) + (call $log (i32.const 23)) + (suspend $spawn + (cont.bind $pcont $cont + (i32.sub (local.get $d) (i32.const 1)) + (cont.new $pcont (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 + (call $log (i32.const 30)) + (suspend $yield) + (call $log (i32.const 31)) + (suspend $yield) + (call $log (i32.const 32)) + ) + + (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 (cont.new $cont (ref.func $main))) + (call $log (i32.const -2)) + ) +) + +(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))) + + +;; Nested example: generator in a thread + +(module $concurrent-generator + (func $log (import "spectest" "print_i64") (param i64)) + + (tag $syield (import "scheduler" "yield")) + (tag $spawn (import "scheduler" "spawn") (param (ref $cont))) + (func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont))) + + (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)) + + (elem declare func $main $bg-thread $syield) + + (func $syield (param $i i64) + (call $log (local.get $i)) + (suspend $syield) + ) + + (func $bg-thread + (call $log (i64.const -10)) + (loop $l + (call $log (i64.const -11)) + (suspend $syield) + (br_if $l (i32.eqz (global.get $done))) + ) + (call $log (i64.const -12)) + ) + + (func $main (param $i i64) (param $j i64) + (suspend $spawn (cont.new $cont (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)) + ) + + (type $proc (func)) + (type $pproc (func (param i64 i64))) + (type $cont (cont $proc)) + (type $pcont (cont $pproc)) + (func (export "sum") (param $i i64) (param $j i64) (result i64) + (call $log (i64.const -1)) + (call $scheduler + (cont.bind $pcont $cont (local.get $i) (local.get $j) (cont.new $pcont (ref.func $main))) + ) + (call $log (i64.const -2)) + (global.get $result) + ) +) + +(assert_return (invoke "sum" (i64.const 10) (i64.const 20)) (i64.const 165)) + + +;; cont.bind + +(module + (type $f2 (func (param i32 i32) (result i32 i32 i32 i32 i32 i32))) + (type $f4 (func (param i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32))) + (type $f6 (func (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32))) + + (type $k2 (cont $f2)) + (type $k4 (cont $f4)) + (type $k6 (cont $f6)) + + (elem declare func $f) + (func $f (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32) + (local.get 0) (local.get 1) (local.get 2) + (local.get 3) (local.get 4) (local.get 5) + ) + + (func (export "run") (result i32 i32 i32 i32 i32 i32) + (local $k6 (ref null $k6)) + (local $k4 (ref null $k4)) + (local $k2 (ref null $k2)) + (local.set $k6 (cont.new $k6 (ref.func $f))) + (local.set $k4 (cont.bind $k6 $k4 (i32.const 1) (i32.const 2) (local.get $k6))) + (local.set $k2 (cont.bind $k4 $k2 (i32.const 3) (i32.const 4) (local.get $k4))) + (resume $k2 (i32.const 5) (i32.const 6) (local.get $k2)) + ) +) + +(assert_return (invoke "run") + (i32.const 1) (i32.const 2) (i32.const 3) + (i32.const 4) (i32.const 5) (i32.const 6) +) + + +(module + (tag $e (result i32 i32 i32 i32 i32 i32)) + + (type $f0 (func (result i32 i32 i32 i32 i32 i32 i32))) + (type $f2 (func (param i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) + (type $f4 (func (param i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) + (type $f6 (func (param i32 i32 i32 i32 i32 i32) (result i32 i32 i32 i32 i32 i32 i32))) + + (type $k0 (cont $f0)) + (type $k2 (cont $f2)) + (type $k4 (cont $f4)) + (type $k6 (cont $f6)) + + (elem declare func $f) + (func $f (result i32 i32 i32 i32 i32 i32 i32) + (i32.const 0) (suspend $e) + ) + + (func (export "run") (result i32 i32 i32 i32 i32 i32 i32) + (local $k6 (ref null $k6)) + (local $k4 (ref null $k4)) + (local $k2 (ref null $k2)) + (block $l (result (ref $k6)) + (resume $k0 (tag $e $l) (cont.new $k0 (ref.func $f))) + (unreachable) + ) + (local.set $k6) + (local.set $k4 (cont.bind $k6 $k4 (i32.const 1) (i32.const 2) (local.get $k6))) + (local.set $k2 (cont.bind $k4 $k2 (i32.const 3) (i32.const 4) (local.get $k4))) + (resume $k2 (i32.const 5) (i32.const 6) (local.get $k2)) + ) +) + +(assert_return (invoke "run") + (i32.const 0) (i32.const 1) (i32.const 2) (i32.const 3) + (i32.const 4) (i32.const 5) (i32.const 6) +) diff --git a/test/core/table.wast b/test/core/table.wast index a11dce56d..918016242 100644 --- a/test/core/table.wast +++ b/test/core/table.wast @@ -135,6 +135,31 @@ ) +(assert_invalid + (module + (type $f (func)) + (table 10 (ref $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $f (func)) + (table 0 (ref $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $f (func)) + (table 0 0 (ref $f)) + ) + "type mismatch" +) + + ;; Duplicate table identifiers (assert_malformed diff --git a/test/core/tag.wast b/test/core/tag.wast index aba017d2c..29fd404f9 100644 --- a/test/core/tag.wast +++ b/test/core/tag.wast @@ -15,7 +15,9 @@ (import "test" "t3" (tag $t1 (param i32 f32))) ) -(assert_invalid - (module (tag (result i32))) - "non-empty tag result type" -) +;; NOTE(dhil): This test is invalid as our proposal allows non-empty +;; tag result types +;; (assert_invalid +;; (module (tag (result i32))) +;; "non-empty tag result type" +;; )