Skip to content
Snippets Groups Projects
gvn.rs 4.11 KiB
use std::collections::HashMap;

use hercules_ir::ir::*;

use crate::*;

/*
 * Top level function to run global value numbering. In the sea of nodes, GVN is
 * fairly simple compared to in a normal CFG. Needs access to constants for
 * identity function simplification.
 */
pub fn gvn(editor: &mut FunctionEditor, gvn_constants_and_clones: bool) {
    // Create worklist (starts as all nodes) and value number hashmap.
    let mut worklist: Vec<NodeID> = (0..editor.func().nodes.len()).map(NodeID::new).collect();
    let mut value_numbers: HashMap<Node, NodeID> = HashMap::new();

    while let Some(work) = worklist.pop() {
        // Deleted nodes (and technically the original start) should be skipped.
        if editor.func().nodes[work.idx()].is_start() {
            continue;
        }

        // First, simplify the work node by unwrapping identity functions.
        let value = crawl_identities(work, editor);

        // Next, check if there is a value number for this simplified value yet.
        if let Some(number) = value_numbers.get(&editor.func().nodes[value.idx()]) {
            // If the number is this worklist item, there's nothing to be done.
            // Also, don't GVN constants and clones if indicated to not do so.
            if *number == work
                || (!gvn_constants_and_clones
                    && (editor.func().nodes[work.idx()]
                        .try_constant()
                        .map(|cons_id| !editor.get_constant(cons_id).is_scalar())
                        .unwrap_or(false)
                        || editor.func().nodes[work.idx()]
                            .try_write()
                            .map(|(_, _, indices)| indices.is_empty())
                            .unwrap_or(false)))
            {
                continue;
            }

            // Record the users of `work` before making any edits.
            let work_users: Vec<NodeID> = editor.get_users(work).collect();

            // At this point, we know the number of the node IDed `work` is
            // `number`. We want to replace `work` with `number`, which means
            // 1. replacing all uses of `work` with `number`
            // 2. deleting `work`
            let success = editor.edit(|mut edit| {
                edit.sub_edit(work, *number);
                let edit = edit.replace_all_uses(work, *number)?;
                edit.delete_node(work)
            });

            // If the edit was performed, then the old users of `work` may now
            // be congruent to other nodes.
            if success {
                worklist.extend(work_users);
            }
        } else {
            // If there isn't a number yet for this value, assign the value the
            // ID of the node as its number.
            value_numbers.insert(editor.func().nodes[value.idx()].clone(), value);
            // The crawled identity `value` may be different than `work` - add
            // `work` back on to the worklist so that in a subsequent iteration,
            // we can simplify it.
            worklist.push(work);
        }
    }
}

/*
 * Helper function for unwrapping identity functions.
 */
fn crawl_identities(mut work: NodeID, editor: &FunctionEditor) -> NodeID {
    loop {
        let func = editor.func();
        // TODO: replace with API for saner pattern matching on IR. Also,
        // actually add the rest of the identity funcs.
        if let Node::Binary {
            left,
            right,
            op: BinaryOperator::Add,
        } = func.nodes[work.idx()]
        {
            if let Node::Constant { id } = func.nodes[left.idx()] {
                if editor.get_constant(id).is_zero() {
                    work = right;
                    continue;
                }
            }
        }

        if let Node::Binary {
            left,
            right,
            op: BinaryOperator::Add,
        } = func.nodes[work.idx()]
        {
            if let Node::Constant { id } = func.nodes[right.idx()] {
                if editor.get_constant(id).is_zero() {
                    work = left;
                    continue;
                }
            }
        }

        return work;
    }
}