Source file benchmark_accumulator.ml

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
let unique_id =
  let r = ref 0 in
  fun () ->
    incr r;
    !r
;;

(* Used to track the current libname in such a way that for functor applications, it is
   the calling libraries name that gets registered. *)
module Current_libname = struct
  let null = "<unknown>"
  let libname_ref = ref null
  let set str = libname_ref := str
  let unset () = libname_ref := null
  let get () = !libname_ref
end

module Current_bench_module_stack = struct
  let t = ref []
  let push s = t := s :: !t
  let pop_exn () = t := List.tl !t

  let to_name () =
    match !t with
    | [] -> None
    | ms -> Some (String.concat "." (List.rev ms))
  ;;
end

(* This is the main data structure of this module. An [Entry.t] represents a benchmark
   along with some metadata about is position, arguments etc. *)
module Entry = struct
  type ('param, 'a) parameterised_spec =
    { arg_name : string
    ; params : (string * 'param) list
    ; thunk : 'param -> unit -> 'a
    }

  type test_spec =
    | Regular_thunk : ([ `init ] -> unit -> 'a) -> test_spec
    | Parameterised_thunk : ('param, 'a) parameterised_spec -> test_spec

  type t =
    { unique_id : int
    ; code : string
    ; type_conv_path : string
    ; name : string
    ; filename : string
    ; line : int
    ; startpos : int
    ; endpos : int
    ; test_spec : test_spec
    ; bench_module_name : string option
    }

  let compare t1 t2 = compare t1.unique_id t2.unique_id

  (* Extracts module name from ["filename.ml.Module"], which is the format of [ext_name]
     as set by [typeconv]. *)
  let get_module_name_opt t =
    let str = t.type_conv_path in
    let len = String.length str in
    let rec loop i =
      if i + 4 <= len
      then
        if String.sub str i 4 = ".ml."
        then Some (String.sub str (i + 4) (len - i - 4))
        else loop (i + 1)
      else None
    in
    loop 0
  ;;

  let with_test_spec t test_spec = { t with test_spec }
end

(* Inspect system environment variables to decide if benchmarks are being run. This is
   called by the code generated by the [pa_bench] syntax to decide if the global hashtable
   should be populated. *)
let add_environment_var =
  let v =
    try Sys.getenv "BENCHMARKS_RUNNER" with
    | Not_found -> ""
  in
  v = "TRUE"
;;

(* This hashtable contains all the benchmarks from all the of libraries that have been
   loaded. At the time the benchmarks are registering themselves with [ppx_bench_lib] we
   don't yet know which libraries will need to be run.  *)
let libs_to_entries : (string, Entry.t list) Hashtbl.t = Hashtbl.create 10

let lookup_rev_lib ~libname =
  try Hashtbl.find libs_to_entries libname with
  | Not_found -> []
;;

let lookup_lib ~libname = List.rev (lookup_rev_lib ~libname)

let force_drop =
  (* Useful for js_of_ocaml to perform deadcode elimination.
     see ppx/ppx_inline_test/runtime-lib/runtime.ml [Action.get] for more details *)
  try
    ignore (Sys.getenv "FORCE_DROP_BENCH" : string);
    true
  with
  | Not_found -> false
;;

let get_mode () = if force_drop then `Ignore else `Collect

let[@inline never] add_bench
  ~name
  ~code
  ~filename
  ~type_conv_path
  ~line
  ~startpos
  ~endpos
  test_spec
  =
  match get_mode () with
  | `Ignore -> ()
  | `Collect ->
    let libname = Current_libname.get () in
    let entry =
      { Entry.code
      ; unique_id = unique_id ()
      ; type_conv_path
      ; bench_module_name = Current_bench_module_stack.to_name ()
      ; name
      ; filename
      ; line
      ; startpos
      ; endpos
      ; test_spec
      }
    in
    Hashtbl.add libs_to_entries libname (entry :: lookup_rev_lib ~libname)
;;

let[@inline never] add_bench_module
  ~name
  ~code:_
  ~type_conv_path:_
  ~filename:_
  ~line:_
  ~startpos:_
  ~endpos:_
  f
  =
  match get_mode () with
  | `Ignore -> ()
  | `Collect ->
    (* Running f registers the benchmarks using BENCH *)
    Current_bench_module_stack.push name;
    (try
       f ();
       Current_bench_module_stack.pop_exn ()
     with
     | ex ->
       Current_bench_module_stack.pop_exn ();
       raise ex)
;;