waffle/backend/
treeify.rs

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
//! Treeification: placing some values "under" others if only used
//! once, to generate more AST-like Wasm code.

use crate::entity::EntityRef;
use crate::ir::{FunctionBody, Value, ValueDef};
use crate::Operator;
use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::convert::TryFrom;

/// One "argument slot" of an operator defining a value.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ValueArg(Value, u16);

#[derive(Clone, Debug)]
pub struct Trees {
    /// Is a value placed "under" the given arg slot of the given
    /// other value?
    pub owner: HashMap<Value, ValueArg>,
    /// For a given value that is defined by an operator, which
    /// Values, if any, live at each slot?
    pub owned: HashMap<ValueArg, Value>,
    /// Values that are regenerated every time they are used.
    pub remat: HashSet<Value>,
}

fn is_remat(op: &Operator) -> bool {
    // Only ops with no args can be always-rematerialized.
    match op {
        Operator::I32Const { .. }
        | Operator::I64Const { .. }
        | Operator::F32Const { .. }
        | Operator::F64Const { .. } => true,
        _ => false,
    }
}

impl Trees {
    pub fn compute(body: &FunctionBody) -> Trees {
        let mut owner = HashMap::default();
        let mut owned = HashMap::default();
        let mut remat = HashSet::default();
        let mut multi_use = HashSet::default();

        for block_def in body.blocks.values() {
            let mut last_non_pure = None;
            for &value in &block_def.insts {
                match &body.values[value] {
                    &ValueDef::Operator(op, args, _) => {
                        // Ignore operators with invalid args: these must
                        // always be unreachable.
                        if body.arg_pool[args].iter().any(|arg| arg.is_invalid()) {
                            continue;
                        }
                        // If this is an always-rematerialized operator,
                        // mark it as such and continue.
                        if is_remat(&op) {
                            remat.insert(value);
                            continue;
                        }

                        // For each of the args, if the value is produced
                        // by a single-output op and is movable, and is
                        // not already recorded in `multi_use`, place it
                        // in the arg slot. Otherwise if owned already
                        // somewhere else, undo that and put in
                        // `multi_use`.
                        for (i, &arg) in body.arg_pool[args].iter().enumerate() {
                            let arg = body.resolve_alias(arg);
                            if multi_use.contains(&arg) {
                                continue;
                            } else if let Some(old_owner) = owner.remove(&arg) {
                                owned.remove(&old_owner);
                                multi_use.insert(arg);
                            } else if Self::is_movable(body, arg) || Some(arg) == last_non_pure {
                                let pos = u16::try_from(i).unwrap();
                                let value_arg = ValueArg(value, pos);
                                owner.insert(arg, value_arg);
                                owned.insert(value_arg, arg);
                            }
                        }

                        if !op.is_pure() {
                            last_non_pure = Some(value);
                        }
                    }
                    &ValueDef::PickOutput(..) => {
                        // Can ignore use: multi-arity values are never treeified.
                    }
                    &ValueDef::BlockParam(..)
                    | &ValueDef::Alias(..)
                    | &ValueDef::Placeholder(..)
                    | &ValueDef::None => {}
                }
            }
        }
        for block in body.blocks.values() {
            block.terminator.visit_uses(|u| {
                let u = body.resolve_alias(u);
                if let Some(old_owner) = owner.remove(&u) {
                    owned.remove(&old_owner);
                }
            });
        }

        Trees {
            owner,
            owned,
            remat,
        }
    }

    fn is_single_output_op(body: &FunctionBody, value: Value) -> Option<Operator> {
        match &body.values[value] {
            &ValueDef::Operator(op, _, ref tys) if tys.len() == 1 => Some(op),
            _ => None,
        }
    }

    fn is_movable(body: &FunctionBody, value: Value) -> bool {
        Self::is_single_output_op(body, value)
            .map(|op| op.is_pure())
            .unwrap_or(false)
    }
}