1
+use std::collections::HashMap;
2
+
3
+use build_hir::initialize_hir;
4
+use estree::BinaryOperator;
5
+use hir::{
6
+ BlockKind, Environment, Function, GotoKind, IdentifierId, Instruction, InstructionValue,
7
+ LoadGlobal, Operand, Primitive, PrimitiveValue, TerminalValue,
8
+};
9
+use hir_ssa::eliminate_redundant_phis;
10
+
11
+pub fn constant_propagation<'a>(env: &Environment<'a>, fun: &mut Function<'a>) {
12
+ let mut constants = Constants::new();
13
+ let have_terminals_changed = apply_constant_propagation(env, fun, &mut constants);
14
+ if have_terminals_changed {
15
+ // If terminals have changed then blocks may have become newly unreachable,
16
+ // so reinitialize the HIR
17
+ // TODO handle errors
18
+ initialize_hir(&mut fun.body).unwrap();
19
+
20
+ // Now that predecessors have changed, prune phi operands for unreachable blocks
21
+ // for example, a phi node whose operand was eliminated because it was set in a
22
+ // block that is no longer reached
23
+ for (_, block) in fun.body.blocks.iter_mut() {
24
+ // TODO: avoid the clone here
25
+ let predecessors = block.predecessors.clone();
26
+ for phi in block.phis.iter_mut() {
27
+ phi.operands
28
+ .retain(|predecessor, _| predecessors.contains(predecessor))
29
+ }
30
+ }
31
+
32
+ // By removing some phi operands, there may be phis that were not previously
33
+ // redundant but now are
34
+ eliminate_redundant_phis(env, fun);
35
+ }
36
+}
37
+
38
+fn apply_constant_propagation<'a>(
39
+ env: &Environment<'a>,
40
+ fun: &mut Function<'a>,
41
+ constants: &mut Constants<'a>,
42
+) -> bool {
43
+ let mut has_changes = false;
44
+
45
+ for (_, block) in fun.body.blocks.iter_mut() {
46
+ for phi in block.phis.iter() {
47
+ let mut value: Option<Constant<'a>> = None;
48
+ for (_, operand) in &phi.operands {
49
+ if let Some(operand_value) = constants.get(&operand.id) {
50
+ match &mut value {
51
+ Some(value) if value == operand_value => {
52
+ // no-op
53
+ }
54
+ Some(_) => {
55
+ value = None;
56
+ break;
57
+ }
58
+ None => {
59
+ value = Some(operand_value.clone());
60
+ }
61
+ }
62
+ } else {
63
+ // This phi operand's value is unknown, bail out of replacing it
64
+ value = None;
65
+ break;
66
+ }
67
+ }
68
+ if let Some(value) = value {
69
+ constants.insert(phi.identifier.id, value);
70
+ }
71
+ }
72
+ for (ix, instr_ix) in block.instructions.iter().enumerate() {
73
+ if block.kind == BlockKind::Sequence && ix == block.instructions.len() - 1 {
74
+ // Evaluating the last value of a sequence can break order of evaluation
75
+ // so skip these instructions
76
+ continue;
77
+ }
78
+ let instr_ix = usize::from(*instr_ix);
79
+ let mut instr = std::mem::replace(
80
+ &mut fun.body.instructions[instr_ix].value,
81
+ InstructionValue::Tombstone,
82
+ );
83
+ evaluate_instruction(env, &fun.body.instructions, &mut instr, constants);
84
+ fun.body.instructions[instr_ix].value = instr;
85
+ }
86
+
87
+ if block.kind != BlockKind::Block {
88
+ // can't rewrite terminals in value blocks yet
89
+ continue;
90
+ }
91
+
92
+ if let TerminalValue::If(terminal) = &mut block.terminal.value {
93
+ if let Some(primitive) =
94
+ read_primitive_instruction(&fun.body.instructions, &terminal.test)
95
+ {
96
+ let target_block_id = if primitive.value.is_truthy() {
97
+ terminal.consequent
98
+ } else {
99
+ terminal.alternate
100
+ };
101
+ block.terminal.value = TerminalValue::Goto(hir::GotoTerminal {
102
+ block: target_block_id,
103
+ kind: GotoKind::Break,
104
+ });
105
+ has_changes = true;
106
+ }
107
+ }
108
+ }
109
+
110
+ has_changes
111
+}
112
+
113
+fn read_primitive_instruction<'a>(
114
+ instrs: &[Instruction<'a>],
115
+ operand: &Operand,
116
+) -> Option<Primitive<'a>> {
117
+ let instr = &instrs[usize::from(operand.ix)].value;
118
+ if let InstructionValue::Primitive(primitive) = instr {
119
+ Some(primitive.clone())
120
+ } else {
121
+ None
122
+ }
123
+}
124
+
125
+fn evaluate_instruction<'a>(
126
+ env: &Environment<'a>,
127
+ instrs: &[Instruction<'a>],
128
+ mut instr: &mut InstructionValue<'a>,
129
+ constants: &mut Constants<'a>,
130
+) {
131
+ let read_constant = |operand: &Operand| {
132
+ let instr = &instrs[usize::from(operand.ix)].value;
133
+ match instr {
134
+ InstructionValue::Primitive(value) => Some(Constant::Primitive(value.clone())),
135
+ InstructionValue::LoadGlobal(value) => Some(Constant::Global(value.clone())),
136
+ _ => None,
137
+ }
138
+ };
139
+ match &mut instr {
140
+ InstructionValue::Binary(value) => {
141
+ let left = read_primitive_instruction(instrs, &value.left);
142
+ let right = read_primitive_instruction(instrs, &value.right);
143
+ match (left, right) {
144
+ (Some(left), Some(right)) => {
145
+ if let Some(result) = apply_binary_operator(env, left, value.operator, right) {
146
+ *instr = InstructionValue::Primitive(result);
147
+ }
148
+ }
149
+ _ => {
150
+ // no-op, not all operands are known
151
+ }
152
+ }
153
+ }
154
+ InstructionValue::LoadLocal(value) => {
155
+ if let Some(const_value) = constants.get(&value.place.identifier.id) {
156
+ *instr = const_value.into();
157
+ }
158
+ }
159
+ InstructionValue::StoreLocal(value) => {
160
+ if let Some(const_value) = read_constant(&value.value) {
161
+ constants.insert(value.lvalue.identifier.identifier.id, const_value);
162
+ }
163
+ }
164
+ _ => {
165
+ // no-op, not all instructions can be processed
166
+ }
167
+ }
168
+}
169
+
170
+fn apply_binary_operator<'a>(
171
+ env: &Environment<'a>,
172
+ left: Primitive<'a>,
173
+ operator: BinaryOperator,
174
+ right: Primitive<'a>,
175
+) -> Option<Primitive<'a>> {
176
+ match (left.value, right.value) {
177
+ (PrimitiveValue::Number(left), PrimitiveValue::Number(right)) => match operator {
178
+ BinaryOperator::Add => Some(Primitive {
179
+ value: PrimitiveValue::Number(left + right),
180
+ }),
181
+ BinaryOperator::Subtract => Some(Primitive {
182
+ value: PrimitiveValue::Number(left - right),
183
+ }),
184
+ BinaryOperator::Multiply => Some(Primitive {
185
+ value: PrimitiveValue::Number(left * right),
186
+ }),
187
+ BinaryOperator::Divide => Some(Primitive {
188
+ value: PrimitiveValue::Number(left / right),
189
+ }),
190
+ BinaryOperator::LessThan => Some(Primitive {
191
+ value: PrimitiveValue::Boolean(left < right),
192
+ }),
193
+ BinaryOperator::LessThanOrEqual => Some(Primitive {
194
+ value: PrimitiveValue::Boolean(left <= right),
195
+ }),
196
+ BinaryOperator::GreaterThan => Some(Primitive {
197
+ value: PrimitiveValue::Boolean(left > right),
198
+ }),
199
+ BinaryOperator::GreaterThanOrEqual => Some(Primitive {
200
+ value: PrimitiveValue::Boolean(left >= right),
201
+ }),
202
+ BinaryOperator::Equals => Some(Primitive {
203
+ value: PrimitiveValue::Boolean(left.equals(right)),
204
+ }),
205
+ BinaryOperator::NotEquals => Some(Primitive {
206
+ value: PrimitiveValue::Boolean(left.not_equals(right)),
207
+ }),
208
+ BinaryOperator::StrictEquals => Some(Primitive {
209
+ value: PrimitiveValue::Boolean(left.equals(right)),
210
+ }),
211
+ BinaryOperator::NotStrictEquals => Some(Primitive {
212
+ value: PrimitiveValue::Boolean(left.not_equals(right)),
213
+ }),
214
+ _ => None,
215
+ },
216
+ (left, right) => match operator {
217
+ BinaryOperator::Equals => left.loosely_equals(&right).map(|value| Primitive {
218
+ value: PrimitiveValue::Boolean(value),
219
+ }),
220
+ BinaryOperator::NotEquals => left.not_loosely_equals(&right).map(|value| Primitive {
221
+ value: PrimitiveValue::Boolean(value),
222
+ }),
223
+ BinaryOperator::StrictEquals => Some(Primitive {
224
+ value: PrimitiveValue::Boolean(left.strictly_equals(&right)),
225
+ }),
226
+ BinaryOperator::NotStrictEquals => Some(Primitive {
227
+ value: PrimitiveValue::Boolean(left.not_strictly_equals(&right)),
228
+ }),
229
+ _ => None,
230
+ },
231
+ }
232
+}
233
+
234
+type Constants<'a> = HashMap<IdentifierId, Constant<'a>>;
235
+
236
+#[derive(Debug, Clone, PartialEq, Eq)]
237
+enum Constant<'a> {
238
+ Global(LoadGlobal<'a>),
239
+ Primitive(Primitive<'a>),
240
+}
241
+
242
+impl<'a> From<&Constant<'a>> for InstructionValue<'a> {
243
+ fn from(value: &Constant<'a>) -> Self {
244
+ match value {
245
+ Constant::Global(value) => InstructionValue::LoadGlobal(value.clone()),
246
+ Constant::Primitive(value) => InstructionValue::Primitive(value.clone()),
247
+ }
248
+ }
249
+}
250
+
251
+impl<'a> From<Constant<'a>> for InstructionValue<'a> {
252
+ fn from(value: Constant<'a>) -> Self {
253
+ match value {
254
+ Constant::Global(value) => InstructionValue::LoadGlobal(value),
255
+ Constant::Primitive(value) => InstructionValue::Primitive(value),
256
+ }
257
+ }
258
+}