# HG changeset patch # User Matthias Görgens # Date 1265652437 0 # Node ID e4653f775be2883d26dab52534cc15d1d48c4d7d # Parent 35e60cbfd1ad9ea6bde371ae1da2a6e8e389a1f0 mlvm-refactor: allocator (first part) changed to applicative style Signed-off-by: Matthias Görgens diff -r 35e60cbfd1ad -r e4653f775be2 mlvm/allocator.ml --- a/mlvm/allocator.ml +++ b/mlvm/allocator.ml @@ -1,94 +1,141 @@ -type t = (string * (int64 * int64)) list with rpc +open Pervasiveext +open Listext + +(* Sparse allocation should be fast. Expanding memory should be fast, for a bunch of volumes. *) + +type area = (string * (int64 * int64)) with rpc +type t = area list with rpc let create name size = [(name,(0L,size))] +let empty = [] -let get_size (_,(_,s)) = s -let get_start (_,(s,_)) = s -let get_name (n,(_,_)) = n +let get_name (name,(_,_)) = name +let get_start (_,(start,_)) = start +let get_size (_,(_,size)) = size +let unpack_area (pv_name, (start,size)) = (pv_name, (start,size)) -let make_area name start size = (name,(start,size)) +let get_end a = Int64.add (get_start a) (get_size a) -let alloc_specified_area (t : t) a = - let size = get_size a in - let start = get_start a in - let name = get_name a in - let test a2 = - let size2 = get_size a2 in - let start2 = get_start a2 in - let name2 = get_name a2 in - name=name2 && start >= start2 && start < Int64.add size2 start2 - in - let containing_areas,other_areas = List.partition test t in - let containing_area = List.hd containing_areas in - let ca_start = get_start containing_area in - let ca_size = get_size containing_area in - let ca_name = get_name containing_area in - let new_areas = - if start=ca_start then other_areas else (make_area ca_name ca_start (Int64.sub start ca_start))::other_areas - in - let new_areas = - if (Int64.add start size) = (Int64.add ca_start ca_size) - then new_areas - else (make_area ca_name (Int64.add start size) (Int64.sub (Int64.add ca_start ca_size) (Int64.add start size)))::new_areas - in - new_areas +let make_area pv_name start size = (pv_name, (start,size)) +let make_area_by_end name start endAr = make_area name start (Int64.sub endAr start) -let alloc_specified_areas = - List.fold_left alloc_specified_area - -let rec alloc t newsize = - let l = List.sort (fun a1 a2 -> compare (get_size a1) (get_size a2)) t in - let rec find xs ys = - match ys with - | seg::[] -> - (* If there's only one segment left, it's the largest. Allocate. *) - seg,xs - | seg::rest -> - let size = get_size seg in - if size >= newsize - then seg,(xs@rest) - else find (seg::xs) rest - | _ -> failwith "Failed to find individual segment!" - in - let seg,rest = find [] l in - let size = get_size seg in - if (size < newsize) then - (* We couldn't find one contiguous region to allocate. Call alloc again - with the remainder of the size and the new list of allocated segments *) - let allocd,newt = alloc (rest) (Int64.sub newsize size) in - (seg::allocd, newt) - else - let name = get_name seg in - let start = get_start seg in - let area = make_area name start newsize in - ([area], alloc_specified_area t area) +(* Define operations on areas, and then use those to build the + allocation algorithms. That should make it easier to test, and the + algorithms are easier to read without arithmetic in them. +*) -let rec setify = function - | [] -> [] - | (x::xs) -> if List.mem x xs then setify xs else x::(setify xs) +let intersect : area -> area -> area list = + fun a a2 -> + let (name, (start, size)) = unpack_area a in + let (name2, (start2, size2)) = unpack_area a2 in + let enda = get_end a in + let enda2 = get_end a2 in + let startI = max start start2 in + let endI = min enda enda2 in + let sizeI = max Int64.zero (Int64.sub endI startI) in + if name = name2 + then make_area name (max start start2) (max Int64.zero sizeI) :: [] + else [] -let free t segs = - let l = List.sort (fun a1 a2 -> compare (get_start a1) (get_start a2)) (t@segs) in - let pvs = List.map get_name l in - let pvs = setify pvs in +let combine : t -> t -> t = (* does not guarantee normalization *) + fun t1 t2 -> + t1 @ t2 - let rec test acc segs = - match segs with - | a1::a2::rest -> - let start1 = get_start a1 in - let size1 = get_size a1 in - let start2 = get_start a2 in - let size2 = get_size a2 in - let name = get_name a1 in - if (Int64.add start1 size1) = start2 then - test acc ((make_area name start1 (Int64.add size1 size2))::rest) - else - test ((List.hd segs)::acc) (List.tl segs) - | [x] -> x::acc - | [] -> acc (* shouldn't be necessary! *) - in - - List.fold_left (fun acc pv -> test acc (List.filter (fun seg -> get_name seg = pv) l)) [] pvs +let union : area -> area -> t = (* does not guarantee normalization *) + fun a a2 -> + a::a2::[] +let minus : area -> area -> t = (* does not guarantee normalization *) + fun a a2 -> + let (name, (start, size)) = unpack_area a in + let (name2, (start2, size2)) = unpack_area a2 in + let enda = get_end a in + let enda2 = get_end a2 in + if name = name2 + then List.filter ((<) Int64.zero ++ get_size) ++ List.fold_left combine [] ++ List.map (intersect a ++ Fun.uncurry (make_area_by_end name2)) $ ((start, start2) :: (enda2, enda)::[]) + else a :: [] + +(* Is a contained in a2? *) +let contained : area -> area -> bool = + fun a a2 -> + let (name, (start, size)) = unpack_area a in + let (name2, (start2, size2)) = unpack_area a2 in + (* (* This would only check if `a' starts within `a2': *) + name=name2 && start >= start2 && start < Int64.add size2 start2 + *) + name=name2 && start >= start2 && Int64.add start size <= Int64.add start2 size2 + +exception PVS_DONT_MATCH + +(* assumes all areas stem from the same pv *) +let normalize_single_pv areas = + (* Underlying structure for merge1: foldM merge1 (for a1) on WriterMonad (for acc) over segs *) + (* The type of the accumulator here is a bit ugly. Perhaps a real non-empty list would be better? *) + let merge1 (a1, acc) a2 = + let (name, (start1, size1)) = unpack_area a1 + and (name2, (start2, size2)) = unpack_area a2 in + if (name != name2) then raise PVS_DONT_MATCH + else if (Int64.add start1 size1) = start2 then + (make_area name start1 (Int64.add size1 size2), acc) + else + (a2, List.cons a1 acc) in + (function + | start::segs -> + (Fun.uncurry List.cons) $ List.fold_left merge1 (start, []) segs + | [] -> [] (* shouldn't be necessary! *)) + ++ List.sort (Fun.on compare get_start) $ areas +let normalize : t -> t = + fun areas -> + (* The next lines are to be read backwards, since we defined function composition that way. *) + + let module StringMap = Mapext.Make (String) in + (* put free areas of all PVs back together *) + List.flatten ++ StringMap.values + (* normalize each pv's areas *) + ++ StringMap.map normalize_single_pv + (* separate by pv *) + ++ StringMap.fromListWith List.append ++ List.map (fun seg -> (get_name seg, [seg])) + $ areas + +(* Which invariants does t have to satisfy? Which invariants does our + result here satisfy? + + E.g. is it possible for areas to overlap or contain each other? If not, should we warn if they do? + + t is a free list. + + What if there's no containing area? Is this only called under certain circumstances? Verify. *) +let alloc_specified_area (free_list : t) (a : area) = + (* We assume areas don't overlap, or do we? *) + (* Match against [] instead of _: Better die as soon as possible, when something is wrong. *) + let (containing_area::[]),other_areas = List.partition (contained a) $ free_list in + + normalize $ combine (minus containing_area a) other_areas + +let alloc_specified_areas : t -> t -> t = + List.fold_left alloc_specified_area + +let alloc (free_list : t) (newsize : int64) = + (* switched from best-fit (smallest free area that's large + enough) to worst-fit (largest area): This may reduce + fragmentation, and makes the code slightly easier. *) + let rec alloc_h newsize = function + | (seg::rest) -> + let remainder = Int64.sub newsize (get_size seg) in + if (remainder > Int64.zero) then + (* We couldn't find one contiguous region to allocate. Call alloc again + with the remainder of the size and the new list of allocated areas *) + let allocd,newt = alloc_h remainder rest in + (seg::allocd, newt) + else + let (name, (start, _)) = unpack_area seg in + let area = make_area name start newsize in + ([area], alloc_specified_area (seg::rest) area) + | [] -> failwith "Failed to find individual area!" in + (alloc_h newsize + ++ List.rev ++ List.sort (Fun.on compare get_size) $ free_list) + +(* Probably de-allocation won't be used much. *) +let free to_free free_list = normalize (combine to_free free_list) let to_string t = String.concat ", "