upstream: https://github.com/janestreet/memtrace
1open Buf
2
3(* Move-to-front codec *)
4module Mtf_table : sig
5 type 'a t
6
7 val create : unit -> 'a t
8
9 type index = int
10
11 val not_found : index
12 val encode : 'a t -> if_absent:(unit -> 'a) -> string -> index * 'a
13 val decode : 'a t -> if_absent:(unit -> string * 'a) -> index -> string * 'a
14 val last : 'a t -> 'a option
15end = struct
16 type 'a entry = Empty | Full of string * 'a
17 type 'a t = 'a entry array
18
19 let length = 31
20 let create () = Array.make length Empty
21
22 type index = int
23
24 let not_found = length
25
26 let swap mtf i =
27 assert (i <> not_found);
28 let e = match mtf.(i) with Empty -> assert false | Full _ as e -> e in
29 Array.blit mtf 0 mtf 1 i;
30 mtf.(0) <- e
31
32 let push mtf k v =
33 Array.blit mtf 0 mtf 1 (length - 1);
34 mtf.(0) <- Full (k, v)
35
36 let encode mtf ~if_absent x =
37 let rec go mtf x i =
38 if i = length then (
39 let v = if_absent () in
40 push mtf x v;
41 (not_found, v))
42 else
43 match mtf.(i) with
44 | Empty ->
45 let v = if_absent () in
46 push mtf x v;
47 (not_found, v)
48 | Full (k, v) when String.equal k x ->
49 swap mtf i;
50 (i, v)
51 | Full _ -> go mtf x (i + 1)
52 in
53 go mtf x 0
54
55 let decode mtf ~if_absent i =
56 if i = not_found then (
57 let ((k, v) as kv) = if_absent () in
58 push mtf k v;
59 kv)
60 else (
61 swap mtf i;
62 match mtf.(0) with Empty -> assert false | Full (k, v) -> (k, v))
63
64 let last mtf =
65 match mtf.(length - 1) with Empty -> None | Full (_, v) -> Some v
66end
67
68(** Source locations *)
69module Location = struct
70 type t = {
71 filename : string;
72 line : int;
73 start_char : int;
74 end_char : int;
75 defname : string;
76 }
77
78 let to_string { filename; line; start_char; end_char; defname } =
79 Fmt.str "%s@%s:%d:%d-%d" defname filename line start_char end_char
80
81 let unknown =
82 {
83 filename = "<unknown>";
84 line = 1;
85 start_char = 1;
86 end_char = 1;
87 defname = "??";
88 }
89end
90
91type state = unit Mtf_table.t Mtf_table.t
92
93module Writer = struct
94 open Buf.Write
95
96 type t = state
97
98 let create () = Mtf_table.create ()
99 let max_length = 4 * 1024
100
101 let put_location (file_mtf : t) b (id, locs) =
102 let total_size_max =
103 (* Worst-case size, assuming no MTF hits *)
104 List.fold_left
105 (fun sz (loc : Location.t) ->
106 sz + 6
107 + (String.length loc.filename + 1)
108 + (String.length loc.defname + 1))
109 (8 + 1) locs
110 in
111 let no_truncation =
112 List.length locs <= 255 && total_size_max <= max_length
113 in
114 let locs = if no_truncation then locs else [ Location.unknown ] in
115 let start_pos = b.Write.pos in
116 put_64 b (Int64.of_int id);
117 put_8 b (List.length locs);
118 locs
119 |> List.iter (fun (loc : Location.t) ->
120 let clamp n lim = if n < 0 || n > lim then lim else n in
121 let line_number =
122 (* 20 bits *)
123 clamp loc.line 0xfffff
124 in
125 let start_char =
126 (* 8 bits *)
127 clamp loc.start_char 0xff
128 in
129 let end_char =
130 (* 10 bits *)
131 clamp loc.end_char 0x3ff
132 in
133 let filename_code, defn_mtf =
134 Mtf_table.encode file_mtf ~if_absent:Mtf_table.create loc.filename
135 in
136 let defname_code, () =
137 Mtf_table.encode defn_mtf ~if_absent:(fun () -> ()) loc.defname
138 in
139 let encoded =
140 Int64.(
141 logor (of_int line_number)
142 (logor
143 (shift_left (of_int start_char) 20)
144 (logor
145 (shift_left (of_int end_char) (20 + 8))
146 (logor
147 (shift_left
148 (of_int (filename_code :> int))
149 (20 + 8 + 10))
150 (shift_left
151 (of_int (defname_code :> int))
152 (20 + 8 + 10 + 5))))))
153 in
154 put_32 b (Int64.to_int32 encoded);
155 put_16 b Int64.(to_int (shift_right encoded 32));
156 if filename_code = Mtf_table.not_found then put_string b loc.filename;
157 if defname_code = Mtf_table.not_found then put_string b loc.defname);
158 if no_truncation then assert (b.pos - start_pos <= total_size_max)
159end
160
161module Reader = struct
162 open Buf.Read
163
164 type t = state
165
166 let create () = Mtf_table.create ()
167
168 let get_location (file_mtf : t) b =
169 let id = Int64.to_int (get_64 b) in
170 let nlocs = get_8 b in
171 let locs =
172 List.init nlocs (fun _ ->
173 let low = get_32 b in
174 let high = get_16 b in
175 let encoded =
176 Int64.(
177 logor
178 (shift_left (of_int high) 32)
179 (logand (of_int32 low) 0xffffffffL))
180 in
181 let line, start_char, end_char, filename_code, defname_code =
182 Int64.
183 ( to_int (logand 0xfffffL encoded),
184 to_int (logand 0xffL (shift_right encoded 20)),
185 to_int (logand 0x3ffL (shift_right encoded (20 + 8))),
186 to_int (logand 0x1fL (shift_right encoded (20 + 8 + 10))),
187 to_int (logand 0x1fL (shift_right encoded (20 + 8 + 10 + 5))) )
188 in
189 let filename, defn_mtf =
190 Mtf_table.decode file_mtf
191 ~if_absent:(fun () ->
192 let s = get_string b in
193 (* Reuse the defname MTF table that's about to be pushed off.
194 This is only present to match a bug in the v001 encoder,
195 which sometimes generated traces relying on this behaviour.
196 The current encoder never relies on this, so once v001
197 trace files stop mattering, this match can be deleted *)
198 let d =
199 match Mtf_table.last file_mtf with
200 | Some v -> v
201 | None -> Mtf_table.create ()
202 in
203 (s, d))
204 filename_code
205 in
206 let defname, () =
207 Mtf_table.decode defn_mtf
208 ~if_absent:(fun () ->
209 let s = get_string b in
210 (s, ()))
211 defname_code
212 in
213 { Location.line; start_char; end_char; filename; defname })
214 in
215 (id, locs)
216end