diff --git a/ml-proto/README.md b/ml-proto/README.md index e9a9104d9f..bd64bd5074 100644 --- a/ml-proto/README.md +++ b/ml-proto/README.md @@ -150,7 +150,7 @@ result: ( result ) local: ( local * ) | ( local ) func_sig: ( type ) | * ? -global_sig: +global_sig: | ( mut ) table_sig: ? memory_sig: ? diff --git a/ml-proto/host/arrange.ml b/ml-proto/host/arrange.ml index 18e8db611f..f396dcc659 100644 --- a/ml-proto/host/arrange.ml +++ b/ml-proto/host/arrange.ml @@ -56,6 +56,10 @@ let struct_type = func_type let limits int {min; max} = String.concat " " (int min :: opt int max) +let global_type = function + | GlobalType (t, Immutable) -> atom string_of_value_type t + | GlobalType (t, Mutable) -> Node ("mut", [atom string_of_value_type t]) + (* Operators *) @@ -296,8 +300,7 @@ let import_kind i k = Node ("func $" ^ string_of_int i, [Node ("type", [atom var x])]) | TableImport t -> table 0 i ({ttype = t} @@ k.at) | MemoryImport t -> memory 0 i ({mtype = t} @@ k.at) - | GlobalImport t -> - Node ("global $" ^ string_of_int i, [atom value_type t]) + | GlobalImport t -> Node ("global $" ^ string_of_int i, [global_type t]) let import i im = let {module_name; item_name; ikind} = im.it in @@ -320,9 +323,7 @@ let export ex = let global off i g = let {gtype; value} = g.it in - Node ("global $" ^ string_of_int (off + i), - [atom value_type gtype; expr value] - ) + Node ("global $" ^ string_of_int (off + i), [global_type gtype; expr value]) (* Modules *) diff --git a/ml-proto/host/encode.ml b/ml-proto/host/encode.ml index af4aa77d7e..71a8af1575 100644 --- a/ml-proto/host/encode.ml +++ b/ml-proto/host/encode.ml @@ -100,6 +100,13 @@ let encode m = let memory_type = function | MemoryType lim -> limits vu32 lim + let mutability = function + | Immutable -> u8 0 + | Mutable -> u8 1 + + let global_type = function + | GlobalType (t, mut) -> value_type t; mutability mut + (* Expressions *) open Source @@ -330,7 +337,7 @@ let encode m = | FuncImport x -> u8 0x00; var x | TableImport t -> u8 0x01; table_type t | MemoryImport t -> u8 0x02; memory_type t - | GlobalImport t -> u8 0x03; value_type t + | GlobalImport t -> u8 0x03; global_type t let import imp = let {module_name; item_name; ikind} = imp.it in @@ -364,7 +371,7 @@ let encode m = (* Global section *) let global g = let {gtype; value} = g.it in - value_type gtype; expr value; op 0x0f + global_type gtype; expr value; op 0x0f let global_section gs = section "global" (vec global) gs (gs <> []) diff --git a/ml-proto/host/import/spectest.ml b/ml-proto/host/import/spectest.ml index ef25246ecf..2bf70e0735 100644 --- a/ml-proto/host/import/spectest.ml +++ b/ml-proto/host/import/spectest.ml @@ -26,7 +26,7 @@ let lookup name t = match name, t with | "print", ExternalFuncType ft -> ExternalFunc (HostFunc (print ft.out)) | "print", _ -> ExternalFunc (HostFunc (print None)) - | "global", ExternalGlobalType t -> ExternalGlobal (global t) + | "global", ExternalGlobalType (GlobalType (t, _)) -> ExternalGlobal (global t) | "global", _ -> ExternalGlobal (global Int32Type) | "table", _ -> ExternalTable table | "memory", _ -> ExternalMemory memory diff --git a/ml-proto/host/lexer.mll b/ml-proto/host/lexer.mll index 58ba61a5da..d6fa44d504 100644 --- a/ml-proto/host/lexer.mll +++ b/ml-proto/host/lexer.mll @@ -140,6 +140,7 @@ rule token = parse F64_const (n @@ s.at), Values.Float64 n)) } | "anyfunc" { ANYFUNC } + | "mut" { MUT } | "nop" { NOP } | "unreachable" { UNREACHABLE } diff --git a/ml-proto/host/parser.mly b/ml-proto/host/parser.mly index c54898fcbd..495471b282 100644 --- a/ml-proto/host/parser.mly +++ b/ml-proto/host/parser.mly @@ -145,7 +145,7 @@ let inline_type c t at = %} -%token NAT INT FLOAT TEXT VAR VALUE_TYPE ANYFUNC LPAR RPAR +%token NAT INT FLOAT TEXT VAR VALUE_TYPE ANYFUNC MUT LPAR RPAR %token NOP DROP BLOCK IF THEN ELSE SELECT LOOP BR BR_IF BR_TABLE %token CALL CALL_INDIRECT RETURN %token GET_LOCAL SET_LOCAL TEE_LOCAL GET_GLOBAL SET_GLOBAL @@ -205,6 +205,11 @@ elem_type : | ANYFUNC { AnyFuncType } ; +global_type : + | VALUE_TYPE { GlobalType ($1, Immutable) } + | LPAR MUT VALUE_TYPE RPAR { GlobalType ($3, Mutable) } +; + func_type : | LPAR FUNC func_sig RPAR { $3 } ; @@ -397,7 +402,7 @@ func : ; -/* Tables & Memories */ +/* Tables, Memories & Globals */ elem : | LPAR ELEM var expr var_list RPAR @@ -453,6 +458,19 @@ memory : [] } ; +global : + | LPAR GLOBAL bind_var_opt inline_export global_type expr RPAR + { let at = at () in + fun c -> $3 c anon_global bind_global; + (fun () -> {gtype = $5; value = $6 c} @@ at), + $4 GlobalExport c.globals.count c } + /* Need to duplicate above for empty inline_export_opt to avoid LR(1) conflict. */ + | LPAR GLOBAL bind_var_opt global_type expr RPAR + { let at = at () in + fun c -> $3 c anon_global bind_global; + (fun () -> {gtype = $4; value = $5 c} @@ at), [] } +; + /* Imports & Exports */ @@ -466,7 +484,7 @@ import_kind : { fun c -> $3 c anon_table bind_table; TableImport $4 } | LPAR MEMORY bind_var_opt memory_sig RPAR { fun c -> $3 c anon_memory bind_memory; MemoryImport $4 } - | LPAR GLOBAL bind_var_opt VALUE_TYPE RPAR + | LPAR GLOBAL bind_var_opt global_type RPAR { fun c -> $3 c anon_global bind_global; GlobalImport $4 } ; import : @@ -489,7 +507,7 @@ import : { let at = at () in fun c -> $3 c anon_memory bind_memory; {module_name = fst $4; item_name = snd $4; ikind = MemoryImport $5 @@ at} @@ at } - | LPAR GLOBAL bind_var_opt inline_import VALUE_TYPE RPAR /* Sugar */ + | LPAR GLOBAL bind_var_opt inline_import global_type RPAR /* Sugar */ { let at = at () in fun c -> $3 c anon_global bind_global; {module_name = fst $4; item_name = snd $4; ikind = GlobalImport $5 @@ at} @@ at } @@ -532,14 +550,6 @@ type_def : { fun c -> bind_type c $3 $4 } ; -global : - | LPAR GLOBAL bind_var_opt inline_export_opt VALUE_TYPE expr RPAR - { let at = at () in - fun c -> $3 c anon_global bind_global; - (fun () -> {gtype = $5; value = $6 c} @@ at), - $4 GlobalExport c.globals.count c } -; - start : | LPAR START var RPAR { fun c -> $3 c func } diff --git a/ml-proto/host/print.ml b/ml-proto/host/print.ml index 5495265c20..021d9ef87a 100644 --- a/ml-proto/host/print.ml +++ b/ml-proto/host/print.ml @@ -1,27 +1,25 @@ open Kernel open Source open Printf +open Types -(* Types *) - -open Types +(* Ast *) -let print_no_sig prefix i = - printf "%s %d\n" prefix i +let print_sig prefix i string_of_type t = + printf "%s %d : %s\n" prefix i (string_of_type t) -let print_var_sig prefix i t = - printf "%s %d : %s\n" prefix i (string_of_value_type t) +let print_func m i f = + print_sig "func" i string_of_func_type (List.nth m.it.types f.it.ftype.it) -let print_func_sig m prefix i x = - let t = List.nth m.it.types x.it in - printf "%s %d : %s\n" prefix i (string_of_func_type t) +let print_table m i tab = + print_sig "table" i string_of_table_type tab.it.ttype -let print_table_sig prefix i t = - printf "%s %d : %s\n" prefix i (string_of_table_type t) +let print_memory m i mem = + print_sig "memory" i string_of_memory_type mem.it.mtype -let print_memory_sig prefix i t = - printf "%s %d : %s\n" prefix i (string_of_memory_type t) +let print_global m i glob = + print_sig "global" i string_of_global_type glob.it.gtype let print_export m i ex = let {name; ekind; item} = ex.it in @@ -36,21 +34,6 @@ let print_export m i ex = let print_start start = Lib.Option.app (fun x -> printf "start = func %d\n" x.it) start - -(* Ast *) - -let print_func m i f = - print_func_sig m "func" i f.it.ftype - -let print_global m i glob = - print_var_sig "global" i glob.it.gtype - -let print_table m i tab = - print_table_sig "table" i tab.it.ttype - -let print_memory m i mem = - print_memory_sig "memory" i mem.it.mtype - let print_module m = (* TODO: more complete print function *) let {funcs; globals; tables; memories; start; exports; _} = m.it in @@ -77,4 +60,3 @@ let print_value vo = | None -> printf "()\n"; flush_all () - diff --git a/ml-proto/spec/ast.ml b/ml-proto/spec/ast.ml index b82509f758..266800aa1b 100644 --- a/ml-proto/spec/ast.ml +++ b/ml-proto/spec/ast.ml @@ -200,7 +200,7 @@ and expr' = type global = global' Source.phrase and global' = { - gtype : Types.value_type; + gtype : Types.global_type; value : expr; } diff --git a/ml-proto/spec/check.ml b/ml-proto/spec/check.ml index 47d87e7818..a3a15893ce 100644 --- a/ml-proto/spec/check.ml +++ b/ml-proto/spec/check.ml @@ -22,7 +22,7 @@ type context = types : func_type list; funcs : func_type list; locals : value_type list; - globals : value_type list; + globals : global_type list; return : expr_type; labels : expr_type_future list; tables : table_type list; @@ -201,10 +201,13 @@ let rec check_expr c et e = check_type (Some (local c x)) et e.at | GetGlobal x -> - check_type (Some (global c x)) et e.at + let GlobalType (t, mut) = global c x in + check_type (Some t) et e.at | SetGlobal (x, e1) -> - check_expr c (some (global c x)) e1; + let GlobalType (t, mut) = global c x in + require (mut = Mutable) x.at "global is immutable"; + check_expr c (some t) e1; check_type None et e.at | Load (memop, e1) -> @@ -322,7 +325,7 @@ let check_func c f = check_expr c' (known s.out) body -(* Tables & Memories *) +(* Tables, Memories, & Globals *) let check_table_type (t : table_type) at = let TableType ({min; max}, _) = t in @@ -381,14 +384,15 @@ let check_memory_segment c prev_end seg = "data segment does not fit into memory"; end_ - -(* Modules *) - let check_global c glob = let {gtype; value} = glob.it in - check_const c gtype value; + let GlobalType (t, mut) = gtype in + check_const c t value; {c with globals = c.globals @ [gtype]} + +(* Modules *) + let check_start c start = Lib.Option.app (fun x -> let start_type = func c x in @@ -408,6 +412,8 @@ let check_import im c = | MemoryImport t -> check_memory_type t ikind.at; {c with memories = t :: c.memories} | GlobalImport t -> + let GlobalType (_, mut) = t in + require (mut = Immutable) ikind.at "mutable globals cannot be imported (yet)"; {c with globals = t :: c.globals} module NameSet = Set.Make(String) @@ -418,7 +424,9 @@ let check_export c set ex = | FuncExport -> ignore (func c item) | TableExport -> ignore (table c item) | MemoryExport -> ignore (memory c item) - | GlobalExport -> ignore (global c item) + | GlobalExport -> + let GlobalType (_, mut) = global c item in + require (mut = Immutable) ekind.at "mutable globals cannot be exported (yet)" ); require (not (NameSet.mem name set)) ex.at "duplicate export name"; NameSet.add name set diff --git a/ml-proto/spec/decode.ml b/ml-proto/spec/decode.ml index 7ab971efa9..155c4d9571 100644 --- a/ml-proto/spec/decode.ml +++ b/ml-proto/spec/decode.ml @@ -149,6 +149,17 @@ let memory_type s = let lim = limits vu32 s in MemoryType lim +let mutability s = + match u8 s with + | 0 -> Immutable + | 1 -> Mutable + | _ -> error s (pos s - 1) "invalid mutability" + +let global_type s = + let t = value_type s in + let mut = mutability s in + GlobalType (t, mut) + (* Decode expressions *) @@ -505,7 +516,7 @@ let import_kind s = | 0x00 -> FuncImport (at var s) | 0x01 -> TableImport (table_type s) | 0x02 -> MemoryImport (memory_type s) - | 0x03 -> GlobalImport (value_type s) + | 0x03 -> GlobalImport (global_type s) | _ -> error s (pos s - 1) "invalid import kind" let import s = @@ -547,7 +558,7 @@ let memory_section s = (* Global section *) let global s = - let t = value_type s in + let t = global_type s in let pos = pos s in let es = expr_block s in require (List.length es = 1) s pos "single expression expected"; diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 3b03ac12ff..480a0c262a 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -320,7 +320,7 @@ let create_memory mem = Memory.create lim let create_global glob = - let {gtype = t; _} = glob.it in + let {gtype = GlobalType (t, _); _} = glob.it in ref (default_value t) let init_func c f = @@ -369,15 +369,13 @@ let add_import (ext : extern) (imp : import) (inst : instance) : instance = ); {inst with funcs = f :: inst.funcs} | ExternalTable tab, TableImport (TableType (lim, _)) -> - (* TODO: no checking of element type? *) check_limits (Table.limits tab) lim imp.it.ikind.at; {inst with tables = tab :: inst.tables} | ExternalMemory mem, MemoryImport (MemoryType lim) -> check_limits (Memory.limits mem) lim imp.it.ikind.at; {inst with memories = mem :: inst.memories} - | ExternalGlobal glob, GlobalImport _ -> - (* TODO: no checking of value type? *) - {inst with globals = ref glob :: inst.globals} + | ExternalGlobal v, GlobalImport (GlobalType _) -> + {inst with globals = ref v :: inst.globals} | _ -> Link.error imp.it.ikind.at "type mismatch" diff --git a/ml-proto/spec/kernel.ml b/ml-proto/spec/kernel.ml index 5267d5dbfe..accca26cc7 100644 --- a/ml-proto/spec/kernel.ml +++ b/ml-proto/spec/kernel.ml @@ -107,7 +107,7 @@ and expr' = type global = global' Source.phrase and global' = { - gtype : value_type; + gtype : global_type; value : expr; } @@ -164,7 +164,7 @@ and import_kind' = | FuncImport of var | TableImport of table_type | MemoryImport of memory_type - | GlobalImport of value_type + | GlobalImport of global_type type import = import' Source.phrase and import' = diff --git a/ml-proto/spec/types.ml b/ml-proto/spec/types.ml index d1d9ae244a..8a6b8113a0 100644 --- a/ml-proto/spec/types.ml +++ b/ml-proto/spec/types.ml @@ -6,13 +6,15 @@ type expr_type = value_type option type func_type = {ins : value_type list; out : expr_type} type 'a limits = {min : 'a; max : 'a option} +type mutability = Immutable | Mutable type table_type = TableType of Int32.t limits * elem_type type memory_type = MemoryType of Int32.t limits +type global_type = GlobalType of value_type * mutability type external_type = | ExternalFuncType of func_type | ExternalTableType of table_type | ExternalMemoryType of memory_type - | ExternalGlobalType of value_type + | ExternalGlobalType of global_type (* Attributes *) @@ -47,6 +49,10 @@ let string_of_memory_type = function let string_of_table_type = function | TableType (lim, t) -> string_of_limits lim ^ " " ^ string_of_elem_type t +let string_of_global_type = function + | GlobalType (t, Immutable) -> string_of_value_type t + | GlobalType (t, Mutable) -> "(mut " ^ string_of_value_type t ^ ")" + let string_of_expr_type = function | None -> "()" | Some t -> string_of_value_type t diff --git a/ml-proto/test/globals.wast b/ml-proto/test/globals.wast index 7fd1b8b91c..58cc51b6b1 100644 --- a/ml-proto/test/globals.wast +++ b/ml-proto/test/globals.wast @@ -1,11 +1,18 @@ -;; TODO: more tests +;; Test globals (module - (global $x i32 (i32.const -2)) - (global f32 (f32.const -3)) - (global f64 (f64.const -4)) - (global $y i64 (i64.const -5)) + (global $a i32 (i32.const -2)) + (global (;1;) f32 (f32.const -3)) + (global (;2;) f64 (f64.const -4)) + (global $b i64 (i64.const -5)) + (global $x (mut i32) (i32.const -12)) + (global (;5;) (mut f32) (f32.const -13)) + (global (;6;) (mut f64) (f64.const -14)) + (global $y (mut i64) (i64.const -15)) + + (func (export "get-a") (result i32) (get_global $a)) + (func (export "get-b") (result i64) (get_global $b)) (func (export "get-x") (result i32) (get_global $x)) (func (export "get-y") (result i64) (get_global $y)) (func (export "set-x") (param i32) (set_global $x (get_local 0))) @@ -13,24 +20,56 @@ (func (export "get-1") (result f32) (get_global 1)) (func (export "get-2") (result f64) (get_global 2)) - (func (export "set-1") (param f32) (set_global 1 (get_local 0))) - (func (export "set-2") (param f64) (set_global 2 (get_local 0))) + (func (export "get-5") (result f32) (get_global 5)) + (func (export "get-6") (result f64) (get_global 6)) + (func (export "set-5") (param f32) (set_global 5 (get_local 0))) + (func (export "set-6") (param f64) (set_global 6 (get_local 0))) ) -(assert_return (invoke "get-x") (i32.const -2)) -(assert_return (invoke "get-y") (i64.const -5)) +(assert_return (invoke "get-a") (i32.const -2)) +(assert_return (invoke "get-b") (i64.const -5)) +(assert_return (invoke "get-x") (i32.const -12)) +(assert_return (invoke "get-y") (i64.const -15)) + (assert_return (invoke "get-1") (f32.const -3)) (assert_return (invoke "get-2") (f64.const -4)) +(assert_return (invoke "get-5") (f32.const -13)) +(assert_return (invoke "get-6") (f64.const -14)) (assert_return (invoke "set-x" (i32.const 6))) (assert_return (invoke "set-y" (i64.const 7))) -(assert_return (invoke "set-1" (f32.const 8))) -(assert_return (invoke "set-2" (f64.const 9))) +(assert_return (invoke "set-5" (f32.const 8))) +(assert_return (invoke "set-6" (f64.const 9))) (assert_return (invoke "get-x") (i32.const 6)) (assert_return (invoke "get-y") (i64.const 7)) -(assert_return (invoke "get-1") (f32.const 8)) -(assert_return (invoke "get-2") (f64.const 9)) +(assert_return (invoke "get-5") (f32.const 8)) +(assert_return (invoke "get-6") (f64.const 9)) + +(assert_invalid + (module (global f32 (f32.const 0)) (func (set_global 0 (i32.const 1)))) + "global is immutable" +) + +(assert_invalid + (module (import "m" "a" (global (mut i32)))) + "mutable globals cannot be imported" +) + +(assert_invalid + (module (global (import "m" "a") (mut i32))) + "mutable globals cannot be imported" +) + +(assert_invalid + (module (global (mut f32) (f32.const 0)) (export "a" (global 0))) + "mutable globals cannot be exported" +) + +(assert_invalid + (module (global (export "a") (mut f32) (f32.const 0))) + "mutable globals cannot be exported" +) (assert_invalid (module (global f32 (f32.neg (f32.const 0))))