d3a25d37创建于 2024年6月4日历史提交
(* Compression algorithm is slightly simplified version of
   external/memtrace/backtrace_codec.ml
   (callstacks and backtraces are essentially the same) *)

open Core

type t =
  { symbol_to_id : (Symbol.t, int) Hashtbl.t
  ; id_to_symbol : (int, Symbol.t) Hashtbl.t
  ; mutable next_symbol_id : int
  ; follow_table : (int, int) Hashtbl.t
  }

let init () =
  { symbol_to_id = Hashtbl.create (module Symbol)
  ; id_to_symbol = Hashtbl.create (module Int)
  ; next_symbol_id = 0
  ; follow_table = Hashtbl.create (module Int)
  }
;;

type compression_event =
  { new_symbols : (Symbol.t * int) list
  ; callstack : (int * int) list
  }
[@@deriving sexp, bin_io]

let update_follow_table t stack =
  (let%bind.Option suffix = List.tl stack in
   let%map.Option prefix = List.drop_last stack in
   List.iter2_exn prefix suffix ~f:(fun prev next ->
     Hashtbl.set t.follow_table ~key:prev ~data:next))
  |> Option.value ~default:()
;;

let compress_with_follow_table t stack =
  let compressed_stack =
    List.fold stack ~init:[] ~f:(fun acc x ->
      match acc with
      | [] -> [ x, x, 0 ]
      | (from_s, to_s, steps) :: tl ->
        let pred_next = Hashtbl.find t.follow_table to_s in
        (match pred_next with
         | Some a when a = x -> (from_s, x, steps + 1) :: tl
         | _ -> (x, x, 0) :: acc))
    |> List.rev
  in
  List.map compressed_stack ~f:(fun (from_s, _, steps) -> from_s, steps)
;;

let rec follow_symbol_steps t symbol = function
  | 0 -> []
  | steps ->
    let next_symbol = Hashtbl.find_exn t.follow_table symbol in
    next_symbol :: follow_symbol_steps t next_symbol (steps - 1)
;;

let decompress_with_follow_table t comp_stack =
  List.concat_map comp_stack ~f:(fun (from_s, steps) ->
    from_s :: follow_symbol_steps t from_s steps)
;;

let compress_callstack t callstack =
  let symbols = callstack in
  let new_symbol_events =
    List.filter_map symbols ~f:(fun s ->
      match Hashtbl.mem t.symbol_to_id s with
      | true -> None
      | false ->
        Hashtbl.add_exn t.symbol_to_id ~key:s ~data:t.next_symbol_id;
        t.next_symbol_id <- t.next_symbol_id + 1;
        Some (s, t.next_symbol_id - 1))
  in
  let symbol_ids = List.map symbols ~f:(fun s -> Hashtbl.find_exn t.symbol_to_id s) in
  let compressed_stack = compress_with_follow_table t symbol_ids in
  update_follow_table t symbol_ids;
  { new_symbols = new_symbol_events; callstack = compressed_stack }
;;

let decompress_callstack t { new_symbols; callstack } =
  List.iter new_symbols ~f:(fun (symbol, id) ->
    Hashtbl.set t.id_to_symbol ~key:id ~data:symbol);
  let symbol_ids = decompress_with_follow_table t callstack in
  update_follow_table t symbol_ids;
  List.map symbol_ids ~f:(fun s -> Hashtbl.find_exn t.id_to_symbol s)
;;