1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
use crate::analysis::graph::Graph;
use crate::intermediate_representation::*;
use std::collections::BTreeSet;
/// Given the variables that are alive after execution of the given `Def` term,
/// modify the set of variables to the ones that are alive before the execution of the `Def` term.
pub fn update_alive_vars_by_def(alive_variables: &mut BTreeSet<Variable>, def: &Term<Def>) {
match &def.term {
Def::Assign { var, value } => {
if alive_variables.contains(var) {
alive_variables.remove(var);
for input_var in value.input_vars() {
alive_variables.insert(input_var.clone());
}
} // The else-case is a dead store whose inputs do not change the set of alive variables.
}
Def::Load { var, address } => {
alive_variables.remove(var);
for input_var in address.input_vars() {
alive_variables.insert(input_var.clone());
}
}
Def::Store { address, value } => {
for input_var in address.input_vars() {
alive_variables.insert(input_var.clone());
}
for input_var in value.input_vars() {
alive_variables.insert(input_var.clone());
}
}
}
}
/// The context struct for the alive variables fixpoint computation.
///
/// The computation is a intraprocedural backwards fixpoint calculation
/// that stores at each node the set of all registers that are assumed to be alive.
/// A register is alive if its content is (assumed to be) read before it is overwritten by another value assignment.
pub struct Context<'a> {
/// The reversed control flow graph of the program.
graph: &'a Graph<'a>,
/// The set of all physical base registers (i.e. no sub registers).
/// This is the set of registers that are assumed to be alive at call/return instructions
/// and all other places in the control flow graph,
/// where the next instruction to be executed may not be known.
pub all_physical_registers: &'a BTreeSet<Variable>,
}
impl<'a> Context<'a> {
/// Create a new context object for the given project and reversed control flow graph.
pub fn new(project: &'a Project, graph: &'a Graph) -> Context<'a> {
Context {
graph,
all_physical_registers: &project.register_set,
}
}
}
impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Context<'a> {
/// The value at each node is the set of variables that are known to be alive.
type Value = BTreeSet<Variable>;
/// Get the reversed control flow graph on which the fixpoint computation operates.
fn get_graph(&self) -> &Graph<'a> {
self.graph
}
/// Merge by taking the union of the two sets of alive registers.
fn merge(&self, var_set_1: &Self::Value, var_set_2: &Self::Value) -> Self::Value {
var_set_1.union(var_set_2).cloned().collect()
}
/// Update the set of alive registers according to the effect of the given `Def` term.
fn update_def(&self, alive_variables: &Self::Value, def: &Term<Def>) -> Option<Self::Value> {
let mut alive_variables = alive_variables.clone();
update_alive_vars_by_def(&mut alive_variables, def);
Some(alive_variables)
}
/// Update the set of alive registers according to the effect of the given jump term.
/// Adds input variables of jump conditions or jump target computations to the set of alive variables.
fn update_jumpsite(
&self,
alive_vars_after_jump: &Self::Value,
jump: &Term<Jmp>,
untaken_conditional: Option<&Term<Jmp>>,
_jumpsite: &Term<Blk>,
) -> Option<Self::Value> {
let mut alive_variables = alive_vars_after_jump.clone();
match &jump.term {
Jmp::CBranch {
condition: expression,
..
}
| Jmp::BranchInd(expression) => {
for input_var in expression.input_vars() {
alive_variables.insert(input_var.clone());
}
}
_ => (),
}
if let Some(Term {
tid: _,
term: Jmp::CBranch { condition, .. },
}) = untaken_conditional
{
for input_var in condition.input_vars() {
alive_variables.insert(input_var.clone());
}
}
Some(alive_variables)
}
/// At a call instruction we assume all physical registers to be alive.
/// Also adds inputs for the call target computation to the set of alive registers.
fn update_callsite(
&self,
_target_value: Option<&Self::Value>,
_return_value: Option<&Self::Value>,
_caller_sub: &Term<Sub>,
call: &Term<Jmp>,
_return_: &Term<Jmp>,
) -> Option<Self::Value> {
let mut alive_variables = self.all_physical_registers.clone();
if let Jmp::CallInd { target, .. } = &call.term {
for input_var in target.input_vars() {
alive_variables.insert(input_var.clone());
}
}
Some(alive_variables)
}
/// Interprocedural edge that is ignored by the fixpoint computation.
fn split_call_stub(&self, _combined_value: &Self::Value) -> Option<Self::Value> {
None
}
/// At a return instruction we assume all physical registers to be alive.
fn split_return_stub(
&self,
_combined_value: &Self::Value,
_returned_from_sub: &Term<Sub>,
) -> Option<Self::Value> {
Some(self.all_physical_registers.clone())
}
/// At a call instruction we assume all physical registers to be alive.
/// Also adds inputs for the call target computation to the set of alive registers.
fn update_call_stub(
&self,
_value_after_call: &Self::Value,
call: &Term<Jmp>,
) -> Option<Self::Value> {
let mut alive_variables = self.all_physical_registers.clone();
if let Jmp::CallInd { target, .. } = &call.term {
for input_var in target.input_vars() {
alive_variables.insert(input_var.clone());
}
}
Some(alive_variables)
}
/// This function just clones its input as it is not used by the fixpoint computation.
fn specialize_conditional(
&self,
alive_vars_after_jump: &Self::Value,
_condition: &Expression,
_is_true: bool,
) -> Option<Self::Value> {
Some(alive_vars_after_jump.clone())
}
}