ocaml/stdlib/weak.ml

311 lines
9.3 KiB
OCaml

(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Damien Doligez, projet Para, INRIA Rocquencourt *)
(* *)
(* Copyright 1997 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
(** Weak array operations *)
type 'a t;;
external create : int -> 'a t = "caml_weak_create";;
(** number of additional values in a weak pointer *)
let additional_values = 2
let length x = Obj.size(Obj.repr x) - additional_values;;
external set : 'a t -> int -> 'a option -> unit = "caml_weak_set";;
external get : 'a t -> int -> 'a option = "caml_weak_get";;
external get_copy : 'a t -> int -> 'a option = "caml_weak_get_copy";;
external check : 'a t -> int -> bool = "caml_weak_check";;
external blit : 'a t -> int -> 'a t -> int -> int -> unit = "caml_weak_blit";;
(* blit: src srcoff dst dstoff len *)
let fill ar ofs len x =
if ofs < 0 || len < 0 || ofs + len > length ar
then raise (Invalid_argument "Weak.fill")
else begin
for i = ofs to (ofs + len - 1) do
set ar i x
done
end
;;
(** Weak hash tables *)
module type S = sig
type data
type t
val create : int -> t
val clear : t -> unit
val merge : t -> data -> data
val add : t -> data -> unit
val remove : t -> data -> unit
val find : t -> data -> data
val find_all : t -> data -> data list
val mem : t -> data -> bool
val iter : (data -> unit) -> t -> unit
val fold : (data -> 'a -> 'a) -> t -> 'a -> 'a
val count : t -> int
val stats : t -> int * int * int * int * int * int
end;;
module Make (H : Hashtbl.HashedType) : (S with type data = H.t) = struct
type 'a weak_t = 'a t;;
let weak_create = create;;
let emptybucket = weak_create 0;;
type data = H.t;;
type t = {
mutable table : data weak_t array;
mutable hashes : int array array;
mutable limit : int; (* bucket size limit *)
mutable oversize : int; (* number of oversize buckets *)
mutable rover : int; (* for internal bookkeeping *)
};;
let get_index t h = (h land max_int) mod (Array.length t.table);;
let limit = 7;;
let over_limit = 2;;
let create sz =
let sz = if sz < 7 then 7 else sz in
let sz = if sz > Sys.max_array_length then Sys.max_array_length else sz in
{
table = Array.make sz emptybucket;
hashes = Array.make sz [| |];
limit = limit;
oversize = 0;
rover = 0;
};;
let clear t =
for i = 0 to Array.length t.table - 1 do
t.table.(i) <- emptybucket;
t.hashes.(i) <- [| |];
done;
t.limit <- limit;
t.oversize <- 0;
;;
let fold f t init =
let rec fold_bucket i b accu =
if i >= length b then accu else
match get b i with
| Some v -> fold_bucket (i+1) b (f v accu)
| None -> fold_bucket (i+1) b accu
in
Array.fold_right (fold_bucket 0) t.table init
;;
let iter f t =
let rec iter_bucket i b =
if i >= length b then () else
match get b i with
| Some v -> f v; iter_bucket (i+1) b
| None -> iter_bucket (i+1) b
in
Array.iter (iter_bucket 0) t.table
;;
let iter_weak f t =
let rec iter_bucket i j b =
if i >= length b then () else
match check b i with
| true -> f b t.hashes.(j) i; iter_bucket (i+1) j b
| false -> iter_bucket (i+1) j b
in
Array.iteri (iter_bucket 0) t.table
;;
let rec count_bucket i b accu =
if i >= length b then accu else
count_bucket (i+1) b (accu + (if check b i then 1 else 0))
;;
let count t =
Array.fold_right (count_bucket 0) t.table 0
;;
let next_sz n = min (3 * n / 2 + 3) Sys.max_array_length;;
let prev_sz n = ((n - 3) * 2 + 2) / 3;;
let test_shrink_bucket t =
let bucket = t.table.(t.rover) in
let hbucket = t.hashes.(t.rover) in
let len = length bucket in
let prev_len = prev_sz len in
let live = count_bucket 0 bucket 0 in
if live <= prev_len then begin
let rec loop i j =
if j >= prev_len then begin
if check bucket i then loop (i + 1) j
else if check bucket j then begin
blit bucket j bucket i 1;
hbucket.(i) <- hbucket.(j);
loop (i + 1) (j - 1);
end else loop i (j - 1);
end;
in
loop 0 (length bucket - 1);
if prev_len = 0 then begin
t.table.(t.rover) <- emptybucket;
t.hashes.(t.rover) <- [| |];
end else begin
Obj.truncate (Obj.repr bucket) (prev_len + additional_values);
Obj.truncate (Obj.repr hbucket) prev_len;
end;
if len > t.limit && prev_len <= t.limit then t.oversize <- t.oversize - 1;
end;
t.rover <- (t.rover + 1) mod (Array.length t.table);
;;
let rec resize t =
let oldlen = Array.length t.table in
let newlen = next_sz oldlen in
if newlen > oldlen then begin
let newt = create newlen in
let add_weak ob oh oi =
let setter nb ni _ = blit ob oi nb ni 1 in
let h = oh.(oi) in
add_aux newt setter None h (get_index newt h);
in
iter_weak add_weak t;
t.table <- newt.table;
t.hashes <- newt.hashes;
t.limit <- newt.limit;
t.oversize <- newt.oversize;
t.rover <- t.rover mod Array.length newt.table;
end else begin
t.limit <- max_int; (* maximum size already reached *)
t.oversize <- 0;
end
and add_aux t setter d h index =
let bucket = t.table.(index) in
let hashes = t.hashes.(index) in
let sz = length bucket in
let rec loop i =
if i >= sz then begin
let newsz = min (3 * sz / 2 + 3) (Sys.max_array_length - 1) in
if newsz <= sz then failwith "Weak.Make: hash bucket cannot grow more";
let newbucket = weak_create newsz in
let newhashes = Array.make newsz 0 in
blit bucket 0 newbucket 0 sz;
Array.blit hashes 0 newhashes 0 sz;
setter newbucket sz d;
newhashes.(sz) <- h;
t.table.(index) <- newbucket;
t.hashes.(index) <- newhashes;
if sz <= t.limit && newsz > t.limit then begin
t.oversize <- t.oversize + 1;
for _i = 0 to over_limit do test_shrink_bucket t done;
end;
if t.oversize > Array.length t.table / over_limit then resize t;
end else if check bucket i then begin
loop (i + 1)
end else begin
setter bucket i d;
hashes.(i) <- h;
end;
in
loop 0;
;;
let add t d =
let h = H.hash d in
add_aux t set (Some d) h (get_index t h);
;;
let find_or t d ifnotfound =
let h = H.hash d in
let index = get_index t h in
let bucket = t.table.(index) in
let hashes = t.hashes.(index) in
let sz = length bucket in
let rec loop i =
if i >= sz then ifnotfound h index
else if h = hashes.(i) then begin
match get_copy bucket i with
| Some v when H.equal v d
-> begin match get bucket i with
| Some v -> v
| None -> loop (i + 1)
end
| _ -> loop (i + 1)
end else loop (i + 1)
in
loop 0
;;
let merge t d =
find_or t d (fun h index -> add_aux t set (Some d) h index; d)
;;
let find t d = find_or t d (fun _h _index -> raise Not_found);;
let find_shadow t d iffound ifnotfound =
let h = H.hash d in
let index = get_index t h in
let bucket = t.table.(index) in
let hashes = t.hashes.(index) in
let sz = length bucket in
let rec loop i =
if i >= sz then ifnotfound
else if h = hashes.(i) then begin
match get_copy bucket i with
| Some v when H.equal v d -> iffound bucket i
| _ -> loop (i + 1)
end else loop (i + 1)
in
loop 0
;;
let remove t d = find_shadow t d (fun w i -> set w i None) ();;
let mem t d = find_shadow t d (fun _w _i -> true) false;;
let find_all t d =
let h = H.hash d in
let index = get_index t h in
let bucket = t.table.(index) in
let hashes = t.hashes.(index) in
let sz = length bucket in
let rec loop i accu =
if i >= sz then accu
else if h = hashes.(i) then begin
match get_copy bucket i with
| Some v when H.equal v d
-> begin match get bucket i with
| Some v -> loop (i + 1) (v :: accu)
| None -> loop (i + 1) accu
end
| _ -> loop (i + 1) accu
end else loop (i + 1) accu
in
loop 0 []
;;
let stats t =
let len = Array.length t.table in
let lens = Array.map length t.table in
Array.sort compare lens;
let totlen = Array.fold_left ( + ) 0 lens in
(len, count t, totlen, lens.(0), lens.(len/2), lens.(len-1))
;;
end;;