To protect your data, the CISO officer has suggested users to enable GitLab 2FA as soon as possible.

Commit 68af6af6 authored by qinsoon's avatar qinsoon
Browse files

add comments for compiler passes

parent eb11865b
......@@ -3,7 +3,6 @@ use ast::ptr::*;
use ast::inst::*;
use ast::op;
use ast::op::*;
use ast::types;
use ast::types::*;
use vm::VM;
use runtime::mm;
......
......@@ -17,6 +17,50 @@ impl ControlFlowAnalysis {
}
}
impl CompilerPass for ControlFlowAnalysis {
fn name(&self) -> &'static str {
self.name
}
fn as_any(&self) -> &Any {
self
}
#[allow(unused_variables)]
fn visit_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
let mut stack : Vec<MuID> = vec![];
let mut visited : Vec<MuID> = vec![];
// depth-first search
dfs(func.content.as_ref().unwrap().entry, &mut stack, &mut visited, func);
}
#[allow(unused_variables)]
fn finish_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
// after CFA, we will know all the exceptional edges, and exception blocks
{
let mut exception_blocks = LinkedHashSet::new();
for block in func.content.as_ref().unwrap().blocks.iter() {
let ref control_flow = block.1.control_flow;
for edge in control_flow.succs.iter() {
if edge.is_exception {
exception_blocks.insert(edge.target);
}
}
}
func.content.as_mut().unwrap().exception_blocks.add_all(exception_blocks);
}
debug!("check control flow for {}", func);
for entry in func.content.as_ref().unwrap().blocks.iter() {
debug!("block {}", entry.0);
debug!("{}", entry.1.control_flow);
}
}
}
/// if an edge target already appears in the stack, it is a backedge, otherwise forward edge
fn check_edge_kind(target: MuID, stack: &Vec<MuID>) -> EdgeKind {
if stack.contains(&target) {
EdgeKind::Backward
......@@ -25,6 +69,7 @@ fn check_edge_kind(target: MuID, stack: &Vec<MuID>) -> EdgeKind {
}
}
/// creates info for a new edge (set predecessor, successors, and recursively do dfs
fn new_edge(cur: MuID, edge: BlockEdge, stack: &mut Vec<MuID>, visited: &mut Vec<MuID>, func: &mut MuFunctionVersion) {
// add current block to target's predecessors
{
......@@ -38,17 +83,19 @@ fn new_edge(cur: MuID, edge: BlockEdge, stack: &mut Vec<MuID>, visited: &mut Vec
let cur = func.content.as_mut().unwrap().get_block_mut(cur);
cur.control_flow.succs.push(edge);
}
// if we havent visited the successor, visit it
if !visited.contains(&succ) {
dfs(succ, stack, visited, func);
}
}
// some random number for edge probability
const WATCHPOINT_DISABLED_CHANCE : f32 = 0.9f32;
const NORMAL_RESUME_CHANCE : f32 = 0.6f32;
const EXN_RESUME_CHANCE : f32 = 1f32 - NORMAL_RESUME_CHANCE;
/// depth first traversal
fn dfs(cur: MuID, stack: &mut Vec<MuID>, visited: &mut Vec<MuID>, func: &mut MuFunctionVersion) {
trace!("dfs visiting block {}", cur);
trace!("current stack: {:?}", stack);
......@@ -114,17 +161,14 @@ fn dfs(cur: MuID, stack: &mut Vec<MuID>, visited: &mut Vec<MuID>, func: &mut MuF
for &(_, ref dest) in branches.iter() {
let target = dest.target;
check_add_edge(&mut ret, target, switch_prob);
}
check_add_edge(&mut ret, default.target, BRANCH_DEFAULT_PROB);
ret
};
let mut ret = vec![];
for edge in map.values() {
ret.push(*edge);
}
......@@ -227,55 +271,9 @@ fn dfs(cur: MuID, stack: &mut Vec<MuID>, visited: &mut Vec<MuID>, func: &mut MuF
};
trace!("out edges for {}: {}", cur, vector_as_str(&out_edges));
for edge in out_edges {
new_edge(cur, edge, stack, visited, func);
}
stack.pop();
}
impl CompilerPass for ControlFlowAnalysis {
fn name(&self) -> &'static str {
self.name
}
fn as_any(&self) -> &Any {
self
}
#[allow(unused_variables)]
fn visit_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
let mut stack : Vec<MuID> = vec![];
let mut visited : Vec<MuID> = vec![];
dfs(func.content.as_ref().unwrap().entry, &mut stack, &mut visited, func);
}
#[allow(unused_variables)]
fn finish_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
{
let mut exception_blocks = LinkedHashSet::new();
for block in func.content.as_ref().unwrap().blocks.iter() {
let ref control_flow = block.1.control_flow;
for edge in control_flow.succs.iter() {
if edge.is_exception {
exception_blocks.insert(edge.target);
}
}
}
func.content.as_mut().unwrap().exception_blocks.add_all(exception_blocks);
}
debug!("check control flow for {}", func);
for entry in func.content.as_ref().unwrap().blocks.iter() {
debug!("block {}", entry.0);
debug!("{}", entry.1.control_flow);
}
}
}
}
\ No newline at end of file
......@@ -15,25 +15,6 @@ impl DefUse {
}
}
fn use_op(op: &P<TreeNode>, func_context: &mut FunctionContext) {
match op.v {
TreeNode_::Value(ref val) => {
use_value(val, func_context);
},
_ => {} // dont worry about instruction
}
}
fn use_value(val: &P<Value>, func_context: &mut FunctionContext) {
match val.v {
Value_::SSAVar(ref id) => {
let entry = func_context.values.get_mut(id).unwrap();
entry.increase_use_count();
},
_ => {} // dont worry about constants
}
}
impl CompilerPass for DefUse {
fn name(&self) -> &'static str {
self.name
......@@ -42,7 +23,7 @@ impl CompilerPass for DefUse {
fn as_any(&self) -> &Any {
self
}
#[allow(unused_variables)]
fn start_block(&mut self, vm: &VM, func_context: &mut FunctionContext, block: &mut Block) {
// if an SSA appears in keepalives, its use count increases
......@@ -53,7 +34,7 @@ impl CompilerPass for DefUse {
}
}
}
#[allow(unused_variables)]
fn visit_inst(&mut self, vm: &VM, func_context: &mut FunctionContext, node: &TreeNode) {
// if an SSA appears in operands of instrs, its use count increases
......@@ -73,13 +54,32 @@ impl CompilerPass for DefUse {
entry.reset_use_count();
}
}
#[allow(unused_variables)]
fn finish_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
debug!("check use count for variables");
for entry in func.context.values.values() {
debug!("{}: {}", entry, entry.use_count())
}
}
}
fn use_op(op: &P<TreeNode>, func_context: &mut FunctionContext) {
match op.v {
TreeNode_::Value(ref val) => {
use_value(val, func_context);
},
_ => {} // dont worry about instruction
}
}
fn use_value(val: &P<Value>, func_context: &mut FunctionContext) {
match val.v {
Value_::SSAVar(ref id) => {
let entry = func_context.values.get_mut(id).unwrap();
entry.increase_use_count();
},
_ => {} // dont worry about constants
}
}
......@@ -34,9 +34,12 @@ impl CompilerPass for GenMovPhi {
fn visit_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
let mut f_content = func.content.take().unwrap();
// we do this with two steps.
// first step collects information about intermediate blocks
// and second step inserts intermediate blocks
let mut new_blocks_to_insert : Vec<IntermediateBlockInfo> = vec![];
// iteratio blocks
// first step - collects info on intermediate blocks
for (blk_id, mut block) in f_content.blocks.iter_mut() {
trace!("block: {}", blk_id);
......@@ -48,22 +51,25 @@ impl CompilerPass for GenMovPhi {
let mut i = 0;
let i_last = block_content.body.len() - 1;
for node in block_content.body.iter() {
// check if this is the last element
if i != i_last {
// if this is last instruction, we simply copy it
new_body.push(node.clone());
} else {
// otherwise, we need to check if we need to insert intermediate block
// and rewrite the inst
trace!("last instruction is {}", node);
let last_inst = node.clone();
match last_inst.v {
TreeNode_::Instruction(inst) => {
let ref ops = inst.ops;
match inst.v {
Instruction_::Branch2{cond, true_dest, false_dest, true_prob} => {
// check and insert intermediate blocks for true/false dest
let true_dest = process_dest(true_dest, &mut new_blocks_to_insert, &ops, vm);
let false_dest = process_dest(false_dest, &mut new_blocks_to_insert, &ops, vm);
// rewrite the instruction
let new_inst = func.new_inst(Instruction{
hdr: inst.hdr.clone(),
value: inst.value.clone(),
......@@ -174,8 +180,9 @@ impl CompilerPass for GenMovPhi {
});
}
// insert new blocks here
// second step - insert new blocks
for block_info in new_blocks_to_insert {
// create intermediate block
let block = {
let target_id = block_info.target;
let name = format!("intermediate_block_{}_to_{}", block_info.blk_id, target_id);
......@@ -183,9 +190,7 @@ impl CompilerPass for GenMovPhi {
let mut ret = Block::new(MuEntityHeader::named(block_info.blk_id, name));
vm.set_name(ret.as_entity());
let mut target_block = f_content.get_block_mut(target_id);
assert!(target_block.content.is_some());
// if target_block is an exception block,
......@@ -243,6 +248,11 @@ impl CompilerPass for GenMovPhi {
}
}
/// returns the destination.
/// if the instruction passes any argument to its destination,
/// we need an intermediate block to move the arguments, return
/// the intermediate block as destination. Otherwise, return
/// the original destination
fn process_dest(dest: Destination, blocks_to_insert: &mut Vec<IntermediateBlockInfo>, ops: &Vec<P<TreeNode>>, vm: &VM) -> Destination {
if dest.args.is_empty() {
dest
......
......@@ -14,6 +14,28 @@ pub struct Inlining {
should_inline: HashMap<MuID, bool>
}
impl CompilerPass for Inlining {
fn name(&self) -> &'static str {
self.name
}
fn as_any(&self) -> &Any {
self
}
fn visit_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
if vm.vm_options.flag_disable_inline {
info!("inlining is disabled");
return;
}
if self.check(vm, func) {
self.inline(vm, func);
debug!("after inlining: {:?}", func);
}
}
}
impl Inlining {
pub fn new() -> Inlining {
Inlining{
......@@ -22,22 +44,28 @@ impl Inlining {
}
}
/// checks whether we need to rewrite the function because of inlining
fn check(&mut self, vm: &VM, func: &mut MuFunctionVersion) -> bool {
debug!("check inline");
// should_inline will store all the calls from this function,
// and whether they should be inlined
self.should_inline.clear();
let mut inline_something = false;
// check each call from this function
for func_id in func.get_static_call_edges().values() {
// check a single callsite, whether it should be inlined
// the result is returned as boolean, and also written into 'should_inline'
let should_inline_this = self.check_should_inline_func(*func_id, func.func_id, vm);
inline_something = inline_something || should_inline_this;
}
inline_something
}
#[allow(unused_variables)]
/// checks whether we should inline the caller into the callee
fn check_should_inline_func(&mut self, callee: MuID, caller: MuID, vm: &VM) -> bool {
// recursive call, do not inline
if callee == caller {
......@@ -49,7 +77,6 @@ impl Inlining {
Some(func) => func.read().unwrap(),
None => panic!("callee {} is undeclared", callee)
};
let fv_id = match func.cur_ver {
Some(fv_id) => fv_id,
None => {
......@@ -59,6 +86,10 @@ impl Inlining {
}
};
// if we have checked this callee before, we use the same result
// (this is not optimal though. The inline criteria we use at the moment
// do not take caller size growth into consideration, so we will
// get the same result anyway. )
match self.should_inline.get(&fv_id) {
Some(flag) => {
trace!("func {} should be inlined (checked before)", callee);
......@@ -70,21 +101,21 @@ impl Inlining {
let fv_guard = vm.func_vers().read().unwrap();
let fv = fv_guard.get(&fv_id).unwrap().read().unwrap();
// if the function is forced inline, then we inline it
// if the function is forced inline, we inline it
if fv.force_inline {
trace!("func {} is forced as inline function", callee);
return true;
}
// some heuristics here to decide if we should inline the function
// to be more precise. we should be target specific
let n_params = fv.sig.arg_tys.len();
let n_insts = estimate_insts(&fv);
let out_calls = fv.get_static_call_edges();
let has_throw = fv.has_throw();
// now we use a simple heuristic here:
// insts fewer than 10, no static out calls, no throw
// simple heuristic here:
// * estimated machine insts are fewer than 10 insts
// * leaf in call graph (no out calls)
// * no throw (otherwise we will need to rearrange catch)
let should_inline = n_insts <= 25 && out_calls.len() == 0 && !has_throw;
trace!("func {} has {} insts (estimated)", callee, n_insts);
......@@ -97,6 +128,7 @@ impl Inlining {
should_inline
}
/// inlines the callee that are marked as 'should inline'
fn inline(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
debug!("inlining for Function {}", func);
......@@ -121,35 +153,32 @@ impl Inlining {
if call_edges.contains_key(&inst_id) {
let call_target = call_edges.get(&inst_id).unwrap();
if self.should_inline.contains_key(call_target) && *self.should_inline.get(call_target).unwrap() {
trace!("inserting inlined function at {}", inst);
// from TreeNode into Inst (we do not need old TreeNode)
let inst = inst.into_inst().unwrap();
// (inline expansion)
// inline expansion starts here
// getting the function being inlined
let inlined_func = *call_edges.get(&inst.id()).unwrap();
trace!("function being inlined is {}", inlined_func);
let inlined_fvid = match vm.get_cur_version_of(inlined_func) {
Some(fvid) => fvid,
None => panic!("cannot resolve current version of Func {}, which is supposed to be inlined", inlined_func)
};
let inlined_fvs_guard = vm.func_vers().read().unwrap();
let inlined_fv_lock = inlined_fvs_guard.get(&inlined_fvid).unwrap();
let inlined_fv_guard = inlined_fv_lock.read().unwrap();
trace!("orig_content: {:?}", inlined_fv_guard.get_orig_ir().unwrap());
trace!("content : {:?}", inlined_fv_guard.content.as_ref().unwrap());
// creates a new block ID which will be the entry block for the inlined function
let new_inlined_entry_id = vm.next_id();
// change current call insts to a branch
// change current call instruction to a branch
trace!("turning CALL instruction into a branch");
let ref ops = inst.ops;
match inst.v {
Instruction_::ExprCall {ref data, ..} => {
let arg_nodes : Vec<P<TreeNode>> = data.args.iter().map(|x| ops[*x].clone()).collect();
......@@ -160,12 +189,11 @@ impl Inlining {
value: None,
ops: arg_nodes.clone(),
v: Instruction_::Branch1(Destination{
// this block doesnt exist yet, we will fix it later
// this block doesnt exist yet, we will create it later
target: new_inlined_entry_id,
args: arg_indices.iter().map(|x| DestArg::Normal(*x)).collect()
})
});
trace!("branch inst: {}", branch);
// add branch to current block
......@@ -173,12 +201,11 @@ impl Inlining {
// finish current block
new_blocks.push(cur_block.clone());
let old_name = cur_block.name().unwrap();
// start a new block
// creates a new block after inlined part, which will receive results from inlined function
let old_name = cur_block.name().unwrap();
let new_name = format!("{}_cont_after_inline_{}", old_name, inst_id);
trace!("create continue block for EXPRCALL/CCALL: {}", &new_name);
cur_block = Block::new(MuEntityHeader::named(vm.next_id(), new_name));
cur_block.content = Some(BlockContent{
args: {
......@@ -288,6 +315,7 @@ impl Inlining {
}
}
/// copies blocks from callee to caller, with specified entry block and return block
fn copy_inline_blocks(caller: &mut Vec<Block>, ret_block: MuID, callee: &FunctionContent, entry_block: MuID, vm: &VM) {
trace!("trying to copy inlined function blocks to caller");
......@@ -458,6 +486,7 @@ fn copy_inline_blocks(caller: &mut Vec<Block>, ret_block: MuID, callee: &Functio
}
}
/// copies inlined function context into caller
fn copy_inline_context(caller: &mut FunctionContext, callee: &FunctionContext) {
trace!("trying to copy inlined function context to caller");
for (id, entry) in callee.values.iter() {
......@@ -465,6 +494,7 @@ fn copy_inline_context(caller: &mut FunctionContext, callee: &FunctionContext) {
}
}
/// calculate estimate machine instruction for a Mu function
fn estimate_insts(fv: &MuFunctionVersion) -> usize {
let f_content = fv.content.as_ref().unwrap();
......@@ -484,27 +514,4 @@ fn estimate_insts(fv: &MuFunctionVersion) -> usize {
}
insts
}
impl CompilerPass for Inlining {
fn name(&self) -> &'static str {
self.name
}
fn as_any(&self) -> &Any {
self
}
fn visit_function(&mut self, vm: &VM, func: &mut MuFunctionVersion) {
if vm.vm_options.flag_disable_inline {
info!("inlining is disabled");
return;
}
if self.check(vm, func) {
self.inline(vm, func);
debug!("after inlining: {:?}", func);
}
}
}
}
\ No newline at end of file
use ast::ir::*;
use vm::VM;
use std::any::Any;
mod def_use;
mod tree_gen;
mod control_flow;
mod trace_gen;
mod gen_mov_phi;
/// An inlining pass. Based on a certain criteria, the compiler chooses certain functions to be
/// inlined in their callsite by rewriting the call into a branch with several copied blocks from
/// the inlined function
mod inlining;
pub use compiler::passes::inlining::Inlining;
/// A Def-Use pass. Getting use info and count for SSA variables in the IR (we are not collecting
/// define info)
mod def_use;
pub use compiler::passes::def_use::DefUse;
/// A tree generation pass. Mu IR is a flat IR instruction sequence, this pass turns it into a
/// depth tree which is easier for instruction selection.
mod tree_gen;
pub use compiler::passes::tree_gen::TreeGen;
pub use compiler::passes::control_flow::ControlFlowAnalysis;
pub use compiler::passes::trace_gen::TraceGen;
/// A phi node eliminating pass. Mu IR is SSA based with goto-with-values variants, it still has
/// phi node (implicitly). We get out of SSA form at this pass by removing phi nodes, and inserting
/// intermediate blocks for moving values around.
mod gen_mov_phi;
pub use compiler::passes::gen_mov_phi::GenMovPhi;
use std::any::Any;
/// A control flow analysis pass at IR level.
mod control_flow;
pub use compiler::passes::control_flow::ControlFlowAnalysis;
/// A trace scheduling pass. It uses the CFA result from last pass, to schedule blocks that
/// favors hot path execution.
mod trace_gen;
pub use compiler::passes::trace_gen::TraceGen;
/// A trait for implementing compiler passes.
///
/// A Mu function is supposed to be travelled in the following order:
/// * start_function()
/// * visit_function()
/// for each block
/// * start_block()
/// * visit_block()
/// for each instruction
/// * visit_inst()
/// * finish_block()
/// * finish_function()
///
/// functions can be overridden for each pass' own purpose.
#[allow(unused_variables)]
pub trait CompilerPass {
fn name(&self) -> &'static str;
......
......@@ -37,12 +37,12 @@ impl CompilerPass for TraceGen {
while !work_stack.is_empty() {
let cur = work_stack.pop().unwrap();
let cur_block = func.content.as_ref().unwrap().get_block(cur);
trace!("check block {}", cur);
trace!("add {:?} to trace", cur);
trace.push(cur);