@@ -563,11 +563,109 @@ type pers_struct = {
563563 ps_filename : string ;
564564}
565565
566+ (* Request-local memo of type path lookups ([find_type_full] on regular
567+ paths) and of [normalize_path] calls that leave a type path unchanged.
568+
569+ The typechecker resolves the same type paths over and over (expanding
570+ [option], [Js.Null.t], ... in [expand_head], [update_level], [occur_univar],
571+ ...), each time walking the [open] layers of [env.types] or the components
572+ of a persistent module. Both lookups only read immutable environment tables
573+ ([env.types] for [Pident] type paths, [env.components] for [Pdot] ones,
574+ [env.modules] and [env.components] for normalization) plus request-wide
575+ state that only grows: persistent structures (replacing one starts a new
576+ memo epoch, see [add_persistent_structure]) and module components, which
577+ are forced at most once. An entry is therefore
578+ reused only when the tables it read are physically the same, and only
579+ successful results are stored. A repeated call has no side effect the memo
580+ would skip: the first, uncached call already loaded the [.cmi]s and forced
581+ the lazy components it needed. Paths that contain a functor application are
582+ never cached ([find_module_descr] applies the functor against [env]), and
583+ the memo is bypassed while [.cmi] loading is disabled ([without_cmis] may
584+ backtrack components). *)
585+ module Type_path_memo = struct
586+ let size = 4096 (* a power of two *)
587+
588+ let string_hash s =
589+ let n = String. length s in
590+ if n = 0 then 0
591+ else
592+ (n * 31 )
593+ + (Char. code (String. unsafe_get s 0 ) * 7 )
594+ + Char. code (String. unsafe_get s (n - 1 ))
595+
596+ exception Not_cacheable
597+
598+ (* Consistent with [equal]: it ignores [pos] and ident flags, which no lookup
599+ reads. *)
600+ let rec hash (path : Path.t ) =
601+ match path with
602+ | Pident id -> (id.Ident. stamp * 65599 ) + string_hash id.name
603+ | Pdot (p , s , _pos ) -> (hash p * 31 ) + string_hash s
604+ | Papply _ -> raise_notrace Not_cacheable
605+
606+ let slot path = hash path land (size - 1 )
607+
608+ (* Paths that every lookup in this file resolves identically: identifiers are
609+ looked up by name and stamp, and components by name. *)
610+ let rec equal (p1 : Path.t ) (p2 : Path.t ) =
611+ p1 == p2
612+ ||
613+ match (p1, p2) with
614+ | Pident id1 , Pident id2 ->
615+ id1.Ident. stamp = id2.Ident. stamp && String. equal id1.name id2.name
616+ | Pdot (p1 , s1 , _ ), Pdot (p2 , s2 , _ ) -> String. equal s1 s2 && equal p1 p2
617+ | _ -> false
618+
619+ type ('types, 'modules, 'components, 'result) entry = {
620+ epoch : unit ref ;
621+ path : Path .t ;
622+ types : 'types ;
623+ modules : 'modules ;
624+ components : 'components ;
625+ result : 'result ;
626+ }
627+
628+ type ('types, 'modules, 'components, 'result) t =
629+ ('types , 'modules , 'components , 'result ) entry option array
630+
631+ let create () : _ t = Array. make size None
632+ let clear (t : _ t ) = Array. fill t 0 size None
633+ end
634+
635+ (* The tables are allocated once per domain rather than per request: they are
636+ large, and requests are small. An entry is valid only in the epoch that
637+ stored it. Each request starts a fresh epoch (and a nested request restores
638+ the outer one when it returns), so entries never cross requests; clearing
639+ starts a new epoch too. *)
640+ let type_path_memo_key =
641+ Domain.DLS. new_key (fun () ->
642+ (Type_path_memo. create () , Type_path_memo. create () ))
643+
644+ let type_path_memo_epoch_key = Request_local. new_key (fun () -> ref (ref () ))
645+ let type_path_memo_epoch () = ! (Domain.DLS. get type_path_memo_epoch_key)
646+
647+ let clear_type_path_memo_tables () =
648+ let found, unchanged = Domain.DLS. get type_path_memo_key in
649+ Type_path_memo. clear found;
650+ Type_path_memo. clear unchanged
651+
652+ let clear_type_path_memo () =
653+ Domain.DLS. get type_path_memo_epoch_key := ref () ;
654+ clear_type_path_memo_tables ()
655+
566656let persistent_structures_key =
567657 Request_local. new_key (fun () ->
568658 (Hashtbl. create 17 : (string, pers_struct option ) Hashtbl. t))
569659let persistent_structures () = Domain.DLS. get persistent_structures_key
570660
661+ (* [Hashtbl.add] shadows an earlier binding for [name], which the type path
662+ memo may have read (directly, or as a missing module), so replacing one
663+ starts a new memo epoch. *)
664+ let add_persistent_structure name ps =
665+ let tbl = persistent_structures () in
666+ if Hashtbl. mem tbl name then clear_type_path_memo () ;
667+ Hashtbl. add tbl name ps
668+
571669(* Consistency between persistent structures *)
572670
573671let crc_units_key = Request_local. new_key Consistbl. create
@@ -604,7 +702,7 @@ let check_consistency ps =
604702
605703let save_pers_struct crc ps =
606704 let modname = ps.ps_name in
607- Hashtbl. add (persistent_structures () ) modname (Some ps);
705+ add_persistent_structure modname (Some ps);
608706 Consistbl. set (crc_units () ) modname crc ps.ps_filename;
609707 add_import modname
610708
@@ -649,7 +747,7 @@ let acknowledge_pers_struct check modname {Persistent_signature.filename; cmi} =
649747 if ps.ps_name <> modname then
650748 error (Illegal_renaming (modname, ps.ps_name, filename));
651749 if check then check_consistency ps;
652- Hashtbl. add (persistent_structures () ) modname (Some ps);
750+ add_persistent_structure modname (Some ps);
653751 ps
654752
655753let read_pers_struct check modname filename =
@@ -670,7 +768,7 @@ let find_pers_struct check name =
670768 match ! Persistent_signature. load ~unit_name: name with
671769 | Some ps -> ps
672770 | None ->
673- Hashtbl. add (persistent_structures () ) name None ;
771+ add_persistent_structure name None ;
674772 raise Not_found
675773 in
676774 add_import name;
@@ -715,6 +813,7 @@ let check_pers_struct name =
715813
716814let reset_cache () =
717815 current_unit () := " " ;
816+ clear_type_path_memo () ;
718817 Hashtbl. clear (persistent_structures () );
719818 clear_imports () ;
720819 Hashtbl. clear (value_declarations () );
@@ -731,13 +830,16 @@ let reset_cache_toplevel () =
731830 (persistent_structures () ) []
732831 in
733832 List. iter (Hashtbl. remove (persistent_structures () )) l;
833+ clear_type_path_memo () ;
734834 Hashtbl. clear (value_declarations () );
735835 Hashtbl. clear (type_declarations () );
736836 Hashtbl. clear (module_declarations () );
737837 Hashtbl. clear (used_constructors () );
738838 Hashtbl. clear (prefixed_sg () )
739839
740- let set_unit_name name = current_unit () := name
840+ let set_unit_name name =
841+ clear_type_path_memo () ;
842+ current_unit () := name
741843
742844let get_unit_name () = ! (current_unit () )
743845
@@ -784,11 +886,44 @@ let type_of_cstr path = function
784886 (d, ([] , List. map snd (Datarepr. labels_of_type path d)))
785887 | _ -> assert false
786888
889+ (* [find_type_full p env] for a regular type path, memoized as described at
890+ [Type_path_memo]. *)
891+ let find_regular_type_full p env =
892+ match ! (can_load_cmis () ) with
893+ | Cannot_load_cmis _ -> find_type_full p env
894+ | Can_load_cmis -> (
895+ match Type_path_memo. slot p with
896+ | exception Type_path_memo. Not_cacheable -> find_type_full p env
897+ | i -> (
898+ let memo, _ = Domain.DLS. get type_path_memo_key in
899+ let epoch = type_path_memo_epoch () in
900+ match Array. unsafe_get memo i with
901+ | Some e
902+ when e.epoch == epoch
903+ && (match p with
904+ | Pident _ -> e.types == env.types
905+ | Pdot _ | Papply _ -> e.components == env.components)
906+ && Type_path_memo. equal e.path p ->
907+ e.result
908+ | _ ->
909+ let result = find_type_full p env in
910+ Array. unsafe_set memo i
911+ (Some
912+ {
913+ epoch;
914+ path = p;
915+ types = env.types;
916+ modules = () ;
917+ components = env.components;
918+ result;
919+ });
920+ result))
921+
787922let find_type_full path env =
788923 match Path. constructor_typath path with
789924 | Regular p -> (
790925 try (Path_map. find p env.local_constraints, ([] , [] ))
791- with Not_found -> find_type_full p env)
926+ with Not_found -> find_regular_type_full p env)
792927 | Cstr (ty_path , s ) ->
793928 let _, (cstrs, _) =
794929 try find_type_full ty_path env with Not_found -> assert false
@@ -895,6 +1030,46 @@ let normalize_path oloc env path =
8951030 | Some loc ->
8961031 raise (Error (Missing_module (loc, path, normalize_path true env path))))
8971032
1033+ (* [Some (normalize_path None env path)] when normalization changes [path]
1034+ ([Path.same] is false), [None] when it leaves it unchanged. The unchanged
1035+ outcome is memoized as described at [Type_path_memo]; a changed path is
1036+ always computed afresh, so callers get a new path value as before. *)
1037+ let normalize_type_path env path =
1038+ let normalize () =
1039+ let path' = normalize_path None env path in
1040+ if Path. same path path' then None else Some path'
1041+ in
1042+ match ! (can_load_cmis () ) with
1043+ | Cannot_load_cmis _ -> normalize ()
1044+ | Can_load_cmis -> (
1045+ match Type_path_memo. slot path with
1046+ | exception Type_path_memo. Not_cacheable -> normalize ()
1047+ | i -> (
1048+ let _, memo = Domain.DLS. get type_path_memo_key in
1049+ let epoch = type_path_memo_epoch () in
1050+ match Array. unsafe_get memo i with
1051+ | Some e
1052+ when e.epoch == epoch && e.modules == env.modules
1053+ && e.components == env.components
1054+ && Type_path_memo. equal e.path path ->
1055+ None
1056+ | _ ->
1057+ let result = normalize () in
1058+ (match result with
1059+ | None ->
1060+ Array. unsafe_set memo i
1061+ (Some
1062+ {
1063+ epoch;
1064+ path;
1065+ types = () ;
1066+ modules = env.modules;
1067+ components = env.components;
1068+ result = () ;
1069+ })
1070+ | Some _ -> () );
1071+ result))
1072+
8981073let normalize_path_prefix oloc env path =
8991074 match path with
9001075 | Pdot (p , s , pos ) -> Pdot (normalize_path oloc env p, s, pos)
@@ -1992,7 +2167,25 @@ let reset_initial_for_request () =
19922167let with_fresh_initial action =
19932168 let previous = initial_safe_string () in
19942169 Fun. protect action ~finally: (fun () ->
1995- Domain.DLS. set initial_safe_string_key previous)
2170+ Domain.DLS. set initial_safe_string_key previous;
2171+ (* The per-domain memo tables would otherwise keep the finished
2172+ request's environments alive until their slots are overwritten. Their
2173+ entries are already invalid outside the request's epoch. *)
2174+ clear_type_path_memo_tables () )
2175+
2176+ module For_test = struct
2177+ let type_path_memo_entries () =
2178+ let found, unchanged = Domain.DLS. get type_path_memo_key in
2179+ let count t =
2180+ Array. fold_left
2181+ (fun n e ->
2182+ match e with
2183+ | None -> n
2184+ | Some _ -> n + 1 )
2185+ 0 t
2186+ in
2187+ count found + count unchanged
2188+ end
19962189
19972190let last_env_key = Request_local. new_key (fun () -> ref empty)
19982191let last_env () = Domain.DLS. get last_env_key
0 commit comments