Skip to content
Snippets Groups Projects
delete_uncalled.rs 2.86 KiB
use bitvec::prelude::*;

use hercules_ir::callgraph::*;
use hercules_ir::ir::*;

use crate::*;

/**
 * First renumber functions by deleting all functions unreachable from any entry function.
 * Then, updates all functions to use the new function numbering.
 * Returns a vec where the element at index i is the new idx of the ith function (if it was not
 * deleted).
 */
pub fn delete_uncalled(
    editors: &mut Vec<FunctionEditor>,
    callgraph: &CallGraph,
) -> Vec<Option<usize>> {
    // Step 1. Identify which functions are not reachable from entry nodes using BFS.
    let mut reachable = bitvec![u8, Lsb0; 0; editors.len()];
    let mut worklist = vec![];
    for (idx, editor) in editors.iter().enumerate() {
        if editor.func().entry {
            worklist.push(idx);
            reachable.set(idx, true);
        }
    }

    while let Some(func_idx) = worklist.pop() {
        for callee in callgraph.get_callees(FunctionID::new(func_idx)) {
            if !reachable[callee.idx()] {
                reachable.set(callee.idx(), true);
                worklist.push(callee.idx());
            }
        }
    }

    // Step 2. Compute the new index of each function, which is obtained by
    // deleteting all unreachable non-entry functions before it
    let mut new_idx = vec![None; editors.len()];
    let mut deleted_count = 0;
    for idx in 0..editors.len() {
        if !reachable[idx] {
            deleted_count += 1;
        } else {
            new_idx[idx] = Some(idx - deleted_count);
        }
    }

    // Step 3. Update all function callsites to use new indices. We assume
    // that all nodes in all functions will be mutable, so panic if any
    // edit fails.
    for editor in editors.iter_mut() {
        // Don't make edits to dead functions as they may include calls to dead functions
        if new_idx[editor.func_id().idx()].is_none() {
            continue;
        }

        let callsites: Vec<_> = editor
            .node_ids()
            .filter(|id| editor.func().nodes[id.idx()].is_call())
            .collect();

        for callsite in callsites {
            let success = editor.edit(|mut edit| {
                let (control, function, dynamic_constants, args) =
                    edit.get_node(callsite).try_call().unwrap();
                let new_node = edit.add_node(Node::Call {
                    control,
                    function: FunctionID::new(new_idx[function.idx()].unwrap()),
                    dynamic_constants: dynamic_constants.clone(),
                    args: args.clone(),
                });
                edit.sub_edit(callsite, new_node);
                let edit = edit.replace_all_uses(callsite, new_node)?;
                edit.delete_node(callsite)
            });
            assert!(
                success,
                "Expected all delete_uncalled callsite edits to succeed!"
            );
        }
    }

    new_idx
}