src/state/heaps/parsing/parsing_heaps.ml (1,172 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 Utils_js
module Heap = SharedMem.NewAPI
module MSet = Modulename.Set
module FileHeap =
SharedMem.NoCacheAddr
(File_key)
(struct
type t = Heap.file
end)
module FileModuleHeap =
SharedMem.NoCacheAddr
(File_key)
(struct
type t = Heap.file_module
end)
module HasteModuleHeap =
SharedMem.NoCacheAddr
(StringKey)
(struct
type t = Heap.haste_module
end)
exception File_not_found of string
exception File_not_parsed of string
exception File_not_typed of string
exception Ast_not_found of string
exception ALoc_table_not_found of string
exception Docblock_not_found of string
exception Requires_not_found of string
exception Type_sig_not_found of string
exception Haste_module_not_found of string
exception File_module_not_found of string
exception Resolved_requires_not_found of string
type locs_tbl = Loc.t Type_sig_collections.Locs.t
type type_sig = Type_sig_collections.Locs.index Packed_type_sig.Module.t
type file_addr = Heap.file SharedMem.addr
type +'a parse_addr = 'a Heap.parse SharedMem.addr
type haste_module_addr = Heap.haste_module SharedMem.addr
type file_module_addr = Heap.file_module SharedMem.addr
type provider_addr = Heap.file Heap.entity SharedMem.addr
type resolved_requires = {
resolved_modules: Modulename.t SMap.t;
phantom_dependencies: SSet.t;
hash: Xx.hash;
}
[@@deriving show]
let ( let* ) = Option.bind
let mk_resolved_requires ~resolved_modules ~phantom_dependencies =
let state = Xx.init 0L in
SMap.iter
(fun reference modulename ->
Xx.update state reference;
Xx.update state (Modulename.to_string modulename))
resolved_modules;
SSet.iter (Xx.update state) phantom_dependencies;
{ resolved_modules; phantom_dependencies; hash = Xx.digest state }
(* There's some redundancy in the visitors here, but an attempt to avoid repeated code led,
* inexplicably, to a shared heap size regression under types-first: D15481813 *)
let loc_compactifier =
object (this)
inherit [Loc.t, Loc.t, RelativeLoc.t, RelativeLoc.t] Flow_polymorphic_ast_mapper.mapper
method private compactify_loc loc = RelativeLoc.of_loc loc
method on_loc_annot = this#compactify_loc
method on_type_annot = this#compactify_loc
end
let compactify_loc ast = loc_compactifier#program ast
let loc_decompactifier source =
object (this)
inherit [RelativeLoc.t, RelativeLoc.t, Loc.t, Loc.t] Flow_polymorphic_ast_mapper.mapper
method private decompactify_loc loc = RelativeLoc.to_loc loc source
method on_loc_annot = this#decompactify_loc
method on_type_annot = this#decompactify_loc
end
let decompactify_loc file ast = (loc_decompactifier (Some file))#program ast
let file_kind_and_name = function
| File_key.Builtins -> invalid_arg "builtins"
| File_key.SourceFile f -> (Heap.Source_file, f)
| File_key.ResourceFile f -> (Heap.Resource_file, f)
| File_key.JsonFile f -> (Heap.Json_file, f)
| File_key.LibFile f -> (Heap.Lib_file, f)
let get_file_addr = FileHeap.get
let get_file_addr_unsafe file =
match get_file_addr file with
| Some addr -> addr
| None -> raise (File_not_found (File_key.to_string file))
let get_haste_module = HasteModuleHeap.get
let get_file_module = FileModuleHeap.get
let get_haste_module_unsafe name =
match get_haste_module name with
| Some addr -> addr
| None -> raise (Haste_module_not_found name)
let get_file_module_unsafe key =
match get_file_module key with
| Some addr -> addr
| None -> raise (File_module_not_found (File_key.to_string key))
let get_provider_ent = function
| Modulename.String name ->
let* haste_module = get_haste_module name in
Some (Heap.get_haste_provider haste_module)
| Modulename.Filename file_key ->
let* file_module = get_file_module file_key in
Some (Heap.get_file_provider file_module)
let haste_modulename m = Modulename.String (Heap.read_string (Heap.get_haste_name m))
let prepare_add_file_module_maybe size file_key =
match file_key with
| File_key.LibFile _ -> (size, Fun.const None)
| _ ->
let file_module_key = Files.chop_flow_ext file_key in
(match FileModuleHeap.get file_module_key with
| Some _ as addr -> (size, Fun.const addr)
| None ->
let open Heap in
let size = size + (2 * header_size) + file_module_size + entity_size in
let write chunk =
let provider = write_entity chunk None in
let m = write_file_module chunk provider in
Some (FileModuleHeap.add file_module_key m)
in
(size, write))
let prepare_add_haste_module_maybe size = function
| None -> (size, Fun.const None)
| Some name ->
(match HasteModuleHeap.get name with
| Some _ as addr -> (size, Fun.const addr)
| None ->
let open Heap in
let size = size + (3 * header_size) + haste_module_size + string_size name + entity_size in
let write chunk =
let heap_name = write_string chunk name in
let provider = write_entity chunk None in
let m = write_haste_module chunk heap_name provider in
Some (HasteModuleHeap.add name m)
in
(size, write))
(* Calculate the set of dirty modules and prepare those modules to be committed.
*
* If this file became a provider to a haste/file module, we add this file to
* the module's "all providers" list and mark the module as dirty.
*
* If this file no longer providers a haste/file module, we do not remove the
* file now, to avoid complexity around concurrent deletion. Instead, old
* providers are "logically" deleted, the module is marked as dirty, and we
* perform deferred deletions during commit_modules.
*
* We also mark modules as dirty even if the module itself does not need to be
* committed -- that is, we do not need to pick a new provider. A module is also
* considered dirty if the provider file's contents have changed.
*
* TODO: Regarding the above, we might profitably separate these two notions of
* dirtiness! We can skip re-picking a provider for modules which keep the same
* provider, but we still need to re-check its dependents.
*)
let calc_dirty_modules file_key file parse old_haste_module new_haste_module new_file_module =
let open Heap in
let dirty_modules =
match (old_haste_module, new_haste_module) with
| (None, None) -> MSet.empty
| (None, Some m) ->
add_haste_provider m file parse;
MSet.singleton (haste_modulename m)
| (Some m, None) -> MSet.singleton (haste_modulename m)
| (Some old_m, Some new_m) ->
if haste_modules_equal old_m new_m then
(* Changing `file` does not cause `new_m`'s provider to be re-picked,
* but the module is still dirty because `file` changed. (see TODO) *)
MSet.singleton (haste_modulename new_m)
else (
add_haste_provider new_m file parse;
MSet.empty |> MSet.add (haste_modulename old_m) |> MSet.add (haste_modulename new_m)
)
in
Option.iter (fun m -> add_file_provider m file) new_file_module;
(* Changing `file` does not cause the eponymous module's provider to be
* re-picked, but it is still dirty because `file` changed. (see TODO) *)
MSet.add (Files.eponymous_module file_key) dirty_modules
(* Write parsed data for checked file to shared memory. If we loaded from saved
* state, a checked file entry will already exist without parse data and this
* function will update the existing entry in place. Otherwise, we will create a
* new entry and add it to the shared hash table. *)
let add_checked_file file_key hash module_name docblock ast locs type_sig file_sig exports =
let open Type_sig_collections in
let serialize x = Marshal.to_string x [] in
let ast = serialize (compactify_loc ast) in
let docblock = serialize docblock in
let file_sig = serialize file_sig in
let aloc_table = Packed_locs.pack (Locs.length locs) (fun f -> Locs.iter f locs) in
let (sig_bsize, write_sig) = Type_sig_bin.write type_sig in
let (file_sig_size, write_file_sig) = Heap.prepare_write_file_sig file_sig in
let (ast_size, write_ast) = Heap.prepare_write_ast ast in
let open Heap in
let size =
(5 * header_size)
+ ast_size
+ docblock_size docblock
+ aloc_table_size aloc_table
+ type_sig_size sig_bsize
+ file_sig_size
in
let unchanged_or_fresh_parse =
match FileHeap.get file_key with
| Some file ->
let parse_ent = get_parse file in
let write new_file_module _ parse =
entity_advance parse_ent (Some parse);
(file, new_file_module)
in
(* If we loaded from a saved state, we will have some existing data with a
* matching hash. In this case, we want to update the existing data with
* parse information. *)
(match entity_read_latest parse_ent with
| None ->
let new_file_module = get_file_module file in
Either.Right (size, write new_file_module, None)
| Some existing_parse ->
let existing_hash = read_int64 (get_file_hash existing_parse) in
if Int64.equal existing_hash hash then
(* We know that file is typed (we are in add_checked_file) and the
* existing record's hash matches, so the file must have been typed
* before as well. *)
Either.Left (Option.get (coerce_typed existing_parse))
else
(* Because a parsed file record already existed, a file module
* certainly also exists and the file is already a provider. *)
let new_file_module = None in
Either.Right (size, write new_file_module, get_haste_module existing_parse))
| None ->
let (file_kind, file_name) = file_kind_and_name file_key in
let size = size + (3 * header_size) + entity_size + string_size file_name + file_size in
let (size, add_file_module_maybe) = prepare_add_file_module_maybe size file_key in
let write chunk parse =
let file_name = write_string chunk file_name in
let file_module = add_file_module_maybe chunk in
let parse_ent = write_entity chunk (Some parse) in
let file = write_file chunk file_kind file_name file_module parse_ent in
assert (file = FileHeap.add file_key file);
(file, file_module)
in
Either.Right (size, write, None)
in
let (size, add_file_maybe) =
match unchanged_or_fresh_parse with
| Either.Left unchanged_parse -> (size, Fun.const (unchanged_parse, MSet.empty))
| Either.Right (size, add_file_maybe, old_haste_module) ->
let exports = serialize exports in
let (exports_size, write_exports) = prepare_write_exports exports in
let size =
size + (4 * header_size) + typed_parse_size + int64_size + exports_size + entity_size
in
let (size, add_haste_module_maybe) = prepare_add_haste_module_maybe size module_name in
let write chunk =
let hash = write_int64 chunk hash in
let haste_module = add_haste_module_maybe chunk in
let exports = write_exports chunk in
let resolved_requires = write_entity chunk None in
let parse = write_typed_parse chunk hash haste_module exports resolved_requires in
let (file, new_file_module) =
add_file_maybe chunk (parse :> [ `typed | `untyped ] parse_addr)
in
let dirty_modules =
calc_dirty_modules file_key file parse old_haste_module haste_module new_file_module
in
(parse, dirty_modules)
in
(size, write)
in
alloc size (fun chunk ->
let (parse, dirty_modules) = add_file_maybe chunk in
let ast = write_ast chunk in
let docblock = write_docblock chunk docblock in
let aloc_table = write_aloc_table chunk aloc_table in
let type_sig = write_type_sig chunk sig_bsize write_sig in
let file_sig = write_file_sig chunk in
set_ast parse ast;
set_docblock parse docblock;
set_aloc_table parse aloc_table;
set_type_sig parse type_sig;
set_file_sig parse file_sig;
dirty_modules
)
let add_unparsed_file file_key hash module_name =
let open Heap in
let size = (2 * header_size) + untyped_parse_size + int64_size in
let (size, add_haste_module_maybe) = prepare_add_haste_module_maybe size module_name in
let (size, add_file_maybe, old_haste_module) =
match FileHeap.get file_key with
| Some file ->
let parse_ent = get_parse file in
let write new_file_module _ parse =
entity_advance parse_ent (Some parse);
(file, new_file_module)
in
(match entity_read_latest parse_ent with
| None ->
let new_file_module = get_file_module file in
(size, write new_file_module, None)
| Some parse ->
(* Because a parsed file record already existed, a file module
* certainly also exists and the file is already a provider. *)
let new_file_module = None in
(size, write new_file_module, get_haste_module parse))
| None ->
let (file_kind, file_name) = file_kind_and_name file_key in
let size = size + (3 * header_size) + entity_size + string_size file_name + file_size in
let (size, add_file_module_maybe) = prepare_add_file_module_maybe size file_key in
let write chunk parse =
let file_name = write_string chunk file_name in
let file_module = add_file_module_maybe chunk in
let parse_ent = write_entity chunk (Some parse) in
let file = write_file chunk file_kind file_name file_module parse_ent in
assert (file = FileHeap.add file_key file);
(file, file_module)
in
(size, write, None)
in
alloc size (fun chunk ->
let hash = write_int64 chunk hash in
let haste_module = add_haste_module_maybe chunk in
let parse = write_untyped_parse chunk hash haste_module in
let (file, new_file_module) =
add_file_maybe chunk (parse :> [ `typed | `untyped ] parse_addr)
in
calc_dirty_modules file_key file parse old_haste_module haste_module new_file_module
)
(* If this file used to exist, but no longer does, then it was deleted. Record
* the deletion by clearing parse information. Deletion might also require
* re-picking module providers, so we return dirty modules. *)
let clear_file file_key =
let open Heap in
match FileHeap.get file_key with
| None -> MSet.empty
| Some file ->
let parse_ent = get_parse file in
(match entity_read_latest parse_ent with
| None -> MSet.empty
| Some parse ->
entity_advance parse_ent None;
let dirty_modules =
match get_file_module file with
| None -> MSet.empty
| Some _ -> MSet.singleton (Files.eponymous_module file_key)
in
(match get_haste_module parse with
| None -> dirty_modules
| Some m -> MSet.add (haste_modulename m) dirty_modules))
(* Rolling back a transaction requires that we undo changes we made to the file
* as well as changes we made to modules affected by the file changes. Rolling
* back changes to all_providers in particular is kind of tricky...
*
* If we added a file to a module's all_providers, we remove it. This case is
* relatively simple.
*
* Recall that deletions are deferred. During parsing, we "logically" delete by
* changing the file object itself and marking a module dirty. Later we perform
* the deletions when re-picking a new provider for each dirty module.
*
* For a file module (M), a provider file (F) is logically deleted if its parse
* entity's (E) latest data is null, meaning the file is deleted:
*
* +---+---+---+ +---+---+---+
* | M | * |...| | E | 0 |...|
* +---+---+---+ +---+---+---+
* | ^ ^
* providers | |
* | parse +- latest data (null)
* v |
* +---+---+---+---+
* | F | | * |...|
* +---+---+---+---+
*
* For a haste module (M), a provider (F) is logically deleted if its parse
* entity's (E) latest data (P) no longer points back to the haste module. In
* this case, the list continues from the committed parse data (P'):
*
* +---+---+---+ +---+---+---+ +---+---+---+
* | M | * |...| | E | * | * |--->| P'| M | * |---> next provider
* +---+---+---+ +---+---+---+ +---+---+---+
* | ^ |
* providers | +---+
* | parse | +--> haste module
* v | v | null / does not point to M
* +---+---+---+---+ +---+---+
* | F | | * |...| | P | * |
* +---+---+---+---+ +---+---+
*
* Both of the above rules depend on the latest/committed state of the parse
* entity, which also needs to be rolled back. We need to be careful about when
* the parse entity is rolled back.
*
* To deal with rolling back deferred deletions, we first ensure that any
* deferred deletions are fully performed, which must happen before we roll back
* parse entity.
*
* We then can re-add the file to the all providers list, but this must happen
* *after* we roll back the parse data. Otherwise, the file will still appear to
* be logically deleted.
*
* In addition to rolling back changes to the file and to dirty modules' all
* providers list, we also roll back each dirty module's provider entity which
* stores the committed provider.
*)
let rollback_file =
let open Heap in
let get_haste_module_p p = Option.map (fun m -> (m, p)) (get_haste_module p) in
let rollback_file file =
let parse_ent = get_parse file in
let old_parse = entity_read_committed parse_ent in
let new_parse = entity_read_latest parse_ent in
let (old_file_module, new_file_module, old_haste_module, new_haste_module) =
match (old_parse, new_parse) with
| (None, None) -> (None, None, None, None)
| (None, Some new_p) -> (None, get_file_module file, None, get_haste_module new_p)
| (Some old_p, None) -> (get_file_module file, None, get_haste_module_p old_p, None)
| (Some old_p, Some new_p) -> (None, None, get_haste_module_p old_p, get_haste_module new_p)
in
let (old_haste_module, new_haste_module) =
match (old_haste_module, new_haste_module) with
| (Some (old_m, _), Some new_m) when Heap.haste_modules_equal old_m new_m -> (None, None)
| _ -> (old_haste_module, new_haste_module)
in
(* Remove new providers and process deferred deletions for old providers
* before rolling back this file's parse entity. *)
old_file_module
|> Option.iter (fun m ->
entity_rollback (get_file_provider m);
ignore (get_file_all_providers_exclusive m)
);
new_file_module
|> Option.iter (fun m ->
entity_rollback (get_file_provider m);
remove_file_provider_exclusive m file
);
old_haste_module
|> Option.iter (fun (m, _) ->
entity_rollback (get_haste_provider m);
ignore (get_haste_all_providers_exclusive m)
);
new_haste_module
|> Option.iter (fun m ->
entity_rollback (get_haste_provider m);
remove_haste_provider_exclusive m file
);
(* Add back the deleted providers after rolling back the file's parse
* entity. *)
entity_rollback parse_ent;
old_file_module |> Option.iter (fun m -> add_file_provider m file);
old_haste_module |> Option.iter (fun (m, p) -> add_haste_provider m file p)
in
fun file_key ->
match FileHeap.get file_key with
| None -> ()
| Some file -> if file_changed file then rollback_file file
let read_file_name file =
let open Heap in
get_file_name file |> read_string
let read_file_key file =
let open Heap in
let fn = read_file_name file in
match get_file_kind file with
| Source_file -> File_key.SourceFile fn
| Json_file -> File_key.JsonFile fn
| Resource_file -> File_key.ResourceFile fn
| Lib_file -> File_key.LibFile fn
let read_file_hash parse =
let open Heap in
get_file_hash parse |> read_int64
let read_module_name parse =
let open Heap in
get_haste_module parse |> Option.map (fun m -> get_haste_name m |> read_string)
let read_ast file_key parse =
let open Heap in
let deserialize x = Marshal.from_string x 0 in
get_ast parse |> Option.map (fun addr -> read_ast addr |> deserialize |> decompactify_loc file_key)
let read_ast_unsafe file_key parse =
match read_ast file_key parse with
| Some ast -> ast
| None -> raise (Ast_not_found (File_key.to_string file_key))
let read_docblock parse : Docblock.t option =
let open Heap in
let deserialize x = Marshal.from_string x 0 in
get_docblock parse |> Option.map (fun addr -> read_docblock addr |> deserialize)
let read_docblock_unsafe file_key parse =
match read_docblock parse with
| Some docblock -> docblock
| None -> raise (Docblock_not_found (File_key.to_string file_key))
let read_aloc_table file_key parse =
let open Heap in
let init = ALoc.ALocRepresentationDoNotUse.init_table file_key in
get_aloc_table parse
|> Option.map (fun addr -> read_aloc_table addr |> Packed_locs.unpack (Some file_key) init)
let read_aloc_table_unsafe file_key parse =
match read_aloc_table file_key parse with
| Some aloc_table -> aloc_table
| None -> raise (ALoc_table_not_found (File_key.to_string file_key))
let read_type_sig parse =
let open Heap in
get_type_sig parse |> Option.map (fun addr -> read_type_sig addr Type_sig_bin.read)
let read_type_sig_unsafe file_key parse =
match read_type_sig parse with
| Some type_sig -> type_sig
| None -> raise (Type_sig_not_found (File_key.to_string file_key))
let read_tolerable_file_sig parse : File_sig.With_Loc.tolerable_t option =
let open Heap in
let deserialize x = Marshal.from_string x 0 in
get_file_sig parse |> Option.map (fun addr -> read_file_sig addr |> deserialize)
let read_file_sig parse = Option.map fst (read_tolerable_file_sig parse)
let read_tolerable_file_sig_unsafe file_key parse =
match read_tolerable_file_sig parse with
| Some file_sig -> file_sig
| None -> raise (Requires_not_found (File_key.to_string file_key))
let read_file_sig_unsafe file_key parse = fst (read_tolerable_file_sig_unsafe file_key parse)
let read_exports parse : Exports.t =
let open Heap in
let deserialize x = Marshal.from_string x 0 in
get_exports parse |> read_exports |> deserialize
let read_resolved_requires addr : resolved_requires =
Marshal.from_string (Heap.read_resolved_requires addr) 0
module Reader_cache : sig
val get_ast : File_key.t -> (Loc.t, Loc.t) Flow_ast.Program.t option
val add_ast : File_key.t -> (Loc.t, Loc.t) Flow_ast.Program.t -> unit
val get_aloc_table : File_key.t -> ALoc.table option
val add_aloc_table : File_key.t -> ALoc.table -> unit
val remove_batch : FilenameSet.t -> unit
end = struct
module ASTCache = SharedMem.LocalCache (struct
type key = File_key.t
type value = (Loc.t, Loc.t) Flow_ast.Program.t
let capacity = 1000
end)
module ALocTableCache = SharedMem.LocalCache (struct
type key = File_key.t
type value = ALoc.table
let capacity = 1000
end)
let get_ast = ASTCache.get
let add_ast = ASTCache.add
let get_aloc_table = ALocTableCache.get
let add_aloc_table = ALocTableCache.add
let remove file =
ASTCache.remove file;
ALocTableCache.remove file
let remove_batch files = FilenameSet.iter remove files
end
module Mutator_cache : sig
val get_aloc_table : File_key.t -> ALoc.table option
val add_aloc_table : File_key.t -> ALoc.table -> unit
val clear : unit -> unit
end = struct
module ALocTableCache = SharedMem.LocalCache (struct
type key = File_key.t
type value = ALoc.table
let capacity = 1000
end)
let get_aloc_table = ALocTableCache.get
let add_aloc_table = ALocTableCache.add
let clear = ALocTableCache.clear
end
let add_parsed file ~exports hash module_name docblock ast file_sig locs type_sig =
WorkerCancel.with_no_cancellations (fun () ->
add_checked_file file hash module_name docblock ast locs type_sig file_sig exports
)
let add_unparsed file hash module_name =
WorkerCancel.with_no_cancellations (fun () -> add_unparsed_file file hash module_name)
let clear_not_found file_key = WorkerCancel.with_no_cancellations (fun () -> clear_file file_key)
module type READER = sig
type reader
val get_provider : reader:reader -> Modulename.t -> file_addr option
val is_typed_file : reader:reader -> file_addr -> bool
val get_parse : reader:reader -> file_addr -> [ `typed | `untyped ] parse_addr option
val get_typed_parse : reader:reader -> file_addr -> [ `typed ] parse_addr option
val has_ast : reader:reader -> File_key.t -> bool
val get_ast : reader:reader -> File_key.t -> (Loc.t, Loc.t) Flow_ast.Program.t option
val get_aloc_table : reader:reader -> File_key.t -> ALoc.table option
val get_docblock : reader:reader -> File_key.t -> Docblock.t option
val get_exports : reader:reader -> File_key.t -> Exports.t option
val get_tolerable_file_sig : reader:reader -> File_key.t -> File_sig.With_Loc.tolerable_t option
val get_file_sig : reader:reader -> File_key.t -> File_sig.With_Loc.t option
val get_type_sig : reader:reader -> File_key.t -> type_sig option
val get_file_hash : reader:reader -> File_key.t -> Xx.hash option
val get_parse_unsafe :
reader:reader -> File_key.t -> file_addr -> [ `typed | `untyped ] parse_addr
val get_typed_parse_unsafe : reader:reader -> File_key.t -> file_addr -> [ `typed ] parse_addr
val get_resolved_requires_unsafe :
reader:reader -> File_key.t -> [ `typed ] parse_addr -> resolved_requires
val get_ast_unsafe : reader:reader -> File_key.t -> (Loc.t, Loc.t) Flow_ast.Program.t
val get_aloc_table_unsafe : reader:reader -> File_key.t -> ALoc.table
val get_docblock_unsafe : reader:reader -> File_key.t -> Docblock.t
val get_exports_unsafe : reader:reader -> File_key.t -> Exports.t
val get_tolerable_file_sig_unsafe : reader:reader -> File_key.t -> File_sig.With_Loc.tolerable_t
val get_file_sig_unsafe : reader:reader -> File_key.t -> File_sig.With_Loc.t
val get_type_sig_unsafe : reader:reader -> File_key.t -> type_sig
val get_file_hash_unsafe : reader:reader -> File_key.t -> Xx.hash
val loc_of_aloc : reader:reader -> ALoc.t -> Loc.t
end
let loc_of_aloc ~reader ~get_aloc_table_unsafe aloc =
let table =
lazy
(let source =
match ALoc.source aloc with
| None -> failwith "Expected `aloc` to have a `source`"
| Some x -> x
in
get_aloc_table_unsafe ~reader source
)
in
ALoc.to_loc table aloc
(* Init/recheck will use Mutator_reader to read the shared memory *)
module Mutator_reader = struct
type reader = Mutator_state_reader.t
let read ~reader:_ addr = Heap.entity_read_latest addr
let read_old ~reader:_ addr = Heap.entity_read_committed addr
let get_provider ~reader m =
let* provider = get_provider_ent m in
read ~reader provider
let is_typed_file ~reader file =
match read ~reader (Heap.get_parse file) with
| Some parse -> Heap.is_typed parse
| None -> false
let get_parse ~reader file = read ~reader (Heap.get_parse file)
let get_typed_parse ~reader file =
let* parse = get_parse ~reader file in
Heap.coerce_typed parse
let get_old_parse ~reader file = read_old ~reader (Heap.get_parse file)
let get_old_typed_parse ~reader file =
let* parse = get_old_parse ~reader file in
Heap.coerce_typed parse
let has_ast ~reader file =
let parse_opt =
let* file_addr = get_file_addr file in
get_typed_parse ~reader file_addr
in
match parse_opt with
| None -> false
| Some parse -> Heap.get_ast parse |> Option.is_some
let get_ast ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_ast file parse
let get_aloc_table ~reader file =
match Mutator_cache.get_aloc_table file with
| Some _ as cached -> cached
| None ->
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
let* aloc_table = read_aloc_table file parse in
Mutator_cache.add_aloc_table file aloc_table;
Some aloc_table
let get_docblock ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_docblock parse
let get_exports ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
Some (read_exports parse)
let get_old_exports ~reader file =
let* addr = get_file_addr file in
let* parse = get_old_typed_parse ~reader addr in
Some (read_exports parse)
let get_tolerable_file_sig ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_tolerable_file_sig parse
let get_file_sig ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_file_sig parse
let get_type_sig ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_type_sig parse
let get_file_hash ~reader file =
let* addr = get_file_addr file in
let* parse = get_parse ~reader addr in
Some (read_file_hash parse)
let get_old_file_hash ~reader file =
let* addr = get_file_addr file in
let* parse = get_old_parse ~reader addr in
Some (read_file_hash parse)
let get_parse_unsafe ~reader file addr =
match get_parse ~reader addr with
| Some parse -> parse
| None -> raise (File_not_parsed (File_key.to_string file))
let get_typed_parse_unsafe ~reader file addr =
let parse = get_parse_unsafe ~reader file addr in
match Heap.coerce_typed parse with
| Some parse -> parse
| None -> raise (File_not_typed (File_key.to_string file))
let get_resolved_requires_unsafe ~reader file parse =
let resolved_requires = Heap.get_resolved_requires parse in
match read ~reader resolved_requires with
| Some resolved_requires -> read_resolved_requires resolved_requires
| None -> raise (Resolved_requires_not_found (File_key.to_string file))
let get_ast_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_ast_unsafe file parse
let get_aloc_table_unsafe ~reader file =
match Mutator_cache.get_aloc_table file with
| Some aloc_table -> aloc_table
| None ->
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
let aloc_table = read_aloc_table_unsafe file parse in
Mutator_cache.add_aloc_table file aloc_table;
aloc_table
let get_docblock_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_docblock_unsafe file parse
let get_exports_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_exports parse
let get_tolerable_file_sig_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_tolerable_file_sig_unsafe file parse
let get_file_sig_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_file_sig_unsafe file parse
let get_type_sig_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_type_sig_unsafe file parse
let get_file_hash_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_parse_unsafe ~reader file addr in
read_file_hash parse
let loc_of_aloc = loc_of_aloc ~get_aloc_table_unsafe
end
(* For use by a worker process *)
type worker_mutator = {
add_parsed:
File_key.t ->
exports:Exports.t ->
Xx.hash ->
string option ->
Docblock.t ->
(Loc.t, Loc.t) Flow_ast.Program.t ->
File_sig.With_Loc.tolerable_t ->
locs_tbl ->
type_sig ->
MSet.t;
add_unparsed: File_key.t -> Xx.hash -> string option -> MSet.t;
clear_not_found: File_key.t -> MSet.t;
}
(* Parsing is pretty easy - there is no before state and no chance of rollbacks, so we don't
* need to worry about a transaction *)
module Parse_mutator : sig
val create : unit -> worker_mutator
end = struct
let clear_not_found = Fun.const MSet.empty
let create () = { add_parsed; add_unparsed; clear_not_found }
end
(* Reparsing is more complicated than parsing, since we need to worry about transactions.
*
* Modified files will be advanced based on the current transaction. Advancing a
* file ensures that the previous committed data is still available. Committing
* the transaction will publish the new data to all readers by bumping the
* global transaction counter, see Mutator_state_reader.
*
* If the transaction is rolled back, we will revert changed entities. Ideally,
* we would not need to roll back / undo any writes when a transaction rolls
* back, assuming the next recheck is guaranteed to contain a superset of this
* recheck's files.
*
* This assumption does not hold for priority rechecks, where we cancel a
* recheck, schedule a minimal recheck to unblock the IDE request, then
* re-start the original recheck.
*)
module Reparse_mutator : sig
type master_mutator (* Used by the master process *)
val create : Transaction.t -> FilenameSet.t -> master_mutator * worker_mutator
val record_unchanged : master_mutator -> FilenameSet.t -> unit
val record_not_found : master_mutator -> FilenameSet.t -> unit
end = struct
type master_mutator = unit
(* We can conservatively invalidate caches for all `files`, but we can be a
* bit more precise by only invalidating changed files. If parsing reveals
* unchanged files, we remove them from this set. *)
let changed_files = ref FilenameSet.empty
(* When the transaction commits, we will remove these from the shared hash
* table, so sharedmem GC can collect them. *)
let not_found_files = ref FilenameSet.empty
let rollback_changed () = FilenameSet.iter rollback_file !changed_files
let reset_refs () =
changed_files := FilenameSet.empty;
not_found_files := FilenameSet.empty
let create transaction files =
changed_files := files;
let commit () =
WorkerCancel.with_no_cancellations (fun () ->
Hh_logger.debug "Committing parsing heaps";
Mutator_cache.clear ();
Reader_cache.remove_batch !changed_files;
FileHeap.remove_batch !not_found_files;
reset_refs ()
);
Lwt.return_unit
in
let rollback () =
WorkerCancel.with_no_cancellations (fun () ->
Hh_logger.debug "Rolling back parsing heaps";
Mutator_cache.clear ();
rollback_changed ();
reset_refs ()
);
Lwt.return_unit
in
Transaction.add ~singleton:"Reparse" ~commit ~rollback transaction;
((), { add_parsed; add_unparsed; clear_not_found })
let record_unchanged () unchanged = changed_files := FilenameSet.diff !changed_files unchanged
let record_not_found () not_found = not_found_files := not_found
end
module Commit_modules_mutator = struct
type t = unit
let no_providers = ref MSet.empty
let reset_refs () = no_providers := MSet.empty
let remove_module = function
| Modulename.String name -> HasteModuleHeap.remove name
| Modulename.Filename file_key -> FileModuleHeap.remove file_key
let commit () =
WorkerCancel.with_no_cancellations (fun () ->
MSet.iter remove_module !no_providers;
reset_refs ()
);
Lwt.return_unit
let rollback () =
reset_refs ();
Lwt.return_unit
let create transaction = Transaction.add ~singleton:"Commit_modules" ~commit ~rollback transaction
let record_no_providers () modules = no_providers := modules
end
module Resolved_requires_mutator = struct
type t = unit
let dirty_files = ref FilenameSet.empty
let rollback_resolved_requires file_key =
let open SharedMem.NewAPI in
let entity =
let* file = get_file_addr file_key in
let* parse = entity_read_latest (get_parse file) in
let* parse = coerce_typed parse in
Some (get_resolved_requires parse)
in
Option.iter entity_rollback entity
let commit () =
dirty_files := FilenameSet.empty;
Lwt.return_unit
let rollback () =
WorkerCancel.with_no_cancellations (fun () ->
FilenameSet.iter rollback_resolved_requires !dirty_files
);
dirty_files := FilenameSet.empty;
Lwt.return_unit
let create transaction files =
dirty_files := files;
Transaction.add ~commit ~rollback transaction
(* To detect whether resolved requires have changed, we load the old resolved
* requires for this file and compare hashes. There are two interesting cases:
*
* 1. If this file is unchanged, then we advance the resolved requires entity
* of the latest parse. The "old" resolved requires is the latest version
* before updating.
*
* 2. If the file is changed, then the "old" resolved requires is the latest
* version of the *committed* parse, but we advance the resolved requires
* entity of the *latest* parse, as with case (1).
*
* If the hashes are unchanged, then for case (1) we can do nothing, but for
* case (2) we need to advance the resolved requires entity of the latest
* parse, because fresh parses start out without resolved requires.
*)
let add_resolved_requires () file parse resolved_requires =
let module Heap = SharedMem.NewAPI in
let old_resolved_requires =
let* old_parse = Heap.entity_read_committed (Heap.get_parse file) in
let* old_parse = Heap.coerce_typed old_parse in
let old_ent = Heap.get_resolved_requires old_parse in
match Heap.entity_read_latest old_ent with
| Some addr ->
let { hash; _ } = read_resolved_requires addr in
Some (old_ent, hash, addr)
| None -> None
in
let open Heap in
let ent = get_resolved_requires parse in
match old_resolved_requires with
| Some (old_ent, hash, addr) when Int64.equal hash resolved_requires.hash ->
if ent == old_ent then
()
else
entity_advance ent (Some addr);
false
| _ ->
let resolved_requires = Marshal.to_string resolved_requires [] in
let (size, write) = prepare_write_resolved_requires resolved_requires in
alloc (header_size + size) (fun chunk ->
let resolved_requires = write chunk in
entity_advance ent (Some resolved_requires)
);
true
end
(* This uses `entity_read_committed` and can be used by code outside of a
* init/recheck, like commands, to see a consistent snapshot of type state even
* in the middle of a recheck. *)
module Reader = struct
type reader = State_reader.t
let read ~reader:_ addr = Heap.entity_read_latest addr
let get_provider ~reader m =
let* provider = get_provider_ent m in
read ~reader provider
let is_typed_file ~reader file =
match read ~reader (Heap.get_parse file) with
| Some parse -> Heap.is_typed parse
| None -> false
let get_parse ~reader file = read ~reader (Heap.get_parse file)
let get_typed_parse ~reader file =
let* parse = get_parse ~reader file in
Heap.coerce_typed parse
let has_ast ~reader file =
let parse_opt =
let* file_addr = get_file_addr file in
get_typed_parse ~reader file_addr
in
match parse_opt with
| None -> false
| Some parse -> Heap.get_ast parse |> Option.is_some
let get_ast ~reader file =
match Reader_cache.get_ast file with
| Some _ as cached -> cached
| None ->
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
let* ast = read_ast file parse in
Reader_cache.add_ast file ast;
Some ast
let get_aloc_table ~reader file =
match Reader_cache.get_aloc_table file with
| Some _ as cached -> cached
| None ->
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
let* aloc_table = read_aloc_table file parse in
Reader_cache.add_aloc_table file aloc_table;
Some aloc_table
let get_docblock ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_docblock parse
let get_exports ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
Some (read_exports parse)
let get_tolerable_file_sig ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_tolerable_file_sig parse
let get_file_sig ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_file_sig parse
let get_type_sig ~reader file =
let* addr = get_file_addr file in
let* parse = get_typed_parse ~reader addr in
read_type_sig parse
let get_file_hash ~reader file =
let* addr = get_file_addr file in
let* parse = get_parse ~reader addr in
Some (read_file_hash parse)
let get_parse_unsafe ~reader file addr =
match get_parse ~reader addr with
| Some parse -> parse
| None -> raise (File_not_parsed (File_key.to_string file))
let get_typed_parse_unsafe ~reader file addr =
let parse = get_parse_unsafe ~reader file addr in
match Heap.coerce_typed parse with
| Some parse -> parse
| None -> raise (File_not_typed (File_key.to_string file))
let get_resolved_requires_unsafe ~reader file parse =
let resolved_requires = Heap.get_resolved_requires parse in
match read ~reader resolved_requires with
| Some resolved_requires -> read_resolved_requires resolved_requires
| None -> raise (Resolved_requires_not_found (File_key.to_string file))
let get_ast_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_ast_unsafe file parse
let get_aloc_table_unsafe ~reader file =
match Reader_cache.get_aloc_table file with
| Some aloc_table -> aloc_table
| None ->
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
let aloc_table = read_aloc_table_unsafe file parse in
Reader_cache.add_aloc_table file aloc_table;
aloc_table
let get_docblock_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_docblock_unsafe file parse
let get_exports_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_exports parse
let get_tolerable_file_sig_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_tolerable_file_sig_unsafe file parse
let get_file_sig_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_file_sig_unsafe file parse
let get_type_sig_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_typed_parse_unsafe ~reader file addr in
read_type_sig_unsafe file parse
let get_file_hash_unsafe ~reader file =
let addr = get_file_addr_unsafe file in
let parse = get_parse_unsafe ~reader file addr in
read_file_hash parse
let loc_of_aloc = loc_of_aloc ~get_aloc_table_unsafe
end
(* Reader_dispatcher is used by code which may or may not be running inside an init/recheck *)
module Reader_dispatcher : READER with type reader = Abstract_state_reader.t = struct
type reader = Abstract_state_reader.t
open Abstract_state_reader
let get_provider ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_provider ~reader
| State_reader reader -> Reader.get_provider ~reader
let is_typed_file ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.is_typed_file ~reader
| State_reader reader -> Reader.is_typed_file ~reader
let get_parse ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_parse ~reader
| State_reader reader -> Reader.get_parse ~reader
let get_typed_parse ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_typed_parse ~reader
| State_reader reader -> Reader.get_typed_parse ~reader
let has_ast ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.has_ast ~reader
| State_reader reader -> Reader.has_ast ~reader
let get_ast ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_ast ~reader
| State_reader reader -> Reader.get_ast ~reader
let get_aloc_table ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_aloc_table ~reader
| State_reader reader -> Reader.get_aloc_table ~reader
let get_docblock ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_docblock ~reader
| State_reader reader -> Reader.get_docblock ~reader
let get_exports ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_exports ~reader
| State_reader reader -> Reader.get_exports ~reader
let get_tolerable_file_sig ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_tolerable_file_sig ~reader
| State_reader reader -> Reader.get_tolerable_file_sig ~reader
let get_file_sig ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_file_sig ~reader
| State_reader reader -> Reader.get_file_sig ~reader
let get_type_sig ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_type_sig ~reader
| State_reader reader -> Reader.get_type_sig ~reader
let get_file_hash ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_file_hash ~reader
| State_reader reader -> Reader.get_file_hash ~reader
let get_parse_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_parse_unsafe ~reader
| State_reader reader -> Reader.get_parse_unsafe ~reader
let get_typed_parse_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_typed_parse_unsafe ~reader
| State_reader reader -> Reader.get_typed_parse_unsafe ~reader
let get_resolved_requires_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_resolved_requires_unsafe ~reader
| State_reader reader -> Reader.get_resolved_requires_unsafe ~reader
let get_ast_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_ast_unsafe ~reader
| State_reader reader -> Reader.get_ast_unsafe ~reader
let get_aloc_table_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_aloc_table_unsafe ~reader
| State_reader reader -> Reader.get_aloc_table_unsafe ~reader
let get_docblock_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_docblock_unsafe ~reader
| State_reader reader -> Reader.get_docblock_unsafe ~reader
let get_exports_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_exports_unsafe ~reader
| State_reader reader -> Reader.get_exports_unsafe ~reader
let get_tolerable_file_sig_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_tolerable_file_sig_unsafe ~reader
| State_reader reader -> Reader.get_tolerable_file_sig_unsafe ~reader
let get_file_sig_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_file_sig_unsafe ~reader
| State_reader reader -> Reader.get_file_sig_unsafe ~reader
let get_type_sig_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_type_sig_unsafe ~reader
| State_reader reader -> Reader.get_type_sig_unsafe ~reader
let get_file_hash_unsafe ~reader =
match reader with
| Mutator_state_reader reader -> Mutator_reader.get_file_hash_unsafe ~reader
| State_reader reader -> Reader.get_file_hash_unsafe ~reader
let loc_of_aloc = loc_of_aloc ~get_aloc_table_unsafe
end
module From_saved_state = struct
let add_parsed file_key hash module_name exports resolved_requires =
let (file_kind, file_name) = file_kind_and_name file_key in
let exports = Marshal.to_string exports [] in
let resolved_requires = Marshal.to_string resolved_requires [] in
let open Heap in
let (exports_size, write_exports) = prepare_write_exports exports in
let (resolved_requires_size, write_resolved_requires) =
prepare_write_resolved_requires resolved_requires
in
let size =
(8 * header_size)
+ (2 * entity_size)
+ string_size file_name
+ typed_parse_size
+ file_size
+ int64_size
+ exports_size
+ resolved_requires_size
in
let (size, add_file_module_maybe) = prepare_add_file_module_maybe size file_key in
let (size, add_haste_module_maybe) = prepare_add_haste_module_maybe size module_name in
alloc size (fun chunk ->
let file_name = write_string chunk file_name in
let file_module = add_file_module_maybe chunk in
let hash = write_int64 chunk hash in
let haste_module = add_haste_module_maybe chunk in
let exports = write_exports chunk in
let resolved_requires = write_resolved_requires chunk in
let resolved_requires_ent = write_entity chunk (Some resolved_requires) in
let parse = write_typed_parse chunk hash haste_module exports resolved_requires_ent in
let parse_ent = write_entity chunk (Some (parse :> [ `typed | `untyped ] parse_addr)) in
let file = write_file chunk file_kind file_name file_module parse_ent in
assert (file = FileHeap.add file_key file);
calc_dirty_modules file_key file parse None haste_module file_module
)
let add_unparsed = add_unparsed
end
let iter_resolved_requires f =
SharedMem.NewAPI.iter_resolved_requires (fun file resolved_requires ->
f file (read_resolved_requires resolved_requires)
)