source/service/dependencyTrackedMemory.ml (221 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
module Hashtbl = Caml.Hashtbl
module Set = Caml.Set
module EncodedDependency = struct
type t = int [@@deriving compare, sexp]
let to_string = Int.to_string
let of_string = Int.of_string
let increment encoded = encoded + 1
let make v ~hash =
let mask = (1 lsl 31) - 1 in
hash v land mask
module Table = Int.Table
end
module EncodedDependencySet = Core.Set.Make (EncodedDependency)
module DependencyGraph = struct
external hh_add_dep : int -> unit = "hh_add_dep"
external hh_get_dep : int -> int list = "hh_get_dep"
external hh_get_dep_sqlite : int -> int list = "hh_get_dep_sqlite"
external hh_allow_dependency_table_reads : bool -> bool = "hh_allow_dependency_table_reads"
external hh_assert_allow_dependency_table_reads
: unit ->
unit
= "hh_assert_allow_dependency_table_reads"
let hh_add_dep x = WorkerCancel.with_worker_exit (fun () -> hh_add_dep x)
let hh_get_dep x = WorkerCancel.with_worker_exit (fun () -> hh_get_dep x)
let add x y = hh_add_dep ((x lsl 31) lor y)
let get x =
hh_assert_allow_dependency_table_reads ();
let deps = EncodedDependencySet.empty in
let deps = List.fold_left ~init:deps ~f:EncodedDependencySet.add (hh_get_dep x) in
let deps = List.fold_left ~init:deps ~f:EncodedDependencySet.add (hh_get_dep_sqlite x) in
deps
end
(* This is not currently used, but I'd like to keep it in the module for
documentation/discoverability purposes *)
let _ = DependencyGraph.hh_allow_dependency_table_reads
type 'keyset transaction_element = {
before: unit -> unit;
after: unit -> 'keyset;
}
module DependencyKey = struct
module type S = sig
type key
type registered
module RegisteredSet : Set.S with type elt = registered
module KeySet : Set.S with type elt = key
val mark : registered -> depends_on:EncodedDependency.t -> unit
val query : EncodedDependency.t -> RegisteredSet.t
module Transaction : sig
type t
val empty : scheduler:Scheduler.t -> configuration:Configuration.Analysis.t -> t
val add : t -> RegisteredSet.t transaction_element -> t
val execute : t -> update:(unit -> 'a) -> 'a * RegisteredSet.t
val scheduler : t -> Scheduler.t
val configuration : t -> Configuration.Analysis.t
end
end
module type In = sig
type key [@@deriving compare, sexp]
type registered [@@deriving compare, sexp]
module RegisteredSet : Set.S with type elt = registered
module KeySet : Set.S with type elt = key
module Registry : sig
val encode : registered -> EncodedDependency.t
val decode : EncodedDependency.t -> registered list option
end
end
module Make (In : In) = struct
type key = In.key
type registered = In.registered
module KeySet = In.KeySet
module RegisteredSet = In.RegisteredSet
let mark registered ~depends_on = DependencyGraph.add depends_on (In.Registry.encode registered)
let query trigger =
DependencyGraph.get trigger
|> EncodedDependencySet.fold ~init:RegisteredSet.empty ~f:(fun sofar hash ->
match In.Registry.decode hash with
| Some keys ->
let add sofar key = RegisteredSet.add key sofar in
List.fold keys ~init:sofar ~f:add
| None -> sofar)
module Transaction = struct
type t = {
elements: RegisteredSet.t transaction_element list;
scheduler: Scheduler.t;
configuration: Configuration.Analysis.t;
}
let empty ~scheduler ~configuration = { elements = []; scheduler; configuration }
let add ({ elements = existing; _ } as transaction) element =
{ transaction with elements = element :: existing }
let execute { elements; _ } ~update =
List.iter elements ~f:(fun { before; _ } -> before ());
let update_result = update () in
let f sofar { after; _ } = RegisteredSet.union sofar (after ()) in
update_result, List.fold elements ~init:RegisteredSet.empty ~f
let scheduler { scheduler; _ } = scheduler
let configuration { configuration; _ } = configuration
end
end
end
module DependencyKind = struct
type t =
| Get
| Mem
end
module DependencyTracking = struct
module type TableType = sig
include Memory.NoCache.S
module Value : Memory.ComparableValueType with type t = value
end
module Make (DependencyKey : DependencyKey.S) (Table : TableType) = struct
let add_dependency
~(kind : DependencyKind.t)
(key : Table.key)
(value : DependencyKey.registered)
=
DependencyKey.mark
value
~depends_on:(EncodedDependency.make ~hash:Hashtbl.hash (Table.Value.prefix, key, kind))
let get_dependents ~(kind : DependencyKind.t) (key : Table.key) =
DependencyKey.query
(EncodedDependency.make ~hash:Hashtbl.hash (Table.Value.prefix, key, kind))
let get_all_dependents keys =
let keys = Table.KeySet.elements keys in
let init =
List.map keys ~f:(get_dependents ~kind:Get)
|> List.fold ~init:DependencyKey.RegisteredSet.empty ~f:DependencyKey.RegisteredSet.union
in
List.map keys ~f:(get_dependents ~kind:Mem)
|> List.fold ~init ~f:DependencyKey.RegisteredSet.union
let get ?dependency key =
Option.iter dependency ~f:(add_dependency key ~kind:Get);
Table.get key
let mem ?dependency key =
Option.iter dependency ~f:(add_dependency key ~kind:Mem);
Table.mem key
let deprecate_keys = Table.oldify_batch
let dependencies_since_last_deprecate keys ~scheduler:_ ~configuration:_ =
let add_dependencies init keys =
let add_dependency sofar key =
let value_has_changed, presence_has_changed =
match Table.get_old key, Table.get key with
| None, None -> false, false
| Some old_value, Some new_value ->
not (Int.equal 0 (Table.Value.compare old_value new_value)), false
| None, Some _
| Some _, None ->
true, true
in
let sofar =
if value_has_changed then
get_dependents ~kind:Get key |> DependencyKey.RegisteredSet.union sofar
else
sofar
in
if presence_has_changed then
get_dependents ~kind:Mem key |> DependencyKey.RegisteredSet.union sofar
else
sofar
in
List.fold ~f:add_dependency keys ~init
in
let dependencies =
add_dependencies DependencyKey.RegisteredSet.empty (Table.KeySet.elements keys)
in
Table.remove_old_batch keys;
dependencies
let add_to_transaction transaction ~keys =
let scheduler = DependencyKey.Transaction.scheduler transaction in
let configuration = DependencyKey.Transaction.configuration transaction in
DependencyKey.Transaction.add
transaction
{
before = (fun () -> deprecate_keys keys);
after = (fun () -> dependencies_since_last_deprecate keys ~scheduler ~configuration);
}
let add_pessimistic_transaction (transaction : DependencyKey.Transaction.t) ~keys =
DependencyKey.Transaction.add
transaction
{
before = (fun () -> Table.remove_batch keys);
after = (fun () -> get_all_dependents keys);
}
end
end
module DependencyTrackedTableWithCache
(Key : Memory.KeyType)
(DependencyKey : DependencyKey.S)
(Value : Memory.ComparableValueType) =
struct
module Table = Memory.WithCache.Make (Key) (Value)
include Table
include
DependencyTracking.Make
(DependencyKey)
(struct
include Table
module Value = Value
end)
end
module DependencyTrackedTableNoCache
(Key : Memory.KeyType)
(DependencyKey : DependencyKey.S)
(Value : Memory.ComparableValueType) =
struct
module Table = Memory.NoCache.Make (Key) (Value)
include Table
include
DependencyTracking.Make
(DependencyKey)
(struct
include Table
module Value = Value
end)
end