diff --git a/backend/reg.ml b/backend/reg.ml index 8e418703d56..44b01650605 100644 --- a/backend/reg.ml +++ b/backend/reg.ml @@ -40,9 +40,7 @@ type t = { mutable raw_name: Raw_name.t; stamp: int; typ: Cmm.machtype_component; - mutable loc: location; - mutable spill: bool; - mutable spill_cost: int; } + mutable loc: location; } and location = Unknown @@ -58,19 +56,14 @@ and stack_location = type reg = t let dummy = - { raw_name = Raw_name.Anon; stamp = 0; typ = Int; loc = Unknown; - spill = false; spill_cost = 0; - } + { raw_name = Raw_name.Anon; stamp = 0; typ = Int; loc = Unknown; } let currstamp = ref 0 let reg_list = ref([] : t list) let hw_reg_list = ref ([] : t list) let create ty = - let r = { raw_name = Raw_name.Anon; stamp = !currstamp; typ = ty; - loc = Unknown; - spill = false; - spill_cost = 0; } in + let r = { raw_name = Raw_name.Anon; stamp = !currstamp; typ = ty; loc = Unknown; } in reg_list := r :: !reg_list; incr currstamp; r @@ -93,9 +86,7 @@ let clone r = nr let at_location ty loc = - let r = { raw_name = Raw_name.R; stamp = !currstamp; typ = ty; loc; - spill = false; - spill_cost = 0; } in + let r = { raw_name = Raw_name.R; stamp = !currstamp; typ = ty; loc; } in hw_reg_list := r :: !hw_reg_list; incr currstamp; r @@ -121,11 +112,7 @@ let is_unknown t = let name t = match Raw_name.to_string t.raw_name with | None -> "" - | Some raw_name -> - if t.spill then - "spilled-" ^ raw_name - else - raw_name + | Some raw_name -> raw_name let first_virtual_reg_stamp = ref (-1) @@ -155,11 +142,7 @@ let all_registers() = !reg_list let num_registers() = !currstamp let reinit_reg r = - r.loc <- Unknown; - (* Preserve the very high spill costs introduced by the reloading pass *) - if r.spill_cost >= 100000 - then r.spill_cost <- 100000 - else r.spill_cost <- 0 + r.loc <- Unknown let reinit() = List.iter reinit_reg !reg_list diff --git a/backend/reg.mli b/backend/reg.mli index c2d7bd5ccec..75627f3327a 100644 --- a/backend/reg.mli +++ b/backend/reg.mli @@ -25,9 +25,7 @@ type t = { mutable raw_name: Raw_name.t; (* Name *) stamp: int; (* Unique stamp *) typ: Cmm.machtype_component; (* Type of contents *) - mutable loc: location; (* Actual location *) - mutable spill: bool; (* "true" to force stack allocation *) - mutable spill_cost: int; } (* Estimate of spilling cost *) + mutable loc: location; } (* Actual location *) and location = Unknown diff --git a/backend/regalloc/regalloc_gi.ml b/backend/regalloc/regalloc_gi.ml index 34b9b895ebc..d4672646355 100644 --- a/backend/regalloc/regalloc_gi.ml +++ b/backend/regalloc/regalloc_gi.ml @@ -134,16 +134,15 @@ let rec main : round:int -> flat:bool -> State.t -> Cfg_with_infos.t -> unit = log "main, round #%d" round; log_cfg_with_infos cfg_with_infos); if debug then log "updating spilling costs"; - update_spill_cost cfg_with_infos ~flat (); + let costs = SpillCosts.compute cfg_with_infos ~flat () in State.iter_introduced_temporaries state ~f:(fun (reg : Reg.t) -> - reg.Reg.spill_cost <- reg.Reg.spill_cost + 10_000); + SpillCosts.add_to_reg costs reg 10_000); if debug then ( log "spilling costs"; indent (); - List.iter (Reg.all_registers ()) ~f:(fun (reg : Reg.t) -> - reg.Reg.spill <- false; - log "%a: %d" Printreg.reg reg reg.spill_cost); + SpillCosts.iter costs ~f:(fun (reg : Reg.t) (cost : int) -> + log "%a: %d" Printreg.reg reg cost); dedent ()); let hardware_registers, prio_queue = make_hardware_registers_and_prio_queue cfg_with_infos @@ -162,7 +161,7 @@ let rec main : round:int -> flat:bool -> State.t -> Cfg_with_infos.t -> unit = indent (); log "got register %a (prio=%d)" Printreg.reg reg priority); (match - Hardware_registers.find_available hardware_registers reg interval + Hardware_registers.find_available hardware_registers costs reg interval with | For_assignment { hardware_reg } -> if debug @@ -211,7 +210,6 @@ let rec main : round:int -> flat:bool -> State.t -> Cfg_with_infos.t -> unit = | Split_or_spill -> (* CR xclerc for xclerc: we should actually try to split. *) if debug then log "spilling %a" Printreg.reg reg; - reg.Reg.spill <- true; spilling := (reg, interval) :: !spilling); if debug then dedent () done; @@ -280,7 +278,6 @@ let run : Cfg_with_infos.t -> Cfg_with_infos.t = (match Reg.Set.elements spilling_because_unused with | [] -> () | _ :: _ as spilled_nodes -> - List.iter spilled_nodes ~f:(fun reg -> reg.Reg.spill <- true); (* note: rewrite will remove the `spilling` registers from the "spilled" work list and set the field to unknown. *) let (_ : bool) = rewrite state cfg_with_infos ~spilled_nodes in diff --git a/backend/regalloc/regalloc_gi_utils.ml b/backend/regalloc/regalloc_gi_utils.ml index c6dbb5b6e7f..1fd8cf4dcf5 100644 --- a/backend/regalloc/regalloc_gi_utils.ml +++ b/backend/regalloc/regalloc_gi_utils.ml @@ -620,11 +620,11 @@ module Hardware_registers = struct fun t ~of_reg ~f ~init -> Array.fold_left t.(Proc.register_class of_reg) ~f ~init - let actual_cost (reg : Reg.t) : int = + let actual_cost (costs : SpillCosts.t) (reg : Reg.t) : int = (* CR xclerc for xclerc: it could make sense to give a lower cost to reg already spilled (e.g. by the split preprocessing) since they already have a stack slot *) - reg.Reg.spill_cost + SpillCosts.for_reg costs reg let overlap (hardware_reg : Hardware_register.t) (interval : Interval.t) : bool = @@ -668,7 +668,8 @@ module Hardware_registers = struct else acc) |> Option.map fst - let find_evictable (t : t) (reg : Reg.t) (interval : Interval.t) : available = + let find_evictable (t : t) (costs : SpillCosts.t) (reg : Reg.t) + (interval : Interval.t) : available = let eviction = fold_class t ~of_reg:reg ~init:None ~f:(fun acc hardware_reg -> if debug @@ -705,7 +706,8 @@ module Hardware_registers = struct (acc_cost, acc_evictable) { Hardware_register.pseudo_reg; interval = _; evictable } -> - acc_cost + actual_cost pseudo_reg, acc_evictable && evictable) + ( acc_cost + actual_cost costs pseudo_reg, + acc_evictable && evictable )) in if debug then dedent (); if not evictable @@ -714,7 +716,7 @@ module Hardware_registers = struct let evict_cost = match acc with None -> max_int | Some (_, _, c) -> c in - if cost < evict_cost && cost < actual_cost reg + if cost < evict_cost && cost < actual_cost costs reg then ( if debug then @@ -729,8 +731,8 @@ module Hardware_registers = struct For_eviction { hardware_reg; evicted_regs } | None -> Split_or_spill - let find_available : t -> Reg.t -> Interval.t -> available = - fun t reg interval -> + let find_available : t -> SpillCosts.t -> Reg.t -> Interval.t -> available = + fun t costs reg interval -> let with_no_overlap = let heuristic = match Lazy.force Selection_heuristics.value with @@ -756,5 +758,5 @@ module Hardware_registers = struct | Some hardware_reg -> For_assignment { hardware_reg } | None -> if debug then log "trying to find an evictable register"; - find_evictable t reg interval + find_evictable t costs reg interval end diff --git a/backend/regalloc/regalloc_gi_utils.mli b/backend/regalloc/regalloc_gi_utils.mli index 571a4897990..a5a349a5fbf 100644 --- a/backend/regalloc/regalloc_gi_utils.mli +++ b/backend/regalloc/regalloc_gi_utils.mli @@ -190,5 +190,5 @@ module Hardware_registers : sig val of_reg : t -> Reg.t -> Hardware_register.t option - val find_available : t -> Reg.t -> Interval.t -> available + val find_available : t -> SpillCosts.t -> Reg.t -> Interval.t -> available end diff --git a/backend/regalloc/regalloc_irc.ml b/backend/regalloc/regalloc_irc.ml index cf4bf1d7a77..36ed73e3064 100644 --- a/backend/regalloc/regalloc_irc.ml +++ b/backend/regalloc/regalloc_irc.ml @@ -256,8 +256,9 @@ let freeze : State.t -> unit = State.add_simplify_work_list state reg; freeze_moves state reg -let select_spilling_register_using_heuristics : State.t -> Reg.t = - fun state -> +let select_spilling_register_using_heuristics : State.t -> SpillCosts.t -> Reg.t + = + fun state costs -> match Lazy.force Spilling_heuristics.value with | Set_choose -> ( (* This is the "heuristics" from the IRC paper: pick any candidate, just try @@ -280,8 +281,9 @@ let select_spilling_register_using_heuristics : State.t -> Reg.t = let weighted_cost (reg : Reg.t) = if debug then - log "register %a has spill cost %d" Printreg.reg reg reg.Reg.spill_cost; - (float reg.Reg.spill_cost /. float (State.degree state reg)) + log "register %a has spill cost %d" Printreg.reg reg + (SpillCosts.for_reg costs reg); + (float (SpillCosts.for_reg costs reg) /. float (State.degree state reg)) (* note: while this magic constant is questionable, it is key to not favor the introduced temporaries which, by construct, have very few occurrences. *) @@ -300,13 +302,13 @@ let select_spilling_register_using_heuristics : State.t -> Reg.t = else acc) |> fst) -let select_spill : State.t -> unit = - fun state -> +let select_spill : State.t -> SpillCosts.t -> unit = + fun state costs -> if debug then ( log "select_spill"; indent ()); - let reg = select_spilling_register_using_heuristics state in + let reg = select_spilling_register_using_heuristics state costs in if debug then log "chose %a using heuristics %S" Printreg.reg reg @@ -461,7 +463,7 @@ let rec main : round:int -> State.t -> Cfg_with_infos.t -> unit = make_work_list state; State.invariant state; if debug then log_work_list_desc "before loop"; - let spill_cost_is_up_to_date = ref false in + let spill_costs = ref (None : SpillCosts.t option) in let continue = ref true in while !continue do if not (State.is_empty_simplify_work_list state) @@ -471,16 +473,24 @@ let rec main : round:int -> State.t -> Cfg_with_infos.t -> unit = else if not (State.is_empty_freeze_work_list state) then freeze state else if not (State.is_empty_spill_work_list state) - then ( - if not !spill_cost_is_up_to_date - then ( - (match Lazy.force Spilling_heuristics.value with - | Set_choose -> - (* note: `spill_cost` will not be used by the heuristics *) () - | Flat_uses -> update_spill_cost cfg_with_infos ~flat:true () - | Hierarchical_uses -> update_spill_cost cfg_with_infos ~flat:false ()); - spill_cost_is_up_to_date := true); - select_spill state) + then + let costs = + match !spill_costs with + | Some costs -> costs + | None -> + let costs = + match Lazy.force Spilling_heuristics.value with + | Set_choose -> + (* note: `spill_cost` will not be used by the heuristics *) + SpillCosts.empty () + | Flat_uses -> SpillCosts.compute cfg_with_infos ~flat:true () + | Hierarchical_uses -> + SpillCosts.compute cfg_with_infos ~flat:false () + in + spill_costs := Some costs; + costs + in + select_spill state costs else continue := false; if debug then log_work_list_desc "end of loop"; State.invariant state diff --git a/backend/regalloc/regalloc_ls.ml b/backend/regalloc/regalloc_ls.ml index 23695d9af63..18966d39993 100644 --- a/backend/regalloc/regalloc_ls.ml +++ b/backend/regalloc/regalloc_ls.ml @@ -110,7 +110,6 @@ let allocate_stack_slot : Reg.t -> spilling_reg = fun reg -> indent (); log "spilling register %a" Printreg.reg reg; - reg.spill <- true; dedent (); Spilling reg @@ -119,9 +118,8 @@ exception No_free_register let allocate_free_register : State.t -> Interval.t -> spilling_reg = fun state interval -> let reg = interval.reg in - match reg.loc, reg.spill with - | Unknown, true -> allocate_stack_slot reg - | Unknown, _ -> ( + match reg.loc with + | Unknown -> ( let reg_class = Proc.register_class reg in let intervals = State.active state ~reg_class in let first_available = Proc.first_available_register.(reg_class) in @@ -161,7 +159,6 @@ let allocate_free_register : State.t -> Interval.t -> spilling_reg = else if available.(idx) then ( reg.loc <- Reg (first_available + idx); - reg.spill <- false; Interval.DLL.insert_sorted intervals.active_dll interval; if debug then ( @@ -172,7 +169,7 @@ let allocate_free_register : State.t -> Interval.t -> spilling_reg = else assign (succ idx) in assign 0) - | (Reg _ | Stack _), _ -> Not_spilling + | Reg _ | Stack _ -> Not_spilling let allocate_blocked_register : State.t -> Interval.t -> spilling_reg = fun state interval -> @@ -282,7 +279,6 @@ let run : Cfg_with_infos.t -> Cfg_with_infos.t = (match Reg.Set.elements spilling_because_unused with | [] -> () | _ :: _ as spilled_nodes -> - List.iter spilled_nodes ~f:(fun reg -> reg.Reg.spill <- true); rewrite state cfg_with_infos ~spilled_nodes ~block_temporaries:false; Cfg_with_infos.invalidate_liveness cfg_with_infos); main ~round:1 state cfg_with_infos; diff --git a/backend/regalloc/regalloc_utils.ml b/backend/regalloc/regalloc_utils.ml index f28206f3dba..5ca1fe56d03 100644 --- a/backend/regalloc/regalloc_utils.ml +++ b/backend/regalloc/regalloc_utils.ml @@ -370,71 +370,83 @@ let update_live_fields : Cfg_with_layout.t -> liveness -> unit = DLL.iter block.body ~f:set_liveness; set_liveness block.terminator) -(* CR-soon xclerc for xclerc: consider adding an overflow check. *) -let pow ~base n = - let res = ref 1 in - for _ = 1 to n do - res := !res * base - done; - !res +module SpillCosts = struct + type t = int Reg.Tbl.t -let spill_normal_cost = lazy (find_param_value "SPILL_NORMAL_COST") + let empty () = Reg.Tbl.create 1 -let spill_cold_cost = lazy (find_param_value "SPILL_COLD_COST") + let iter costs ~f = Reg.Tbl.iter f costs -let spill_loop_cost = lazy (find_param_value "SPILL_LOOP_COST") + let for_reg costs reg = + match Reg.Tbl.find_opt costs reg with None -> 0 | Some cost -> cost -let cost_for_block : Cfg.basic_block -> int = - fun block -> - let param = - match block.cold with false -> spill_normal_cost | true -> spill_cold_cost - in - match Lazy.force param with None -> 1 | Some cost -> int_of_string cost - -let update_spill_cost : Cfg_with_infos.t -> flat:bool -> unit -> unit = - fun cfg_with_infos ~flat () -> - List.iter (Reg.all_registers ()) ~f:(fun reg -> reg.Reg.spill_cost <- 0); - let update_reg (cost : int) (reg : Reg.t) : unit = - (* CR-soon xclerc for xclerc: consider adding an overflow check. *) - reg.Reg.spill_cost <- reg.Reg.spill_cost + cost - in - let update_array (cost : int) (regs : Reg.t array) : unit = - Array.iter regs ~f:(fun reg -> update_reg cost reg) - in - let update_instr (cost : int) (instr : _ Cfg.instruction) : unit = - update_array cost instr.arg; - update_array cost instr.res - in - let cfg = Cfg_with_infos.cfg cfg_with_infos in - let loops_depths : Cfg_loop_infos.loop_depths = - if flat - then Label.Map.empty - else (Cfg_with_infos.loop_infos cfg_with_infos).loop_depths - in - Cfg.iter_blocks cfg ~f:(fun label block -> - let base_cost = cost_for_block block in - let cost_multiplier = - match Label.Map.find_opt label loops_depths with - | None -> - assert flat; - 1 - | Some depth -> - let base = - match Lazy.force spill_loop_cost with - | None -> 10 - | Some cost -> int_of_string cost - in - pow ~base depth - in - let cost = base_cost * cost_multiplier in - DLL.iter ~f:(fun instr -> update_instr cost instr) block.body; - (* Ignore probes *) - match[@ocaml.warning "-4"] block.terminator.desc with - | Prim { op = Probe _; _ } -> () - | Never | Always _ | Parity_test _ | Truth_test _ | Float_test _ - | Int_test _ | Switch _ | Return | Raise _ | Tailcall_self _ - | Tailcall_func _ | Call_no_return _ | Call _ | Prim _ -> - update_instr cost block.terminator) + let add_to_reg costs reg delta = + let curr = + match Reg.Tbl.find_opt costs reg with None -> 0 | Some cost -> cost + in + Reg.Tbl.replace costs reg (curr + delta) + + let normal_cost = lazy (find_param_value "SPILL_NORMAL_COST") + + let cold_cost = lazy (find_param_value "SPILL_COLD_COST") + + let loop_cost = lazy (find_param_value "SPILL_LOOP_COST") + + let cost_for_block : Cfg.basic_block -> int = + fun block -> + let param = + match block.cold with false -> normal_cost | true -> cold_cost + in + match Lazy.force param with None -> 1 | Some cost -> int_of_string cost + + let compute : Cfg_with_infos.t -> flat:bool -> unit -> t = + fun cfg_with_infos ~flat () -> + let costs = Reg.Tbl.create (List.length (Reg.all_registers ())) in + List.iter (Reg.all_registers ()) ~f:(fun reg -> Reg.Tbl.replace costs reg 0); + let update_reg (cost : int) (reg : Reg.t) : unit = + (* CR-soon xclerc for xclerc: consider adding an overflow check. *) + add_to_reg costs reg cost + in + let update_array (cost : int) (regs : Reg.t array) : unit = + Array.iter regs ~f:(fun reg -> update_reg cost reg) + in + let update_instr (cost : int) (instr : _ Cfg.instruction) : unit = + update_array cost instr.arg; + update_array cost instr.res + in + let cfg = Cfg_with_infos.cfg cfg_with_infos in + let loops_depths : Cfg_loop_infos.loop_depths = + if flat + then Label.Map.empty + else (Cfg_with_infos.loop_infos cfg_with_infos).loop_depths + in + Cfg.iter_blocks cfg ~f:(fun label block -> + let base_cost = cost_for_block block in + let cost_multiplier = + match Label.Map.find_opt label loops_depths with + | None -> + assert flat; + 1 + | Some depth -> + let base = + match Lazy.force loop_cost with + | None -> 10 + | Some cost -> int_of_string cost + in + (* CR-soon xclerc for xclerc: consider adding an overflow check. *) + Misc.power ~base depth + in + let cost = base_cost * cost_multiplier in + DLL.iter ~f:(fun instr -> update_instr cost instr) block.body; + (* Ignore probes *) + match[@ocaml.warning "-4"] block.terminator.desc with + | Prim { op = Probe _; _ } -> () + | Never | Always _ | Parity_test _ | Truth_test _ | Float_test _ + | Int_test _ | Switch _ | Return | Raise _ | Tailcall_self _ + | Tailcall_func _ | Call_no_return _ | Call _ | Prim _ -> + update_instr cost block.terminator); + costs +end let check_length str arr expected = let actual = Array.length arr in diff --git a/backend/regalloc/regalloc_utils.mli b/backend/regalloc/regalloc_utils.mli index 32f61c4fc4d..5a187383318 100644 --- a/backend/regalloc/regalloc_utils.mli +++ b/backend/regalloc/regalloc_utils.mli @@ -113,11 +113,23 @@ val remove_prologue_if_not_required : Cfg_with_layout.t -> unit val update_live_fields : Cfg_with_layout.t -> liveness -> unit -(* The spill cost is currently the number of occurrences of the register. If - [flat] is true, the same weight is given to all uses; if [flat] is false, the - information about loops is computed and used to give more weight to uses - inside (nested) loops. *) -val update_spill_cost : Cfg_with_infos.t -> flat:bool -> unit -> unit +module SpillCosts : sig + type t + + val empty : unit -> t + + val iter : t -> f:(Reg.t -> int -> unit) -> unit + + val for_reg : t -> Reg.t -> int + + val add_to_reg : t -> Reg.t -> int -> unit + + (* The spill cost is currently the number of occurrences of the register. If + [flat] is true, the same weight is given to all uses; if [flat] is false, + the information about loops is computed and used to give more weight to + uses inside (nested) loops. *) + val compute : Cfg_with_infos.t -> flat:bool -> unit -> t +end val check_length : string -> 'a array -> int -> unit diff --git a/backend/regalloc/regalloc_validate.ml b/backend/regalloc/regalloc_validate.ml index 00a35134797..73e3f2c5aa8 100644 --- a/backend/regalloc/regalloc_validate.ml +++ b/backend/regalloc/regalloc_validate.ml @@ -229,8 +229,7 @@ end = struct } let to_dummy_reg (t : t) : Reg.t = - { Reg.dummy with - raw_name = t.for_print.raw_name; + { raw_name = t.for_print.raw_name; typ = t.for_print.typ; stamp = t.for_print.stamp; loc = Reg_id.to_loc_lossy t.reg_id diff --git a/utils/misc.ml b/utils/misc.ml index a2fde2f2dd3..000bca4de86 100644 --- a/utils/misc.ml +++ b/utils/misc.ml @@ -764,6 +764,13 @@ let rec log2 n = let rec log2_nativeint n = if n <= 1n then 0 else 1 + log2_nativeint (Nativeint.shift_right n 1) +let power ~base n = + let res = ref 1 in + for _ = 1 to n do + res := !res * base + done; + !res + let align n a = if n >= 0 then (n + a - 1) land (-a) else n land (-a) diff --git a/utils/misc.mli b/utils/misc.mli index 4da3343cd5a..5f85bd8e194 100644 --- a/utils/misc.mli +++ b/utils/misc.mli @@ -498,6 +498,9 @@ val log2_nativeint: nativeint -> int [n = Nativeint.shift_left 1n s] *) +val power : base:int -> int -> int +(** [power ~base x] computes [base**x]. *) + val align: int -> int -> int (** [align n a] rounds [n] upwards to a multiple of [a] (a power of 2). *)