dpc/passes/opt/simplify/
mir.rs

1use crate::common::condition::Condition;
2use crate::common::mc::instr::MinecraftInstr;
3use crate::common::mc::modifier::{MIRModifier, StoreModLocation};
4use crate::common::ty::{DataTypeContents, NBTTypeContents, ScoreTypeContents};
5use crate::common::val::MutableValue;
6use crate::common::{val::Value, DeclareBinding};
7use crate::mir::{MIRBlock, MIRInstrKind};
8use crate::passes::opt::are_blocks_equivalent;
9use crate::passes::util::RunAgain;
10use crate::passes::{MIRPass, MIRPassData, Pass};
11use crate::project::{OptimizationLevel, ProjectSettings};
12use crate::util::{remove_indices, HashSetEmptyTracker, Only};
13
14use num_traits::Zero;
15
16pub struct MIRSimplifyPass;
17
18impl Pass for MIRSimplifyPass {
19	fn get_name(&self) -> &'static str {
20		"simplify_mir"
21	}
22
23	fn should_run(&self, proj: &ProjectSettings) -> bool {
24		proj.op_level >= OptimizationLevel::Basic
25	}
26}
27
28impl MIRPass for MIRSimplifyPass {
29	fn run_pass(&mut self, data: &mut MIRPassData) -> anyhow::Result<()> {
30		for func in data.mir.functions.values_mut() {
31			simplify_block(&mut func.block, true);
32		}
33
34		Ok(())
35	}
36}
37
38fn simplify_block(block: &mut MIRBlock, is_root: bool) -> RunAgain {
39	let mut out = RunAgain::new();
40	let mut instrs_to_remove = HashSetEmptyTracker::new();
41	loop {
42		let run_again = run_iter(block, &mut instrs_to_remove, is_root);
43		out.merge(run_again);
44		if !run_again {
45			break;
46		}
47	}
48	remove_indices(&mut block.contents, &instrs_to_remove);
49	out
50}
51
52fn run_iter(
53	block: &mut MIRBlock,
54	instrs_to_remove: &mut HashSetEmptyTracker<usize>,
55	is_root: bool,
56) -> RunAgain {
57	let mut run_again = RunAgain::new();
58
59	for (i, instr) in block.contents.iter_mut().enumerate() {
60		let remove = match &instr.kind {
61			// Reflexive property; set or swap with self
62			// and also min and max with self
63			MIRInstrKind::Assign {
64				left,
65				right: DeclareBinding::Value(Value::Mutable(right)),
66			}
67			| MIRInstrKind::Swap { left, right }
68			| MIRInstrKind::Min {
69				left,
70				right: Value::Mutable(right),
71			}
72			| MIRInstrKind::Max {
73				left,
74				right: Value::Mutable(right),
75			} if left.is_same_val(right) => true,
76			// Adds and subs by 0
77			MIRInstrKind::Add {
78				right: Value::Constant(DataTypeContents::Score(val)),
79				..
80			}
81			| MIRInstrKind::Sub {
82				right: Value::Constant(DataTypeContents::Score(val)),
83				..
84			} if val.get_i32() == 0 => true,
85			// Multiplies and divides by 1
86			MIRInstrKind::Mul {
87				left: _,
88				right: Value::Constant(DataTypeContents::Score(score)),
89			}
90			| MIRInstrKind::Div {
91				left: _,
92				right: Value::Constant(DataTypeContents::Score(score)),
93			} if score.get_i32() == 1 => true,
94			// x / 0 and x % 0 error and dont do anything
95			MIRInstrKind::Div {
96				left: _,
97				right: Value::Constant(DataTypeContents::Score(score)),
98			}
99			| MIRInstrKind::Mod {
100				left: _,
101				right: Value::Constant(DataTypeContents::Score(score)),
102			} if score.get_i32() == 0 => true,
103			// x || 0 can be removed
104			MIRInstrKind::Or {
105				left: _,
106				right: Value::Constant(DataTypeContents::Score(score)),
107			} if score.get_i32() == 0 => true,
108			// x && 1 can be removed
109			MIRInstrKind::And {
110				left: _,
111				right: Value::Constant(DataTypeContents::Score(score)),
112			} if score.get_i32() == 1 => true,
113			// And / or with self is identity
114			MIRInstrKind::And {
115				left,
116				right: Value::Mutable(right),
117			}
118			| MIRInstrKind::Or {
119				left,
120				right: Value::Mutable(right),
121			} if left.is_same_val(right) => true,
122			// Different Minecraft add instructions with zero as the amount can be removed
123			MIRInstrKind::MC(MinecraftInstr::AddTime { time }) if time.amount.is_zero() => true,
124			MIRInstrKind::MC(
125				MinecraftInstr::AddXP { amount, .. } | MinecraftInstr::TriggerAdd { amount, .. },
126			) if amount.is_zero() => true,
127			MIRInstrKind::MC(MinecraftInstr::WorldBorderAdd { dist, .. }) if *dist == 0.0 => true,
128			// Merge with empty compound doesn't do anything
129			MIRInstrKind::Merge {
130				right: Value::Constant(DataTypeContents::NBT(NBTTypeContents::Compound(_, comp))),
131				..
132			} if comp.is_empty() => true,
133			// Get instructions without their results stored don't do anything, as long as we are
134			// at the root
135			MIRInstrKind::GetConst { .. }
136			| MIRInstrKind::Get { .. }
137			| MIRInstrKind::MC(
138				MinecraftInstr::GetAttribute { .. }
139				| MinecraftInstr::GetAttributeBase { .. }
140				| MinecraftInstr::GetAttributeModifier { .. }
141				| MinecraftInstr::GetDifficulty
142				| MinecraftInstr::GetGamerule { .. }
143				| MinecraftInstr::GetTime { .. }
144				| MinecraftInstr::GetXP { .. },
145			) => is_root,
146			// Empty block inside of an if can be removed
147			MIRInstrKind::If { body, .. } => body.contents.is_empty(),
148			MIRInstrKind::NoOp => true,
149			_ => false,
150		};
151
152		if remove {
153			let is_new = instrs_to_remove.insert(i);
154			if is_new {
155				run_again.yes();
156			}
157
158			continue;
159		}
160
161		// Instructions to replace
162		let kind_repl = match &instr.kind {
163			// x = cond bool y is same as x = y
164			MIRInstrKind::Assign {
165				left,
166				right: DeclareBinding::Condition(Condition::Bool(right)),
167			} => Some(MIRInstrKind::Assign {
168				left: left.clone(),
169				right: DeclareBinding::Value(right.clone()),
170			}),
171			// Div by -1 is same as mul by -1
172			MIRInstrKind::Div {
173				left,
174				right: Value::Constant(DataTypeContents::Score(score)),
175			} if score.get_i32() == -1 => Some(MIRInstrKind::Mul {
176				left: left.clone(),
177				right: Value::Constant(DataTypeContents::Score(ScoreTypeContents::Score(-1))),
178			}),
179			// x ^ 0 = 1
180			MIRInstrKind::Pow { base, exp: 0 } => Some(MIRInstrKind::Assign {
181				left: base.clone(),
182				right: DeclareBinding::Value(Value::Constant(DataTypeContents::Score(
183					ScoreTypeContents::Score(1),
184				))),
185			}),
186			// x - x = 0
187			MIRInstrKind::Sub {
188				left,
189				right: Value::Mutable(right),
190			} if left.is_same_val(right) => Some(MIRInstrKind::Assign {
191				left: left.clone(),
192				right: DeclareBinding::Value(Value::Constant(DataTypeContents::Score(
193					ScoreTypeContents::Score(0),
194				))),
195			}),
196			// x + x = x * 2
197			MIRInstrKind::Add {
198				left,
199				right: Value::Mutable(right),
200			} if left.is_same_val(right) => Some(MIRInstrKind::Mul {
201				left: left.clone(),
202				right: Value::Constant(DataTypeContents::Score(ScoreTypeContents::Score(2))),
203			}),
204			// x * x = x ^ 2
205			MIRInstrKind::Mul {
206				left,
207				right: Value::Mutable(right),
208			} if left.is_same_val(right) => Some(MIRInstrKind::Pow {
209				base: left.clone(),
210				exp: 2,
211			}),
212			// Merge with compound with single item = set that item
213			MIRInstrKind::Merge {
214				left,
215				right: Value::Constant(DataTypeContents::NBT(NBTTypeContents::Compound(_, right))),
216			} if right.0.len() == 1 => {
217				let right = right.0.iter().next().expect("Len should be 1");
218				Some(MIRInstrKind::Assign {
219					left: MutableValue::Property(Box::new(left.clone()), right.0.clone()),
220					right: DeclareBinding::Value(Value::Constant(DataTypeContents::NBT(
221						right.1.clone(),
222					))),
223				})
224			}
225			// x && 0 is always false
226			MIRInstrKind::And {
227				left,
228				right: Value::Constant(DataTypeContents::Score(score)),
229			} if score.get_i32() == 0 => Some(MIRInstrKind::Assign {
230				left: left.clone(),
231				right: DeclareBinding::Value(Value::Constant(DataTypeContents::Score(
232					ScoreTypeContents::Bool(false),
233				))),
234			}),
235			// x || 1 is always true
236			MIRInstrKind::Or {
237				left,
238				right: Value::Constant(DataTypeContents::Score(score)),
239			} if score.get_i32() == 1 => Some(MIRInstrKind::Assign {
240				left: left.clone(),
241				right: DeclareBinding::Value(Value::Constant(DataTypeContents::Score(
242					ScoreTypeContents::Bool(true),
243				))),
244			}),
245			// Change ifelse with empty branches to just if
246			MIRInstrKind::IfElse {
247				condition,
248				first,
249				second,
250			} if first.contents.is_empty() => Some(MIRInstrKind::If {
251				condition: Condition::Not(Box::new(condition.clone())),
252				body: second.clone(),
253			}),
254			// Change ifelse with empty branches to just if
255			MIRInstrKind::IfElse {
256				condition,
257				first,
258				second,
259			} if second.contents.is_empty() => Some(MIRInstrKind::If {
260				condition: condition.clone(),
261				body: first.clone(),
262			}),
263			// Change ifelse with both branches the same to if
264			MIRInstrKind::IfElse {
265				condition,
266				first,
267				second,
268			} if are_blocks_equivalent(first, second) => Some(MIRInstrKind::If {
269				condition: condition.clone(),
270				body: first.clone(),
271			}),
272			// if x == y: x = y -> x = y
273			MIRInstrKind::If {
274				condition: Condition::Equal(Value::Mutable(left1), right1),
275				body,
276			} => match body.contents.only().map(|x| &x.kind) {
277				Some(MIRInstrKind::Assign {
278					left: left2,
279					right: DeclareBinding::Value(right2),
280				}) if left1.is_same_val(left2) && right1.is_value_eq(right2) => Some(MIRInstrKind::Assign {
281					left: left1.clone(),
282					right: DeclareBinding::Value(right1.clone()),
283				}),
284				_ => None,
285			},
286			// Not conditions
287			MIRInstrKind::If {
288				condition: Condition::Not(condition),
289				body,
290			} => match condition.as_ref() {
291				// if x != y: x = y -> x = y
292				Condition::Equal(Value::Mutable(left1), right1) => {
293					match body.contents.only().map(|x| &x.kind) {
294						Some(MIRInstrKind::Assign {
295							left: left2,
296							right: DeclareBinding::Value(right2),
297						}) if left1.is_same_val(left2) && right1.is_value_eq(right2) => Some(MIRInstrKind::Assign {
298							left: left1.clone(),
299							right: DeclareBinding::Value(right1.clone()),
300						}),
301						_ => None,
302					}
303				}
304				// if x: y *= 0  |  if x: y = 0 -> x *= y
305				Condition::Bool(b) => match body.contents.only().map(|x| &x.kind) {
306					Some(
307						MIRInstrKind::Mul {
308							left,
309							right: Value::Constant(DataTypeContents::Score(val)),
310						}
311						| MIRInstrKind::Assign {
312							left,
313							right:
314								DeclareBinding::Value(Value::Constant(DataTypeContents::Score(val))),
315						},
316					) if val.get_i32() == 0 => Some(MIRInstrKind::Mul {
317						left: left.clone(),
318						right: b.clone(),
319					}),
320					_ => None,
321				},
322				_ => None,
323			},
324			MIRInstrKind::If {
325				condition:
326					Condition::GreaterThan(Value::Mutable(left1), right1)
327					| Condition::GreaterThanOrEqual(Value::Mutable(left1), right1),
328				body,
329			} => {
330				match body.contents.only().map(|x| &x.kind) {
331					// if x > y: x = y -> min x, y;
332					Some(MIRInstrKind::Assign {
333						left: left2,
334						right: DeclareBinding::Value(right2),
335					}) if left1.is_same_val(left2) && right1.is_value_eq(right2) => Some(MIRInstrKind::Min {
336						left: left1.clone(),
337						right: right1.clone(),
338					}),
339					_ => None,
340				}
341			}
342			MIRInstrKind::If {
343				condition:
344					Condition::LessThan(Value::Mutable(left1), right1)
345					| Condition::LessThanOrEqual(Value::Mutable(left1), right1),
346				body,
347			} => {
348				match body.contents.only().map(|x| &x.kind) {
349					// if x < y: x = y -> max x, y;
350					Some(MIRInstrKind::Assign {
351						left: left2,
352						right: DeclareBinding::Value(right2),
353					}) if left1.is_same_val(left2) && right1.is_value_eq(right2) => Some(MIRInstrKind::Max {
354						left: left1.clone(),
355						right: right1.clone(),
356					}),
357					_ => None,
358				}
359			}
360			MIRInstrKind::If {
361				condition: Condition::Bool(b),
362				body,
363			} => match body.contents.only().map(|x| &x.kind) {
364				// if x: y += 1 -> y += x
365				Some(MIRInstrKind::Add {
366					left,
367					right: Value::Constant(DataTypeContents::Score(val)),
368				}) if val.get_i32() == 1 => Some(MIRInstrKind::Add {
369					left: left.clone(),
370					right: b.clone(),
371				}),
372				// if x: y -= 1 -> y -= x
373				Some(MIRInstrKind::Sub {
374					left,
375					right: Value::Constant(DataTypeContents::Score(val)),
376				}) if val.get_i32() == 1 => Some(MIRInstrKind::Sub {
377					left: left.clone(),
378					right: b.clone(),
379				}),
380				// if x: y = true -> y |= x
381				Some(MIRInstrKind::Assign {
382					left,
383					right: DeclareBinding::Value(Value::Constant(DataTypeContents::Score(val))),
384				}) if val.get_i32() == 1 => Some(MIRInstrKind::Or {
385					left: left.clone(),
386					right: b.clone(),
387				}),
388				_ => None,
389			},
390			MIRInstrKind::If {
391				condition: Condition::NotBool(b),
392				body,
393			} => match body.contents.only().map(|x| &x.kind) {
394				// if not x: y = false -> y &= x
395				Some(MIRInstrKind::Assign {
396					left,
397					right: DeclareBinding::Value(Value::Constant(DataTypeContents::Score(val))),
398				}) if val.get_i32() == 0 => Some(MIRInstrKind::And {
399					left: left.clone(),
400					right: b.clone(),
401				}),
402				_ => None,
403			},
404			MIRInstrKind::Modify {
405				modifier: MIRModifier::StoreResult(StoreModLocation::Reg(left, left_scale)),
406				body,
407			} => {
408				match body.contents.only().map(|x| &x.kind) {
409					// str x: get y -> x = y (essentially)
410					Some(MIRInstrKind::Get {
411						value: right,
412						scale: right_scale,
413					}) => {
414						if left_scale * right_scale == 1.0 {
415							Some(MIRInstrKind::Assign {
416								left: MutableValue::Reg(left.clone()),
417								right: DeclareBinding::Value(Value::Mutable(right.clone())),
418							})
419						} else {
420							None
421						}
422					}
423					_ => None,
424				}
425			}
426			MIRInstrKind::Modify {
427				modifier: MIRModifier::StoreSuccess(StoreModLocation::Reg(left, left_scale)),
428				body,
429			} if left_scale == &1.0 => {
430				match body.contents.only().map(|x| &x.kind) {
431					// Canonicalize to let cond
432					Some(MIRInstrKind::If { condition, body }) => {
433						match body.contents.only().map(|x| &x.kind) {
434							Some(MIRInstrKind::NoOp) | None => Some(MIRInstrKind::Assign {
435								left: MutableValue::Reg(left.clone()),
436								right: DeclareBinding::Condition(condition.clone()),
437							}),
438							_ => None,
439						}
440					}
441					_ => None,
442				}
443			}
444			_ => None,
445		};
446
447		if let Some(kind_repl) = kind_repl {
448			instr.kind = kind_repl;
449			run_again.yes();
450		}
451
452		if let Some(condition) = instr.kind.get_condition_mut() {
453			simplify_condition(condition, &mut run_again);
454		}
455
456		// Nested ifs become and
457		if let MIRInstrKind::If { condition, body } = &mut instr.kind {
458			if let Some(MIRInstrKind::If {
459				condition: condition2,
460				body: body2,
461			}) = body.contents.only().map(|x| &x.kind)
462			{
463				let condition2 = condition2.clone();
464				*body = body2.clone();
465				*condition = Condition::And(Box::new(condition.clone()), Box::new(condition2));
466			}
467		}
468
469		for body in instr.kind.get_bodies_mut() {
470			run_again.merge(simplify_block(body, false));
471		}
472	}
473
474	run_again
475}
476
477fn simplify_condition(condition: &mut Condition, run_again: &mut RunAgain) {
478	match condition {
479		Condition::Not(inner) => match inner.as_ref() {
480			// not bool -> nbool for better lowering
481			Condition::Bool(b) => *condition = Condition::NotBool(b.clone()),
482			// not not -> remove
483			Condition::Not(inner) => {
484				*condition = *inner.clone();
485				run_again.yes();
486			}
487			_ => simplify_condition(inner, run_again),
488		},
489		Condition::And(l, r) => match (l.as_mut(), r.as_mut()) {
490			(Condition::Bool(Value::Constant(DataTypeContents::Score(b))), inner)
491			| (inner, Condition::Bool(Value::Constant(DataTypeContents::Score(b)))) => {
492				if b.get_i32() == 1 {
493					*condition = inner.clone();
494				} else {
495					*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
496						ScoreTypeContents::Bool(true),
497					)));
498				}
499				run_again.yes();
500			}
501			(Condition::NotBool(Value::Constant(DataTypeContents::Score(b))), inner)
502			| (inner, Condition::NotBool(Value::Constant(DataTypeContents::Score(b)))) => {
503				if b.get_i32() == 0 {
504					*condition = inner.clone();
505				} else {
506					*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
507						ScoreTypeContents::Bool(true),
508					)));
509				}
510				run_again.yes();
511			}
512			(Condition::Not(boxed), cond) | (cond, Condition::Not(boxed))
513				if boxed.as_ref() == cond =>
514			{
515				*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
516					ScoreTypeContents::Bool(false),
517				)));
518				run_again.yes();
519			}
520			(l, r) => {
521				if l == r {
522					*condition = l.clone();
523					run_again.yes();
524				} else {
525					simplify_condition(l, run_again);
526					simplify_condition(r, run_again);
527				}
528			}
529		},
530		Condition::Or(l, r) => match (l.as_mut(), r.as_mut()) {
531			(Condition::Bool(Value::Constant(DataTypeContents::Score(b))), inner)
532			| (inner, Condition::Bool(Value::Constant(DataTypeContents::Score(b)))) => {
533				if b.get_i32() == 1 {
534					*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
535						ScoreTypeContents::Bool(true),
536					)));
537				} else {
538					*condition = inner.clone();
539				}
540				run_again.yes();
541			}
542			(Condition::NotBool(Value::Constant(DataTypeContents::Score(b))), inner)
543			| (inner, Condition::NotBool(Value::Constant(DataTypeContents::Score(b)))) => {
544				if b.get_i32() == 0 {
545					*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
546						ScoreTypeContents::Bool(true),
547					)));
548				} else {
549					*condition = inner.clone();
550				}
551				run_again.yes();
552			}
553			(Condition::Not(boxed), cond) | (cond, Condition::Not(boxed))
554				if boxed.as_ref() == cond =>
555			{
556				*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
557					ScoreTypeContents::Bool(true),
558				)));
559				run_again.yes();
560			}
561			(l, r) => {
562				if l == r {
563					*condition = l.clone();
564					run_again.yes();
565				} else {
566					simplify_condition(l, run_again);
567					simplify_condition(r, run_again);
568				}
569			}
570		},
571		Condition::Xor(l, r) => {
572			if l == r {
573				*condition = Condition::Bool(Value::Constant(DataTypeContents::Score(
574					ScoreTypeContents::Bool(true),
575				)));
576				run_again.yes();
577			} else {
578				simplify_condition(l, run_again);
579				simplify_condition(r, run_again);
580			}
581		}
582		_ => {}
583	}
584}