4
use forget_estree::{BinaryOperator, JsValue};
5
use forget_hir::{
6
initialize_hir, merge_consecutive_blocks, BlockKind, Environment, Function, GotoKind,
7
- GotoTerminal, IdentifierId, Instruction, InstructionValue, LoadGlobal, Operand, Primitive,
8
- TerminalValue,
7
+ GotoTerminal, IdentifierId, InstructionValue, LoadGlobal, Primitive, TerminalValue,
8
};
9
use forget_ssa::eliminate_redundant_phis;
10
11
pub fn constant_propagation(env: &Environment, fun: &mut Function) -> Result<(), Diagnostic> {
13
- let mut constants = Constants::new();
12
+ let mut constants = Constants::default();
13
constant_propagation_impl(env, fun, &mut constants)
14
}
15
62
for phi in block.phis.iter() {
63
let mut value: Option<Constant> = None;
64
for (_, operand) in &phi.operands {
66
- if let Some(operand_value) = constants.get(&operand.id) {
65
+ if let Some(operand_value) = constants.get(operand.id) {
66
match &mut value {
67
Some(value) if value == operand_value => {
68
// no-op
92
continue;
93
}
94
let instr_ix = usize::from(*instr_ix);
96
- let mut instr = std::mem::replace(
95
+ let lvalue_id = fun.body.instructions[instr_ix].lvalue.identifier.id;
96
+ let mut value = std::mem::replace(
97
&mut fun.body.instructions[instr_ix].value,
98
InstructionValue::Tombstone,
99
);
100
- evaluate_instruction(env, &fun.body.instructions, &mut instr, constants)?;
101
- fun.body.instructions[instr_ix].value = instr;
100
+ let const_value = evaluate_instruction(env, &mut value, constants)?;
101
+ if let Some(const_value) = const_value {
102
+ constants.insert(lvalue_id, const_value);
103
+ }
104
+ fun.body.instructions[instr_ix].value = value;
105
}
106
107
// If the block ends in an `if` and the test value is a constant primitive,
108
// then convert the terminal into a goto to either the consequent or alternate
109
// in this case, only the selected branch is reachable
110
if let TerminalValue::If(terminal) = &mut block.terminal.value {
108
- if let Some(primitive) =
109
- read_primitive_instruction(&fun.body.instructions, &terminal.test)
110
- {
111
+ if let Some(primitive) = constants.get_primitive(terminal.test.identifier.id) {
112
let target_block_id = if primitive.value.is_truthy() {
113
terminal.consequent
114
} else {
126
Ok(has_changes)
127
}
128
128
-fn read_primitive_instruction(instrs: &[Instruction], operand: &Operand) -> Option<Primitive> {
129
- let instr = &instrs[usize::from(operand.ix)].value;
130
- if let InstructionValue::Primitive(primitive) = instr {
131
- Some(primitive.clone())
132
- } else {
133
- None
134
- }
135
-}
136
-
129
fn evaluate_instruction(
130
env: &Environment,
139
- instrs: &[Instruction],
131
mut instr: &mut InstructionValue,
132
constants: &mut Constants,
142
-) -> Result<(), Diagnostic> {
143
- let read_constant = |operand: &Operand| {
144
- let instr = &instrs[usize::from(operand.ix)].value;
145
- match instr {
146
- InstructionValue::Primitive(value) => Some(Constant::Primitive(value.clone())),
147
- InstructionValue::LoadGlobal(value) => Some(Constant::Global(value.clone())),
148
- _ => None,
149
- }
150
- };
133
+) -> Result<Option<Constant>, Diagnostic> {
134
match &mut instr {
135
+ InstructionValue::Primitive(value) => Ok(Some(Constant::Primitive(value.clone()))),
136
+ InstructionValue::LoadGlobal(value) => Ok(Some(Constant::Global(value.clone()))),
137
InstructionValue::Binary(value) => {
153
- let left = read_primitive_instruction(instrs, &value.left);
154
- let right = read_primitive_instruction(instrs, &value.right);
138
+ let left = constants.get_primitive(value.left.identifier.id);
139
+ let right = constants.get_primitive(value.right.identifier.id);
140
match (left, right) {
141
(Some(left), Some(right)) => {
157
- if let Some(result) = apply_binary_operator(env, left, value.operator, right) {
158
- *instr = InstructionValue::Primitive(result);
142
+ if let Some(result) =
143
+ apply_binary_operator(env, &left.value, value.operator, &right.value)
144
+ {
145
+ *instr = InstructionValue::Primitive(Primitive {
146
+ value: result.clone(),
147
+ });
148
+ Ok(Some(Constant::Primitive(Primitive { value: result })))
149
+ } else {
150
+ Ok(None)
151
}
152
}
153
_ => {
154
// no-op, not all operands are known
155
+ Ok(None)
156
}
157
}
158
}
159
InstructionValue::LoadLocal(value) => {
167
- if let Some(const_value) = constants.get(&value.place.identifier.id) {
160
+ if let Some(const_value) = constants.get(value.place.identifier.id) {
161
*instr = const_value.into();
162
+ Ok(Some(const_value.clone()))
163
+ } else {
164
+ Ok(None)
165
}
166
}
167
InstructionValue::StoreLocal(value) => {
172
- if let Some(const_value) = read_constant(&value.value) {
173
- constants.insert(value.lvalue.identifier.identifier.id, const_value);
168
+ if let Some(const_value) = constants.get(value.value.identifier.id).cloned() {
169
+ constants.insert(value.lvalue.identifier.identifier.id, const_value.clone());
170
+ Ok(Some(const_value))
171
+ } else {
172
+ Ok(None)
173
}
174
}
175
InstructionValue::Function(value) => {
176
// TODO: due to the outer fixpoint iteration this could visit the same
177
// function many times. However we only strictly have to visit the function
178
// again if the context variable's constant values have changed since last
180
- // time.
181
- // Instead, we can:
182
- // - Create a filtered Constants instance that extracts just the values for
183
- // the function (using its context variables list)
184
- // - Track the last such filtered Constants instance we visited the function
185
- // with. Only visit again if the Constants have changed.
179
+ // time. Improve this by tracking the inner_constants value with which we
180
+ // last visited, and skip visiting if the same
181
let mut inner_constants: Constants = value
182
.lowered_function
183
.context
184
.iter()
185
.filter_map(|id| {
191
- let value = constants.get(&id.identifier.id);
186
+ let value = constants.get(id.identifier.id);
187
value.map(|value| (id.identifier.id, value.clone()))
188
})
189
.collect();
190
constant_propagation_impl(env, &mut value.lowered_function, &mut inner_constants)?;
191
+ Ok(None)
192
}
193
_ => {
194
// no-op, not all instructions can be processed
195
+ Ok(None)
196
}
197
}
201
- Ok(())
198
}
199
200
fn apply_binary_operator(
201
_env: &Environment,
206
- left: Primitive,
202
+ left: &JsValue,
203
operator: BinaryOperator,
208
- right: Primitive,
209
-) -> Option<Primitive> {
210
- match (left.value, right.value) {
204
+ right: &JsValue,
205
+) -> Option<JsValue> {
206
+ match (left, right) {
207
(JsValue::Number(left), JsValue::Number(right)) => match operator {
212
- BinaryOperator::Add => Some(Primitive {
213
- value: JsValue::Number(left + right),
214
- }),
215
- BinaryOperator::Subtract => Some(Primitive {
216
- value: JsValue::Number(left - right),
217
- }),
218
- BinaryOperator::Multiply => Some(Primitive {
219
- value: JsValue::Number(left * right),
220
- }),
221
- BinaryOperator::Divide => Some(Primitive {
222
- value: JsValue::Number(left / right),
223
- }),
224
- BinaryOperator::LessThan => Some(Primitive {
225
- value: JsValue::Boolean(left < right),
226
- }),
227
- BinaryOperator::LessThanOrEqual => Some(Primitive {
228
- value: JsValue::Boolean(left <= right),
229
- }),
230
- BinaryOperator::GreaterThan => Some(Primitive {
231
- value: JsValue::Boolean(left > right),
232
- }),
233
- BinaryOperator::GreaterThanOrEqual => Some(Primitive {
234
- value: JsValue::Boolean(left >= right),
235
- }),
236
- BinaryOperator::Equals => Some(Primitive {
237
- value: JsValue::Boolean(left.equals(right)),
238
- }),
239
- BinaryOperator::NotEquals => Some(Primitive {
240
- value: JsValue::Boolean(left.not_equals(right)),
241
- }),
242
- BinaryOperator::StrictEquals => Some(Primitive {
243
- value: JsValue::Boolean(left.equals(right)),
244
- }),
245
- BinaryOperator::NotStrictEquals => Some(Primitive {
246
- value: JsValue::Boolean(left.not_equals(right)),
247
- }),
208
+ BinaryOperator::Add => Some(JsValue::Number(*left + *right)),
209
+ BinaryOperator::Subtract => Some(JsValue::Number(*left - *right)),
210
+ BinaryOperator::Multiply => Some(JsValue::Number(*left * *right)),
211
+ BinaryOperator::Divide => Some(JsValue::Number(*left / *right)),
212
+ BinaryOperator::LessThan => Some(JsValue::Boolean(*left < *right)),
213
+ BinaryOperator::LessThanOrEqual => Some(JsValue::Boolean(*left <= *right)),
214
+ BinaryOperator::GreaterThan => Some(JsValue::Boolean(*left > *right)),
215
+ BinaryOperator::GreaterThanOrEqual => Some(JsValue::Boolean(*left >= *right)),
216
+ BinaryOperator::Equals => Some(JsValue::Boolean(left.equals(*right))),
217
+ BinaryOperator::NotEquals => Some(JsValue::Boolean(left.not_equals(*right))),
218
+ BinaryOperator::StrictEquals => Some(JsValue::Boolean(left.equals(*right))),
219
+ BinaryOperator::NotStrictEquals => Some(JsValue::Boolean(left.not_equals(*right))),
220
_ => None,
221
},
222
(left, right) => match operator {
251
- BinaryOperator::Equals => left.loosely_equals(&right).map(|value| Primitive {
252
- value: JsValue::Boolean(value),
253
- }),
254
- BinaryOperator::NotEquals => left.not_loosely_equals(&right).map(|value| Primitive {
255
- value: JsValue::Boolean(value),
256
- }),
257
- BinaryOperator::StrictEquals => Some(Primitive {
258
- value: JsValue::Boolean(left.strictly_equals(&right)),
259
- }),
260
- BinaryOperator::NotStrictEquals => Some(Primitive {
261
- value: JsValue::Boolean(left.not_strictly_equals(&right)),
262
- }),
223
+ BinaryOperator::Equals => left
224
+ .loosely_equals(&right)
225
+ .map(|value| JsValue::Boolean(value)),
226
+ BinaryOperator::NotEquals => left
227
+ .not_loosely_equals(&right)
228
+ .map(|value| JsValue::Boolean(value)),
229
+ BinaryOperator::StrictEquals => Some(JsValue::Boolean(left.strictly_equals(&right))),
230
+ BinaryOperator::NotStrictEquals => {
231
+ Some(JsValue::Boolean(left.not_strictly_equals(&right)))
232
+ }
233
_ => None,
234
},
235
}
236
}
237
268
-type Constants = HashMap<IdentifierId, Constant>;
238
+#[derive(Default)]
239
+struct Constants {
240
+ data: HashMap<IdentifierId, Constant>,
241
+}
242
+
243
+impl Constants {
244
+ fn get_primitive(&self, id: IdentifierId) -> Option<&Primitive> {
245
+ if let Some(Constant::Primitive(primitive)) = &self.data.get(&id) {
246
+ Some(primitive)
247
+ } else {
248
+ None
249
+ }
250
+ }
251
+
252
+ fn get(&self, id: IdentifierId) -> Option<&Constant> {
253
+ self.data.get(&id)
254
+ }
255
+
256
+ fn insert(&mut self, id: IdentifierId, constant: Constant) {
257
+ self.data.insert(id, constant);
258
+ }
259
+}
260
+
261
+impl FromIterator<(IdentifierId, Constant)> for Constants {
262
+ fn from_iter<T: IntoIterator<Item = (IdentifierId, Constant)>>(iter: T) -> Self {
263
+ Self {
264
+ data: FromIterator::from_iter(iter),
265
+ }
266
+ }
267
+}
268
269
#[derive(Debug, Clone, PartialEq, Eq)]
270
enum Constant {