* API and data structures proposal for the SSA construction module * Polished API and implemented trivial functions * API more explicit, Variable now struct parameter * Sample test written to see how the API could be used * Implemented local value numbering for SSABuilder * Implemented SSA within a single Ebb * Unfinished unoptimized implementation for recursive use and seal * Working global value numbering The SSABuilder now create ebb args and modifies jump instructions accordingly * Updated doc and improved branch argument modifying. Removed instructions::branch_arguments and instructions::branch_argument_mut * SSA building: bugfix, asserts and new test case Missing a key optimization to remove cycles of Phi * SSA Building: small changes after code review Created helper function for seal_block (which now contains sanity checks) * Optimization: removed useless phis (ebb arguments) Using pessimistic assumption that when using a non-def variable in an unsealed block we create an ebb argument which is removed when sealing if we detect it as useless Using aliases to avoid rewriting variables * Changed the semantics of remove_ebb_arg and turned it into a proper API method * Adapted ssa branch to changes in the DFG API * Abandonned SparseMaps for EntityMaps, added named structure for headr block data. * Created skeletton for a Cretonne IL builder frontend * Frontend IL builder: first draft of implementation with example of instruction methods * Working basic implementation of the frontend Missing handling of function arguments and return values * Interaction with function signature, sample test, more checks * Test with function verifier, seal and fill sanity check * Implemented python script to generate ILBuilder methods * Added support for jump tables and stack slot * Major API overhaul * No longer generating rust through Python but implements InstBuilder * No longer parametrized by user's blocks but use regular `Ebb` * Reuse of allocated memory via distinction between ILBuilder and FunctionBuilder * Integrate changes from StackSlot * Improved error message * Added support for jump arguments supplied by the user * Added an ebb_args proxy method needed * Adapted to Entity_ref splitted into a new module * Better error messages and fixed tests * Added method to change jump destination * We whould be able to add unreachable code * Added inst_result proxy to frontend * Import support * Added optimization for SSA construction: If multiple predecessors but agree on value don't create EBB argument * Move unsafe and not write-only funcs apart, improved doc * Added proxy function for append_ebb_arg * Support for unreachable code and better layout of the Ebbs * Fixed a bug yielding an infinite loop in SSA construction * SSA predecessors lookup code refactoring * Fixed bug in unreachable definition * New sanity check and display debug function * Fixed bug in verifier and added is_pristine ;ethod for frontend * Extended set of characters printable in function names To be able to print names of functions in test suite * Fixes and improvements of SSA construction after code review * Bugfixes for frontend code simplification * On-the-fly critical edge splitting in case of br_table with jump arguments * No more dangling undefined values, now attached as EBB args * Bugfix: only split corresponding edges on demand, not all br_table edges * Added signature retrieval method * Bugfix for critical edge splitting not sealing the ebbs it created * Proper handling of SSA side effects by the frontend * Code refactoring: moving frontend and SSA to new crate * Frontend: small changes and bugfixes after code review
1277 lines
51 KiB
Rust
1277 lines
51 KiB
Rust
//! A SSA-building API that handles incomplete CFGs.
|
|
//!
|
|
//! The algorithm is based upon Braun M., Buchwald S., Hack S., Leißa R., Mallon C.,
|
|
//! Zwinkau A. (2013) Simple and Efficient Construction of Static Single Assignment Form.
|
|
//! In: Jhala R., De Bosschere K. (eds) Compiler Construction. CC 2013.
|
|
//! Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg
|
|
|
|
use cretonne::ir::{Ebb, Value, Inst, Type, DataFlowGraph, JumpTables, Layout, Cursor, InstBuilder};
|
|
use cretonne::ir::instructions::BranchInfo;
|
|
use std::hash::Hash;
|
|
use cretonne::entity_map::{EntityMap, PrimaryEntityData};
|
|
use cretonne::entity_ref::EntityRef;
|
|
use cretonne::packed_option::PackedOption;
|
|
use cretonne::packed_option::ReservedValue;
|
|
use std::u32;
|
|
use cretonne::ir::types::{F32, F64};
|
|
use cretonne::ir::immediates::{Ieee32, Ieee64};
|
|
use std::collections::HashMap;
|
|
|
|
/// Structure containing the data relevant the construction of SSA for a given function.
|
|
///
|
|
/// The parameter struct `Variable` corresponds to the way variables are represented in the
|
|
/// non-SSA language you're translating from.
|
|
///
|
|
/// The SSA building relies on information about the variables used and defined, as well as
|
|
/// their position relative to basic blocks which are stricter than extended basic blocks since
|
|
/// they don't allow branching in the middle of them.
|
|
///
|
|
/// This SSA building module allows you to def and use variables on the fly while you are
|
|
/// constructing the CFG, no need for a separate SSA pass after the CFG is completed.
|
|
///
|
|
/// A basic block is said _filled_ if all the instruction that it contains have been translated,
|
|
/// and it is said _sealed_ if all of its predecessors have been declared. Only filled predecessors
|
|
/// can be declared.
|
|
pub struct SSABuilder<Variable>
|
|
where Variable: EntityRef + Default
|
|
{
|
|
// Records for every variable and for every revelant block, the last definition of
|
|
// the variable in the block.
|
|
variables: EntityMap<Variable, HashMap<Block, Value>>,
|
|
// Records the position of the basic blocks and the list of values used but not defined in the
|
|
// block.
|
|
blocks: EntityMap<Block, BlockData<Variable>>,
|
|
// Records the basic blocks at the beginning of the `Ebb`s.
|
|
ebb_headers: EntityMap<Ebb, PackedOption<Block>>,
|
|
}
|
|
|
|
/// Side effects of a `use_var` or a `seal_ebb_header_block` method call.
|
|
pub struct SideEffects {
|
|
/// When we want to append jump arguments to a `br_table` instruction, the critical edge is
|
|
/// splitted and the newly created `Ebb`s are signaled here.
|
|
pub split_ebbs_created: Vec<Ebb>,
|
|
/// When a variable is used but has never been defined before (this happens in the case of
|
|
/// unreachable code), a placeholder `iconst` or `fconst` value is added to the right `Ebb`.
|
|
/// This field signals if it is the case and return the `Ebb` to which the initialization has
|
|
/// been added.
|
|
pub instructions_added_to_ebbs: Vec<Ebb>,
|
|
}
|
|
|
|
// Describes the current position of a basic block in the control flow graph.
|
|
enum BlockData<Variable> {
|
|
// A block at the top of an `Ebb`.
|
|
EbbHeader(EbbHeaderBlockData<Variable>),
|
|
// A block inside an `Ebb` with an unique other block as its predecessor.
|
|
// The block is implicitely sealed at creation.
|
|
EbbBody { predecessor: Block },
|
|
}
|
|
impl<Variable> PrimaryEntityData for BlockData<Variable> {}
|
|
|
|
impl<Variable> BlockData<Variable> {
|
|
fn add_predecessor(&mut self, pred: Block, inst: Inst) {
|
|
match self {
|
|
&mut BlockData::EbbBody { .. } => panic!("you can't add a predecessor to a body block"),
|
|
&mut BlockData::EbbHeader(ref mut data) => {
|
|
data.predecessors.insert(pred, inst);
|
|
()
|
|
}
|
|
}
|
|
}
|
|
fn remove_predecessor(&mut self, inst: Inst) -> Block {
|
|
match self {
|
|
&mut BlockData::EbbBody { .. } => panic!("should not happen"),
|
|
&mut BlockData::EbbHeader(ref mut data) => {
|
|
// This a linear complexity operation but the number of predecessors is low
|
|
// in all non-pathological cases
|
|
let pred: Block = match data.predecessors
|
|
.iter()
|
|
.find(|&(_, &jump_inst)| jump_inst == inst) {
|
|
None => panic!("the predecessor you are trying to remove is not declared"),
|
|
Some((&b, _)) => b.clone(),
|
|
};
|
|
data.predecessors.remove(&pred);
|
|
pred
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct EbbHeaderBlockData<Variable> {
|
|
// The predecessors of the Ebb header block, with the block and branch instruction.
|
|
predecessors: HashMap<Block, Inst>,
|
|
// A ebb header block is sealed if all of its predecessors have been declared.
|
|
sealed: bool,
|
|
// The ebb which this block is part of.
|
|
ebb: Ebb,
|
|
// List of current Ebb arguments for which a earlier def has not been found yet.
|
|
undef_variables: Vec<(Variable, Value)>,
|
|
}
|
|
|
|
/// A opaque reference to a basic block.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct Block(u32);
|
|
impl EntityRef for Block {
|
|
fn new(index: usize) -> Self {
|
|
assert!(index < (u32::MAX as usize));
|
|
Block(index as u32)
|
|
}
|
|
|
|
fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
}
|
|
|
|
impl ReservedValue for Block {
|
|
fn reserved_value() -> Block {
|
|
Block(u32::MAX)
|
|
}
|
|
}
|
|
|
|
impl<Variable> SSABuilder<Variable>
|
|
where Variable: EntityRef + Default
|
|
{
|
|
/// Allocate a new blank SSA builder struct. Use the API function to interact with the struct.
|
|
pub fn new() -> SSABuilder<Variable> {
|
|
SSABuilder {
|
|
variables: EntityMap::new(),
|
|
blocks: EntityMap::new(),
|
|
ebb_headers: EntityMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Clears a `SSABuilder` from all its data, letting it in a pristine state without
|
|
/// deallocating memory.
|
|
pub fn clear(&mut self) {
|
|
self.variables.clear();
|
|
self.blocks.clear();
|
|
self.ebb_headers.clear();
|
|
}
|
|
}
|
|
|
|
// Small enum used for clarity in some functions.
|
|
#[derive(Debug)]
|
|
enum ZeroOneOrMore<T> {
|
|
Zero(),
|
|
One(T),
|
|
More(),
|
|
}
|
|
|
|
/// TODO: use entity list instead of vec
|
|
#[derive(Debug)]
|
|
enum UseVarCases {
|
|
Unsealed(Value),
|
|
SealedOnePredecessor(Block),
|
|
SealedMultiplePredecessors(Vec<(Block, Inst)>, Value, Ebb),
|
|
}
|
|
|
|
/// The following methods are the API of the SSA builder. Here is how it should be used when
|
|
/// translating to Cretonne IL:
|
|
///
|
|
/// - for each sequence of contiguous instructions (with no branches), create a corresponding
|
|
/// basic block with `declare_ebb_body_block` or `declare_ebb_header_block` depending on the
|
|
/// position of the basic block;
|
|
///
|
|
/// - while traversing a basic block and translating instruction, use `def_var` and `use_var`
|
|
/// to record definitions and uses of variables, these methods will give you the corresponding
|
|
/// SSA values;
|
|
///
|
|
/// - when all the instructions in a basic block have translated, the block is said _filled_ and
|
|
/// only then you can add it as a predecessor to other blocks with `declare_ebb_predecessor`;
|
|
///
|
|
/// - when you have constructed all the predecessor to a basic block at the beginning of an `Ebb`,
|
|
/// call `seal_ebb_header_block` on it with the `Function` that you are building.
|
|
///
|
|
/// This API will give you the correct SSA values to use as arguments of your instructions,
|
|
/// as well as modify the jump instruction and `Ebb` headers arguments to account for the SSA
|
|
/// Phi functions.
|
|
///
|
|
impl<Variable> SSABuilder<Variable>
|
|
where Variable: EntityRef + Hash + Default
|
|
{
|
|
/// Declares a new definition of a variable in a given basic block.
|
|
/// The SSA value is passed as an argument because it should be created with
|
|
/// `ir::DataFlowGraph::append_result`.
|
|
pub fn def_var(&mut self, var: Variable, val: Value, block: Block) {
|
|
self.variables.ensure(var).insert(block, val);
|
|
}
|
|
|
|
/// Declares a use of a variable in a given basic block. Returns the SSA value corresponding
|
|
/// to the current SSA definition of this variable and a list of newly created Ebbs that
|
|
/// are the results of critical edge splitting for `br_table` with arguments.
|
|
///
|
|
/// If the variable has never been defined in this blocks or recursively in its predecessors,
|
|
/// this method will silently create an initializer with `iconst` or `fconst`. You are
|
|
/// responsible for making sure that you initialize your variables.
|
|
pub fn use_var(&mut self,
|
|
dfg: &mut DataFlowGraph,
|
|
layout: &mut Layout,
|
|
jts: &mut JumpTables,
|
|
var: Variable,
|
|
ty: Type,
|
|
block: Block)
|
|
-> (Value, SideEffects) {
|
|
// First we lookup for the current definition of the variable in this block
|
|
if let Some(var_defs) = self.variables.get(var) {
|
|
if let Some(val) = var_defs.get(&block) {
|
|
return (*val,
|
|
SideEffects {
|
|
split_ebbs_created: Vec::new(),
|
|
instructions_added_to_ebbs: Vec::new(),
|
|
});
|
|
}
|
|
};
|
|
// At this point if we haven't returned it means that we have to search in the
|
|
// predecessors.
|
|
let case = match self.blocks[block] {
|
|
BlockData::EbbHeader(ref mut data) => {
|
|
// The block has multiple predecessors so we append an Ebb argument that
|
|
// will serve as a value.
|
|
if data.sealed {
|
|
if data.predecessors.len() == 1 {
|
|
// Only one predecessor, straightforward case
|
|
UseVarCases::SealedOnePredecessor(*data.predecessors.keys().next().unwrap())
|
|
} else {
|
|
let val = dfg.append_ebb_arg(data.ebb, ty);
|
|
let preds = data.predecessors
|
|
.iter()
|
|
.map(|(&pred, &inst)| (pred, inst))
|
|
.collect();
|
|
UseVarCases::SealedMultiplePredecessors(preds, val, data.ebb)
|
|
}
|
|
} else {
|
|
let val = dfg.append_ebb_arg(data.ebb, ty);
|
|
data.undef_variables.push((var, val));
|
|
UseVarCases::Unsealed(val)
|
|
}
|
|
}
|
|
BlockData::EbbBody { predecessor: pred, .. } => UseVarCases::SealedOnePredecessor(pred),
|
|
};
|
|
// TODO: avoid recursion for the calls to use_var and predecessors_lookup.
|
|
match case {
|
|
// The block has a single predecessor or multiple predecessor with
|
|
// the same value, we look into it.
|
|
UseVarCases::SealedOnePredecessor(pred) => {
|
|
let (val, mids) = self.use_var(dfg, layout, jts, var, ty, pred);
|
|
self.def_var(var, val, block);
|
|
return (val, mids);
|
|
}
|
|
// The block has multiple predecessors, we register the ebb argument as the current
|
|
// definition for the variable.
|
|
UseVarCases::Unsealed(val) => {
|
|
self.def_var(var, val, block);
|
|
return (val,
|
|
SideEffects {
|
|
split_ebbs_created: Vec::new(),
|
|
instructions_added_to_ebbs: Vec::new(),
|
|
});
|
|
}
|
|
UseVarCases::SealedMultiplePredecessors(preds, val, ebb) => {
|
|
// If multiple predecessor we look up a use_var in each of them:
|
|
// if they all yield the same value no need for an Ebb argument
|
|
self.def_var(var, val, block);
|
|
return self.predecessors_lookup(dfg, layout, jts, val, var, ebb, &preds);
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
/// Declares a new basic block belonging to the body of a certain `Ebb` and having `pred`
|
|
/// as a predecessor. `pred` is the only predecessor of the block and the block is sealed
|
|
/// at creation.
|
|
///
|
|
/// To declare a `Ebb` header block, see `declare_ebb_header_block`.
|
|
pub fn declare_ebb_body_block(&mut self, pred: Block) -> Block {
|
|
self.blocks.push(BlockData::EbbBody { predecessor: pred })
|
|
}
|
|
|
|
/// Declares a new basic block at the beginning of an `Ebb`. No predecessors are declared
|
|
/// here and the block is not sealed.
|
|
/// Predecessors have to be added with `declare_ebb_predecessor`.
|
|
pub fn declare_ebb_header_block(&mut self, ebb: Ebb) -> Block {
|
|
let block = self.blocks
|
|
.push(BlockData::EbbHeader(EbbHeaderBlockData {
|
|
predecessors: HashMap::new(),
|
|
sealed: false,
|
|
ebb: ebb,
|
|
undef_variables: Vec::new(),
|
|
}));
|
|
*self.ebb_headers.ensure(ebb) = block.into();
|
|
block
|
|
}
|
|
/// Gets the header block corresponding to an Ebb, panics if the Ebb or the header block
|
|
/// isn't declared.
|
|
pub fn header_block(&self, ebb: Ebb) -> Block {
|
|
match self.ebb_headers.get(ebb) {
|
|
Some(&header) => {
|
|
match header.expand() {
|
|
Some(header) => header,
|
|
None => panic!("the header block has not been defined"),
|
|
}
|
|
}
|
|
None => panic!("the ebb has not been declared"),
|
|
}
|
|
}
|
|
|
|
/// Declares a new predecessor for an `Ebb` header block and record the branch instruction
|
|
/// of the predecessor that leads to it.
|
|
///
|
|
/// Note that the predecessor is a `Block` and not an `Ebb`. This `Block` must be filled
|
|
/// before added as predecessor. Note that you must provide no jump arguments to the branch
|
|
/// instruction when you create it since `SSABuilder` will fill them for you.
|
|
pub fn declare_ebb_predecessor(&mut self, ebb: Ebb, pred: Block, inst: Inst) {
|
|
let header_block = match self.blocks[self.header_block(ebb)] {
|
|
BlockData::EbbBody { .. } => panic!("you can't add predecessors to an Ebb body block"),
|
|
BlockData::EbbHeader(ref data) => {
|
|
assert!(!data.sealed);
|
|
self.header_block(ebb)
|
|
}
|
|
};
|
|
self.blocks[header_block].add_predecessor(pred, inst)
|
|
}
|
|
|
|
/// Remove a previously declared Ebb predecessor by giving a reference to the jump
|
|
/// instruction. Returns the basic block containing the instruction.
|
|
///
|
|
/// Note: use only when you know what you are doing, this might break the SSA bbuilding problem
|
|
pub fn remove_ebb_predecessor(&mut self, ebb: Ebb, inst: Inst) -> Block {
|
|
let header_block = match self.blocks[self.header_block(ebb)] {
|
|
BlockData::EbbBody { .. } => panic!("you can't add predecessors to an Ebb body block"),
|
|
BlockData::EbbHeader(ref data) => {
|
|
assert!(!data.sealed);
|
|
self.header_block(ebb)
|
|
}
|
|
};
|
|
self.blocks[header_block].remove_predecessor(inst)
|
|
}
|
|
|
|
/// Completes the global value numbering for an `Ebb`, all of its predecessors having been
|
|
/// already sealed.
|
|
///
|
|
/// This method modifies the function's `Layout` by adding arguments to the `Ebb`s to
|
|
/// take into account the Phi function placed by the SSA algorithm.
|
|
pub fn seal_ebb_header_block(&mut self,
|
|
ebb: Ebb,
|
|
dfg: &mut DataFlowGraph,
|
|
layout: &mut Layout,
|
|
jts: &mut JumpTables)
|
|
-> SideEffects {
|
|
let block = self.header_block(ebb);
|
|
|
|
// Sanity check
|
|
match self.blocks[block] {
|
|
BlockData::EbbBody { .. } => panic!("you can't seal an Ebb body block"),
|
|
BlockData::EbbHeader(ref data) => {
|
|
assert!(!data.sealed);
|
|
}
|
|
}
|
|
|
|
// Recurse over the predecessors to find good definitions.
|
|
let side_effects = self.resolve_undef_vars(block, dfg, layout, jts);
|
|
|
|
// Then we mark the block as sealed.
|
|
match self.blocks[block] {
|
|
BlockData::EbbBody { .. } => panic!("this should not happen"),
|
|
BlockData::EbbHeader(ref mut data) => data.sealed = true,
|
|
};
|
|
side_effects
|
|
}
|
|
|
|
// For each undef_var in an Ebb header block, lookup in the predecessors to append the right
|
|
// jump argument to the branch instruction.
|
|
// Panics if called with a non-header block.
|
|
// Returns the list of newly created ebbs for critical edge splitting.
|
|
fn resolve_undef_vars(&mut self,
|
|
block: Block,
|
|
dfg: &mut DataFlowGraph,
|
|
layout: &mut Layout,
|
|
jts: &mut JumpTables)
|
|
-> SideEffects {
|
|
// TODO: find a way to not allocate vectors
|
|
let (predecessors, undef_vars, ebb): (Vec<(Block, Inst)>,
|
|
Vec<(Variable, Value)>,
|
|
Ebb) = match self.blocks[block] {
|
|
BlockData::EbbBody { .. } => panic!("this should not happen"),
|
|
BlockData::EbbHeader(ref mut data) => {
|
|
(data.predecessors.iter().map(|(&x, &y)| (x, y)).collect(),
|
|
data.undef_variables.clone(),
|
|
data.ebb)
|
|
}
|
|
};
|
|
|
|
let mut side_effects = SideEffects {
|
|
split_ebbs_created: Vec::new(),
|
|
instructions_added_to_ebbs: Vec::new(),
|
|
};
|
|
// For each undef var we look up values in the predecessors and create an Ebb argument
|
|
// only if necessary.
|
|
for &(var, val) in undef_vars.iter() {
|
|
let (_, mut local_side_effects) =
|
|
self.predecessors_lookup(dfg, layout, jts, val, var, ebb, &predecessors);
|
|
side_effects
|
|
.split_ebbs_created
|
|
.append(&mut local_side_effects.split_ebbs_created);
|
|
side_effects
|
|
.instructions_added_to_ebbs
|
|
.append(&mut local_side_effects.instructions_added_to_ebbs);
|
|
}
|
|
|
|
// Then we clear the undef_vars and mark the block as sealed.
|
|
match self.blocks[block] {
|
|
BlockData::EbbBody { .. } => panic!("this should not happen"),
|
|
BlockData::EbbHeader(ref mut data) => {
|
|
data.undef_variables.clear();
|
|
}
|
|
};
|
|
side_effects
|
|
}
|
|
|
|
/// Look up in the predecessors of an Ebb the def for a value an decides wether or not
|
|
/// to keep the eeb arg, and act accordingly. Returns the chosen value and optionnaly a
|
|
/// list of Ebb that are the middle of newly created critical edges splits.
|
|
fn predecessors_lookup(&mut self,
|
|
dfg: &mut DataFlowGraph,
|
|
layout: &mut Layout,
|
|
jts: &mut JumpTables,
|
|
temp_arg_val: Value,
|
|
temp_arg_var: Variable,
|
|
dest_ebb: Ebb,
|
|
preds: &Vec<(Block, Inst)>)
|
|
-> (Value, SideEffects) {
|
|
let mut pred_values: ZeroOneOrMore<Value> = ZeroOneOrMore::Zero();
|
|
// TODO: find a way not not allocate a vector
|
|
let mut jump_args_to_append: Vec<(Block, Inst, Value)> = Vec::new();
|
|
let ty = dfg.value_type(temp_arg_val);
|
|
let mut side_effects = SideEffects {
|
|
split_ebbs_created: Vec::new(),
|
|
instructions_added_to_ebbs: Vec::new(),
|
|
};
|
|
for &(pred, last_inst) in preds.iter() {
|
|
// For undef value and each predecessor we query what is the local SSA value
|
|
// corresponding to var and we put it as an argument of the branch instruction.
|
|
let (pred_val, mut local_side_effects) =
|
|
self.use_var(dfg, layout, jts, temp_arg_var, ty, pred);
|
|
pred_values = match pred_values {
|
|
ZeroOneOrMore::Zero() => {
|
|
if pred_val == temp_arg_val {
|
|
ZeroOneOrMore::Zero()
|
|
} else {
|
|
ZeroOneOrMore::One(pred_val)
|
|
}
|
|
}
|
|
ZeroOneOrMore::One(old_val) => {
|
|
if pred_val == temp_arg_val || pred_val == old_val {
|
|
ZeroOneOrMore::One(old_val)
|
|
} else {
|
|
ZeroOneOrMore::More()
|
|
}
|
|
}
|
|
ZeroOneOrMore::More() => ZeroOneOrMore::More(),
|
|
};
|
|
jump_args_to_append.push((pred, last_inst, pred_val));
|
|
side_effects
|
|
.split_ebbs_created
|
|
.append(&mut local_side_effects.split_ebbs_created);
|
|
side_effects
|
|
.instructions_added_to_ebbs
|
|
.append(&mut local_side_effects.instructions_added_to_ebbs);
|
|
}
|
|
match pred_values {
|
|
ZeroOneOrMore::Zero() => {
|
|
// The variable is used but never defined before. This is an irregularity in the
|
|
// code, but rather than throwing an error we silently initialize the variable to
|
|
// 0. This will have no effect since this situation happens in unreachable code.
|
|
if !layout.is_ebb_inserted(dest_ebb) {
|
|
layout.append_ebb(dest_ebb)
|
|
};
|
|
let mut cur = Cursor::new(layout);
|
|
cur.goto_top(dest_ebb);
|
|
cur.next_inst();
|
|
let ty = dfg.value_type(temp_arg_val);
|
|
let val = if ty.is_int() {
|
|
dfg.ins(&mut cur).iconst(ty, 0)
|
|
} else if ty == F32 {
|
|
dfg.ins(&mut cur).f32const(Ieee32::new(0.0))
|
|
} else if ty == F64 {
|
|
dfg.ins(&mut cur).f64const(Ieee64::new(0.0))
|
|
} else {
|
|
panic!("value used but never declared and initialization not supported")
|
|
};
|
|
side_effects.instructions_added_to_ebbs.push(dest_ebb);
|
|
(val, side_effects)
|
|
}
|
|
ZeroOneOrMore::One(pred_val) => {
|
|
// Here all the predecessors use a single value to represent our variable
|
|
// so we don't need to have it as an ebb argument.
|
|
// We need to replace all the occurences of val with pred_val but since
|
|
// we can't afford a re-writing pass right now we just declare an alias.
|
|
dfg.remove_ebb_arg(temp_arg_val);
|
|
dfg.change_to_alias(temp_arg_val, pred_val);
|
|
(pred_val, side_effects)
|
|
}
|
|
ZeroOneOrMore::More() => {
|
|
// There is disagreement in the predecessors on which value to use so we have
|
|
// to keep the ebb argument.
|
|
for (pred_block, last_inst, pred_val) in jump_args_to_append {
|
|
match self.append_jump_argument(dfg,
|
|
layout,
|
|
last_inst,
|
|
pred_block,
|
|
dest_ebb,
|
|
pred_val,
|
|
temp_arg_var,
|
|
jts) {
|
|
None => (),
|
|
Some(middle_ebb) => side_effects.split_ebbs_created.push(middle_ebb),
|
|
};
|
|
}
|
|
(temp_arg_val, side_effects)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Appends a jump argument to a jump instruction, returns ebb created in case of
|
|
/// critical edge splitting.
|
|
fn append_jump_argument(&mut self,
|
|
dfg: &mut DataFlowGraph,
|
|
layout: &mut Layout,
|
|
jump_inst: Inst,
|
|
jump_inst_block: Block,
|
|
dest_ebb: Ebb,
|
|
val: Value,
|
|
var: Variable,
|
|
jts: &mut JumpTables)
|
|
-> Option<Ebb> {
|
|
match dfg[jump_inst].analyze_branch(&dfg.value_lists) {
|
|
BranchInfo::NotABranch => {
|
|
panic!("you have declared a non-branch instruction as a predecessor to an ebb");
|
|
}
|
|
// For a single destination appending a jump argument to the instruction
|
|
// is sufficient.
|
|
BranchInfo::SingleDest(_, _) => {
|
|
dfg.append_inst_arg(jump_inst, val);
|
|
None
|
|
}
|
|
BranchInfo::Table(jt) => {
|
|
// In the case of a jump table, the situation is tricky because br_table doesn't
|
|
// support arguments.
|
|
// We have to split the critical edge
|
|
let indexes: Vec<usize> = jts[jt]
|
|
.entries()
|
|
.fold(Vec::new(), |mut acc, (index, dest)| if dest == dest_ebb {
|
|
acc.push(index);
|
|
acc
|
|
} else {
|
|
acc
|
|
});
|
|
let middle_ebb = dfg.make_ebb();
|
|
layout.append_ebb(middle_ebb);
|
|
let block = self.declare_ebb_header_block(middle_ebb);
|
|
self.blocks[block].add_predecessor(jump_inst_block, jump_inst);
|
|
self.seal_ebb_header_block(middle_ebb, dfg, layout, jts);
|
|
for index in indexes {
|
|
jts[jt].set_entry(index, middle_ebb)
|
|
}
|
|
let mut cur = Cursor::new(layout);
|
|
cur.goto_bottom(middle_ebb);
|
|
let middle_jump_inst = dfg.ins(&mut cur).jump(dest_ebb, &[val]);
|
|
let dest_header_block = self.header_block(dest_ebb);
|
|
self.blocks[dest_header_block].add_predecessor(block, middle_jump_inst);
|
|
self.blocks[dest_header_block].remove_predecessor(jump_inst);
|
|
self.def_var(var, val, block);
|
|
Some(middle_ebb)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the list of `Ebb`s that have been declared as predecessors of the argument.
|
|
pub fn predecessors(&self, ebb: Ebb) -> &HashMap<Block, Inst> {
|
|
let block = match self.ebb_headers[ebb].expand() {
|
|
Some(block) => block,
|
|
None => panic!("the ebb has not been declared yet"),
|
|
};
|
|
match self.blocks[block] {
|
|
BlockData::EbbBody { .. } => panic!("should not happen"),
|
|
BlockData::EbbHeader(ref data) => &data.predecessors,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if and only if `seal_ebb_header_block` has been called on the argument.
|
|
pub fn is_sealed(&self, ebb: Ebb) -> bool {
|
|
match self.blocks[self.header_block(ebb)] {
|
|
BlockData::EbbBody { .. } => panic!("should not happen"),
|
|
BlockData::EbbHeader(ref data) => data.sealed,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use cretonne::entity_ref::EntityRef;
|
|
use cretonne::ir::{Function, InstBuilder, Cursor, Inst, JumpTableData};
|
|
use cretonne::ir::types::*;
|
|
use cretonne::verify_function;
|
|
use cretonne::ir::instructions::BranchInfo;
|
|
use ssa::SSABuilder;
|
|
use std::u32;
|
|
|
|
/// An opaque reference to variable.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct Variable(u32);
|
|
impl EntityRef for Variable {
|
|
fn new(index: usize) -> Self {
|
|
assert!(index < (u32::MAX as usize));
|
|
Variable(index as u32)
|
|
}
|
|
|
|
fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
}
|
|
impl Default for Variable {
|
|
fn default() -> Variable {
|
|
Variable(u32::MAX)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn simple_block() {
|
|
let mut func = Function::new();
|
|
let mut ssa: SSABuilder<Variable> = SSABuilder::new();
|
|
let ebb0 = func.dfg.make_ebb();
|
|
// Here is the pseudo-program we want to translate:
|
|
// x = 1;
|
|
// y = 2;
|
|
// z = x + y;
|
|
// z = x + z;
|
|
|
|
let block = ssa.declare_ebb_header_block(ebb0);
|
|
let x_var = Variable(0);
|
|
let x_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.insert_ebb(ebb0);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 1)
|
|
};
|
|
ssa.def_var(x_var, x_ssa, block);
|
|
let y_var = Variable(1);
|
|
let y_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 2)
|
|
};
|
|
ssa.def_var(y_var, y_ssa, block);
|
|
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block)
|
|
.0,
|
|
x_ssa);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block)
|
|
.0,
|
|
y_ssa);
|
|
let z_var = Variable(2);
|
|
let x_use1 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block)
|
|
.0;
|
|
let y_use1 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block)
|
|
.0;
|
|
let z1_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iadd(x_use1, y_use1)
|
|
};
|
|
ssa.def_var(z_var, z1_ssa, block);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block)
|
|
.0,
|
|
z1_ssa);
|
|
let x_use2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block)
|
|
.0;
|
|
let z_use1 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block)
|
|
.0;
|
|
let z2_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iadd(x_use2, z_use1)
|
|
};
|
|
ssa.def_var(z_var, z2_ssa, block);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block)
|
|
.0,
|
|
z2_ssa);
|
|
}
|
|
|
|
#[test]
|
|
fn sequence_of_blocks() {
|
|
let mut func = Function::new();
|
|
let mut ssa: SSABuilder<Variable> = SSABuilder::new();
|
|
let ebb0 = func.dfg.make_ebb();
|
|
let ebb1 = func.dfg.make_ebb();
|
|
// Here is the pseudo-program we want to translate:
|
|
// ebb0:
|
|
// x = 1;
|
|
// y = 2;
|
|
// z = x + y;
|
|
// brnz y, ebb1;
|
|
// z = x + z;
|
|
// ebb1:
|
|
// y = x + y;
|
|
|
|
let block0 = ssa.declare_ebb_header_block(ebb0);
|
|
let x_var = Variable(0);
|
|
let x_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.insert_ebb(ebb0);
|
|
cur.insert_ebb(ebb1);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 1)
|
|
};
|
|
ssa.def_var(x_var, x_ssa, block0);
|
|
let y_var = Variable(1);
|
|
let y_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 2)
|
|
};
|
|
ssa.def_var(y_var, y_ssa, block0);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block0)
|
|
.0,
|
|
x_ssa);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block0)
|
|
.0,
|
|
y_ssa);
|
|
let z_var = Variable(2);
|
|
let x_use1 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block0)
|
|
.0;
|
|
let y_use1 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block0)
|
|
.0;
|
|
let z1_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iadd(x_use1, y_use1)
|
|
};
|
|
ssa.def_var(z_var, z1_ssa, block0);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block0)
|
|
.0,
|
|
z1_ssa);
|
|
let y_use2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block0)
|
|
.0;
|
|
let jump_inst: Inst = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).brnz(y_use2, ebb1, &[])
|
|
};
|
|
let block1 = ssa.declare_ebb_body_block(block0);
|
|
let x_use2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
assert_eq!(x_use2, x_ssa);
|
|
let z_use1 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
assert_eq!(z_use1, z1_ssa);
|
|
let z2_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iadd(x_use2, z_use1)
|
|
};
|
|
ssa.def_var(z_var, z2_ssa, block1);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block1)
|
|
.0,
|
|
z2_ssa);
|
|
ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let block2 = ssa.declare_ebb_header_block(ebb1);
|
|
ssa.declare_ebb_predecessor(ebb1, block0, jump_inst);
|
|
ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let x_use3 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block2)
|
|
.0;
|
|
assert_eq!(x_ssa, x_use3);
|
|
let y_use3 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block2)
|
|
.0;
|
|
assert_eq!(y_ssa, y_use3);
|
|
let y2_ssa = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).iadd(x_use3, y_use3)
|
|
};
|
|
ssa.def_var(y_var, y2_ssa, block2);
|
|
match func.dfg[jump_inst].analyze_branch(&func.dfg.value_lists) {
|
|
BranchInfo::SingleDest(dest, jump_args) => {
|
|
assert_eq!(dest, ebb1);
|
|
assert_eq!(jump_args.len(), 0);
|
|
}
|
|
_ => assert!(false),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn program_with_loop() {
|
|
let mut func = Function::new();
|
|
let mut ssa: SSABuilder<Variable> = SSABuilder::new();
|
|
let ebb0 = func.dfg.make_ebb();
|
|
let ebb1 = func.dfg.make_ebb();
|
|
let ebb2 = func.dfg.make_ebb();
|
|
// Here is the pseudo-program we want to translate:
|
|
// ebb0:
|
|
// x = 1;
|
|
// y = 2;
|
|
// z = x + y;
|
|
// jump ebb1
|
|
// ebb1:
|
|
// z = z + y;
|
|
// brnz y, ebb1;
|
|
// z = z - x;
|
|
// return y
|
|
// ebb2:
|
|
// y = y - x
|
|
// jump ebb1
|
|
|
|
let block0 = ssa.declare_ebb_header_block(ebb0);
|
|
ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let x_var = Variable(0);
|
|
let x1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.insert_ebb(ebb0);
|
|
cur.insert_ebb(ebb1);
|
|
cur.insert_ebb(ebb2);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 1)
|
|
};
|
|
ssa.def_var(x_var, x1, block0);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block0)
|
|
.0,
|
|
x1);
|
|
let y_var = Variable(1);
|
|
let y1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 2)
|
|
};
|
|
ssa.def_var(y_var, y1, block0);
|
|
assert_eq!(ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block0)
|
|
.0,
|
|
y1);
|
|
let z_var = Variable(2);
|
|
let x2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block0)
|
|
.0;
|
|
assert_eq!(x2, x1);
|
|
let y2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block0)
|
|
.0;
|
|
assert_eq!(y2, y1);
|
|
let z1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iadd(x2, y2)
|
|
};
|
|
ssa.def_var(z_var, z1, block0);
|
|
let jump_ebb0_ebb1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).jump(ebb1, &[])
|
|
};
|
|
let block1 = ssa.declare_ebb_header_block(ebb1);
|
|
ssa.declare_ebb_predecessor(ebb1, block0, jump_ebb0_ebb1);
|
|
let z2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
let y3 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
let z3 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).iadd(z2, y3)
|
|
};
|
|
ssa.def_var(z_var, z3, block1);
|
|
let y4 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
assert_eq!(y4, y3);
|
|
let jump_ebb1_ebb2 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).brnz(y4, ebb2, &[])
|
|
};
|
|
let block2 = ssa.declare_ebb_body_block(block1);
|
|
let z4 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block2)
|
|
.0;
|
|
assert_eq!(z4, z3);
|
|
let x3 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block2)
|
|
.0;
|
|
let z5 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).isub(z4, x3)
|
|
};
|
|
ssa.def_var(z_var, z5, block2);
|
|
let y5 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block2)
|
|
.0;
|
|
assert_eq!(y5, y3);
|
|
{
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).return_(&[y5])
|
|
};
|
|
|
|
let block3 = ssa.declare_ebb_header_block(ebb2);
|
|
ssa.declare_ebb_predecessor(ebb2, block1, jump_ebb1_ebb2);
|
|
ssa.seal_ebb_header_block(ebb2, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let y6 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block3)
|
|
.0;
|
|
assert_eq!(y6, y3);
|
|
let x4 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block3)
|
|
.0;
|
|
assert_eq!(x4, x3);
|
|
let y7 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb2);
|
|
func.dfg.ins(cur).isub(y6, x4)
|
|
};
|
|
ssa.def_var(y_var, y7, block3);
|
|
let jump_ebb2_ebb1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb2);
|
|
func.dfg.ins(cur).jump(ebb1, &[])
|
|
};
|
|
|
|
ssa.declare_ebb_predecessor(ebb1, block3, jump_ebb2_ebb1);
|
|
ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[0], z2);
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[1], y3);
|
|
assert_eq!(func.dfg.resolve_aliases(x3), x1);
|
|
|
|
}
|
|
|
|
#[test]
|
|
fn br_table_with_args() {
|
|
// This tests the on-demand splitting of critical edges for br_table with jump arguments
|
|
let mut func = Function::new();
|
|
let mut ssa: SSABuilder<Variable> = SSABuilder::new();
|
|
let ebb0 = func.dfg.make_ebb();
|
|
let ebb1 = func.dfg.make_ebb();
|
|
// Here is the pseudo-program we want to translate:
|
|
// ebb0:
|
|
// x = 0;
|
|
// br_table x ebb1
|
|
// x = 1
|
|
// jump ebb1
|
|
// ebb1:
|
|
// x = x + 1
|
|
// return
|
|
//
|
|
let block0 = ssa.declare_ebb_header_block(ebb0);
|
|
ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let x_var = Variable(0);
|
|
let x1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.insert_ebb(ebb0);
|
|
cur.insert_ebb(ebb1);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 1)
|
|
};
|
|
ssa.def_var(x_var, x1, block0);
|
|
let mut jt_data = JumpTableData::new();
|
|
jt_data.set_entry(0, ebb1);
|
|
let jt = func.jump_tables.push(jt_data);
|
|
ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block0)
|
|
.0;
|
|
let br_table = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).br_table(x1, jt)
|
|
};
|
|
let block1 = ssa.declare_ebb_body_block(block0);
|
|
let x3 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 2)
|
|
};
|
|
ssa.def_var(x_var, x3, block1);
|
|
let jump_inst = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).jump(ebb1, &[])
|
|
};
|
|
let block2 = ssa.declare_ebb_header_block(ebb1);
|
|
ssa.declare_ebb_predecessor(ebb1, block1, jump_inst);
|
|
ssa.declare_ebb_predecessor(ebb1, block0, br_table);
|
|
ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let x4 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block2)
|
|
.0;
|
|
{
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).iadd_imm(x4, 1)
|
|
};
|
|
{
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).return_(&[])
|
|
};
|
|
match verify_function(&func, None) {
|
|
Ok(()) => {}
|
|
Err(err) => panic!(err.message),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn undef_values_reordering() {
|
|
let mut func = Function::new();
|
|
let mut ssa: SSABuilder<Variable> = SSABuilder::new();
|
|
let ebb0 = func.dfg.make_ebb();
|
|
let ebb1 = func.dfg.make_ebb();
|
|
// Here is the pseudo-program we want to translate:
|
|
// ebb0:
|
|
// x = 0
|
|
// y = 1
|
|
// z = 2
|
|
// jump ebb1
|
|
// ebb1:
|
|
// x = z + x
|
|
// y = y - x
|
|
// jump ebb1
|
|
//
|
|
let block0 = ssa.declare_ebb_header_block(ebb0);
|
|
let x_var = Variable(0);
|
|
let y_var = Variable(1);
|
|
let z_var = Variable(2);
|
|
ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
let x1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.insert_ebb(ebb0);
|
|
cur.insert_ebb(ebb1);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 0)
|
|
};
|
|
ssa.def_var(x_var, x1, block0);
|
|
let y1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 1)
|
|
};
|
|
ssa.def_var(y_var, y1, block0);
|
|
let z1 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).iconst(I32, 2)
|
|
};
|
|
ssa.def_var(z_var, z1, block0);
|
|
let jump_inst = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb0);
|
|
func.dfg.ins(cur).jump(ebb1, &[])
|
|
};
|
|
let block1 = ssa.declare_ebb_header_block(ebb1);
|
|
ssa.declare_ebb_predecessor(ebb1, block0, jump_inst);
|
|
let z2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
z_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[0], z2);
|
|
let x2 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[1], x2);
|
|
let x3 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).iadd(x2, z2)
|
|
};
|
|
ssa.def_var(x_var, x3, block1);
|
|
let x4 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
x_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
let y3 = ssa.use_var(&mut func.dfg,
|
|
&mut func.layout,
|
|
&mut func.jump_tables,
|
|
y_var,
|
|
I32,
|
|
block1)
|
|
.0;
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[2], y3);
|
|
let y4 = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).isub(y3, x4)
|
|
};
|
|
ssa.def_var(y_var, y4, block1);
|
|
let jump_inst = {
|
|
let cur = &mut Cursor::new(&mut func.layout);
|
|
cur.goto_bottom(ebb1);
|
|
func.dfg.ins(cur).jump(ebb1, &[])
|
|
};
|
|
ssa.declare_ebb_predecessor(ebb1, block1, jump_inst);
|
|
ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables);
|
|
// At sealing the "z" argument disappear but the remaining "x" and "y" args have to be
|
|
// in the right order.
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[1], y3);
|
|
assert_eq!(func.dfg.ebb_args(ebb1)[0], x2);
|
|
}
|
|
}
|