upstream: https://github.com/janestreet/memtrace
at main 216 lines 6.3 kB view raw
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