1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
(* SPDX-License-Identifier: AGPL-3.0-or-later *)
(* Copyright © 2021-2024 OCamlPro *)
(* Written by the Owi programmers *)

open Syntax

module Type = struct
  type t = Text.func_type

  let compare = Text.compare_func_type
end

module TypeMap = Map.Make (Type)

type t =
  { id : string option
  ; typ : Type.t Named.t
  ; global : (Text.global, Text.global_type) Runtime.t Named.t
  ; table : (Text.table, Text.table_type) Runtime.t Named.t
  ; mem : (Text.mem, Text.limits) Runtime.t Named.t
  ; func : (Text.func, Text.block_type) Runtime.t Named.t
  ; elem : Text.elem Named.t
  ; data : Text.data Named.t
  ; exports : Grouped.opt_exports
  ; start : Text.indice option
  }

let pp_id fmt id = Text.pp_id_opt fmt id

let pp_typ fmt typ = Named.pp Text.pp_func_type fmt typ

let pp_runtime_named ~pp_local ~pp_imported fmt l =
  Named.pp (Runtime.pp ~pp_local ~pp_imported) fmt l

let pp_global fmt g =
  pp_runtime_named ~pp_local:Text.pp_global ~pp_imported:Text.pp_global_type fmt
    g

let pp_table fmt t =
  pp_runtime_named ~pp_local:Text.pp_table ~pp_imported:Text.pp_table_type fmt t

let pp_mem fmt m =
  pp_runtime_named ~pp_local:Text.pp_mem ~pp_imported:Text.pp_limits fmt m

let pp_func fmt f =
  pp_runtime_named ~pp_local:Text.pp_func ~pp_imported:Text.pp_block_type fmt f

let pp_elem fmt e = Named.pp Text.pp_elem fmt e

let pp_data fmt d = Named.pp Text.pp_data fmt d

let pp_start fmt s = Text.pp_indice_opt fmt s

let pp fmt { id; typ; global; table; mem; func; elem; data; exports; start } =
  Fmt.pf fmt
    "{@\n\
    \  @[<v>id: %a@\n\
     typ: %a@\n\
     global: %a@\n\
     table: %a@\n\
     mem: %a@\n\
     func: %a@\n\
     elem: %a@\n\
     data: %a@\n\
     exports: %a@\n\
     start: %a@\n\
     }"
    pp_id id pp_typ typ pp_global global pp_table table pp_mem mem pp_func func
    pp_elem elem pp_data data Grouped.pp_opt_exports exports pp_start start

type type_acc =
  { declared_types : Text.func_type Indexed.t list
  ; func_types : Text.func_type Indexed.t list
  ; named_types : int String_map.t
  ; last_assigned_int : int
  ; all_types : int TypeMap.t
  }

let assign_type (acc : type_acc) (name, func_type) : type_acc =
  let { declared_types; func_types; named_types; last_assigned_int; all_types }
      =
    acc
  in
  let last_assigned_int, declared_types, named_types, all_types =
    let id = last_assigned_int in
    let last_assigned_int = succ last_assigned_int in
    let declared_types = Indexed.return id func_type :: declared_types in
    let named_types =
      match name with
      | None -> named_types
      | Some name -> String_map.add name id named_types
    in
    let all_types = TypeMap.add func_type id all_types in
    (last_assigned_int, declared_types, named_types, all_types)
  in

  (* Is there something to do/check when a type is already declared ? *)
  { declared_types; func_types; named_types; last_assigned_int; all_types }

let assign_heap_type (acc : type_acc) typ : type_acc =
  let { func_types; last_assigned_int; all_types; _ } = acc in
  match TypeMap.find_opt typ all_types with
  | Some _id -> acc
  | None ->
    let id = last_assigned_int in
    let last_assigned_int = succ last_assigned_int in
    let all_types = TypeMap.add typ id all_types in
    let func_types = Indexed.return id typ :: func_types in
    { acc with func_types; last_assigned_int; all_types }

let assign_types (modul : Grouped.t) : Text.func_type Named.t =
  let empty_acc : type_acc =
    { declared_types = []
    ; func_types = []
    ; named_types = String_map.empty
    ; last_assigned_int = 0
    ; all_types = TypeMap.empty
    }
  in
  let acc = List.fold_left assign_type empty_acc (List.rev modul.typ) in
  let acc =
    List.fold_left assign_heap_type acc (List.rev modul.function_type)
  in
  let values = List.rev acc.declared_types @ List.rev acc.func_types in
  Named.create values acc.named_types

let get_runtime_name (get_name : 'a -> string option) (elt : ('a, 'b) Runtime.t)
  : string option =
  match elt with
  | Local v -> get_name v
  | Imported { assigned_name; _ } -> assigned_name

let name kind ~get_name values =
  let assign_one (named : int String_map.t) (elt : _ Indexed.t) =
    let elt_v = Indexed.get elt in
    match get_name elt_v with
    | None -> Ok named
    | Some name ->
      let index = Indexed.get_index elt in
      if String_map.mem name named then
        Fmt.error_msg "duplicate %s %s" kind name
      else ok @@ String_map.add name index named
  in
  let+ named = list_fold_left assign_one String_map.empty values in
  Named.create values named

let check_type_id (types : Text.func_type Named.t)
  ((id, func_type) : Grouped.type_check) =
  let id =
    match id with
    | Raw i -> i
    | Text name -> (
      match String_map.find_opt name types.named with
      | None -> (* TODO: unchecked, is this actually reachable? *) assert false
      | Some v -> v )
  in
  (* TODO more efficient version of that *)
  match Indexed.get_at id types.values with
  | None -> Error (`Unknown_type (Text.Raw id))
  | Some func_type' ->
    if not (Text.func_type_eq func_type func_type') then
      Error `Inline_function_type
    else Ok ()

let of_grouped (modul : Grouped.t) : t Result.t =
  Log.debug (fun m -> m "assigning    ...");
  let typ = assign_types modul in
  let* global =
    name "global"
      ~get_name:(get_runtime_name (fun ({ id; _ } : Text.global) -> id))
      modul.global
  in
  let* table =
    name "table"
      ~get_name:(get_runtime_name (fun ((id, _) : Text.table) -> id))
      modul.table
  in
  let* mem =
    name "mem"
      ~get_name:(get_runtime_name (fun ((id, _) : Text.mem) -> id))
      modul.mem
  in
  let* func =
    name "func"
      ~get_name:(get_runtime_name (fun ({ id; _ } : Text.func) -> id))
      modul.func
  in
  let* elem =
    name "elem" ~get_name:(fun (elem : Text.elem) -> elem.id) modul.elem
  in
  let* data =
    name "data" ~get_name:(fun (data : Text.data) -> data.id) modul.data
  in
  let+ () = list_iter (check_type_id typ) modul.type_checks in
  let modul =
    { id = modul.id
    ; typ
    ; global
    ; table
    ; mem
    ; func
    ; elem
    ; data
    ; exports = modul.exports
    ; start = modul.start
    }
  in
  Log.debug (fun m -> m "%a" pp modul);
  modul

let find (named : 'a Named.t) err : _ -> Binary.indice Result.t = function
  | Text.Raw i -> Ok i
  | Text name -> (
    match String_map.find_opt name named.named with
    | None -> Error err
    | Some i -> Ok i )

let find_func modul id = find modul.func (`Unknown_func id) id

let find_global modul id = find modul.global (`Unknown_global id) id

let find_memory modul id = find modul.mem (`Unknown_memory id) id

let find_table modul id = find modul.table (`Unknown_table id) id

let find_data modul id = find modul.data (`Unknown_data id) id

let find_elem modul id = find modul.elem (`Unknown_elem id) id

let find_type modul id = find modul.typ (`Unknown_type id) id