| open Ast |
| open Source |
| open Types |
| |
| |
| (* Errors *) |
| |
| module Invalid = Error.Make () |
| exception Invalid = Invalid.Error |
| |
| let error = Invalid.error |
| let require b at s = if not b then error at s |
| |
| |
| (* Context *) |
| |
| type context = |
| { |
| types : func_type list; |
| funcs : func_type list; |
| tables : table_type list; |
| memories : memory_type list; |
| globals : global_type list; |
| elems : ref_type list; |
| datas : unit list; |
| locals : value_type list; |
| results : value_type list; |
| labels : result_type list; |
| refs : Free.t; |
| } |
| |
| let empty_context = |
| { types = []; funcs = []; tables = []; memories = []; |
| globals = []; elems = []; datas = []; |
| locals = []; results = []; labels = []; |
| refs = Free.empty |
| } |
| |
| let lookup category list x = |
| try Lib.List32.nth list x.it with Failure _ -> |
| error x.at ("unknown " ^ category ^ " " ^ I32.to_string_u x.it) |
| |
| 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 global (c : context) x = lookup "global" c.globals 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 |
| let label (c : context) x = lookup "label" c.labels x |
| |
| let refer category (s : Free.Set.t) x = |
| if not (Free.Set.mem x.it s) then |
| error x.at |
| ("undeclared " ^ category ^ " reference " ^ Int32.to_string x.it) |
| |
| let refer_func (c : context) x = refer "function" c.refs.Free.funcs x |
| |
| |
| (* Stack typing *) |
| |
| (* |
| * Note: The declarative typing rules are non-deterministic, that is, they |
| * have the liberty to locally "guess" the right types implied by the context. |
| * In the algorithmic formulation required here, stack types are hence modelled |
| * as lists of _options_ of types, where `None` represents a locally |
| * unknown type. Furthermore, an ellipses flag represents arbitrary sequences |
| * of unknown types, in order to handle stack polymorphism algorithmically. |
| *) |
| |
| type ellipses = NoEllipses | Ellipses |
| type infer_result_type = ellipses * value_type option list |
| type op_type = {ins : infer_result_type; outs : infer_result_type} |
| |
| let known = List.map (fun t -> Some t) |
| let stack ts = (NoEllipses, known ts) |
| let (-~>) ts1 ts2 = {ins = NoEllipses, ts1; outs = NoEllipses, ts2} |
| let (-->) ts1 ts2 = {ins = NoEllipses, known ts1; outs = NoEllipses, known ts2} |
| let (-~>...) ts1 ts2 = {ins = Ellipses, ts1; outs = Ellipses, ts2} |
| let (-->...) ts1 ts2 = {ins = Ellipses, known ts1; outs = Ellipses, known ts2} |
| |
| let string_of_infer_type t = |
| Lib.Option.get (Lib.Option.map string_of_value_type t) "_" |
| let string_of_infer_types ts = |
| "[" ^ String.concat " " (List.map string_of_infer_type ts) ^ "]" |
| |
| let eq_ty t1 t2 = (t1 = t2 || t1 = None || t2 = None) |
| let check_stack ts1 ts2 at = |
| require (List.length ts1 = List.length ts2 && List.for_all2 eq_ty ts1 ts2) at |
| ("type mismatch: instruction requires " ^ string_of_infer_types ts1 ^ |
| " but stack has " ^ string_of_infer_types ts2) |
| |
| let pop (ell1, ts1) (ell2, ts2) at = |
| let n1 = List.length ts1 in |
| let n2 = List.length ts2 in |
| let n = min n1 n2 in |
| let n3 = if ell2 = Ellipses then (n1 - n) else 0 in |
| check_stack ts1 (Lib.List.make n3 None @ Lib.List.drop (n2 - n) ts2) at; |
| (ell2, if ell1 = Ellipses then [] else Lib.List.take (n2 - n) ts2) |
| |
| let push (ell1, ts1) (ell2, ts2) = |
| assert (ell1 = NoEllipses || ts2 = []); |
| (if ell1 = Ellipses || ell2 = Ellipses then Ellipses else NoEllipses), |
| ts2 @ ts1 |
| |
| let peek i (ell, ts) = |
| try List.nth (List.rev ts) i with Failure _ -> None |
| |
| |
| (* Type Synthesis *) |
| |
| let type_num = Values.type_of_num |
| let type_vec = Values.type_of_vec |
| let type_vec_lane = function |
| | Values.V128 laneop -> V128.type_of_lane laneop |
| |
| let type_cvtop at = function |
| | Values.I32 cvtop -> |
| let open I32Op in |
| (match cvtop with |
| | ExtendSI32 | ExtendUI32 -> error at "invalid conversion" |
| | WrapI64 -> I64Type |
| | TruncSF32 | TruncUF32 | TruncSatSF32 | TruncSatUF32 |
| | ReinterpretFloat -> F32Type |
| | TruncSF64 | TruncUF64 | TruncSatSF64 | TruncSatUF64 -> F64Type |
| ), I32Type |
| | Values.I64 cvtop -> |
| let open I64Op in |
| (match cvtop with |
| | ExtendSI32 | ExtendUI32 -> I32Type |
| | WrapI64 -> error at "invalid conversion" |
| | TruncSF32 | TruncUF32 | TruncSatSF32 | TruncSatUF32 -> F32Type |
| | TruncSF64 | TruncUF64 | TruncSatSF64 | TruncSatUF64 |
| | ReinterpretFloat -> F64Type |
| ), I64Type |
| | Values.F32 cvtop -> |
| let open F32Op in |
| (match cvtop with |
| | ConvertSI32 | ConvertUI32 | ReinterpretInt -> I32Type |
| | ConvertSI64 | ConvertUI64 -> I64Type |
| | PromoteF32 -> error at "invalid conversion" |
| | DemoteF64 -> F64Type |
| ), F32Type |
| | Values.F64 cvtop -> |
| let open F64Op in |
| (match cvtop with |
| | ConvertSI32 | ConvertUI32 -> I32Type |
| | ConvertSI64 | ConvertUI64 | ReinterpretInt -> I64Type |
| | PromoteF32 -> F32Type |
| | DemoteF64 -> error at "invalid conversion" |
| ), F64Type |
| |
| let num_lanes = function |
| | Values.V128 laneop -> V128.num_lanes laneop |
| |
| let lane_extractop = function |
| | Values.V128 extractop -> |
| let open V128 in let open V128Op in |
| match extractop with |
| | I8x16 (Extract (i, _)) | I16x8 (Extract (i, _)) |
| | I32x4 (Extract (i, _)) | I64x2 (Extract (i, _)) |
| | F32x4 (Extract (i, _)) | F64x2 (Extract (i, _)) -> i |
| |
| let lane_replaceop = function |
| | Values.V128 replaceop -> |
| let open V128 in let open V128Op in |
| match replaceop with |
| | I8x16 (Replace i) | I16x8 (Replace i) |
| | I32x4 (Replace i) | I64x2 (Replace i) |
| | F32x4 (Replace i) | F64x2 (Replace i) -> i |
| |
| |
| (* Expressions *) |
| |
| let check_pack sz t_sz at = |
| require (packed_size sz < t_sz) at "invalid sign extension" |
| |
| let check_unop unop at = |
| match unop with |
| | Values.I32 (IntOp.ExtendS sz) | Values.I64 (IntOp.ExtendS sz) -> |
| check_pack sz (num_size (Values.type_of_num unop)) at |
| | _ -> () |
| |
| let check_vec_binop binop at = |
| match binop with |
| | Values.(V128 (V128.I8x16 (V128Op.Shuffle is))) -> |
| if List.exists ((<=) 32) is then |
| error at "invalid lane index" |
| | _ -> () |
| |
| let check_memop (c : context) (memop : ('t, 's) memop) ty_size get_sz at = |
| let _mt = memory c (0l @@ at) in |
| let size = |
| match get_sz memop.pack with |
| | None -> ty_size memop.ty |
| | Some sz -> |
| check_pack sz (ty_size memop.ty) at; |
| packed_size sz |
| in |
| require (1 lsl memop.align <= size) at |
| "alignment must not be larger than natural" |
| |
| |
| (* |
| * Conventions: |
| * c : context |
| * e : instr |
| * es : instr list |
| * v : value |
| * t : value_type var |
| * ts : result_type |
| * x : variable |
| * |
| * Note: To deal with the non-determinism in some of the declarative rules, |
| * the function takes the current stack `s` as an additional argument, allowing |
| * it to "peek" when it would otherwise have to guess an input type. |
| * |
| * Furthermore, stack-polymorphic types are given with the `-->...` operator: |
| * a type `ts1 -->... ts2` expresses any type `(ts1' @ ts1) -> (ts2' @ ts2)` |
| * where `ts1'` and `ts2'` would be chosen non-deterministically in the |
| * declarative typing rules. |
| *) |
| |
| let check_block_type (c : context) (bt : block_type) : func_type = |
| match bt with |
| | VarBlockType x -> type_ c x |
| | ValBlockType None -> FuncType ([], []) |
| | ValBlockType (Some t) -> FuncType ([], [t]) |
| |
| let rec check_instr (c : context) (e : instr) (s : infer_result_type) : op_type = |
| match e.it with |
| | Unreachable -> |
| [] -->... [] |
| |
| | Nop -> |
| [] --> [] |
| |
| | Drop -> |
| [peek 0 s] -~> [] |
| |
| | Select None -> |
| let t = peek 1 s in |
| require (match t with None -> true | Some t -> is_num_type t || is_vec_type t) e.at |
| ("type mismatch: instruction requires numeric or vector type" ^ |
| " but stack has " ^ string_of_infer_type t); |
| [t; t; Some (NumType I32Type)] -~> [t] |
| |
| | Select (Some ts) -> |
| require (List.length ts = 1) e.at |
| "invalid result arity other than 1 is not (yet) allowed"; |
| (ts @ ts @ [NumType I32Type]) --> ts |
| |
| | Block (bt, es) -> |
| let FuncType (ts1, ts2) as ft = check_block_type c bt in |
| check_block {c with labels = ts2 :: c.labels} es ft e.at; |
| ts1 --> ts2 |
| |
| | Loop (bt, es) -> |
| let FuncType (ts1, ts2) as ft = check_block_type c bt in |
| check_block {c with labels = ts1 :: c.labels} es ft e.at; |
| ts1 --> ts2 |
| |
| | If (bt, es1, es2) -> |
| let FuncType (ts1, ts2) as ft = check_block_type c bt in |
| check_block {c with labels = ts2 :: c.labels} es1 ft e.at; |
| check_block {c with labels = ts2 :: c.labels} es2 ft e.at; |
| (ts1 @ [NumType I32Type]) --> ts2 |
| |
| | Br x -> |
| label c x -->... [] |
| |
| | BrIf x -> |
| (label c x @ [NumType I32Type]) --> label c x |
| |
| | BrTable (xs, x) -> |
| let n = List.length (label c x) in |
| let ts = Lib.List.table n (fun i -> peek (n - i) s) in |
| check_stack ts (known (label c x)) x.at; |
| List.iter (fun x' -> check_stack ts (known (label c x')) x'.at) xs; |
| (ts @ [Some (NumType I32Type)]) -~>... [] |
| |
| | Return -> |
| c.results -->... [] |
| |
| | Call x -> |
| let FuncType (ts1, ts2) = func c x in |
| ts1 --> ts2 |
| |
| | CallIndirect (x, y) -> |
| let TableType (lim, t) = table c x in |
| let FuncType (ts1, ts2) = type_ c y in |
| require (t = FuncRefType) x.at |
| ("type mismatch: instruction requires table of functions" ^ |
| " but table has " ^ string_of_ref_type t); |
| (ts1 @ [NumType I32Type]) --> ts2 |
| |
| | LocalGet x -> |
| [] --> [local c x] |
| |
| | LocalSet x -> |
| [local c x] --> [] |
| |
| | LocalTee x -> |
| [local c x] --> [local c x] |
| |
| | GlobalGet x -> |
| let GlobalType (t, _mut) = global c x in |
| [] --> [t] |
| |
| | GlobalSet x -> |
| let GlobalType (t, mut) = global c x in |
| require (mut = Mutable) x.at "global is immutable"; |
| [t] --> [] |
| |
| | TableGet x -> |
| let TableType (_lim, t) = table c x in |
| [NumType I32Type] --> [RefType t] |
| |
| | TableSet x -> |
| let TableType (_lim, t) = table c x in |
| [NumType I32Type; RefType t] --> [] |
| |
| | TableSize x -> |
| let _tt = table c x in |
| [] --> [NumType I32Type] |
| |
| | TableGrow x -> |
| let TableType (_lim, t) = table c x in |
| [RefType t; NumType I32Type] --> [NumType I32Type] |
| |
| | TableFill x -> |
| let TableType (_lim, t) = table c x in |
| [NumType I32Type; RefType t; NumType I32Type] --> [] |
| |
| | TableCopy (x, y) -> |
| let TableType (_lim1, t1) = table c x in |
| let TableType (_lim2, t2) = table c y in |
| require (t1 = t2) x.at |
| ("type mismatch: source element type " ^ string_of_ref_type t1 ^ |
| " does not match destination element type " ^ string_of_ref_type t2); |
| [NumType I32Type; NumType I32Type; NumType I32Type] --> [] |
| |
| | TableInit (x, y) -> |
| let TableType (_lim1, t1) = table c x in |
| let t2 = elem c y in |
| require (t1 = t2) x.at |
| ("type mismatch: element segment's type " ^ string_of_ref_type t1 ^ |
| " does not match table's element type " ^ string_of_ref_type t2); |
| [NumType I32Type; NumType I32Type; NumType I32Type] --> [] |
| |
| | ElemDrop x -> |
| ignore (elem c x); |
| [] --> [] |
| |
| | Load memop -> |
| check_memop c memop num_size (Lib.Option.map fst) e.at; |
| [NumType I32Type] --> [NumType memop.ty] |
| |
| | Store memop -> |
| check_memop c memop num_size (fun sz -> sz) e.at; |
| [NumType I32Type; NumType memop.ty] --> [] |
| |
| | VecLoad memop -> |
| check_memop c memop vec_size (Lib.Option.map fst) e.at; |
| [NumType I32Type] --> [VecType memop.ty] |
| |
| | VecStore memop -> |
| check_memop c memop vec_size (fun _ -> None) e.at; |
| [NumType I32Type; VecType memop.ty] --> [] |
| |
| | VecLoadLane (memop, i) -> |
| check_memop c memop vec_size (fun sz -> Some sz) e.at; |
| require (i < vec_size memop.ty / packed_size memop.pack) e.at |
| "invalid lane index"; |
| [NumType I32Type; VecType memop.ty] --> [VecType memop.ty] |
| |
| | VecStoreLane (memop, i) -> |
| check_memop c memop vec_size (fun sz -> Some sz) e.at; |
| require (i < vec_size memop.ty / packed_size memop.pack) e.at |
| "invalid lane index"; |
| [NumType I32Type; VecType memop.ty] --> [] |
| |
| | MemorySize -> |
| let _mt = memory c (0l @@ e.at) in |
| [] --> [NumType I32Type] |
| |
| | MemoryGrow -> |
| let _mt = memory c (0l @@ e.at) in |
| [NumType I32Type] --> [NumType I32Type] |
| |
| | MemoryFill -> |
| ignore (memory c (0l @@ e.at)); |
| [NumType I32Type; NumType I32Type; NumType I32Type] --> [] |
| |
| | MemoryCopy -> |
| ignore (memory c (0l @@ e.at)); |
| [NumType I32Type; NumType I32Type; NumType I32Type] --> [] |
| |
| | MemoryInit x -> |
| ignore (memory c (0l @@ e.at)); |
| ignore (data c x); |
| [NumType I32Type; NumType I32Type; NumType I32Type] --> [] |
| |
| | DataDrop x -> |
| ignore (data c x); |
| [] --> [] |
| |
| | RefNull t -> |
| [] --> [RefType t] |
| |
| | RefIsNull -> |
| let t = peek 0 s in |
| require (match t with None -> true | Some t -> is_ref_type t) e.at |
| ("type mismatch: instruction requires reference type" ^ |
| " but stack has " ^ string_of_infer_type t); |
| [t] -~> [Some (NumType I32Type)] |
| |
| | RefFunc x -> |
| let _ft = func c x in |
| refer_func c x; |
| [] --> [RefType FuncRefType] |
| |
| | Const v -> |
| let t = NumType (type_num v.it) in |
| [] --> [t] |
| |
| | Test testop -> |
| let t = NumType (type_num testop) in |
| [t] --> [NumType I32Type] |
| |
| | Compare relop -> |
| let t = NumType (type_num relop) in |
| [t; t] --> [NumType I32Type] |
| |
| | Unary unop -> |
| check_unop unop e.at; |
| let t = NumType (type_num unop) in |
| [t] --> [t] |
| |
| | Binary binop -> |
| let t = NumType (type_num binop) in |
| [t; t] --> [t] |
| |
| | Convert cvtop -> |
| let t1, t2 = type_cvtop e.at cvtop in |
| [NumType t1] --> [NumType t2] |
| |
| | VecConst v -> |
| let t = VecType (type_vec v.it) in |
| [] --> [t] |
| |
| | VecTest testop -> |
| let t = VecType (type_vec testop) in |
| [t] --> [NumType I32Type] |
| |
| | VecUnary unop -> |
| let t = VecType (type_vec unop) in |
| [t] --> [t] |
| |
| | VecBinary binop -> |
| check_vec_binop binop e.at; |
| let t = VecType (type_vec binop) in |
| [t; t] --> [t] |
| |
| | VecCompare relop -> |
| let t = VecType (type_vec relop) in |
| [t; t] --> [t] |
| |
| | VecConvert cvtop -> |
| let t = VecType (type_vec cvtop) in |
| [t] --> [t] |
| |
| | VecShift shiftop -> |
| let t = VecType (type_vec shiftop) in |
| [t; NumType I32Type] --> [VecType V128Type] |
| |
| | VecBitmask bitmaskop -> |
| let t = VecType (type_vec bitmaskop) in |
| [t] --> [NumType I32Type] |
| |
| | VecTestBits vtestop -> |
| let t = VecType (type_vec vtestop) in |
| [t] --> [NumType I32Type] |
| |
| | VecUnaryBits vunop -> |
| let t = VecType (type_vec vunop) in |
| [t] --> [t] |
| |
| | VecBinaryBits vbinop -> |
| let t = VecType (type_vec vbinop) in |
| [t; t] --> [t] |
| |
| | VecTernaryBits vternop -> |
| let t = VecType (type_vec vternop) in |
| [t; t; t] --> [t] |
| |
| | VecSplat splatop -> |
| let t1 = type_vec_lane splatop in |
| let t2 = VecType (type_vec splatop) in |
| [NumType t1] --> [t2] |
| |
| | VecExtract extractop -> |
| let t = VecType (type_vec extractop) in |
| let t2 = type_vec_lane extractop in |
| require (lane_extractop extractop < num_lanes extractop) e.at |
| "invalid lane index"; |
| [t] --> [NumType t2] |
| |
| | VecReplace replaceop -> |
| let t = VecType (type_vec replaceop) in |
| let t2 = type_vec_lane replaceop in |
| require (lane_replaceop replaceop < num_lanes replaceop) e.at |
| "invalid lane index"; |
| [t; NumType t2] --> [t] |
| |
| and check_seq (c : context) (s : infer_result_type) (es : instr list) |
| : infer_result_type = |
| match es with |
| | [] -> |
| s |
| |
| | _ -> |
| let es', e = Lib.List.split_last es in |
| let s' = check_seq c s es' in |
| let {ins; outs} = check_instr c e s' in |
| push outs (pop ins s' e.at) |
| |
| and check_block (c : context) (es : instr list) (ft : func_type) at = |
| let FuncType (ts1, ts2) = ft in |
| let s = check_seq c (stack ts1) es in |
| let s' = pop (stack ts2) s at in |
| require (snd s' = []) at |
| ("type mismatch: block requires " ^ string_of_result_type ts2 ^ |
| " but stack has " ^ string_of_infer_types (snd s)) |
| |
| |
| (* Types *) |
| |
| let check_limits {min; max} range at msg = |
| require (I32.le_u min range) at msg; |
| match max with |
| | None -> () |
| | Some max -> |
| require (I32.le_u max range) at msg; |
| require (I32.le_u min max) at |
| "size minimum must not be greater than maximum" |
| |
| let check_num_type (t : num_type) at = |
| () |
| |
| let check_vec_type (t : vec_type) at = |
| () |
| |
| let check_ref_type (t : ref_type) at = |
| () |
| |
| let check_value_type (t : value_type) at = |
| match t with |
| | NumType t' -> check_num_type t' at |
| | VecType t' -> check_vec_type t' at |
| | RefType t' -> check_ref_type t' at |
| |
| let check_func_type (ft : func_type) at = |
| let FuncType (ts1, ts2) = ft in |
| List.iter (fun t -> check_value_type t at) ts1; |
| List.iter (fun t -> check_value_type t at) ts2 |
| |
| let check_table_type (tt : table_type) at = |
| let TableType (lim, t) = tt in |
| check_limits lim 0xffff_ffffl at "table size must be at most 2^32-1"; |
| check_ref_type t at |
| |
| let check_memory_type (mt : memory_type) at = |
| let MemoryType lim = mt in |
| check_limits lim 0x1_0000l at |
| "memory size must be at most 65536 pages (4GiB)" |
| |
| let check_global_type (gt : global_type) at = |
| let GlobalType (t, mut) = gt in |
| check_value_type t at |
| |
| |
| let check_type (t : type_) = |
| check_func_type t.it t.at |
| |
| |
| (* Functions & Constants *) |
| |
| (* |
| * Conventions: |
| * c : context |
| * m : module_ |
| * f : func |
| * e : instr |
| * v : value |
| * t : value_type |
| * s : func_type |
| * x : variable |
| *) |
| |
| let check_func (c : context) (f : func) = |
| let {ftype; locals; body} = f.it in |
| let FuncType (ts1, ts2) = type_ c ftype in |
| let c' = {c with locals = ts1 @ locals; results = ts2; labels = [ts2]} in |
| check_block c' body (FuncType ([], ts2)) f.at |
| |
| |
| let is_const (c : context) (e : instr) = |
| match e.it with |
| | RefNull _ |
| | RefFunc _ |
| | Const _ |
| | VecConst _ -> true |
| | GlobalGet x -> let GlobalType (_, mut) = global c x in mut = Immutable |
| | _ -> false |
| |
| let check_const (c : context) (const : const) (t : value_type) = |
| require (List.for_all (is_const c) const.it) const.at |
| "constant expression required"; |
| check_block c const.it (FuncType ([], [t])) const.at |
| |
| |
| (* Tables, Memories, & Globals *) |
| |
| let check_table (c : context) (tab : table) = |
| let {ttype} = tab.it in |
| check_table_type ttype tab.at |
| |
| let check_memory (c : context) (mem : memory) = |
| let {mtype} = mem.it in |
| check_memory_type mtype mem.at |
| |
| let check_elem_mode (c : context) (t : ref_type) (mode : segment_mode) = |
| match mode.it with |
| | Passive -> () |
| | Active {index; offset} -> |
| let TableType (_, et) = table c index in |
| require (t = et) mode.at |
| ("type mismatch: element segment's type " ^ string_of_ref_type t ^ |
| " does not match table's element type " ^ string_of_ref_type et); |
| check_const c offset (NumType I32Type) |
| | Declarative -> () |
| |
| let check_elem (c : context) (seg : elem_segment) = |
| let {etype; einit; emode} = seg.it in |
| List.iter (fun const -> check_const c const (RefType etype)) einit; |
| check_elem_mode c etype emode |
| |
| let check_data_mode (c : context) (mode : segment_mode) = |
| match mode.it with |
| | Passive -> () |
| | Active {index; offset} -> |
| ignore (memory c index); |
| check_const c offset (NumType I32Type) |
| | Declarative -> assert false |
| |
| let check_data (c : context) (seg : data_segment) = |
| let {dinit; dmode} = seg.it in |
| check_data_mode c dmode |
| |
| let check_global (c : context) (glob : global) = |
| let {gtype; ginit} = glob.it in |
| let GlobalType (t, mut) = gtype in |
| check_const c ginit t |
| |
| |
| (* Modules *) |
| |
| let check_start (c : context) (start : start) = |
| let {sfunc} = start.it in |
| require (func c sfunc = FuncType ([], [])) start.at |
| "start function must not have parameters or results" |
| |
| let check_import (im : import) (c : context) : context = |
| let {module_name = _; item_name = _; idesc} = im.it in |
| match idesc.it with |
| | FuncImport x -> |
| {c with funcs = type_ c x :: c.funcs} |
| | TableImport tt -> |
| check_table_type tt idesc.at; |
| {c with tables = tt :: c.tables} |
| | MemoryImport mt -> |
| check_memory_type mt idesc.at; |
| {c with memories = mt :: c.memories} |
| | GlobalImport gt -> |
| check_global_type gt idesc.at; |
| {c with globals = gt :: c.globals} |
| |
| module NameSet = Set.Make(struct type t = Ast.name let compare = compare end) |
| |
| let check_export (c : context) (set : NameSet.t) (ex : export) : NameSet.t = |
| let {name; edesc} = ex.it in |
| (match edesc.it with |
| | FuncExport x -> ignore (func c x) |
| | TableExport x -> ignore (table c x) |
| | MemoryExport x -> ignore (memory c x) |
| | GlobalExport x -> ignore (global c x) |
| ); |
| require (not (NameSet.mem name set)) ex.at "duplicate export name"; |
| NameSet.add name set |
| |
| let check_module (m : module_) = |
| let |
| { types; imports; tables; memories; globals; funcs; start; elems; datas; |
| exports } = m.it |
| in |
| let c0 = |
| List.fold_right check_import imports |
| { empty_context with |
| refs = Free.module_ ({m.it with funcs = []; start = None} @@ m.at); |
| types = List.map (fun ty -> ty.it) types; |
| } |
| in |
| let c1 = |
| { c0 with |
| funcs = c0.funcs @ List.map (fun f -> type_ c0 f.it.ftype) funcs; |
| tables = c0.tables @ List.map (fun tab -> tab.it.ttype) tables; |
| memories = c0.memories @ List.map (fun mem -> mem.it.mtype) memories; |
| elems = List.map (fun elem -> elem.it.etype) elems; |
| datas = List.map (fun _data -> ()) datas; |
| } |
| in |
| let c = |
| { c1 with globals = c1.globals @ List.map (fun g -> g.it.gtype) globals } |
| in |
| List.iter check_type types; |
| List.iter (check_global c1) globals; |
| List.iter (check_table c1) tables; |
| List.iter (check_memory c1) memories; |
| List.iter (check_elem c1) elems; |
| List.iter (check_data c1) datas; |
| List.iter (check_func c) funcs; |
| Lib.Option.app (check_start c) start; |
| ignore (List.fold_left (check_export c) NameSet.empty exports); |
| require (List.length c.memories <= 1) m.at |
| "multiple memories are not allowed (yet)" |