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 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
//! This module contains a fixpoint computation for intra-procedual expression propagation
//! and contains a function for inserting such expressions.
use super::fixpoint::Computation;
use super::forward_interprocedural_fixpoint::GeneralizedContext;
use crate::analysis::forward_interprocedural_fixpoint::create_computation;
use crate::analysis::graph::Graph;
use crate::analysis::graph::Node;
use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::intermediate_representation::*;
use std::collections::HashMap;
/// The context struct for the expression propagation fixpoint computation.
///
/// The computation is a intra procedural forward fixpoint calculation
/// that stores at each node the set of registers with their propagated expressions.
/// This expressions can be used for expression propagation among basic blocks.
pub struct Context<'a> {
graph: &'a Graph<'a>,
}
impl<'a> Context<'a> {
/// Create a new context object for the given project and control flow graph.
pub fn new(graph: &'a Graph) -> Context<'a> {
Context { graph }
}
}
impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
type Value = HashMap<Variable, Expression>;
fn get_graph(&self) -> &Graph<'a> {
self.graph
}
/// Merges two values by intersecting their variable-expression pairs.
fn merge(&self, value1: &Self::Value, value2: &Self::Value) -> Self::Value {
value1
.iter()
.filter(|(var, expr)| value2.get(var) == Some(expr))
.map(|(var, expr)| (var.clone(), expr.clone()))
.collect()
}
/// Adds the expression for the assigned variable to the table.
///
/// Invalid pairs are removed and new expressions are supplemented if possible.
fn update_def(&self, value: &Self::Value, def: &Term<Def>) -> Option<Self::Value> {
let mut insertable_expressions = value.clone();
match &def.term {
Def::Assign {
var,
value: expression,
} => {
// Extend the considered expression with already known expressions.
let mut extended_expression = expression.clone();
for input_var in expression.input_vars().into_iter() {
if let Some(expr) = insertable_expressions.get(input_var) {
// We limit the complexity of expressions to insert.
// This prevents extremely large expressions that can lead to extremely high RAM usage.
// FIXME: Right now this limit is quite arbitrary. Maybe there is a better way to achieve the same result?
if expr.recursion_depth() < 10 {
extended_expression.substitute_input_var(input_var, expr)
}
}
}
extended_expression.substitute_trivial_operations();
insertable_expressions.insert(var.clone(), extended_expression.clone());
// Expressions dependent on the assigned variable are no longer insertable.
insertable_expressions.retain(|_input_var, input_expr| {
!input_expr.input_vars().into_iter().any(|x| x == var)
});
Some(insertable_expressions)
}
Def::Load {
var,
address: _expression,
} => {
// Expressions dependent on the assigned variable are no longer insertable
insertable_expressions.retain(|_input_var, input_expr| {
!input_expr.input_vars().into_iter().any(|x| x == var)
});
Some(insertable_expressions)
}
Def::Store { .. } => Some(insertable_expressions),
}
}
fn update_call_stub(
&self,
_value_before_call: &Self::Value,
_call: &Term<Jmp>,
) -> Option<Self::Value> {
Some(HashMap::new())
}
fn update_jump(
&self,
value: &Self::Value,
_jump: &Term<Jmp>,
_untaken_conditional: Option<&Term<Jmp>>,
_target: &Term<Blk>,
) -> Option<Self::Value> {
Some(value.clone())
}
fn update_call(
&self,
_value: &Self::Value,
_call: &Term<Jmp>,
_target: &Node,
_calling_convention: &Option<String>,
) -> Option<Self::Value> {
None // This propagation is intra-procedural
}
fn update_return(
&self,
_value: Option<&Self::Value>,
_value_before_call: Option<&Self::Value>,
_call_term: &Term<Jmp>,
_return_term: &Term<Jmp>,
_calling_convention: &Option<String>,
) -> Option<Self::Value> {
Some(HashMap::new()) // Start with no prior knowledge
}
fn specialize_conditional(
&self,
value: &Self::Value,
_condition: &Expression,
_block_before_condition: &Term<Blk>,
_is_true: bool,
) -> Option<Self::Value> {
Some(value.clone())
}
}
/// Performs the fixpoint algorithm and returns the computation.
///
/// Panics, if the computation does not stabilizes.
fn compute_expression_propagation<'a>(
graph: &'a Graph,
) -> Computation<GeneralizedContext<'a, Context<'a>>> {
let context = Context::new(graph);
let mut computation = create_computation(context, None);
for node in graph.node_indices() {
if let Node::BlkStart(_blk, _sub) = graph[node] {
// A start in the CFG has no incoming edges in the CFG and
// are mostly due to cases where the control flow graph is incomplete.
// We assume that no expressions are insertable at such starting nodes.
// Additionally, we initialize every function's entrypoint.
if graph
.neighbors_directed(node, petgraph::Incoming)
.next()
.is_none()
|| graph[node].get_sub().term.blocks.first() == Some(graph[node].get_block())
{
computation.set_node_value(node, NodeValue::Value(HashMap::new()));
}
}
}
computation.compute_with_max_steps(100);
if !computation.has_stabilized() {
panic!("Fixpoint for expression propagation did not stabilize.");
}
computation
}
/// Returns the computed result for every basic block.
///
/// This returns the table of variable-expression pairs that hold at the beginning of the blocks.
fn extract_results<'a>(
graph: &Graph,
computation: Computation<GeneralizedContext<'a, Context<'a>>>,
) -> HashMap<Tid, HashMap<Variable, Expression>> {
let mut results = HashMap::new();
for node in graph.node_indices() {
if let Node::BlkStart(blk, _sub) = graph[node] {
if let Some(NodeValue::Value(insertables)) = computation.get_node_value(node) {
results.insert(blk.tid.clone(), insertables.clone());
}
}
}
results
}
/// Replaces for every basic block all propagated expressions.
///
/// This uses the expression propagation of basic blocks, thus performs intra-basic-block insertion of expressions.
fn insert_expressions(
insertables: HashMap<Tid, HashMap<Variable, Expression>>,
program: &mut Program,
) {
for sub in program.subs.values_mut() {
for block in sub.term.blocks.iter_mut() {
propagate_input_expressions(block, insertables.get(&block.tid).cloned());
}
}
}
/// Merges consecutive assignment expressions for the same variable.
fn merge_same_var_assignments(project: &mut Project) {
for sub in project.program.term.subs.values_mut() {
for blk in sub.term.blocks.iter_mut() {
merge_def_assignments_to_same_var(blk);
}
}
}
/// Wherever possible, substitute input variables of expressions
/// with the input expression that defines the input variable.
///
/// Note that substitution is only possible
/// if the input variables of the input expression itself did not change since the definition of said variable.
///
/// The expression propagation allows more dead stores to be removed during
/// [dead variable elimination](crate::analysis::dead_variable_elimination).
pub fn propagate_input_expressions(
blk: &mut Term<Blk>,
apriori_insertable_expressions: Option<HashMap<Variable, Expression>>,
) {
let mut insertable_expressions = HashMap::new();
if let Some(insertables) = apriori_insertable_expressions {
insertable_expressions = insertables;
}
for def in blk.term.defs.iter_mut() {
match &mut def.term {
Def::Assign {
var,
value: expression,
} => {
// Extend the considered expression with already known expressions.
let mut extended_expression = expression.clone();
for input_var in expression.input_vars().into_iter() {
if let Some(expr) = insertable_expressions.get(input_var) {
// We limit the complexity of expressions to insert.
// This prevents extremely large expressions that can lead to extremely high RAM usage.
// FIXME: Right now this limit is quite arbitrary. Maybe there is a better way to achieve the same result?
if expr.recursion_depth() < 10 {
extended_expression.substitute_input_var(input_var, expr)
}
}
}
extended_expression.substitute_trivial_operations();
*expression = extended_expression;
// expressions dependent on the assigned variable are no longer insertable
insertable_expressions.retain(|input_var, input_expr| {
input_var != var && !input_expr.input_vars().into_iter().any(|x| x == var)
});
// If the value of the assigned variable does not depend on the former value of the variable,
// then it is insertable for future expressions.
if !expression.input_vars().into_iter().any(|x| x == var) {
insertable_expressions.insert(var.clone(), expression.clone());
}
}
Def::Load {
var,
address: expression,
} => {
// insert known input expressions
for (input_var, input_expr) in insertable_expressions.iter() {
expression.substitute_input_var(input_var, input_expr);
}
// expressions dependent on the assigned variable are no longer insertable
insertable_expressions.retain(|input_var, input_expr| {
input_var != var && !input_expr.input_vars().into_iter().any(|x| x == var)
});
}
Def::Store { address, value } => {
// insert known input expressions
for (input_var, input_expr) in insertable_expressions.iter() {
address.substitute_input_var(input_var, input_expr);
value.substitute_input_var(input_var, input_expr);
}
}
}
}
for jump in blk.term.jmps.iter_mut() {
match &mut jump.term {
Jmp::Branch(_) | Jmp::Call { .. } | Jmp::CallOther { .. } => (),
Jmp::BranchInd(expr)
| Jmp::CBranch {
condition: expr, ..
}
| Jmp::CallInd { target: expr, .. }
| Jmp::Return(expr) => {
// insert known input expressions
for (input_var, input_expr) in insertable_expressions.iter() {
expr.substitute_input_var(input_var, input_expr);
}
}
}
}
}
/// Merge subsequent assignments to the same variable to a single assignment to that variable.
pub fn merge_def_assignments_to_same_var(blk: &mut Term<Blk>) {
let mut new_defs = Vec::new();
let mut last_def_opt = None;
for def in blk.term.defs.iter() {
if let Def::Assign {
var: current_var, ..
} = &def.term
{
if let Some(Term {
term:
Def::Assign {
var: last_var,
value: last_value,
},
..
}) = &last_def_opt
{
if current_var == last_var {
let mut substituted_def = def.clone();
substituted_def.substitute_input_var(last_var, last_value);
last_def_opt = Some(substituted_def);
} else {
new_defs.push(last_def_opt.unwrap());
last_def_opt = Some(def.clone());
}
} else if last_def_opt.is_some() {
panic!(); // Only assign-defs should be saved in last_def.
} else {
last_def_opt = Some(def.clone());
}
} else {
if let Some(last_def) = last_def_opt {
new_defs.push(last_def);
}
new_defs.push(def.clone());
last_def_opt = None;
}
}
if let Some(last_def) = last_def_opt {
new_defs.push(last_def);
}
blk.term.defs = new_defs;
}
/// Replaces variables by expressions that can be propagated within functions.
///
/// This is performed by a fixpoint computation and might panic, if it does not stabilize.
pub fn propagate_input_expression(project: &mut Project) {
merge_same_var_assignments(project);
let graph = crate::analysis::graph::get_program_cfg(&project.program);
let computation = compute_expression_propagation(&graph);
let results = extract_results(&graph, computation);
insert_expressions(results, &mut project.program.term);
}
#[cfg(test)]
mod tests;