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
//! Optimization passes for manipulating constant values.
//!
//! - combining - compile time evaluation of constant expressions.
//!   - combine insert_values - reduce expressions which insert a constant value into a constant
//!     struct.

use crate::{
    constant::{Constant, ConstantValue},
    context::Context,
    error::IrError,
    function::Function,
    instruction::Instruction,
    value::{Value, ValueContent, ValueDatum},
    Predicate,
};

/// Find constant expressions which can be reduced to fewer opterations.
pub fn combine_constants(context: &mut Context, function: &Function) -> Result<bool, IrError> {
    let mut modified = false;
    loop {
        if combine_const_insert_values(context, function) {
            modified = true;
            continue;
        }

        if combine_cmp(context, function) {
            modified = true;
            continue;
        }

        if combine_cbr(context, function)? {
            modified = true;
            continue;
        }

        // Other passes here... always continue to the top if pass returns true.
        break;
    }

    Ok(modified)
}

fn combine_cbr(context: &mut Context, function: &Function) -> Result<bool, IrError> {
    let candidate = function
        .instruction_iter(context)
        .find_map(
            |(in_block, inst_val)| match &context.values[inst_val.0].value {
                ValueDatum::Instruction(Instruction::ConditionalBranch {
                    cond_value,
                    true_block,
                    false_block,
                }) if cond_value.is_constant(context) => {
                    match cond_value.get_constant(context).unwrap().value {
                        ConstantValue::Bool(true) => {
                            Some(Ok((inst_val, in_block, *true_block, *false_block)))
                        }
                        ConstantValue::Bool(false) => {
                            Some(Ok((inst_val, in_block, *false_block, *true_block)))
                        }
                        _ => Some(Err(IrError::VerifyConditionExprNotABool)),
                    }
                }
                _ => None,
            },
        )
        .transpose()?;

    candidate.map_or(Ok(false), |(cbr, from_block, dest, no_more_dest)| {
        no_more_dest.remove_phi_val_coming_from(context, &from_block);
        cbr.replace(context, ValueDatum::Instruction(Instruction::Branch(dest)));
        Ok(true)
    })
}

fn combine_cmp(context: &mut Context, function: &Function) -> bool {
    let candidate = function
        .instruction_iter(context)
        .find_map(
            |(block, inst_val)| match &context.values[inst_val.0].value {
                ValueDatum::Instruction(Instruction::Cmp(pred, val1, val2))
                    if val1.is_constant(context) && val2.is_constant(context) =>
                {
                    let val1 = val1.get_constant(context).unwrap();
                    let val2 = val2.get_constant(context).unwrap();
                    match pred {
                        Predicate::Equal => {
                            if val1.eq(context, val2) {
                                Some((inst_val, block, true))
                            } else {
                                Some((inst_val, block, false))
                            }
                        }
                    }
                }
                _ => None,
            },
        );

    candidate.map_or(false, |(inst_val, block, cn_replace)| {
        // Replace this `cmp` instruction with a constant.
        inst_val.replace(
            context,
            ValueDatum::Constant(Constant::new_bool(cn_replace)),
        );
        block.remove_instruction(context, inst_val);
        true
    })
}

fn combine_const_insert_values(context: &mut Context, function: &Function) -> bool {
    // Find a candidate `insert_value` instruction.
    let candidate = function
        .instruction_iter(context)
        .find_map(|(block, ins_val)| {
            match &context.values[ins_val.0].value {
                // We only want inject this constant value into a constant aggregate declaration,
                // not another `insert_value` instruction.
                //
                // We *could* trace back to the original aggregate through other `insert_value`s
                // but we'd have to be careful that this constant value isn't clobbered by the
                // chain.  It's simpler to just combine the instruction which modifies the
                // aggregate directly and then to iterate.
                ValueDatum::Instruction(Instruction::InsertValue {
                    aggregate,
                    ty: _,
                    value,
                    indices,
                }) if value.is_constant(context)
                    && matches!(
                        &context.values[aggregate.0].value,
                        ValueDatum::Constant(Constant {
                            value: ConstantValue::Struct(_),
                            ..
                        }),
                    ) =>
                {
                    Some((block, ins_val, *aggregate, *value, indices.clone()))
                }
                _otherwise => None,
            }
        });

    if let Some((block, ins_val, aggregate, const_val, indices)) = candidate {
        // OK, here we have an `insert_value` of a constant directly into a constant aggregate.  We
        // want to replace the constant aggregate with an updated one.
        let new_aggregate = combine_const_aggregate_field(context, aggregate, const_val, &indices);

        // Replace uses of the `insert_value` instruction with the new aggregate.
        function.replace_value(context, ins_val, new_aggregate, None);

        // Remove the `insert_value` instruction.
        block.remove_instruction(context, ins_val);

        // Let's return now, since our iterator may get confused and let the pass iterate further
        // itself.
        return true;
    }

    false
}

fn combine_const_aggregate_field(
    context: &mut Context,
    aggregate: Value,
    const_value: Value,
    indices: &[u64],
) -> Value {
    // Create a copy of the aggregate constant and inserted value.
    let (mut new_aggregate, metadata) = match &context.values[aggregate.0] {
        ValueContent {
            value: ValueDatum::Constant(c),
            metadata,
        } => (c.clone(), *metadata),
        _otherwise => {
            unreachable!("BUG! Invalid aggregate parameter to combine_const_insert_value()")
        }
    };
    let const_value = match &context.values[const_value.0].value {
        ValueDatum::Constant(c) => c.clone(),
        _otherwise => {
            unreachable!("BUG! Invalid const_value parameter to combine_const_insert_value()")
        }
    };

    // Update the new aggregate with the constant field, based in the indices.
    inject_constant_into_aggregate(&mut new_aggregate, const_value, indices);

    // NOTE: Previous versions of this pass were trying to clean up after themselves, by replacing
    // the old aggregate with this new one, and/or removing the old aggregate altogether.  This is
    // too dangerous without proper checking for remaining uses, and is best left to DCE anyway.

    Value::new_constant(context, new_aggregate).add_metadatum(context, metadata)
}

fn inject_constant_into_aggregate(aggregate: &mut Constant, value: Constant, indices: &[u64]) {
    if indices.is_empty() {
        *aggregate = value;
    } else {
        match &mut aggregate.value {
            ConstantValue::Struct(fields) => inject_constant_into_aggregate(
                &mut fields[indices[0] as usize],
                value,
                &indices[1..],
            ),
            _otherwise => {
                unreachable!("Bug! Invalid aggregate parameter to inject_constant_into_aggregate()")
            }
        }
    }
}