File: check_runtime.ml

package info (click to toggle)
js-of-ocaml 5.9.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 32,020 kB
  • sloc: ml: 91,250; javascript: 57,289; ansic: 315; makefile: 271; lisp: 23; sh: 6; perl: 4
file content (165 lines) | stat: -rw-r--r-- 5,745 bytes parent folder | download
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
(* Js_of_ocaml compiler
 * http://www.ocsigen.org/js_of_ocaml/
 * Copyright (C) 2021 Hugo Heuzard
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, with linking exception;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *)

open! Js_of_ocaml_compiler.Stdlib
open Js_of_ocaml_compiler

let group_by_snd l =
  l
  |> List.sort_uniq ~compare:(fun (n1, l1) (n2, l2) ->
         match Poly.compare l1 l2 with
         | 0 -> String.compare n1 n2
         | c -> c)
  |> List.group ~f:(fun (_, g1) (_, g2) -> Poly.equal g1 g2)

let print_groups output l =
  List.iter l ~f:(fun group ->
      match group with
      | [] -> assert false
      | (_, loc) :: _ ->
          (match loc with
          | [] -> ()
          | loc ->
              output_string
                output
                (Printf.sprintf "\nFrom %s:\n" (String.concat ~sep:"," loc)));
          List.iter group ~f:(fun (name, _) ->
              output_string output (Printf.sprintf "%s\n" name)))

let f (runtime_files, bytecode, target_env) =
  Config.set_target `JavaScript;
  Linker.reset ();
  let runtime_files, builtin =
    List.partition_map runtime_files ~f:(fun name ->
        match Builtins.find name with
        | Some t -> `Snd t
        | None -> `Fst name)
  in
  let builtin =
    if false then builtin else Js_of_ocaml_compiler_runtime_files.runtime @ builtin
  in
  List.iter builtin ~f:(fun t ->
      let filename = Builtins.File.name t in
      let runtimes = Linker.Fragment.parse_builtin t in
      Linker.load_fragments ~target_env ~filename runtimes);
  Linker.load_files ~target_env runtime_files;
  Linker.check_deps ();
  let all_prims =
    List.concat_map bytecode ~f:(fun f ->
        let ic = open_in_bin f in
        let prims =
          match Parse_bytecode.from_channel ic with
          | `Cmo x -> x.Cmo_format.cu_primitives
          | `Cma x ->
              List.concat_map
                ~f:(fun x -> x.Cmo_format.cu_primitives)
                x.Cmo_format.lib_units
          | `Exe ->
              let toc = Parse_bytecode.Toc.read ic in
              Parse_bytecode.read_primitives toc ic
        in
        close_in ic;
        List.map ~f:(fun p -> p, f) prims)
  in
  let _percent_prim, needed =
    List.partition all_prims ~f:(fun (x, _) -> Char.equal (String.get x 0) '%')
  in
  let origin =
    List.fold_left
      ~f:(fun acc (x, y) ->
        let l = try StringMap.find x acc with Not_found -> [] in
        StringMap.add x (y :: l) acc)
      ~init:StringMap.empty
      needed
  in
  let needed = StringSet.of_list (List.map ~f:fst needed) in
  let from_runtime1 = Linker.list_all () in
  let from_runtime2 = Primitive.get_external () in
  (* [from_runtime2] is a superset of [from_runtime1].
     Extra primitives are registered on the ocaml side (e.g. generate.ml) *)
  assert (StringSet.is_empty (StringSet.diff from_runtime1 from_runtime2));
  let missing' = StringSet.diff needed from_runtime1 in
  let all_used, missing =
    let state = Linker.init () in
    let state, missing = Linker.resolve_deps state needed in
    StringSet.of_list (Linker.all state), missing
  in
  assert (StringSet.equal missing missing');
  let extra = StringSet.diff from_runtime1 all_used |> StringSet.elements in
  let extra =
    extra
    |> List.map ~f:(fun name ->
           ( (name ^ if Linker.deprecated ~name then " (deprecated)" else "")
           , match Linker.origin ~name with
             | None -> []
             | Some x -> [ x ] ))
    |> group_by_snd
  in

  let missing_for_real =
    StringSet.diff missing from_runtime2
    |> StringSet.elements
    |> List.map ~f:(fun x -> x, StringMap.find x origin)
    |> group_by_snd
  in

  let output = stdout in
  set_binary_mode_out output true;
  output_string output "Missing\n";
  output_string output "-------\n";
  print_groups output missing_for_real;
  output_string output "\n";
  output_string output "Unused\n";
  output_string output "-------\n";
  print_groups output extra;
  output_string output "\n";
  ()

let options =
  let open Cmdliner in
  (* TODO: add flags to only display missing or extra primitives *)
  let files =
    let doc = "Bytecode and JavaScript files [$(docv)]. " in
    Arg.(value & pos_all string [] & info [] ~docv:"FILES" ~doc)
  in
  let build_t files target_env =
    let runtime_files, bc_files =
      List.partition files ~f:(fun file -> Filename.check_suffix file ".js")
    in
    `Ok (runtime_files, bc_files, target_env)
  in
  let target_env =
    let doc = "Runtime compile target." in
    let options = List.map ~f:(fun env -> Target_env.to_string env, env) Target_env.all in
    let docv = Printf.sprintf "{%s}" (String.concat ~sep:"," (List.map ~f:fst options)) in
    Arg.(
      value & opt (enum options) Target_env.Isomorphic & info [ "target-env" ] ~docv ~doc)
  in
  let t = Term.(const build_t $ files $ target_env) in
  Term.ret t

let info =
  Info.make
    ~name:"check-runtime"
    ~doc:"Check runtime"
    ~description:"js_of_ocaml-check-runtime checks the runtime."

let command =
  let t = Cmdliner.Term.(const f $ options) in
  Cmdliner.Cmd.v info t