Skip to content

Commit

Permalink
lib/ir/project: update docs of CF-propagation pass
Browse files Browse the repository at this point in the history
No functional changes.

Signed-off-by: Valentin Obst <[email protected]>
  • Loading branch information
Valentin Obst committed May 3, 2024
1 parent 423a5d3 commit 31211b2
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
/// Contains implementation of the block duplication normalization pass.
mod block_duplication_normalization;
use block_duplication_normalization::*;
/// Contains implementation of the propagate control flow normalization pass.
mod propagate_control_flow;
use propagate_control_flow::*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
//! Control Flow Propagation Normalization Pass
//!
//! The `propagate_control_flow` normalization pass tries to simplify the
//! representation of sequences of if-else blocks that all have the same
//! condition. After this transformation the program should be of a form where
//! either all or none of the blocks are executed. Such sequences are often
//! generated by sequences of conditional assignment assembly instructions.
//!
//! In addition to the above, the pass also removes blocks that consist of a
//! single, unconditional jump.
//!
//! For each "re-targetalbe" intraprocedural control flow transfer, i.e.,
//! call-returns and (conditional) jumps, the pass computes a new target that is
//! equivalent to the old target but skips zero or more intermediate blocks.
//! Knowledge about conditions that are always true when a particular branch is
//! executed are used to resolve the target of intermediate conditional jumps.
//!
//! Lastly, the newly bypassed blocks are considered dead code and are removed.

use crate::analysis::graph::{self, Edge, Graph, Node};
use crate::intermediate_representation::*;

Expand All @@ -6,17 +25,10 @@ use std::collections::{BTreeSet, HashMap, HashSet};
use petgraph::graph::NodeIndex;
use petgraph::Direction::Incoming;

/// The `propagate_control_flow` normalization pass tries to simplify the representation of
/// sequences of if-else blocks that all have the same condition
/// so that they are either all executed or none of the blocks are executed.
/// Such sequences are often generated by sequences of conditional assignment assembly instructions.
/// Performs the Control Flow Propagation normalization pass.
///
/// To simplify the generated control flow graph
/// (and thus propagate the knowledge that either all or none of these blocks are executed to the control flow graph)
/// we look for sequences of (conditional) jumps where the final jump target is determined by the source of the first jump
/// (because we know that the conditionals for all jumps evaluate to the same value along the sequence).
/// For such a sequence we then retarget the destination of the first jump to the final jump destination of the sequence.
/// Lastly, the newly bypassed blocks are considered dead code and are removed.
/// See the module-level documentation for more information on what this pass
/// does.
pub fn propagate_control_flow(project: &mut Project) {
let cfg_before_normalization = graph::get_program_cfg(&project.program);
let nodes_without_incoming_edges_at_beginning =
Expand All @@ -30,6 +42,8 @@ pub fn propagate_control_flow(project: &mut Project) {
// Conditions that we know to be true "on" a particular outgoing
// edge.
let mut true_conditions = Vec::new();
// Check if some condition must be true at the beginning of the block,
// and still holds after all DEFs are executed.
if let Some(block_precondition) =
get_block_precondition_after_defs(&cfg_before_normalization, node)
{
Expand Down Expand Up @@ -123,7 +137,8 @@ pub fn propagate_control_flow(project: &mut Project) {
);
}

/// Insert the new target TIDs into jump instructions for which a new target was computed.
/// Inserts the new target TIDs into jump instructions for which a new target
/// was computed.
fn retarget_jumps(project: &mut Project, mut jmps_to_retarget: HashMap<Tid, Tid>) {
for sub in project.program.term.subs.values_mut() {
for blk in sub.term.blocks.iter_mut() {
Expand Down Expand Up @@ -153,10 +168,14 @@ fn retarget_jumps(project: &mut Project, mut jmps_to_retarget: HashMap<Tid, Tid>
}
}

/// Under the assumption that the given `true_condition` expression evaluates to `true`,
/// check whether we can retarget jumps to the given target to another final jump target.
/// I.e. we follow sequences of jumps that are not interrupted by [`Def`] instructions to their final jump target
/// using the `true_condition` to resolve the targets of conditional jumps if possible.
/// Under the assumption that the given `true_conditions` expressions all
/// evaluate to `true`, check whether we can retarget jumps to the given target
/// to another final jump target.
///
/// In other words, we follow sequences of jumps that are not interrupted by
/// [`Def`] instructions (or other things that may have side-effects) to their
/// final jump target using the `true_condition` to resolve the targets of
/// conditional jumps if possible.
fn find_target_for_retargetable_jump(
target: &Tid,
sub: &Sub,
Expand All @@ -171,7 +190,8 @@ fn find_target_for_retargetable_jump(
};

if !visited_tids.insert(retarget.clone()) {
// The target was already visited, so we abort the search to avoid infinite loops.
// The target was already visited, so we abort the search to avoid
// infinite loops.
break;
}

Expand All @@ -186,9 +206,10 @@ fn find_target_for_retargetable_jump(
}

/// Check whether the given block does not contain any [`Def`] instructions.
/// If yes, check whether the target of the jump at the end of the block is predictable
/// under the assumption that the given `true_condition` expression evaluates to true.
/// If it can be predicted, return the target of the jump.
/// If yes, check whether the target of the jump at the end of the block is
/// predictable under the assumption that the given `true_conditions`
/// expressions all evaluate to true. If it can be predicted, return the target
/// of the jump.
fn check_for_retargetable_block<'a>(
block: &'a Term<Blk>,
true_conditions: &[Expression],
Expand Down Expand Up @@ -286,12 +307,14 @@ fn get_block_precondition_after_defs(cfg: &Graph, node: NodeIndex) -> Option<Exp
};

if block.tid == sub.term.blocks[0].tid {
// Function start blocks always have incoming caller edges
// even if these edges are missing in the CFG because we do not know the callers.
// Function start blocks always have incoming caller edges, even if
// these edges are missing in the CFG because we do not know the
// callers.
return None;
}

// Check whether we know the result of a conditional at the start of the block
// Check whether we know the result of a conditional at the start of the
// block.
let block_precondition = get_precondition_from_incoming_edges(cfg, node)?;

// If we have a known conditional result at the start of the block,
Expand All @@ -311,7 +334,8 @@ fn get_block_precondition_after_defs(cfg: &Graph, node: NodeIndex) -> Option<Exp
Some(block_precondition)
}

/// Negate the given boolean condition expression, removing double negations in the process.
/// Negate the given boolean condition expression, removing double negations in
/// the process.
fn negate_condition(expr: Expression) -> Expression {
if let Expression::UnOp {
op: UnOpType::BoolNegate,
Expand All @@ -327,7 +351,7 @@ fn negate_condition(expr: Expression) -> Expression {
}
}

/// Iterates the CFG and returns all node's blocks, that do not have an incoming edge.
/// Iterates the CFG and returns all node that do not have an incoming edge.
fn get_nodes_without_incoming_edge(cfg: &Graph) -> HashSet<Tid> {
cfg.node_indices()
.filter_map(|node| {
Expand All @@ -340,7 +364,8 @@ fn get_nodes_without_incoming_edge(cfg: &Graph) -> HashSet<Tid> {
.collect()
}

/// Calculates the difference of the orphaned blocks and removes them from the project.
/// Calculates the difference of the orphaned blocks and removes them from the
/// project.
fn remove_new_orphaned_blocks(
project: &mut Project,
orphaned_blocks_before: HashSet<Tid>,
Expand Down

0 comments on commit 31211b2

Please sign in to comment.