package ppx_bench

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file ppx_bench.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
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
open Ppxlib
open Ast_builder.Default

type maybe_drop =
  | Keep
  | Deadcode
  | Remove

let drop_benches = ref Keep

let () =
  Driver.add_arg
    "-bench-drop"
    (Unit (fun () -> drop_benches := Remove))
    ~doc:" Drop inline benchmarks";
  Driver.add_arg
    "-bench-drop-with-deadcode"
    (Unit (fun () -> drop_benches := Deadcode))
    ~doc:
      " Drop inline benchmarks by wrapping them inside deadcode to prevent unused \
       variable warnings."
;;

let () =
  Driver.Cookies.add_simple_handler
    "inline-bench"
    Ast_pattern.(pexp_ident (lident __'))
    ~f:(function
      | None -> ()
      | Some id ->
        (match id.txt with
         | "drop" -> drop_benches := Remove
         | "drop_with_deadcode" -> drop_benches := Deadcode
         | s ->
           Location.raise_errorf
             ~loc:id.loc
             "invalid 'inline-bench' cookie (%s), expected one of: drop, \
              drop_with_deadcode"
             s))
;;

let maybe_drop loc code =
  match !drop_benches with
  | Keep -> [%str let () = [%e code]]
  | Deadcode -> [%str let () = if false then [%e code] else ()]
  | Remove ->
    Attribute.explicitly_drop#expression code;
    [%str]
;;

let descr (loc : Location.t) ?(inner_loc = loc) () =
  let filename = loc.loc_start.pos_fname in
  let line = loc.loc_start.pos_lnum in
  let start_pos = loc.loc_start.pos_cnum - loc.loc_start.pos_bol in
  let end_pos = inner_loc.Location.loc_end.pos_cnum - loc.loc_start.pos_bol in
  estring ~loc filename, eint ~loc line, eint ~loc start_pos, eint ~loc end_pos
;;

let apply_to_descr_bench
  type_conv_path
  lid
  loc
  ?inner_loc
  e_opt
  ?name_suffix
  name
  more_arg
  =
  let filename, line, start_pos, end_pos = descr loc ?inner_loc () in
  let s =
    match e_opt with
    | None -> ""
    | Some e -> Pprintast.string_of_expression e
  in
  let descr = estring ~loc s in
  let name =
    let base_name = estring ~loc name in
    match name_suffix with
    | None -> base_name
    | Some name_suffix -> [%expr [%e base_name] ^ [%e name_suffix]]
  in
  let type_conv_path = estring ~loc type_conv_path in
  maybe_drop
    loc
    [%expr
      if Ppx_bench_lib.Benchmark_accumulator.add_environment_var
      then
        [%e evar ~loc ("Ppx_bench_lib.Benchmark_accumulator." ^ lid)]
          ~name:[%e name]
          ~code:[%e descr]
          ~type_conv_path:[%e type_conv_path]
          ~filename:[%e filename]
          ~line:[%e line]
          ~startpos:[%e start_pos]
          ~endpos:[%e end_pos]
          [%e more_arg]]
;;

type bench_kind =
  | Bench
  | Bench_fun

type arg_kind =
  | Indexed of (string * expression)
  | Parameterised of (string * expression)

let thunk_bench kind e =
  match kind with
  | Bench_fun -> e
  | Bench ->
    let loc = { e.pexp_loc with loc_ghost = true } in
    [%expr fun () -> [%e e]]
;;

let enabled () =
  match Ppx_inline_test_libname.get () with
  | None -> false
  | Some _ -> true
;;

let assert_enabled loc =
  if not (enabled ())
  then
    Location.raise_errorf
      ~loc
      "ppx_bench: extension is disabled as no -inline-test-lib was given"
;;

let expand_bench_exp ~loc ~path kind index name e =
  let loc = { loc with loc_ghost = true } in
  assert_enabled loc;
  match index with
  | None ->
    (* Here and in the other cases below, because functions given to pa_bench can return
       any 'a, we add a dead call to ignore so we can get a warning if the user code
       mistakenly gives a partial application. *)
    apply_to_descr_bench
      path
      "add_bench"
      loc
      (Some e)
      name
      [%expr
        let f `init = [%e thunk_bench kind e] in
        if false then Ppx_bench_lib.Export.ignore (f `init ()) else ();
        Ppx_bench_lib.Benchmark_accumulator.Entry.Regular_thunk f]
  | Some (Indexed (var_name, args)) ->
    apply_to_descr_bench
      path
      "add_bench"
      loc
      (Some e)
      name
      [%expr
        let arg_values = [%e args]
        and f [%p pvar ~loc var_name] = [%e thunk_bench kind e] in
        if false then Ppx_bench_lib.Export.ignore (f 0 ()) else ();
        Ppx_bench_lib.Benchmark_accumulator.Entry.Parameterised_thunk
          { Ppx_bench_lib.Benchmark_accumulator.Entry.arg_name =
              [%e estring ~loc var_name]
          ; Ppx_bench_lib.Benchmark_accumulator.Entry.params =
              (* We use Stdlib.* because this might run without any opens. *)
              Stdlib.List.map
                (fun i -> Stdlib.string_of_int i, i)
                arg_values [@warning "-3"]
          ; Ppx_bench_lib.Benchmark_accumulator.Entry.thunk = f
          }]
  | Some (Parameterised (var_name, args)) ->
    apply_to_descr_bench
      path
      "add_bench"
      loc
      (Some e)
      name
      [%expr
        let params = [%e args]
        and f [%p pvar ~loc var_name] = [%e thunk_bench kind e] in
        if false
        then Ppx_bench_lib.Export.ignore (f (List.hd_exn params |> snd) ())
        else ();
        Ppx_bench_lib.Benchmark_accumulator.Entry.Parameterised_thunk
          { Ppx_bench_lib.Benchmark_accumulator.Entry.arg_name =
              [%e estring ~loc var_name]
          ; Ppx_bench_lib.Benchmark_accumulator.Entry.params
          ; Ppx_bench_lib.Benchmark_accumulator.Entry.thunk = f
          }]
;;

let expand_bench_module ~loc ~path name_suffix name m =
  let loc = { loc with loc_ghost = true } in
  assert_enabled loc;
  apply_to_descr_bench
    path
    "add_bench_module"
    loc
    ~inner_loc:m.pmod_loc
    None
    ?name_suffix
    name
    (pexp_fun
       ~loc
       Nolabel
       None
       (punit ~loc)
       (pexp_letmodule ~loc (Located.mk ~loc (Some "M")) m (eunit ~loc)))
;;

module E = struct
  let indexed =
    Attribute.declare
      "bench.indexed"
      Attribute.Context.pattern
      Ast_pattern.(
        single_expr_payload
          (pexp_apply
             (pexp_ident (lident (string "=")))
             (no_label (pexp_ident (lident __)) ^:: no_label __ ^:: nil)))
      (fun var values -> Indexed (var, values))
  ;;

  let parameterised =
    Attribute.declare
      "bench.params"
      Attribute.Context.pattern
      Ast_pattern.(
        single_expr_payload
          (pexp_apply
             (pexp_ident (lident (string "=")))
             (no_label (pexp_ident (lident __)) ^:: no_label __ ^:: nil)))
      (fun var values -> Parameterised (var, values))
  ;;

  let name_suffix =
    Attribute.declare
      "bench.name_suffix"
      Attribute.Context.pattern
      Ast_pattern.(single_expr_payload __)
      (fun a -> a)
  ;;

  let simple =
    let open Ast_pattern in
    pstr
      (pstr_value
         nonrecursive
         (value_binding
            ~pat:
              (alt
                 (Attribute.pattern indexed (pstring __))
                 (Attribute.pattern parameterised (pstring __)))
            ~expr:__
            ~constraint_:none
          ^:: nil)
       ^:: nil)
  ;;

  let bench =
    Extension.declare_inline
      "bench"
      Extension.Context.structure_item
      simple
      (expand_bench_exp Bench)
  ;;

  let bench_fun =
    Extension.declare_inline
      "bench_fun"
      Extension.Context.structure_item
      simple
      (expand_bench_exp Bench_fun)
  ;;

  let bench_module =
    Extension.declare_inline
      "bench_module"
      Extension.Context.structure_item
      Ast_pattern.(
        pstr
          (pstr_value
             nonrecursive
             (value_binding
                ~constraint_:drop
                ~pat:(Attribute.pattern name_suffix (pstring __))
                ~expr:(pexp_pack __)
              ^:: nil)
           ^:: nil))
      expand_bench_module
  ;;

  let all = [ bench; bench_fun; bench_module ]
end

let () =
  Driver.register_transformation "bench" ~extensions:E.all ~enclose_impl:(fun loc ->
    match loc, Ppx_inline_test_libname.get () with
    | None, _ | _, None -> [], []
    | Some loc, Some (libname, _) ->
      let loc = { loc with loc_ghost = true } in
      (* See comment in benchmark_accumulator.ml *)
      let header =
        let loc = { loc with loc_end = loc.loc_start } in
        maybe_drop
          loc
          [%expr
            Ppx_bench_lib.Benchmark_accumulator.Current_libname.set
              [%e estring ~loc libname]]
      and footer =
        let loc = { loc with loc_start = loc.loc_end } in
        maybe_drop
          loc
          [%expr Ppx_bench_lib.Benchmark_accumulator.Current_libname.unset ()]
      in
      header, footer)
;;
OCaml

Innovation. Community. Security.