Commit 7ab20356 authored by qinsoon's avatar qinsoon

[wip] reg alloc

parent b4e69df4
......@@ -19,48 +19,73 @@ use compiler::backend::reg_alloc::graph_coloring::petgraph::graph::NodeIndex;
const COALESCING : bool = true;
/// GraphColoring algorithm
/// based on Appel's book section 11.4
pub struct GraphColoring<'a> {
// context
pub func: &'a mut MuFunctionVersion,
pub cf: &'a mut CompiledFunction,
pub vm: &'a VM,
pub ig: InterferenceGraph,
/// machine registers, preassigned a color
precolored: LinkedHashSet<NodeIndex>,
/// all colors available
colors: LinkedHashMap<backend::RegGroup, LinkedHashSet<MuID>>,
pub colored_nodes: Vec<NodeIndex>,
/// temporaries, not precolored and not yet processed
initial: Vec<NodeIndex>,
degree: LinkedHashMap<NodeIndex, usize>,
worklist_moves: Vec<Move>,
movelist: LinkedHashMap<NodeIndex, RefCell<Vec<Move>>>,
active_moves: LinkedHashSet<Move>,
/// whether a temp is spillable
// FIXME: not used
spillable: LinkedHashMap<MuID, bool>,
/// list of low-degree non-move-related nodes
worklist_simplify: LinkedHashSet<NodeIndex>,
/// low-degree move related nodes
worklist_freeze: LinkedHashSet<NodeIndex>,
/// nodes marked for spilling during this round
worklist_spill: Vec<NodeIndex>,
/// nodes marked for spilling during this round
spilled_nodes: Vec<NodeIndex>,
/// temps that have been coalesced
/// when u <- v is coalesced, v is added to this set and u put back on some work list
coalesced_nodes: LinkedHashSet<NodeIndex>,
/// nodes successfully colored
colored_nodes: Vec<NodeIndex>,
/// stack containing temporaries removed from the graph
select_stack: Vec<NodeIndex>,
/// moves that have been coalesced
coalesced_moves: LinkedHashSet<Move>,
/// moves whose source and target interfere
constrained_moves: LinkedHashSet<Move>,
/// moves that will no longer be considered for coalescing
frozen_moves: LinkedHashSet<Move>,
/// moves enabled for possible coalescing
worklist_moves: Vec<Move>,
/// moves not yet ready for coalescing
active_moves: LinkedHashSet<Move>,
/// degree of nodes
degree: LinkedHashMap<NodeIndex, usize>,
/// a mapping from a node to the list of moves it is associated with
movelist: LinkedHashMap<NodeIndex, RefCell<Vec<Move>>>,
/// when a move (u, v) has been coalesced, and v put in coalescedNodes, then alias(v) = u
alias: LinkedHashMap<NodeIndex, NodeIndex>,
worklist_spill: Vec<NodeIndex>,
spillable: LinkedHashMap<MuID, bool>,
spilled_nodes: Vec<NodeIndex>,
// for validation
spill_history: LinkedHashMap<MuID, P<Value>>, // we need to log all registers get spilled with their spill location
spill_scratch_temps: LinkedHashMap<MuID, MuID>, // we need to know the mapping between scratch temp -> original temp
worklist_freeze: LinkedHashSet<NodeIndex>,
frozen_moves: LinkedHashSet<Move>,
worklist_simplify: LinkedHashSet<NodeIndex>,
select_stack: Vec<NodeIndex>
// for validation use
/// we need to log all registers get spilled with their spill location
spill_history: LinkedHashMap<MuID, P<Value>>,
/// we need to know the mapping between scratch temp -> original temp
spill_scratch_temps: LinkedHashMap<MuID, MuID>
}
impl <'a> GraphColoring<'a> {
/// starts coloring
pub fn start (func: &'a mut MuFunctionVersion, cf: &'a mut CompiledFunction, vm: &'a VM) -> GraphColoring<'a> {
GraphColoring::start_with_spill_history(LinkedHashMap::new(), LinkedHashMap::new(), func, cf, vm)
}
/// restarts coloring with spill history
fn start_with_spill_history(spill_history: LinkedHashMap<MuID, P<Value>>,
spill_scratch_temps: LinkedHashMap<MuID, MuID>,
func: &'a mut MuFunctionVersion, cf: &'a mut CompiledFunction, vm: &'a VM) -> GraphColoring<'a>
......@@ -74,9 +99,7 @@ impl <'a> GraphColoring<'a> {
func: func,
cf: cf,
vm: vm,
ig: ig,
precolored: LinkedHashSet::new(),
colors: {
let mut map = LinkedHashMap::new();
......@@ -85,10 +108,8 @@ impl <'a> GraphColoring<'a> {
map
},
colored_nodes: Vec::new(),
initial: Vec::new(),
degree: LinkedHashMap::new(),
worklist_moves: Vec::new(),
movelist: LinkedHashMap::new(),
active_moves: LinkedHashSet::new(),
......@@ -96,17 +117,13 @@ impl <'a> GraphColoring<'a> {
coalesced_moves: LinkedHashSet::new(),
constrained_moves: LinkedHashSet::new(),
alias: LinkedHashMap::new(),
worklist_spill: Vec::new(),
spillable: LinkedHashMap::new(),
spilled_nodes: Vec::new(),
spill_history: spill_history,
spill_scratch_temps: spill_scratch_temps,
worklist_freeze: LinkedHashSet::new(),
frozen_moves: LinkedHashSet::new(),
worklist_simplify: LinkedHashSet::new(),
select_stack: Vec::new(),
};
......@@ -114,24 +131,29 @@ impl <'a> GraphColoring<'a> {
coloring.regalloc()
}
/// returns formatted string for a node
fn display_node(&self, node: NodeIndex) -> String {
let id = self.ig.get_temp_of(node);
self.display_id(id)
}
/// returns formatted string for an ID
fn display_id(&self, id: MuID) -> String {
self.func.context.get_temp_display(id)
}
/// returns formatted string for a move
fn display_move(&self, m: Move) -> String {
format!("Move: {} -> {}", self.display_node(m.from), self.display_node(m.to))
}
/// does coloring register allocation
fn regalloc(mut self) -> GraphColoring<'a> {
trace!("---InterenceGraph---");
let _p = hprof::enter("regalloc: graph coloring");
self.ig.print(&self.func.context);
// start timing for graph coloring
let _p = hprof::enter("regalloc: graph coloring");
// precolor for all machine registers
for reg in backend::all_regs().values() {
......@@ -146,20 +168,22 @@ impl <'a> GraphColoring<'a> {
let group = backend::pick_group_for_reg(reg_id);
self.colors.get_mut(&group).unwrap().insert(reg_id);
}
// push uncolored nodes to initial work set
for node in self.ig.nodes() {
if !self.ig.is_colored(node) {
self.initial.push(node);
let outdegree = self.ig.outdegree_of(node);
self.degree.insert(node, outdegree);
trace!("{} has a degree of {}", self.display_node(node), outdegree);
let degree = self.ig.get_degree_of(node);
self.degree.insert(node, degree);
trace!("{} has a degree of {}", self.display_node(node), degree);
}
}
// initialize work
self.build();
self.make_work_list();
// main loop
while {
if !self.worklist_simplify.is_empty() {
self.simplify();
......@@ -176,11 +200,14 @@ impl <'a> GraphColoring<'a> {
&& self.worklist_freeze.is_empty()
&& self.worklist_spill.is_empty())
} {}
// pick color for nodes
self.assign_colors();
// finish
drop(_p);
// if we need to spill
if !self.spilled_nodes.is_empty() {
trace!("spill required");
if cfg!(debug_assertions) {
......@@ -190,8 +217,10 @@ impl <'a> GraphColoring<'a> {
}
}
// rewrite program to insert spilling code
self.rewrite_program();
// recursively redo graph coloring
return GraphColoring::start_with_spill_history(self.spill_history.clone(), self.spill_scratch_temps.clone(), self.func, self.cf, self.vm);
}
......@@ -221,7 +250,7 @@ impl <'a> GraphColoring<'a> {
if {
// condition: degree >= K
let degree = self.ig.degree_of(node);
let degree = self.ig.get_degree_of(node);
let n_regs = self.n_regs_for_node(node);
degree >= n_regs
......@@ -306,7 +335,7 @@ impl <'a> GraphColoring<'a> {
let mut adj = LinkedHashSet::new();
// add n's successors
for s in self.ig.outedges_of(n) {
for s in self.ig.get_edges_of(n) {
adj.insert(s);
}
......@@ -626,7 +655,7 @@ impl <'a> GraphColoring<'a> {
trace!("all the colors for this temp: {:?}", ok_colors);
for w in self.ig.outedges_of(n) {
for w in self.ig.get_edges_of(n) {
let w_alias = self.get_alias(w);
match self.ig.get_color_of(w_alias) {
None => {}, // do nothing
......
......@@ -10,24 +10,40 @@ use compiler::backend::reg_alloc::graph_coloring::petgraph;
use compiler::backend::reg_alloc::graph_coloring::petgraph::Graph;
use compiler::backend::reg_alloc::graph_coloring::petgraph::graph::NodeIndex;
/// GraphNode represents a node in the interference graph.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GraphNode {
/// temp ID (could be register)
temp: MuID,
/// assigned color
color: Option<MuID>,
/// temp register group (which machine register class we should assign)
group: backend::RegGroup,
/// cost to spill this temp
spill_cost: f32
}
/// Move represents a move between two nodes (referred by index)
/// We need to know the moves so that we can coalesce.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Move{pub from: NodeIndex, pub to: NodeIndex}
/// InterferenceGraph represents the interference graph, including
/// * the graph
/// * all the nodes and its NodeIndex (a node is referred to by NodeIndex)
/// * all the moves
pub struct InterferenceGraph {
/// the internal graph
graph: Graph<GraphNode, (), petgraph::Undirected>,
/// a map of all nodes (from temp ID to node index)
/// node index is how nodes are referred to with pet_graph
nodes: LinkedHashMap<MuID, NodeIndex>,
/// a set of all moves
moves: LinkedHashSet<Move>,
}
impl InterferenceGraph {
/// creates a new graph
fn new() -> InterferenceGraph {
InterferenceGraph {
graph: Graph::new_undirected(),
......@@ -35,10 +51,13 @@ impl InterferenceGraph {
moves: LinkedHashSet::new()
}
}
/// creates a new node for a temp (if we already created a temp for the temp, returns the node)
/// This function will increase spill cost for the node by 1 each tiem it is called for the temp
fn new_node(&mut self, reg_id: MuID, context: &FunctionContext) -> NodeIndex {
let entry = context.get_value(reg_id).unwrap();
// if it is the first time, create the node
if !self.nodes.contains_key(&reg_id) {
let node = GraphNode {
temp: reg_id,
......@@ -47,27 +66,31 @@ impl InterferenceGraph {
spill_cost: 0.0f32
};
// add to the graph
let index = self.graph.add_node(node);
// save index
self.nodes.insert(reg_id, index);
}
// get the node index
let node_index = *self.nodes.get(&reg_id).unwrap();
// get node
let node_mut = self.graph.node_weight_mut(node_index).unwrap();
// increase node spill cost
node_mut.spill_cost += 1.0f32;
node_index
}
/// returns the node index for a temp
pub fn get_node(&self, reg: MuID) -> NodeIndex {
match self.nodes.get(&reg) {
Some(index) => *index,
None => panic!("do not have a node for {}", reg)
}
}
/// returns all the temps in the graph
pub fn temps(&self) -> Vec<MuID>{
let mut ret = vec![];
for reg in self.nodes.keys() {
......@@ -75,7 +98,8 @@ impl InterferenceGraph {
}
ret
}
/// returns all the nodes in the graph
pub fn nodes(&self) -> Vec<NodeIndex> {
let mut ret = vec![];
for index in self.nodes.values() {
......@@ -83,31 +107,34 @@ impl InterferenceGraph {
}
ret
}
/// returns all the moves in the graph
pub fn moves(&self) -> &LinkedHashSet<Move> {
&self.moves
}
/// returns the number of nodes in the graph
pub fn n_nodes(&self) -> usize {
self.nodes.len()
}
/// adds a move edge between two nodes
fn add_move(&mut self, src: NodeIndex, dst: NodeIndex) {
let src = {
let temp_src = self.get_temp_of(src);
if temp_src < MACHINE_ID_END {
let alias = backend::get_color_for_precolored(self.get_temp_of(src));
// get the color for the machine register, e.g. rax for eax/ax/al/ah
let alias = backend::get_color_for_precolored(temp_src);
self.get_node(alias)
} else {
src
}
};
let dst = {
let temp_dst = self.get_temp_of(dst);
if temp_dst < MACHINE_ID_END {
let alias = backend::get_color_for_precolored(self.get_temp_of(dst));
let alias = backend::get_color_for_precolored(temp_dst);
self.get_node(alias)
} else {
dst
......@@ -116,14 +143,16 @@ impl InterferenceGraph {
self.moves.insert(Move{from: src, to: dst});
}
/// adds an interference edge between two nodes
pub fn add_interference_edge(&mut self, from: NodeIndex, to: NodeIndex) {
// adds edge to the internal graph
self.graph.update_edge(from, to, ());
// if one of the node is machine register, we also add
// interference edge to its alias
// e.g. if we have %a, %edi interferenced,
// we also add %a, %rdi interference
// e.g. if we have %a - %edi interfered,
// we also add %a - %rdi interference
let from_tmp = self.graph.node_weight(from).unwrap().temp;
let to_tmp = self.graph.node_weight(to).unwrap().temp;
......@@ -147,67 +176,71 @@ impl InterferenceGraph {
}
}
pub fn is_interferenced_with(&self, node1: NodeIndex, node2: NodeIndex) -> bool {
/// is two nodes interfered?
pub fn is_interfered_with(&self, node1: NodeIndex, node2: NodeIndex) -> bool {
let edge = self.graph.find_edge(node1, node2);
edge.is_some()
}
/// set color for a node
pub fn color_node(&mut self, node: NodeIndex, color: MuID) {
self.graph.node_weight_mut(node).unwrap().color = Some(color);
}
/// is a node colored yet?
pub fn is_colored(&self, node: NodeIndex) -> bool {
self.graph.node_weight(node).unwrap().color.is_some()
}
/// gets the color of a node
pub fn get_color_of(&self, node: NodeIndex) -> Option<MuID> {
self.graph.node_weight(node).unwrap().color
}
/// gets the reg group of a node
pub fn get_group_of(&self, node: NodeIndex) -> backend::RegGroup {
self.graph.node_weight(node).unwrap().group
}
/// gets the temporary of a node
pub fn get_temp_of(&self, node: NodeIndex) -> MuID {
self.graph.node_weight(node).unwrap().temp
}
/// gets the spill cost of a node
pub fn get_spill_cost(&self, node: NodeIndex) -> f32 {
self.graph.node_weight(node).unwrap().spill_cost
}
/// are two nodes the same node?
fn is_same_node(&self, node1: NodeIndex, node2: NodeIndex) -> bool {
node1 == node2
}
/// are two nodes from the same reg group?
fn is_same_group(&self, node1: NodeIndex, node2: NodeIndex) -> bool {
let node1 = self.graph.node_weight(node1).unwrap();
let node2 = self.graph.node_weight(node2).unwrap();
node1.group == node2.group
}
/// are two nodes adjacent?
pub fn is_adj(&self, from: NodeIndex, to: NodeIndex) -> bool {
self.is_interferenced_with(from, to)
self.is_interfered_with(from, to)
}
pub fn outedges_of(&self, node: NodeIndex) -> Vec<NodeIndex> {
/// gets edges from a node
pub fn get_edges_of(&self, node: NodeIndex) -> Vec<NodeIndex> {
self.graph.neighbors(node).collect()
}
pub fn outdegree_of(&self, node: NodeIndex) -> usize {
self.outedges_of(node).len()
}
pub fn indegree_of(&self, node: NodeIndex) -> usize {
self.outdegree_of(node)
}
pub fn degree_of(&self, node: NodeIndex) -> usize {
self.outdegree_of(node)
/// gets degree of a node (number of edges from the node)
pub fn get_degree_of(&self, node: NodeIndex) -> usize {
self.get_edges_of(node).len()
}
/// prints current graph for debugging (via trace log)
pub fn print(&self, context: &FunctionContext) {
use compiler::backend::reg_alloc::graph_coloring::petgraph::dot::Dot;
use compiler::backend::reg_alloc::graph_coloring::petgraph::dot::Config;
......@@ -232,91 +265,286 @@ impl InterferenceGraph {
}
}
const TRACE_BUILD_LIVE_SET : bool = false;
/// prints trace during building liveness for debugging?
const TRACE_LIVENESS: bool = false;
/// builds interference graph based on chaitin briggs algorithms
/// (reference: Tailoring Graph-coloring Register Allocation For Runtime Compilation - CGO'06, Figure 4)
pub fn build_interference_graph_chaitin_briggs(cf: &mut CompiledFunction, func: &MuFunctionVersion) -> InterferenceGraph {
let _p = hprof::enter("regalloc: build global liveness");
build_global_liveness(cf, func);
drop(_p);
let _p = hprof::enter("regalloc: build interference graph");
info!("---start building interference graph---");
let mut ig = InterferenceGraph::new();
fn build_live_set (cf: &mut CompiledFunction, func: &MuFunctionVersion) {
// precolor machine register nodes
for reg in backend::all_regs().values() {
let reg_id = reg.extract_ssa_id().unwrap();
let node = ig.new_node(reg_id, &func.context);
let precolor = backend::get_color_for_precolored(reg_id);
ig.color_node(node, precolor);
}
// initialize and creates nodes for all the involved temps/regs
for i in 0..cf.mc().number_of_insts() {
for reg_id in cf.mc().get_inst_reg_defines(i) {
ig.new_node(reg_id, &func.context);
}
for reg_id in cf.mc().get_inst_reg_uses(i) {
ig.new_node(reg_id, &func.context);
}
}
// for each basic block, insert interference edge while reversely traversing instructions
for block in cf.mc().get_all_blocks() {
// Current_Live(B) = LiveOut(B)
let mut current_live = LinkedHashSet::from_vec(match cf.mc().get_ir_block_liveout(&block) {
Some(liveout) => liveout.to_vec(),
None => panic!("cannot find liveout for block {}", block)
});
if TRACE_LIVENESS {
trace!("Block{}: live out", block);
for ele in current_live.iter() {
trace!("{}", func.context.get_temp_display(*ele));
}
}
let range = cf.mc().get_block_range(&block);
if range.is_none() {
warn!("Block{}: has no range (no instructions?)", block);
continue;
}
if TRACE_LIVENESS {
trace!("Block{}: range = {:?}", block, range.as_ref().unwrap());
}
// for every inst I in reverse order
for i in range.unwrap().rev() {
if cfg!(debug_assertions) {
trace!("Block{}: Inst{}", block, i);
cf.mc().trace_inst(i);
trace!("current live: ");
for ele in current_live.iter() {
trace!("{}", func.context.get_temp_display(*ele));
}
}
let src : Option<MuID> = {
if cf.mc().is_move(i) {
let src = cf.mc().get_inst_reg_uses(i);
let dst = cf.mc().get_inst_reg_defines(i);
// src: reg/imm/mem
// dest: reg/mem
// we dont care if src/dest is mem
if cf.mc().is_using_mem_op(i) {
None
} else {
if src.len() == 1 {
let node1 = ig.get_node(src[0]);
let node2 = ig.get_node(dst[0]);
trace!("add move between {} and {}",
func.context.get_temp_display(src[0]),
func.context.get_temp_display(dst[0]));
ig.add_move(node1, node2);
Some(src[0])
} else {
None
}
}
} else {
None
}
};
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: src={:?}", block, i, src);
}
// for every definition D in I
for d in cf.mc().get_inst_reg_defines(i) {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: for definition {}", block, i, func.context.get_temp_display(d));
}
// add an interference from D to every element E in Current_Live - {D}
// creating nodes if necessary
for e in current_live.iter() {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: for each live {}", block, i, func.context.get_temp_display(*e));
}
if src.is_none() || (src.is_some() && *e != src.unwrap()) {
let from = ig.get_node(d);
let to = ig.get_node(*e);
if !ig.is_same_node(from, to) &&ig.is_same_group(from, to) && !ig.is_adj(from, to) {
if !ig.is_colored(from) {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: add interference between {} and {}",
block, i,
func.context.get_temp_display(d),
func.context.get_temp_display(*e));
}
ig.add_interference_edge(from, to);
}
if !ig.is_colored(to) {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: add interference between {} and {}",
block, i,
func.context.get_temp_display(*e),
func.context.get_temp_display(d));
}
ig.add_interference_edge(to, from);
}
}
}
}
}
// for every definition D in I
for d in cf.mc().get_inst_reg_defines(i) {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: remove define {} from current_live",
block, i,
func.context.get_temp_display(d));
}
// remove D from Current_Live
current_live.remove(&d);
}
// for every use U in I
for u in cf.mc().get_inst_reg_uses(i) {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: add use {} to current_live",
block, i,
func.context.get_temp_display(u));
}
// add U to Current_live
current_live.insert(u);
}
if cfg!(debug_assertions) {
if TRACE_LIVENESS {
trace!("Block{}: Inst{}: done. current_live:", block, i);
}
for ele in current_live.iter() {
trace!("{}", func.context.get_temp_display(*ele));
}
}
}
}
drop(_p);
info!("---finish building interference graph---");
ig
}
/// builds global liveness for a compiled function
fn build_global_liveness(cf: &mut CompiledFunction, func: &MuFunctionVersion) {