Implement separate data flow graph module.
The DFG keeps track of instruction definitions, values, and EBBs. Store the primary definition of each instruction: Opcode and operands. Track SSA values as either the result of an instruction or EBB arguments.
This commit is contained in:
420
src/libcretonne/dfg.rs
Normal file
420
src/libcretonne/dfg.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
//! Data flow graph tracking Instructions, Values, and EBBs.
|
||||
|
||||
use entity_map::EntityMap;
|
||||
use entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue};
|
||||
use instructions::InstructionData;
|
||||
use types::Type;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::u16;
|
||||
|
||||
/// A data flow graph defines all instuctions and extended basic blocks in a function as well as
|
||||
/// the data flow dependencies between them. The DFG also tracks values which can be either
|
||||
/// instruction results or EBB arguments.
|
||||
///
|
||||
/// The layout of EBBs in the function and of instructions in each EBB is recorded by the
|
||||
/// `FunctionLayout` data structure which form the other half of the function representation.
|
||||
///
|
||||
pub struct DataFlowGraph {
|
||||
/// Data about all of the instructions in the function, including opcodes and operands.
|
||||
/// The instructions in this map are not in program order. That is tracked by `Layout`, along
|
||||
/// with the EBB containing each instruction.
|
||||
insts: EntityMap<Inst, InstructionData>,
|
||||
|
||||
/// Extended basic blocks in the function and their arguments.
|
||||
/// This map is not in program order. That is handled by `Layout`, and so is the sequence of
|
||||
/// instructions contained in each EBB.
|
||||
ebbs: EntityMap<Ebb, EbbData>,
|
||||
|
||||
/// Extended value table. Most `Value` references refer directly to their defining instruction.
|
||||
/// Others index into this table.
|
||||
///
|
||||
/// This is implemented directly with a `Vec` rather than an `EntityMap<Value, ...>` because
|
||||
/// the Value entity references can refer to two things -- an instruction or an extended value.
|
||||
extended_values: Vec<ValueData>,
|
||||
}
|
||||
|
||||
impl DataFlowGraph {
|
||||
/// Create a new empty `DataFlowGraph`.
|
||||
pub fn new() -> DataFlowGraph {
|
||||
DataFlowGraph {
|
||||
insts: EntityMap::new(),
|
||||
ebbs: EntityMap::new(),
|
||||
extended_values: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handling values.
|
||||
///
|
||||
/// Values are either EBB arguments or instruction results.
|
||||
impl DataFlowGraph {
|
||||
// Allocate an extended value entry.
|
||||
fn make_value(&mut self, data: ValueData) -> Value {
|
||||
let vref = Value::new_table(self.extended_values.len());
|
||||
self.extended_values.push(data);
|
||||
vref
|
||||
}
|
||||
|
||||
/// Get the type of a value.
|
||||
pub fn value_type(&self, v: Value) -> Type {
|
||||
use entities::ExpandedValue::*;
|
||||
match v.expand() {
|
||||
Direct(i) => self.insts[i].first_type(),
|
||||
Table(i) => {
|
||||
match self.extended_values[i] {
|
||||
ValueData::Inst { ty, .. } => ty,
|
||||
ValueData::Arg { ty, .. } => ty,
|
||||
}
|
||||
}
|
||||
None => panic!("NO_VALUE has no type"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the definition of a value.
|
||||
///
|
||||
/// This is either the instruction that defined it or the Ebb that has the value as an
|
||||
/// argument.
|
||||
pub fn value_def(&self, v: Value) -> ValueDef {
|
||||
use entities::ExpandedValue::*;
|
||||
match v.expand() {
|
||||
Direct(inst) => ValueDef::Res(inst, 0),
|
||||
Table(idx) => {
|
||||
match self.extended_values[idx] {
|
||||
ValueData::Inst { inst, num, .. } => ValueDef::Res(inst, num as usize),
|
||||
ValueData::Arg { ebb, num, .. } => ValueDef::Arg(ebb, num as usize),
|
||||
}
|
||||
}
|
||||
None => panic!("NO_VALUE has no def"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where did a value come from?
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ValueDef {
|
||||
/// Value is the n'th result of an instruction.
|
||||
Res(Inst, usize),
|
||||
/// Value is the n'th argument to an EBB.
|
||||
Arg(Ebb, usize),
|
||||
}
|
||||
|
||||
// Internal table storage for extended values.
|
||||
enum ValueData {
|
||||
// Value is defined by an instruction, but it is not the first result.
|
||||
Inst {
|
||||
ty: Type,
|
||||
num: u16, // Result number starting from 0.
|
||||
inst: Inst,
|
||||
next: Value, // Next result defined by `def`.
|
||||
},
|
||||
|
||||
// Value is an EBB argument.
|
||||
Arg {
|
||||
ty: Type,
|
||||
num: u16, // Argument number, starting from 0.
|
||||
ebb: Ebb,
|
||||
next: Value, // Next argument to `ebb`.
|
||||
},
|
||||
}
|
||||
|
||||
/// Iterate through a list of related value references, such as:
|
||||
///
|
||||
/// - All results defined by an instruction. See `DataFlowGraph::inst_results`.
|
||||
/// - All arguments to an EBB. See `DataFlowGraph::ebb_args`.
|
||||
///
|
||||
/// A value iterator borrows a `DataFlowGraph` reference.
|
||||
pub struct Values<'a> {
|
||||
dfg: &'a DataFlowGraph,
|
||||
cur: Value,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Values<'a> {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let prev = self.cur;
|
||||
|
||||
// Advance self.cur to the next value, or NO_VALUE.
|
||||
self.cur = match prev.expand() {
|
||||
ExpandedValue::Direct(inst) => self.dfg.insts[inst].second_result().unwrap_or_default(),
|
||||
ExpandedValue::Table(index) => {
|
||||
match self.dfg.extended_values[index] {
|
||||
ValueData::Inst { next, .. } => next,
|
||||
ValueData::Arg { next, .. } => next,
|
||||
}
|
||||
}
|
||||
ExpandedValue::None => return None,
|
||||
};
|
||||
|
||||
Some(prev)
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructions.
|
||||
///
|
||||
impl DataFlowGraph {
|
||||
/// Create a new instruction.
|
||||
///
|
||||
/// The type of the first result is indicated by `data.ty`. If the instruction produces
|
||||
/// multiple results, also call `make_inst_results` to allocate value table entries.
|
||||
pub fn make_inst(&mut self, data: InstructionData) -> Inst {
|
||||
self.insts.push(data)
|
||||
}
|
||||
|
||||
/// Create result values for an instruction that produces multiple results.
|
||||
///
|
||||
/// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If
|
||||
/// the instruction may produce more than 1 result, call `make_inst_results` to allocate
|
||||
/// value table entries for the additional results.
|
||||
///
|
||||
/// The result value types are determined from the instruction's value type constraints and the
|
||||
/// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic
|
||||
/// instructions, `ctrl_typevar` is ignored, and `VOID` can be used.
|
||||
///
|
||||
/// The type of the first result value is also set, even if it was already set in the
|
||||
/// `InstructionData` passed to `make_inst`. If this function is called with a single-result
|
||||
/// instruction, that is the only effect.
|
||||
///
|
||||
/// Returns the number of results produced by the instruction.
|
||||
pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize {
|
||||
let constraints = self.insts[inst].opcode().constraints();
|
||||
let fixed_results = constraints.fixed_results();
|
||||
|
||||
// Additional values form a linked list starting from the second result value. Generate
|
||||
// the list backwards so we don't have to modify value table entries in place. (This
|
||||
// causes additional result values to be numbered backwards which is not the aestetic
|
||||
// choice, but since it is only visible in extremely rare instructions with 3+ results,
|
||||
// we don't care).
|
||||
let mut head = NO_VALUE;
|
||||
let mut first_type = Type::default();
|
||||
|
||||
// TBD: Function call return values for direct and indirect function calls.
|
||||
|
||||
if fixed_results > 0 {
|
||||
for res_idx in (1..fixed_results).rev() {
|
||||
head = self.make_value(ValueData::Inst {
|
||||
ty: constraints.result_type(res_idx, ctrl_typevar),
|
||||
num: res_idx as u16,
|
||||
inst: inst,
|
||||
next: head,
|
||||
});
|
||||
}
|
||||
first_type = constraints.result_type(0, ctrl_typevar);
|
||||
}
|
||||
|
||||
// Update the second_result pointer in `inst`.
|
||||
if head != NO_VALUE {
|
||||
*self.insts[inst]
|
||||
.second_result_mut()
|
||||
.expect("instruction format doesn't allow multiple results") = head;
|
||||
}
|
||||
*self.insts[inst].first_type_mut() = first_type;
|
||||
|
||||
fixed_results
|
||||
}
|
||||
|
||||
/// Get the first result of an instruction.
|
||||
///
|
||||
/// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type.
|
||||
pub fn first_result(&self, inst: Inst) -> Value {
|
||||
Value::new_direct(inst)
|
||||
}
|
||||
|
||||
/// Iterate through all the results of an instruction.
|
||||
pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> {
|
||||
Values {
|
||||
dfg: self,
|
||||
cur: if self.insts[inst].first_type().is_void() {
|
||||
NO_VALUE
|
||||
} else {
|
||||
Value::new_direct(inst)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow immutable access to instructions via indexing.
|
||||
impl Index<Inst> for DataFlowGraph {
|
||||
type Output = InstructionData;
|
||||
|
||||
fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData {
|
||||
&self.insts[inst]
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow mutable access to instructions via indexing.
|
||||
impl IndexMut<Inst> for DataFlowGraph {
|
||||
fn index_mut<'a>(&'a mut self, inst: Inst) -> &'a mut InstructionData {
|
||||
&mut self.insts[inst]
|
||||
}
|
||||
}
|
||||
|
||||
/// Extended basic blocks.
|
||||
impl DataFlowGraph {
|
||||
/// Create a new basic block.
|
||||
pub fn make_ebb(&mut self) -> Ebb {
|
||||
self.ebbs.push(EbbData::new())
|
||||
}
|
||||
|
||||
/// Get the number of arguments on `ebb`.
|
||||
pub fn num_ebb_args(&self, ebb: Ebb) -> usize {
|
||||
let last_arg = self.ebbs[ebb].last_arg;
|
||||
match last_arg.expand() {
|
||||
ExpandedValue::None => 0,
|
||||
ExpandedValue::Table(idx) => {
|
||||
if let ValueData::Arg { num, .. } = self.extended_values[idx] {
|
||||
num as usize + 1
|
||||
} else {
|
||||
panic!("inconsistent value table entry for EBB arg");
|
||||
}
|
||||
}
|
||||
ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append an argument with type `ty` to `ebb`.
|
||||
pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value {
|
||||
let num_args = self.num_ebb_args(ebb);
|
||||
assert!(num_args <= u16::MAX as usize, "Too many arguments to EBB");
|
||||
let val = self.make_value(ValueData::Arg {
|
||||
ty: ty,
|
||||
ebb: ebb,
|
||||
num: num_args as u16,
|
||||
next: NO_VALUE,
|
||||
});
|
||||
let last_arg = self.ebbs[ebb].last_arg;
|
||||
match last_arg.expand() {
|
||||
// If last_arg is NO_VALUE, we're adding the first EBB argument.
|
||||
ExpandedValue::None => {
|
||||
self.ebbs[ebb].first_arg = val;
|
||||
}
|
||||
// Append to linked list of arguments.
|
||||
ExpandedValue::Table(idx) => {
|
||||
if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] {
|
||||
*next = val;
|
||||
} else {
|
||||
panic!("inconsistent value table entry for EBB arg");
|
||||
}
|
||||
}
|
||||
ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"),
|
||||
};
|
||||
self.ebbs[ebb].last_arg = val;
|
||||
val
|
||||
}
|
||||
|
||||
/// Iterate through the arguments to an EBB.
|
||||
pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> {
|
||||
Values {
|
||||
dfg: self,
|
||||
cur: self.ebbs[ebb].first_arg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contents of an extended basic block.
|
||||
//
|
||||
// Arguments for an extended basic block are values that dominate everything in the EBB. All
|
||||
// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must
|
||||
// match the function arguments.
|
||||
struct EbbData {
|
||||
// First argument to this EBB, or `NO_VALUE` if the block has no arguments.
|
||||
//
|
||||
// The arguments are all ValueData::Argument entries that form a linked list from `first_arg`
|
||||
// to `last_arg`.
|
||||
first_arg: Value,
|
||||
|
||||
// Last argument to this EBB, or `NO_VALUE` if the block has no arguments.
|
||||
last_arg: Value,
|
||||
}
|
||||
|
||||
impl EbbData {
|
||||
fn new() -> EbbData {
|
||||
EbbData {
|
||||
first_arg: NO_VALUE,
|
||||
last_arg: NO_VALUE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use types;
|
||||
use instructions::{Opcode, InstructionData};
|
||||
|
||||
#[test]
|
||||
fn make_inst() {
|
||||
let mut dfg = DataFlowGraph::new();
|
||||
|
||||
let idata = InstructionData::Nullary {
|
||||
opcode: Opcode::Iconst,
|
||||
ty: types::I32,
|
||||
};
|
||||
let inst = dfg.make_inst(idata);
|
||||
assert_eq!(inst.to_string(), "inst0");
|
||||
|
||||
// Immutable reference resolution.
|
||||
{
|
||||
let immdfg = &dfg;
|
||||
let ins = &immdfg[inst];
|
||||
assert_eq!(ins.opcode(), Opcode::Iconst);
|
||||
assert_eq!(ins.first_type(), types::I32);
|
||||
}
|
||||
|
||||
// Result iterator.
|
||||
let mut res = dfg.inst_results(inst);
|
||||
let val = res.next().unwrap();
|
||||
assert!(res.next().is_none());
|
||||
|
||||
assert_eq!(dfg.value_def(val), ValueDef::Res(inst, 0));
|
||||
assert_eq!(dfg.value_type(val), types::I32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_results() {
|
||||
let mut dfg = DataFlowGraph::new();
|
||||
|
||||
let idata = InstructionData::Nullary {
|
||||
opcode: Opcode::Trap,
|
||||
ty: types::VOID,
|
||||
};
|
||||
let inst = dfg.make_inst(idata);
|
||||
|
||||
// Result iterator should be empty.
|
||||
let mut res = dfg.inst_results(inst);
|
||||
assert_eq!(res.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ebb() {
|
||||
let mut dfg = DataFlowGraph::new();
|
||||
|
||||
let ebb = dfg.make_ebb();
|
||||
assert_eq!(ebb.to_string(), "ebb0");
|
||||
assert_eq!(dfg.num_ebb_args(ebb), 0);
|
||||
assert_eq!(dfg.ebb_args(ebb).next(), None);
|
||||
|
||||
let arg1 = dfg.append_ebb_arg(ebb, types::F32);
|
||||
assert_eq!(arg1.to_string(), "vx0");
|
||||
assert_eq!(dfg.num_ebb_args(ebb), 1);
|
||||
{
|
||||
let mut args1 = dfg.ebb_args(ebb);
|
||||
assert_eq!(args1.next(), Some(arg1));
|
||||
assert_eq!(args1.next(), None);
|
||||
}
|
||||
let arg2 = dfg.append_ebb_arg(ebb, types::I16);
|
||||
assert_eq!(arg2.to_string(), "vx1");
|
||||
assert_eq!(dfg.num_ebb_args(ebb), 2);
|
||||
{
|
||||
let mut args2 = dfg.ebb_args(ebb);
|
||||
assert_eq!(args2.next(), Some(arg1));
|
||||
assert_eq!(args2.next(), Some(arg2));
|
||||
assert_eq!(args2.next(), None);
|
||||
}
|
||||
|
||||
assert_eq!(dfg.value_def(arg1), ValueDef::Arg(ebb, 0));
|
||||
assert_eq!(dfg.value_def(arg2), ValueDef::Arg(ebb, 1));
|
||||
assert_eq!(dfg.value_type(arg1), types::F32);
|
||||
assert_eq!(dfg.value_type(arg2), types::I16);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ pub mod immediates;
|
||||
pub mod entities;
|
||||
pub mod instructions;
|
||||
pub mod repr;
|
||||
pub mod dfg;
|
||||
pub mod layout;
|
||||
pub mod write;
|
||||
pub mod cfg;
|
||||
|
||||
Reference in New Issue
Block a user