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
//! 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},
};

/// 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;
        }

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

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, function, 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,
    function: &Function,
    aggregate: Value,
    const_value: Value,
    indices: &[u64],
) -> Value {
    // Create a copy of the aggregate constant and inserted value.
    let (mut new_aggregate, span_md_idx) = match &context.values[aggregate.0] {
        ValueContent {
            value: ValueDatum::Constant(c),
            span_md_idx,
        } => (c.clone(), *span_md_idx),
        _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);

    // Replace the old aggregate with the new aggregate.
    let new_aggregate_value = Value::new_constant(context, new_aggregate, span_md_idx);
    function.replace_value(context, aggregate, new_aggregate_value, None);

    // Remove the old aggregate from the context.
    //
    // OR NOT!  This is too dangerous unless we can
    // guarantee it has no uses, which is something we should implement eventually.  For now, in
    // this case it shouldn't matter if we leave it, even if it's not used.
    //
    // TODO: context.values.remove(aggregate.0);

    new_aggregate_value
}

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()")
            }
        }
    }
}