1use super::instruction::{CompiledProgram, Op, Reg};
46use std::collections::{HashMap, HashSet};
47
48pub fn peephole(program: &mut CompiledProgram) {
52 copy_propagate(program);
53 loop {
54 let live_out = liveness(program);
55 let dead = dead_moves(program, &live_out);
56 if dead.is_empty() {
57 break;
58 }
59 remove_ops(program, &dead);
60 }
61}
62
63fn branch_targets(op: &Op) -> Vec<usize> {
67 match op {
68 Op::Jump { target } => vec![*target],
69 Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } => vec![*target],
70 Op::IterNext { exit, .. } => vec![*exit],
71 _ => vec![],
72 }
73}
74
75fn is_hard_terminator(op: &Op) -> bool {
77 matches!(
78 op,
79 Op::Jump { .. } | Op::Return { .. } | Op::ReturnNothing | Op::Halt | Op::FailWith { .. }
80 )
81}
82
83fn is_branch(op: &Op) -> bool {
86 matches!(
87 op,
88 Op::JumpIfFalse { .. } | Op::JumpIfTrue { .. } | Op::IterNext { .. }
89 )
90}
91
92pub(crate) fn successors(pc: usize, code: &[Op], entries: &HashSet<usize>) -> Vec<usize> {
96 let op = &code[pc];
97 let mut succ = branch_targets(op);
98 if !is_hard_terminator(op) {
99 let next = pc + 1;
100 if next < code.len() && !entries.contains(&next) {
101 succ.push(next);
102 }
103 }
104 succ
105}
106
107fn leaders(program: &CompiledProgram) -> HashSet<usize> {
111 let code = &program.code;
112 let mut ls: HashSet<usize> = HashSet::new();
113 ls.insert(0);
114 for f in &program.functions {
115 ls.insert(f.entry_pc);
116 }
117 for (pc, op) in code.iter().enumerate() {
118 for t in branch_targets(op) {
119 ls.insert(t);
120 }
121 if (is_hard_terminator(op) || is_branch(op)) && pc + 1 < code.len() {
122 ls.insert(pc + 1);
123 }
124 }
125 ls
126}
127
128fn function_entries(program: &CompiledProgram) -> HashSet<usize> {
129 program.functions.iter().map(|f| f.entry_pc).collect()
130}
131
132fn push_range(out: &mut Vec<Reg>, start: Reg, count: u16) {
136 for i in 0..count {
137 out.push(start.wrapping_add(i));
138 }
139}
140
141fn reads(op: &Op, program: &CompiledProgram, out: &mut Vec<Reg>) {
144 op.for_each_read_reg(
145 |func| {
146 program
147 .functions
148 .get(func as usize)
149 .map_or(0, |f| f.captures.len())
150 },
151 |r| out.push(r),
152 );
153}
154
155fn kills(op: &Op, out: &mut Vec<Reg>) {
158 match op {
159 Op::LoadConst { dst, .. }
160 | Op::Move { dst, .. }
161 | Op::Add { dst, .. }
162 | Op::Sub { dst, .. }
163 | Op::Mul { dst, .. }
164 | Op::Div { dst, .. }
165 | Op::ExactDiv { dst, .. }
166 | Op::FloorDiv { dst, .. }
167 | Op::Mod { dst, .. }
168 | Op::DivPow2 { dst, .. }
169 | Op::MagicDivU { dst, .. }
170 | Op::Lt { dst, .. }
171 | Op::Gt { dst, .. }
172 | Op::LtEq { dst, .. }
173 | Op::GtEq { dst, .. }
174 | Op::Eq { dst, .. }
175 | Op::ApproxEq { dst, .. }
176 | Op::NotEq { dst, .. }
177 | Op::Not { dst, .. }
178 | Op::Concat { dst, .. }
179 | Op::SeqConcat { dst, .. }
180 | Op::Pow { dst, .. }
181 | Op::BitXor { dst, .. }
182 | Op::BitAnd { dst, .. }
183 | Op::BitOr { dst, .. }
184 | Op::Shl { dst, .. }
185 | Op::Shr { dst, .. }
186 | Op::Index { dst, .. }
187 | Op::IndexUnchecked { dst, .. }
188 | Op::Length { dst, .. }
189 | Op::Contains { dst, .. }
190 | Op::SliceOp { dst, .. }
191 | Op::DeepClone { dst, .. }
192 | Op::FormatValue { dst, .. }
193 | Op::UnionOp { dst, .. }
194 | Op::IntersectOp { dst, .. }
195 | Op::GetField { dst, .. }
196 | Op::TestArm { dst, .. }
197 | Op::BindArm { dst, .. }
198 | Op::NewList { dst, .. }
199 | Op::NewEmptyList { dst, .. }
200 | Op::NewEmptyListWithCapacity { dst, .. }
201 | Op::NewEmptyListFixed { dst, .. }
202 | Op::NewEmptyListI32 { dst, .. }
203 | Op::NewEmptySet { dst, .. }
204 | Op::NewEmptyMap { dst, .. }
205 | Op::NewEmptyMapWithCapacity { dst, .. }
206 | Op::NewRange { dst, .. }
207 | Op::NewTuple { dst, .. }
208 | Op::NewStruct { dst, .. }
209 | Op::NewInductive { dst, .. }
210 | Op::NewCrdt { dst, .. }
211 | Op::MakeClosure { dst, .. }
212 | Op::Args { dst, .. }
213 | Op::LoadToday { dst, .. }
214 | Op::LoadNow { dst, .. }
215 | Op::GlobalGet { dst, .. }
216 | Op::Call { dst, .. }
217 | Op::CallBuiltin { dst, .. }
218 | Op::CallValue { dst, .. }
219 | Op::ListPop { dst, .. }
220 | Op::ChanNew { dst, .. }
221 | Op::ChanRecv { dst, .. }
222 | Op::ChanTrySend { dst, .. }
223 | Op::ChanTryRecv { dst, .. }
224 | Op::TaskAwait { dst, .. }
225 | Op::SelectWait { dst_arm: dst }
226 | Op::NetMakePeer { dst, .. }
227 | Op::NetAwait { dst, .. }
228 | Op::BigIntWide { dst, .. }
229 | Op::Tcp { dst, .. }
230 | Op::SpawnHandle { dst, .. } => out.push(*dst),
231 Op::AddAssign { dst, .. } => out.push(*dst),
233 Op::RevAdd { place, .. }
235 | Op::RevSub { place, .. }
236 | Op::RevXor { place, .. }
237 | Op::RevNeg { place } => out.push(*place),
238 Op::RevSwap { a, b } => {
239 out.push(*a);
240 out.push(*b);
241 }
242 Op::SelectArmRecv { var, .. } => out.push(*var),
244 Op::DestructureTuple { start, count, .. } => push_range(out, *start, *count),
246 Op::NetSync { dst, .. } => out.push(*dst),
248 Op::RemoveFrom { .. }
250 | Op::SetIndex { .. }
251 | Op::SetIndexUnchecked { .. }
252 | Op::ListPush { .. }
253 | Op::ListPushField { .. }
254 | Op::SetAdd { .. }
255 | Op::StructInsert { .. }
256 | Op::CrdtBump { .. }
257 | Op::CrdtMerge { .. }
258 | Op::CrdtJoin { .. }
259 | Op::CrdtAppend { .. }
260 | Op::CrdtResolve { .. }
261 | Op::EnsureOwned { .. }
262 | Op::GlobalSet { .. }
263 | Op::Return { .. }
264 | Op::ReturnNothing
265 | Op::Show { .. }
266 | Op::Obs { .. }
267 | Op::Sleep { .. }
268 | Op::IterPrepare { .. }
269 | Op::IterNext { .. }
270 | Op::IterPop
271 | Op::RegionBoundsGuard { .. }
272 | Op::CheckPolicy { .. }
273 | Op::ChanSend { .. }
274 | Op::ChanClose { .. }
275 | Op::Spawn { .. }
276 | Op::TaskAbort { .. }
277 | Op::SelectArmTimeout { .. }
278 | Op::NetConnect { .. }
279 | Op::NetListen { .. }
280 | Op::NetSend { .. }
281 | Op::NetStream { .. }
282 | Op::Serve { .. }
283 | Op::Jump { .. }
284 | Op::JumpIfFalse { .. }
285 | Op::JumpIfTrue { .. }
286 | Op::Halt
287 | Op::FailWith { .. } => {}
288 }
289}
290
291pub(crate) fn mutates(op: &Op, out: &mut Vec<Reg>) {
294 match op {
295 Op::ListPush { list, .. } => out.push(*list),
296 Op::ListPushField { obj, .. } => out.push(*obj),
297 Op::SetAdd { set, .. } => out.push(*set),
298 Op::RemoveFrom { collection, .. } => out.push(*collection),
299 Op::SetIndex { collection, .. } | Op::SetIndexUnchecked { collection, .. } => {
300 out.push(*collection)
301 }
302 Op::StructInsert { obj, .. } => out.push(*obj),
303 Op::CrdtBump { obj, .. } => out.push(*obj),
304 Op::CrdtMerge { target, .. } | Op::CrdtJoin { target, .. } => out.push(*target),
305 Op::CrdtAppend { seq, .. } => out.push(*seq),
306 Op::CrdtResolve { obj, .. } => out.push(*obj),
307 Op::ListPop { list, .. } => out.push(*list),
308 Op::EnsureOwned { reg } => out.push(*reg),
310 Op::LoadConst { .. }
313 | Op::Move { .. }
314 | Op::Add { .. }
315 | Op::AddAssign { .. }
316 | Op::Sub { .. }
317 | Op::Mul { .. }
318 | Op::Div { .. }
319 | Op::ExactDiv { .. }
320 | Op::FloorDiv { .. }
321 | Op::Mod { .. }
322 | Op::DivPow2 { .. }
323 | Op::MagicDivU { .. }
324 | Op::Lt { .. }
325 | Op::Gt { .. }
326 | Op::LtEq { .. }
327 | Op::GtEq { .. }
328 | Op::Eq { .. }
329 | Op::ApproxEq { .. }
330 | Op::NotEq { .. }
331 | Op::Not { .. }
332 | Op::Concat { .. }
333 | Op::SeqConcat { .. }
334 | Op::Pow { .. }
335 | Op::BitXor { .. }
336 | Op::BitAnd { .. }
337 | Op::BitOr { .. }
338 | Op::Shl { .. }
339 | Op::Shr { .. }
340 | Op::RevAdd { .. }
341 | Op::RevSub { .. }
342 | Op::RevXor { .. }
343 | Op::RevNeg { .. }
344 | Op::RevSwap { .. }
345 | Op::Index { .. }
346 | Op::IndexUnchecked { .. }
347 | Op::Length { .. }
348 | Op::Contains { .. }
349 | Op::SliceOp { .. }
350 | Op::DeepClone { .. }
351 | Op::FormatValue { .. }
352 | Op::UnionOp { .. }
353 | Op::IntersectOp { .. }
354 | Op::GetField { .. }
355 | Op::TestArm { .. }
356 | Op::BindArm { .. }
357 | Op::NewRange { .. }
358 | Op::NewList { .. }
359 | Op::NewEmptyList { .. }
360 | Op::NewEmptyListWithCapacity { .. }
361 | Op::NewEmptyListFixed { .. }
362 | Op::NewEmptyListI32 { .. }
363 | Op::NewEmptySet { .. }
364 | Op::NewEmptyMap { .. }
365 | Op::NewEmptyMapWithCapacity { .. }
366 | Op::NewTuple { .. }
367 | Op::NewStruct { .. }
368 | Op::NewInductive { .. }
369 | Op::NewCrdt { .. }
370 | Op::MakeClosure { .. }
371 | Op::Args { .. }
372 | Op::LoadToday { .. }
373 | Op::LoadNow { .. }
374 | Op::GlobalGet { .. }
375 | Op::GlobalSet { .. }
376 | Op::Call { .. }
377 | Op::CallBuiltin { .. }
378 | Op::CallValue { .. }
379 | Op::Return { .. }
380 | Op::ReturnNothing
381 | Op::Show { .. }
382 | Op::Obs { .. }
383 | Op::Sleep { .. }
384 | Op::IterPrepare { .. }
385 | Op::IterNext { .. }
386 | Op::IterPop
387 | Op::DestructureTuple { .. }
388 | Op::RegionBoundsGuard { .. }
389 | Op::CheckPolicy { .. }
390 | Op::BigIntWide { .. }
391 | Op::ChanNew { .. }
392 | Op::ChanSend { .. }
393 | Op::ChanRecv { .. }
394 | Op::ChanTrySend { .. }
395 | Op::ChanTryRecv { .. }
396 | Op::ChanClose { .. }
397 | Op::Spawn { .. }
398 | Op::SpawnHandle { .. }
399 | Op::TaskAwait { .. }
400 | Op::TaskAbort { .. }
401 | Op::SelectArmRecv { .. }
402 | Op::SelectArmTimeout { .. }
403 | Op::SelectWait { .. }
404 | Op::NetConnect { .. }
405 | Op::NetListen { .. }
406 | Op::NetSend { .. }
407 | Op::NetStream { .. }
408 | Op::NetAwait { .. }
409 | Op::NetMakePeer { .. }
410 | Op::NetSync { .. }
411 | Op::Tcp { .. }
412 | Op::Serve { .. }
413 | Op::Jump { .. }
414 | Op::JumpIfFalse { .. }
415 | Op::JumpIfTrue { .. }
416 | Op::Halt
417 | Op::FailWith { .. } => {}
418 }
419}
420
421fn clobbers(op: &Op, out: &mut Vec<Reg>) {
424 kills(op, out);
425 mutates(op, out);
426}
427
428fn for_each_rewritable_read(op: &mut Op, f: &mut impl FnMut(&mut Reg)) {
435 match op {
436 Op::Add { lhs, rhs, .. }
437 | Op::Sub { lhs, rhs, .. }
438 | Op::Mul { lhs, rhs, .. }
439 | Op::Div { lhs, rhs, .. }
440 | Op::ExactDiv { lhs, rhs, .. }
441 | Op::FloorDiv { lhs, rhs, .. }
442 | Op::Mod { lhs, rhs, .. }
443 | Op::Lt { lhs, rhs, .. }
444 | Op::Gt { lhs, rhs, .. }
445 | Op::LtEq { lhs, rhs, .. }
446 | Op::GtEq { lhs, rhs, .. }
447 | Op::Eq { lhs, rhs, .. }
448 | Op::ApproxEq { lhs, rhs, .. }
449 | Op::NotEq { lhs, rhs, .. }
450 | Op::Concat { lhs, rhs, .. }
451 | Op::SeqConcat { lhs, rhs, .. }
452 | Op::Pow { lhs, rhs, .. }
453 | Op::BitXor { lhs, rhs, .. }
454 | Op::BitAnd { lhs, rhs, .. }
455 | Op::BitOr { lhs, rhs, .. }
456 | Op::Shl { lhs, rhs, .. }
457 | Op::Shr { lhs, rhs, .. }
458 | Op::UnionOp { lhs, rhs, .. }
459 | Op::IntersectOp { lhs, rhs, .. } => {
460 f(lhs);
461 f(rhs);
462 }
463 Op::DivPow2 { lhs, .. } | Op::MagicDivU { lhs, .. } => f(lhs),
464 Op::Move { src, .. }
465 | Op::Not { src, .. }
466 | Op::DeepClone { src, .. }
467 | Op::FormatValue { src, .. } => f(src),
468 Op::AddAssign { src, .. } => f(src),
469 Op::Index {
470 collection, index, ..
471 }
472 | Op::IndexUnchecked {
473 collection, index, ..
474 } => {
475 f(collection);
476 f(index);
477 }
478 Op::Length { collection, .. } => f(collection),
479 Op::Contains {
480 collection, value, ..
481 } => {
482 f(collection);
483 f(value);
484 }
485 Op::SliceOp {
486 collection,
487 start,
488 end,
489 ..
490 } => {
491 f(collection);
492 f(start);
493 f(end);
494 }
495 Op::GetField { obj, .. } => f(obj),
496 Op::NewRange { start, end, .. } => {
497 f(start);
498 f(end);
499 }
500 Op::NewEmptyListWithCapacity { capacity, .. }
501 | Op::NewEmptyMapWithCapacity { capacity, .. } => f(capacity),
502 Op::CallValue { callee, .. } => f(callee),
503 Op::CrdtMerge { source, .. } | Op::CrdtJoin { source, .. } => f(source),
504 Op::JumpIfFalse { cond, .. } | Op::JumpIfTrue { cond, .. } => f(cond),
505 Op::GlobalSet { src, .. } => f(src),
506 Op::Return { src } => f(src),
507 Op::Show { src } => f(src),
508 Op::DestructureTuple { src, .. } => f(src),
509 Op::TestArm { .. }
522 | Op::BindArm { .. }
523 | Op::LoadConst { .. }
524 | Op::RevSwap { .. }
525 | Op::RevXor { .. }
526 | Op::RevNeg { .. }
527 | Op::RevAdd { .. }
528 | Op::RevSub { .. }
529 | Op::EnsureOwned { .. }
530 | Op::Call { .. }
531 | Op::CallBuiltin { .. }
532 | Op::MakeClosure { .. }
533 | Op::CheckPolicy { .. }
534 | Op::ListPushField { .. }
535 | Op::GlobalGet { .. }
536 | Op::ReturnNothing
537 | Op::NewList { .. }
538 | Op::NewEmptyList { .. }
539 | Op::NewEmptyListFixed { .. }
540 | Op::NewEmptyListI32 { .. }
541 | Op::NewEmptySet { .. }
542 | Op::NewEmptyMap { .. }
543 | Op::NewTuple { .. }
544 | Op::ListPush { .. }
545 | Op::SetAdd { .. }
546 | Op::RemoveFrom { .. }
547 | Op::SetIndex { .. }
548 | Op::SetIndexUnchecked { .. }
549 | Op::RegionBoundsGuard { .. }
550 | Op::NewStruct { .. }
551 | Op::StructInsert { .. }
552 | Op::NewInductive { .. }
553 | Op::CrdtBump { .. }
554 | Op::BigIntWide { .. }
555 | Op::NewCrdt { .. }
556 | Op::CrdtAppend { .. }
557 | Op::CrdtResolve { .. }
558 | Op::IterPrepare { .. }
559 | Op::IterNext { .. }
560 | Op::IterPop
561 | Op::ListPop { .. }
562 | Op::Sleep { .. } => {}
563 Op::LoadToday { .. }
565 | Op::LoadNow { .. }
566 | Op::Args { .. }
567 | Op::Obs { .. }
568 | Op::ChanNew { .. }
569 | Op::ChanSend { .. }
570 | Op::ChanRecv { .. }
571 | Op::ChanTrySend { .. }
572 | Op::ChanTryRecv { .. }
573 | Op::ChanClose { .. }
574 | Op::Spawn { .. }
575 | Op::SpawnHandle { .. }
576 | Op::TaskAwait { .. }
577 | Op::TaskAbort { .. }
578 | Op::SelectArmRecv { .. }
579 | Op::SelectArmTimeout { .. }
580 | Op::SelectWait { .. }
581 | Op::NetConnect { .. }
582 | Op::NetListen { .. }
583 | Op::NetSend { .. }
584 | Op::NetStream { .. }
585 | Op::NetAwait { .. }
586 | Op::NetMakePeer { .. }
587 | Op::NetSync { .. }
588 | Op::Tcp { .. }
589 | Op::Serve { .. }
590 | Op::Jump { .. }
591 | Op::Halt
592 | Op::FailWith { .. } => {}
593 }
594}
595
596fn copy_propagate(program: &mut CompiledProgram) {
604 let ls = leaders(program);
605 let mut copy: HashMap<Reg, Reg> = HashMap::new();
606 let mut scratch: Vec<Reg> = Vec::new();
607 for pc in 0..program.code.len() {
608 if ls.contains(&pc) {
609 copy.clear();
610 }
611 let op = &mut program.code[pc];
612 for_each_rewritable_read(op, &mut |r| {
614 if let Some(&s) = copy.get(r) {
615 *r = s;
616 }
617 });
618 let mov = if let Op::Move { dst, src } = *op {
620 Some((dst, src))
621 } else {
622 None
623 };
624 scratch.clear();
627 clobbers(&program.code[pc], &mut scratch);
628 if let Op::Call {
629 func, args_start, ..
630 } = program.code[pc]
631 {
632 if let Some(f) = program.functions.get(func as usize) {
633 scratch.extend(
634 f.mutable_param_regs
635 .iter()
636 .map(|p| args_start.wrapping_add(*p)),
637 );
638 }
639 }
640 if !scratch.is_empty() {
641 copy.retain(|k, v| !scratch.contains(k) && !scratch.contains(v));
642 }
643 if let Some((dst, src)) = mov {
645 if dst != src {
646 copy.insert(dst, src);
647 }
648 }
649 }
650}
651
652fn liveness(program: &CompiledProgram) -> Vec<HashSet<Reg>> {
659 let code = &program.code;
660 let n = code.len();
661 let entries = function_entries(program);
662 let succ: Vec<Vec<usize>> = (0..n).map(|pc| successors(pc, code, &entries)).collect();
663
664 let mut read_sets: Vec<Vec<Reg>> = Vec::with_capacity(n);
665 let mut kill_sets: Vec<Vec<Reg>> = Vec::with_capacity(n);
666 for op in code {
667 let mut r = Vec::new();
668 reads(op, program, &mut r);
669 read_sets.push(r);
670 let mut k = Vec::new();
671 kills(op, &mut k);
672 kill_sets.push(k);
673 }
674 for f in program
677 .functions
678 .iter()
679 .filter(|f| !f.mutable_param_regs.is_empty())
680 {
681 let mut seen = HashSet::new();
682 let mut stack = vec![f.entry_pc];
683 while let Some(pc) = stack.pop() {
684 if pc >= n || !seen.insert(pc) {
685 continue;
686 }
687 if matches!(code[pc], Op::Return { .. } | Op::ReturnNothing) {
688 read_sets[pc].extend(f.mutable_param_regs.iter().copied());
689 }
690 stack.extend(succ[pc].iter().copied());
691 }
692 }
693
694 let mut live_in: Vec<HashSet<Reg>> = vec![HashSet::new(); n];
695 let mut live_out: Vec<HashSet<Reg>> = vec![HashSet::new(); n];
696 let mut changed = true;
697 while changed {
698 changed = false;
699 for pc in (0..n).rev() {
700 let mut out = HashSet::new();
701 for &s in &succ[pc] {
702 for &r in &live_in[s] {
703 out.insert(r);
704 }
705 }
706 let mut new_in = out.clone();
708 for k in &kill_sets[pc] {
709 new_in.remove(k);
710 }
711 for &r in &read_sets[pc] {
712 new_in.insert(r);
713 }
714 if out != live_out[pc] {
715 live_out[pc] = out;
716 changed = true;
717 }
718 if new_in != live_in[pc] {
719 live_in[pc] = new_in;
720 changed = true;
721 }
722 }
723 }
724 live_out
725}
726
727fn dead_moves(program: &CompiledProgram, live_out: &[HashSet<Reg>]) -> Vec<usize> {
733 let mut dead = Vec::new();
734 for (pc, op) in program.code.iter().enumerate() {
735 if let Op::Move { dst, src } = op {
736 if dst == src || !live_out[pc].contains(dst) {
737 dead.push(pc);
738 }
739 }
740 }
741 dead
742}
743
744fn remove_ops(program: &mut CompiledProgram, dead: &[usize]) {
748 if dead.is_empty() {
749 return;
750 }
751 let dead_set: HashSet<usize> = dead.iter().copied().collect();
752 let n = program.code.len();
753 let mut new_pc = vec![0usize; n + 1];
755 let mut kept = 0usize;
756 for old in 0..n {
757 new_pc[old] = kept;
758 if !dead_set.contains(&old) {
759 kept += 1;
760 }
761 }
762 new_pc[n] = kept;
763
764 let mut code: Vec<Op> = Vec::with_capacity(kept);
766 for (old, mut op) in program.code.drain(..).enumerate() {
767 if dead_set.contains(&old) {
768 continue;
769 }
770 match &mut op {
771 Op::Jump { target }
772 | Op::JumpIfFalse { target, .. }
773 | Op::JumpIfTrue { target, .. } => {
774 *target = new_pc[*target];
775 }
776 Op::IterNext { exit, .. } => {
777 *exit = new_pc[*exit];
778 }
779 _ => {}
780 }
781 code.push(op);
782 }
783 program.code = code;
784
785 for f in &mut program.functions {
786 f.entry_pc = new_pc[f.entry_pc];
787 }
788 if !program.loop_locals.is_empty() {
789 program.loop_locals = program
790 .loop_locals
791 .drain()
792 .map(|(pc, v)| (new_pc[pc], v))
793 .collect();
794 }
795 for (pc, _) in &mut program.site_at {
797 *pc = new_pc[*pc as usize] as u32;
798 }
799 program.declared_types = std::mem::take(&mut program.declared_types)
801 .into_iter()
802 .filter(|(pc, _)| !dead_set.contains(&(*pc as usize)))
803 .map(|(pc, bt)| (new_pc[pc as usize] as u32, bt))
804 .collect();
805}
806
807#[cfg(test)]
808#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/vm/peephole/tests.rs"]
809mod tests;