| (* Handler for "name" section and @name annotations *) | |
| open Custom | |
| open Annot | |
| open Source | |
| module IdxMap = Map.Make(Int32) | |
| type name = Ast.name Source.phrase | |
| type name_map = name IdxMap.t | |
| type indirect_name_map = name_map Source.phrase IdxMap.t | |
| type format = format' Source.phrase | |
| and format' = | |
| { | |
| module_ : name option; | |
| funcs : name_map; | |
| locals : indirect_name_map; | |
| types : name_map; | |
| fields : indirect_name_map; | |
| tags : name_map; | |
| } | |
| let empty = | |
| { | |
| module_ = None; | |
| funcs = IdxMap.empty; | |
| locals = IdxMap.empty; | |
| types = IdxMap.empty; | |
| fields = IdxMap.empty; | |
| tags = IdxMap.empty; | |
| } | |
| let name = Utf8.decode "name" | |
| let place _fmt = After last | |
| (* Decoding *) | |
| (* TODO: make Decode module reusable instead of duplicating code *) | |
| type stream = {bytes : string; pos : int ref} | |
| exception EOS | |
| let stream bs = {bytes = bs; pos = ref 0} | |
| let len s = String.length s.bytes | |
| let pos s = !(s.pos) | |
| let eos s = (pos s = len s) | |
| let check n s = if pos s + n > len s then raise EOS | |
| let skip n s = if n < 0 then raise EOS else check n s; s.pos := !(s.pos) + n | |
| let read s = Char.code (s.bytes.[!(s.pos)]) | |
| let peek s = if eos s then None else Some (read s) | |
| let get s = check 1 s; let b = read s in skip 1 s; b | |
| let get_string n s = let i = pos s in skip n s; String.sub s.bytes i n | |
| let loc pos = Source.{file = "@name section"; line = -1; column = pos} | |
| let region left right = Source.{left = loc left; right = loc right} | |
| let at f s = | |
| let left = pos s in | |
| let x = f s in | |
| let right = pos s in | |
| Source.(x @@ region left right) | |
| let decode_error pos msg = raise (Custom.Code (region pos pos, msg)) | |
| let require b pos msg = if not b then decode_error pos msg | |
| let decode_byte s = | |
| get s | |
| let rec decode_uN n s = | |
| require (n > 0) (pos s) "integer representation too long"; | |
| let b = decode_byte s in | |
| require (n >= 7 || b land 0x7f < 1 lsl n) (pos s - 1) "integer too large"; | |
| let x = Int32.of_int (b land 0x7f) in | |
| if b land 0x80 = 0 then x else | |
| Int32.(logor x (shift_left (decode_uN (n - 7) s) 7)) | |
| let decode_u32 = decode_uN 32 | |
| let decode_size s = | |
| Int32.to_int (decode_u32 s) | |
| let decode_name s = | |
| let n = decode_size s in | |
| let pos = pos s in | |
| try Utf8.decode (get_string n s) with Utf8.Utf8 -> | |
| decode_error pos "malformed UTF-8 encoding" | |
| let decode_name_assoc s = | |
| let x = decode_u32 s in | |
| let n = decode_name s in | |
| (x, n) | |
| let decode_name_map s = | |
| let n = decode_size s in | |
| let m = ref IdxMap.empty in | |
| for _ = 1 to n do | |
| let {it = (x, name); at} = at decode_name_assoc s in | |
| if IdxMap.mem x !m then | |
| decode_error at.left.column "custom @name: multiple function or local names"; | |
| m := IdxMap.add x (name @@ at) !m | |
| done; | |
| !m | |
| let decode_indirect_name_assoc s = | |
| let x = decode_u32 s in | |
| let m = at decode_name_map s in | |
| (x, m) | |
| let decode_indirect_name_map s = | |
| let n = decode_size s in | |
| let m = ref IdxMap.empty in | |
| for _ = 1 to n do | |
| let {it = (x, m'); at} = at decode_indirect_name_assoc s in | |
| if IdxMap.mem x !m then | |
| decode_error at.left.column "custom @name: multiple function names"; | |
| m := IdxMap.add x m' !m | |
| done; | |
| !m | |
| let decode_module s = Some (at decode_name s) | |
| let decode_funcs s = decode_name_map s | |
| let decode_locals s = decode_indirect_name_map s | |
| let decode_types s = decode_name_map s | |
| let decode_fields s = decode_indirect_name_map s | |
| let decode_tags s = decode_name_map s | |
| let decode_subsec id f default s = | |
| match peek s with | |
| | None -> default | |
| | Some id' when id' <> id -> default | |
| | _ -> | |
| let _id = decode_byte s in | |
| let n = decode_size s in | |
| let pos' = pos s in | |
| let ss = f s in | |
| require (pos s = pos' + n) (pos s) "name subsection size mismatch"; | |
| ss | |
| let decode _m _bs custom = | |
| let s = stream custom.it.content in | |
| try | |
| let module_ = decode_subsec 0 decode_module None s in | |
| let funcs = decode_subsec 1 decode_funcs IdxMap.empty s in | |
| let locals = decode_subsec 2 decode_locals IdxMap.empty s in | |
| let types = decode_subsec 4 decode_types IdxMap.empty s in | |
| let fields = decode_subsec 10 decode_fields IdxMap.empty s in | |
| let tags = decode_subsec 11 decode_tags IdxMap.empty s in | |
| require (eos s) (pos s) "invalid name subsection id"; | |
| {module_; funcs; locals; types; fields; tags} @@ custom.at | |
| with EOS -> decode_error (pos s) "unexpected end of name section" | |
| (* Encoding *) | |
| (* TODO: make Encode module reusable *) | |
| let encode_byte buf b = | |
| Buffer.add_char buf (Char.chr b) | |
| let rec encode_u32 buf i = | |
| let b = Int32.(to_int (logand i 0x7fl)) in | |
| if 0l <= i && i < 128l then encode_byte buf b | |
| else ( | |
| encode_byte buf (b lor 0x80); | |
| encode_u32 buf (Int32.shift_right_logical i 7) | |
| ) | |
| let encode_size buf n = | |
| encode_u32 buf (Int32.of_int n) | |
| let encode_name buf n = | |
| let s = Utf8.encode n in | |
| encode_size buf (String.length s); | |
| Buffer.add_string buf s | |
| let encode_name_assoc buf x n = | |
| encode_u32 buf x; | |
| encode_name buf n.it | |
| let encode_name_map buf m = | |
| encode_size buf (IdxMap.cardinal m); | |
| IdxMap.iter (encode_name_assoc buf) m | |
| let encode_indirect_name_assoc buf x m = | |
| encode_u32 buf x; | |
| encode_name_map buf m.it | |
| let encode_indirect_name_map buf m = | |
| encode_size buf (IdxMap.cardinal m); | |
| IdxMap.iter (encode_indirect_name_assoc buf) m | |
| let encode_subsec_begin buf id = | |
| encode_byte buf id; | |
| let pre = Buffer.contents buf in | |
| Buffer.clear buf; | |
| pre | |
| let encode_subsec_end buf pre = | |
| let contents = Buffer.contents buf in | |
| Buffer.clear buf; | |
| Buffer.add_string buf pre; | |
| encode_size buf (String.length contents); | |
| Buffer.add_string buf contents | |
| let encode_module buf name_opt = | |
| match name_opt with | |
| | None -> () | |
| | Some name -> | |
| let subsec = encode_subsec_begin buf 0 in | |
| encode_name buf name.it; | |
| encode_subsec_end buf subsec | |
| let encode_funcs buf name_map = | |
| if not (IdxMap.is_empty name_map) then begin | |
| let subsec = encode_subsec_begin buf 1 in | |
| encode_name_map buf name_map; | |
| encode_subsec_end buf subsec | |
| end | |
| let encode_locals buf name_map_map = | |
| if not (IdxMap.is_empty name_map_map) then begin | |
| let subsec = encode_subsec_begin buf 2 in | |
| encode_indirect_name_map buf name_map_map; | |
| encode_subsec_end buf subsec | |
| end | |
| let encode_types buf name_map = | |
| if not (IdxMap.is_empty name_map) then begin | |
| let subsec = encode_subsec_begin buf 4 in | |
| encode_name_map buf name_map; | |
| encode_subsec_end buf subsec | |
| end | |
| let encode_fields buf name_map_map = | |
| if not (IdxMap.is_empty name_map_map) then begin | |
| let subsec = encode_subsec_begin buf 10 in | |
| encode_indirect_name_map buf name_map_map; | |
| encode_subsec_end buf subsec | |
| end | |
| let encode_tags buf name_map = | |
| if not (IdxMap.is_empty name_map) then begin | |
| let subsec = encode_subsec_begin buf 11 in | |
| encode_name_map buf name_map; | |
| encode_subsec_end buf subsec | |
| end | |
| let encode _m _bs sec = | |
| let {module_; funcs; locals; types; fields; tags} = sec.it in | |
| let buf = Buffer.create 200 in | |
| encode_module buf module_; | |
| encode_funcs buf funcs; | |
| encode_locals buf locals; | |
| encode_types buf types; | |
| encode_fields buf fields; | |
| encode_tags buf tags; | |
| let content = Buffer.contents buf in | |
| {name = Utf8.decode "name"; content; place = After last} @@ sec.at | |
| (* Parsing *) | |
| open Ast | |
| open Types | |
| let parse_error at msg = raise (Custom.Syntax (at, msg)) | |
| let merge_name_opt n1 n2 = | |
| match n1, n2 with | |
| | None, None -> None | |
| | None, some | |
| | some, None -> some | |
| | Some _, Some n2 -> | |
| parse_error n2.at "@name annotation: multiple module names" | |
| let merge_name_map m1 m2 = | |
| IdxMap.union (fun x _ n2 -> | |
| parse_error n2.at "@name annotation: multiple function names" | |
| ) m1 m2 | |
| let merge_indirect_name_map m1 m2 = | |
| IdxMap.union (fun x m1' m2' -> | |
| Some ( | |
| IdxMap.union (fun x _ n2 -> | |
| parse_error n2.at "@name annotation: multiple local names" | |
| ) m1'.it m2'.it @@ {left = m1'.at.left; right = m2'.at.right} | |
| ) | |
| ) m1 m2 | |
| let merge s1 s2 = | |
| { | |
| module_ = merge_name_opt s1.it.module_ s2.it.module_; | |
| funcs = merge_name_map s1.it.funcs s2.it.funcs; | |
| locals = merge_indirect_name_map s1.it.locals s2.it.locals; | |
| types = merge_name_map s1.it.types s2.it.types; | |
| fields = merge_indirect_name_map s1.it.fields s2.it.fields; | |
| tags = merge_name_map s1.it.tags s2.it.tags; | |
| } @@ {left = s1.at.left; right = s2.at.right} | |
| let is_contained r1 r2 = r1.left >= r2.left && r1.right <= r2.right | |
| let is_left r1 r2 = r1.right <= r2.left | |
| let locate_func bs x name at (f : func) = | |
| let Func (y, _, es) = f.it in | |
| if is_left at y.at then | |
| {empty with funcs = IdxMap.singleton x name} | |
| else if es = [] || is_left at (List.hd es).at then | |
| (* TODO re-parse the function params and locals from bs *) | |
| parse_error at "@name annotation: local names not yet supported" | |
| else | |
| parse_error at "@name annotation: misplaced annotation" | |
| let locate_tag bs x name at (tag : tag) = | |
| let Tag _ = tag.it in | |
| if is_left at {tag.at with left = tag.at.right} then | |
| {empty with tags = IdxMap.singleton x name} | |
| else | |
| parse_error at "@name annotation: misplaced annotation" | |
| let locate_type bs x name at (ty : type_) = | |
| (* TODO re-parse types from bs *) | |
| parse_error at "@name annotation: type and field names not yet supported" | |
| let locate_module bs name at (m : module_) = | |
| if not (is_contained at m.at) then | |
| parse_error at "misplaced @name annotation"; | |
| let {types; tags; globals; memories; tables; funcs; start; | |
| datas; elems; imports; exports} = m.it in | |
| let ats = | |
| List.map (fun p -> p.at) types @ | |
| List.map (fun p -> p.at) tags @ | |
| List.map (fun p -> p.at) globals @ | |
| List.map (fun p -> p.at) memories @ | |
| List.map (fun p -> p.at) tables @ | |
| List.map (fun p -> p.at) funcs @ | |
| List.map (fun p -> p.at) (Option.to_list start) @ | |
| List.map (fun p -> p.at) datas @ | |
| List.map (fun p -> p.at) elems @ | |
| List.map (fun p -> p.at) imports @ | |
| List.map (fun p -> p.at) exports |> List.sort compare | |
| in | |
| match ats with | |
| | [] -> {empty with module_ = Some name} | |
| | at1::_ when is_left at at1 -> {empty with module_ = Some name} | |
| | _ -> | |
| match Lib.List.index_where (fun t -> is_contained at t.at) types with | |
| | Some x -> locate_type bs (Int32.of_int x) name at (List.nth types x) | |
| | None -> | |
| match Lib.List.index_where (fun t -> is_contained at t.at) tags with | |
| | Some x -> locate_tag bs (Int32.of_int x) name at (List.nth tags x) | |
| | None -> | |
| match Lib.List.index_where (fun f -> is_contained at f.at) funcs with | |
| | Some x -> locate_func bs (Int32.of_int x) name at (List.nth funcs x) | |
| | None -> parse_error at "misplaced @name annotation" | |
| let rec parse m bs annots = | |
| let ms = List.map (parse_annot m bs) annots in | |
| match ms with | |
| | [] -> [] | |
| | m::ms' -> [List.fold_left merge (empty @@ m.at) ms] | |
| and parse_annot m bs annot = | |
| let {name = n; items} = annot.it in | |
| assert (n = name); | |
| let name, items' = parse_name annot.at items in | |
| parse_end items'; | |
| locate_module bs name annot.at m @@ annot.at | |
| and parse_name at = function | |
| | {it = String s; at} :: items -> | |
| (try Utf8.decode s @@ at, items with Utf8.Utf8 -> | |
| parse_error at "malformed UTF-8 encoding" | |
| ) | |
| | _ -> | |
| parse_error at "@name annotation: string expected" | |
| and parse_end = function | |
| | [] -> () | |
| | item :: _ -> | |
| parse_error item.at "@name annotation: unexpected token" | |
| (* Printing *) | |
| let arrange m bs fmt = | |
| (* Print as generic custom section *) | |
| Handler_custom.arrange m bs (encode m "" fmt) | |
| (* Checking *) | |
| let check_error at msg = raise (Custom.Invalid (at, msg)) | |
| let check (m : module_) (fmt : format) = | |
| let subtypes = | |
| List.concat (List.map (fun {it = RecT ss; _} -> ss) m.it.types) in | |
| let comptypes = List.map (fun (SubT (_, _, ct)) -> ct) subtypes in | |
| IdxMap.iter (fun x name -> | |
| if I32.ge_u x (Lib.List32.length m.it.funcs) then | |
| check_error name.at ("custom @name: invalid function index " ^ | |
| I32.to_string_u x) | |
| ) fmt.it.funcs; | |
| IdxMap.iter (fun x map -> | |
| if I32.ge_u x (Lib.List32.length m.it.funcs) then | |
| check_error map.at ("custom @name: invalid function index " ^ | |
| I32.to_string_u x); | |
| let f = Lib.List32.nth m.it.funcs x in | |
| let Func (y, ls, _) = f.it in | |
| if I32.ge_u y.it (Lib.List32.length comptypes) then | |
| check_error map.at ("custom @name: invalid type index " ^ | |
| I32.to_string_u y.it ^ " for function " ^ I32.to_string_u x); | |
| let ts = | |
| match Lib.List32.nth comptypes y.it with | |
| | FuncT (ts, _) -> ts | |
| | _ -> | |
| check_error map.at ("custom @name: non-function type " ^ | |
| I32.to_string_u y.it ^ " for function " ^ I32.to_string_u x) | |
| in | |
| let n = I32.add (Lib.List32.length ts) (Lib.List32.length ls) in | |
| IdxMap.iter (fun y name -> | |
| if I32.ge_u y n then | |
| check_error name.at ("custom @name: invalid local index " ^ | |
| I32.to_string_u y ^ " for function " ^ I32.to_string_u x) | |
| ) map.it; | |
| ) fmt.it.locals; | |
| IdxMap.iter (fun x name -> | |
| if I32.ge_u x (Lib.List32.length comptypes) then | |
| check_error name.at ("custom @name: invalid type index " ^ | |
| I32.to_string_u x) | |
| ) fmt.it.types; | |
| IdxMap.iter (fun x map -> | |
| if I32.ge_u x (Lib.List32.length comptypes) then | |
| check_error map.at ("custom @name: invalid type index " ^ | |
| I32.to_string_u x); | |
| let n = | |
| match Lib.List32.nth comptypes x with | |
| | StructT fts -> Lib.List32.length fts | |
| | _ -> | |
| check_error map.at ("custom @name: non-struct type " ^ | |
| I32.to_string_u x) | |
| in | |
| IdxMap.iter (fun y name -> | |
| if I32.ge_u y n then | |
| check_error name.at ("custom @name: invalid field index " ^ | |
| I32.to_string_u y ^ " for type " ^ I32.to_string_u x) | |
| ) map.it; | |
| ) fmt.it.fields |