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
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  = File_path.get_default_path loc 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

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 begin
          if false then Ppx_bench_lib.Export.ignore (f `init ()) else ();
          Ppx_bench_lib.Benchmark_accumulator.Entry.Regular_thunk f
        end
      ]
  | Some (var_name, args) ->
    apply_to_descr_bench path "add_bench" loc (Some e) name
      [%expr
        let arg_values = [%e args]
        and f = fun [%p pvar ~loc var_name] -> [%e thunk_bench kind e] in begin
          if false then Ppx_bench_lib.Export.ignore (f 0 ()) else ();
          Ppx_bench_lib.Benchmark_accumulator.Entry.Indexed_thunk
            { Ppx_bench_lib.Benchmark_accumulator.Entry.arg_name =
                [%e estring ~loc var_name]
            ; Ppx_bench_lib.Benchmark_accumulator.Entry.arg_values
            ; Ppx_bench_lib.Benchmark_accumulator.Entry.thunk = f
            }
        end
      ]

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 -> (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:(Attribute.pattern indexed (pstring __)) ~expr:__
             ^:: 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
                                         ~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.