1use super::model::{MAX_STACK_ACCESS, StackModel, StackOp};
19use crate::mir::ValueId;
20use smallvec::{SmallVec, ToSmallVec};
21use solar_data_structures::map::FxHashMap;
22
23#[derive(Clone, Debug, Default)]
25pub struct ShuffleResult {
26 pub ops: Vec<StackOp>,
28 pub dup_count: usize,
30 pub swap_count: usize,
32 pub pop_count: usize,
34}
35
36impl ShuffleResult {
37 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 #[must_use]
45 pub fn is_empty(&self) -> bool {
46 self.ops.is_empty()
47 }
48
49 #[must_use]
51 pub fn op_count(&self) -> usize {
52 self.ops.len()
53 }
54
55 #[must_use]
57 pub fn estimated_gas(&self) -> u64 {
58 (self.dup_count + self.swap_count) as u64 * 3 + self.pop_count as u64 * 2
59 }
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum TargetSlot {
65 Value(ValueId),
67 Empty,
69 Any,
71}
72
73pub struct StackShuffler<'a> {
75 source: SmallVec<[Option<ValueId>; 16]>,
77 target: &'a [TargetSlot],
79 ops: Vec<StackOp>,
81 multiplicities: FxHashMap<ValueId, usize>,
83}
84
85impl<'a> StackShuffler<'a> {
86 pub fn new(source: &StackModel, target: &'a [TargetSlot]) -> Self {
88 let source_stack: SmallVec<[Option<ValueId>; 16]> =
89 source.as_slice().iter().copied().collect();
90
91 let mut multiplicities = FxHashMap::default();
93 for slot in target {
94 if let TargetSlot::Value(v) = slot {
95 *multiplicities.entry(*v).or_insert(0) += 1;
96 }
97 }
98
99 Self { source: source_stack, target, ops: Vec::new(), multiplicities }
100 }
101
102 pub fn shuffle(mut self) -> ShuffleResult {
104 self.ensure_multiplicities();
106
107 self.arrange_positions();
109
110 self.pop_excess();
112
113 let mut result = ShuffleResult::new();
115 for op in &self.ops {
116 match op {
117 StackOp::Dup(_) => result.dup_count += 1,
118 StackOp::Swap(_) => result.swap_count += 1,
119 StackOp::Pop => result.pop_count += 1,
120 }
121 }
122 result.ops = self.ops;
123 result
124 }
125
126 fn ensure_multiplicities(&mut self) {
128 for (&value, &needed) in self.multiplicities.iter() {
130 let current_count = self.source.iter().filter(|&&v| v == Some(value)).count();
131 if current_count < needed {
132 if let Some(depth) = self.find_value(value)
134 && depth < MAX_STACK_ACCESS
135 {
136 for _ in current_count..needed {
137 let dup_n = (self.find_value(value).unwrap_or(0) + 1) as u8;
138 if dup_n <= 16 {
139 self.ops.push(StackOp::Dup(dup_n));
140 self.source.insert(0, Some(value));
141 }
142 }
143 }
144 }
145 }
146 }
147
148 fn arrange_positions(&mut self) {
150 for target_depth in 0..self.target.len().min(self.source.len()) {
152 let target_slot = &self.target[target_depth];
153
154 match target_slot {
155 TargetSlot::Value(target_val) => {
156 if self.source.get(target_depth) == Some(&Some(*target_val)) {
158 continue;
159 }
160
161 if let Some(source_depth) = self.find_value_from(*target_val, target_depth)
163 && source_depth != target_depth
164 && source_depth < MAX_STACK_ACCESS
165 {
166 if target_depth == 0 {
168 let swap_n = source_depth as u8;
170 if (1..=16).contains(&swap_n) {
171 self.ops.push(StackOp::Swap(swap_n));
172 self.source.swap(0, source_depth);
173 }
174 } else {
175 if target_depth < MAX_STACK_ACCESS && source_depth < MAX_STACK_ACCESS {
179 let swap1 = target_depth as u8;
181 if (1..=16).contains(&swap1) {
182 self.ops.push(StackOp::Swap(swap1));
183 self.source.swap(0, target_depth);
184 }
185
186 if let Some(new_depth) = self.find_value(*target_val)
188 && new_depth > 0
189 && new_depth < MAX_STACK_ACCESS
190 {
191 let swap2 = new_depth as u8;
192 if (1..=16).contains(&swap2) {
193 self.ops.push(StackOp::Swap(swap2));
194 self.source.swap(0, new_depth);
195 }
196 }
197
198 if (1..=16).contains(&swap1) {
200 self.ops.push(StackOp::Swap(swap1));
201 self.source.swap(0, target_depth);
202 }
203 }
204 }
205 }
206 }
207 TargetSlot::Empty | TargetSlot::Any => {
208 }
210 }
211 }
212 }
213
214 fn pop_excess(&mut self) {
216 let mut still_needed: FxHashMap<ValueId, usize> = FxHashMap::default();
218 for slot in self.target.iter() {
219 if let TargetSlot::Value(v) = slot {
220 *still_needed.entry(*v).or_insert(0) += 1;
221 }
222 }
223
224 while !self.source.is_empty() {
226 if let Some(Some(top_val)) = self.source.first() {
227 let needed = still_needed.get(top_val).copied().unwrap_or(0);
228 let current = self.source.iter().filter(|&&v| v == Some(*top_val)).count();
229 if current > needed {
230 self.ops.push(StackOp::Pop);
231 self.source.remove(0);
232 } else {
233 break;
234 }
235 } else if self.source.first() == Some(&None) {
236 if self.source.len() > self.target.len() {
238 self.ops.push(StackOp::Pop);
239 self.source.remove(0);
240 } else {
241 break;
242 }
243 } else {
244 break;
245 }
246 }
247 }
248
249 fn find_value(&self, value: ValueId) -> Option<usize> {
251 self.source.iter().position(|&v| v == Some(value))
252 }
253
254 fn find_value_from(&self, value: ValueId, min_depth: usize) -> Option<usize> {
256 self.source
257 .iter()
258 .enumerate()
259 .skip(min_depth)
260 .find(|(_, v)| **v == Some(value))
261 .map(|(i, _)| i)
262 }
263}
264
265#[derive(Clone, Debug)]
271pub struct LayoutAnalysis {
272 pub entry_layouts: FxHashMap<(usize, usize), Vec<TargetSlot>>,
274 pub block_exit_layouts: FxHashMap<usize, Vec<TargetSlot>>,
276}
277
278impl LayoutAnalysis {
279 #[must_use]
281 pub fn new() -> Self {
282 Self { entry_layouts: FxHashMap::default(), block_exit_layouts: FxHashMap::default() }
283 }
284
285 pub fn analyze_backward(
293 instructions: &[(Vec<ValueId>, Option<ValueId>)],
294 exit_layout: &[TargetSlot],
295 ) -> Vec<TargetSlot> {
296 let mut current_layout = exit_layout.to_vec();
297
298 for (operands, result) in instructions.iter().rev() {
300 current_layout =
301 Self::compute_entry_for_instruction(operands, *result, ¤t_layout);
302 }
303
304 current_layout
305 }
306
307 fn compute_entry_for_instruction(
309 operands: &[ValueId],
310 result: Option<ValueId>,
311 exit_layout: &[TargetSlot],
312 ) -> Vec<TargetSlot> {
313 let mut entry = Vec::new();
314
315 for &op in operands {
317 entry.push(TargetSlot::Value(op));
318 }
319
320 for slot in exit_layout {
322 match slot {
323 TargetSlot::Value(v) if Some(*v) == result => {
324 }
326 _ => entry.push(*slot),
327 }
328 }
329
330 entry
331 }
332}
333
334impl Default for LayoutAnalysis {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340#[derive(Clone, Debug, Default, PartialEq, Eq)]
343pub struct BlockStackLayout {
344 pub slots: SmallVec<[Option<ValueId>; 8]>,
347}
348
349impl BlockStackLayout {
350 #[must_use]
352 pub fn new() -> Self {
353 Self { slots: SmallVec::new() }
354 }
355
356 #[must_use]
358 pub fn from_values(values: impl IntoIterator<Item = ValueId>) -> Self {
359 Self { slots: values.into_iter().map(Some).collect() }
360 }
361
362 #[must_use]
364 pub fn from_stack_model(model: &StackModel) -> Self {
365 Self { slots: model.as_slice().to_smallvec() }
366 }
367
368 #[must_use]
370 pub fn depth(&self) -> usize {
371 self.slots.len()
372 }
373
374 #[must_use]
376 pub fn is_empty(&self) -> bool {
377 self.slots.is_empty()
378 }
379
380 #[must_use]
382 pub fn get(&self, depth: usize) -> Option<ValueId> {
383 self.slots.get(depth).copied().flatten()
384 }
385
386 #[must_use]
388 pub fn to_target_layout(&self) -> Vec<TargetSlot> {
389 self.slots
390 .iter()
391 .map(|&v| match v {
392 Some(val) => TargetSlot::Value(val),
393 None => TargetSlot::Any,
394 })
395 .collect()
396 }
397
398 #[must_use]
400 pub fn find(&self, value: ValueId) -> Option<usize> {
401 self.slots.iter().position(|&v| v == Some(value))
402 }
403
404 #[must_use]
406 pub fn contains(&self, value: ValueId) -> bool {
407 self.find(value).is_some()
408 }
409}
410
411pub fn combine_stack_layouts(layouts: &[BlockStackLayout]) -> Option<BlockStackLayout> {
420 if layouts.is_empty() {
421 return Some(BlockStackLayout::new());
422 }
423
424 if layouts.len() == 1 {
425 return Some(layouts[0].clone());
426 }
427
428 let max_depth = layouts.iter().map(|l| l.depth()).max().unwrap_or(0);
430 if max_depth == 0 {
431 return Some(BlockStackLayout::new());
432 }
433
434 let mut all_values: FxHashMap<ValueId, Vec<(usize, usize)>> = FxHashMap::default();
436 for (layout_idx, layout) in layouts.iter().enumerate() {
437 for (depth, slot) in layout.slots.iter().enumerate() {
438 if let Some(val) = slot {
439 all_values.entry(*val).or_default().push((layout_idx, depth));
440 }
441 }
442 }
443
444 let mut combined = BlockStackLayout { slots: smallvec::smallvec![None; max_depth] };
447
448 for depth in 0..max_depth {
450 let mut value_at_depth: Option<ValueId> = None;
451 let mut consistent = true;
452
453 for layout in layouts {
454 let val = layout.get(depth);
455 match (value_at_depth, val) {
456 (None, Some(v)) => value_at_depth = Some(v),
457 (Some(existing), Some(v)) if existing != v => {
458 consistent = false;
459 break;
460 }
461 _ => {}
462 }
463 }
464
465 if consistent {
466 combined.slots[depth] = value_at_depth;
467 }
468 }
469
470 for (&value, positions) in &all_values {
472 if combined.contains(value) {
473 continue; }
475
476 let mut pos_counts: FxHashMap<usize, usize> = FxHashMap::default();
478 for &(_, depth) in positions {
479 *pos_counts.entry(depth).or_default() += 1;
480 }
481
482 if let Some((&best_pos, _)) = pos_counts.iter().max_by(|a, b| {
484 a.1.cmp(b.1).then_with(|| b.0.cmp(a.0)) }) && best_pos < combined.slots.len()
486 && combined.slots[best_pos].is_none()
487 {
488 combined.slots[best_pos] = Some(value);
489 }
490 }
491
492 while combined.slots.last() == Some(&None) {
494 combined.slots.pop();
495 }
496
497 Some(combined)
498}
499
500#[must_use]
503pub fn estimate_shuffle_cost(source: &BlockStackLayout, target: &BlockStackLayout) -> usize {
504 let mut cost = 0;
505
506 for (depth, target_val) in
508 target.slots.iter().enumerate().filter_map(|(i, s)| s.map(|v| (i, v)))
509 {
510 match source.find(target_val) {
511 Some(src_depth) if src_depth != depth => {
512 cost += 1;
514 }
515 None => {
516 cost += 2; }
519 _ => {} }
521 }
522
523 for val in source.slots.iter().flatten() {
525 if !target.contains(*val) {
526 cost += 1; }
528 }
529
530 for val in target.slots.iter().flatten() {
532 let source_count = source.slots.iter().filter(|&&v| v == Some(*val)).count();
533 let target_count = target.slots.iter().filter(|&&v| v == Some(*val)).count();
534 if target_count > source_count {
535 cost += target_count - source_count; }
537 }
538
539 cost
540}
541
542#[derive(Clone, Copy, Debug, PartialEq, Eq)]
545pub enum FreelyGenerable {
546 Immediate,
548 Argument,
550 FunctionLabel,
552}
553
554pub fn is_freely_generable(func: &crate::mir::Function, value: ValueId) -> bool {
561 matches!(func.value(value), crate::mir::Value::Immediate(_) | crate::mir::Value::Arg { .. })
562}
563
564pub fn ideal_operand_layout(operands: &[ValueId]) -> Vec<TargetSlot> {
569 operands.iter().map(|&v| TargetSlot::Value(v)).collect()
570}
571
572pub fn ideal_binary_op_entry(
578 a: ValueId,
579 b: ValueId,
580 result: Option<ValueId>,
581 exit_layout: &[TargetSlot],
582) -> Vec<TargetSlot> {
583 let mut entry = Vec::with_capacity(exit_layout.len() + 1);
584
585 entry.push(TargetSlot::Value(a));
587 entry.push(TargetSlot::Value(b));
588
589 for slot in exit_layout.iter() {
591 match slot {
592 TargetSlot::Value(v) if Some(*v) == result => {
593 }
595 _ => entry.push(*slot),
596 }
597 }
598
599 entry
600}
601
602pub fn ideal_unary_op_entry(
604 operand: ValueId,
605 result: Option<ValueId>,
606 exit_layout: &[TargetSlot],
607) -> Vec<TargetSlot> {
608 let mut entry = Vec::with_capacity(exit_layout.len());
609
610 entry.push(TargetSlot::Value(operand));
612
613 for slot in exit_layout.iter() {
615 match slot {
616 TargetSlot::Value(v) if Some(*v) == result => {
617 }
619 _ => entry.push(*slot),
620 }
621 }
622
623 entry
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 fn make_model(values: &[Option<ValueId>]) -> StackModel {
631 let mut model = StackModel::new();
632 for &v in values.iter().rev() {
634 if let Some(val) = v {
635 model.push(val);
636 } else {
637 model.push_unknown();
638 }
639 }
640 model
641 }
642
643 #[test]
644 fn test_shuffle_already_correct() {
645 let v0 = ValueId::from_usize(0);
646 let v1 = ValueId::from_usize(1);
647
648 let source = make_model(&[Some(v0), Some(v1)]);
649 let target = [TargetSlot::Value(v0), TargetSlot::Value(v1)];
650
651 let result = StackShuffler::new(&source, &target).shuffle();
652 assert!(result.is_empty());
653 }
654
655 #[test]
656 fn test_shuffle_swap_needed() {
657 let v0 = ValueId::from_usize(0);
658 let v1 = ValueId::from_usize(1);
659
660 let source = make_model(&[Some(v1), Some(v0)]);
662 let target = [TargetSlot::Value(v0), TargetSlot::Value(v1)];
664
665 let result = StackShuffler::new(&source, &target).shuffle();
666 assert_eq!(result.swap_count, 1);
667 assert!(result.ops.contains(&StackOp::Swap(1)));
668 }
669
670 #[test]
671 fn test_shuffle_dup_needed() {
672 let v0 = ValueId::from_usize(0);
673
674 let source = make_model(&[Some(v0)]);
676 let target = [TargetSlot::Value(v0), TargetSlot::Value(v0)];
678
679 let result = StackShuffler::new(&source, &target).shuffle();
680 assert_eq!(result.dup_count, 1);
681 }
682
683 #[test]
684 fn test_shuffle_pop_excess() {
685 let v0 = ValueId::from_usize(0);
686 let v1 = ValueId::from_usize(1);
687
688 let source = make_model(&[Some(v0), Some(v1)]);
690 let target = [TargetSlot::Value(v1)];
692
693 let result = StackShuffler::new(&source, &target).shuffle();
694 assert!(result.pop_count >= 1 || result.swap_count >= 1);
696 }
697
698 #[test]
699 fn test_ideal_binary_op_entry() {
700 let a = ValueId::from_usize(0);
701 let b = ValueId::from_usize(1);
702 let result = ValueId::from_usize(2);
703 let extra = ValueId::from_usize(3);
704
705 let exit = [TargetSlot::Value(result), TargetSlot::Value(extra)];
707
708 let entry = ideal_binary_op_entry(a, b, Some(result), &exit);
709
710 assert_eq!(entry.len(), 3);
712 assert_eq!(entry[0], TargetSlot::Value(a));
713 assert_eq!(entry[1], TargetSlot::Value(b));
714 assert_eq!(entry[2], TargetSlot::Value(extra));
715 }
716
717 #[test]
718 fn test_backward_layout_analysis() {
719 let a = ValueId::from_usize(0);
720 let b = ValueId::from_usize(1);
721 let c = ValueId::from_usize(2);
722 let r1 = ValueId::from_usize(3);
723 let r2 = ValueId::from_usize(4);
724
725 let instructions = vec![
729 (vec![a, b], Some(r1)), (vec![r1, c], Some(r2)), ];
732
733 let exit = vec![TargetSlot::Value(r2)];
735
736 let entry = LayoutAnalysis::analyze_backward(&instructions, &exit);
737
738 assert_eq!(entry.len(), 3);
743 assert_eq!(entry[0], TargetSlot::Value(a));
744 assert_eq!(entry[1], TargetSlot::Value(b));
745 assert_eq!(entry[2], TargetSlot::Value(c));
746 }
747
748 #[test]
749 fn test_ideal_operand_layout() {
750 let a = ValueId::from_usize(0);
751 let b = ValueId::from_usize(1);
752 let c = ValueId::from_usize(2);
753
754 let layout = ideal_operand_layout(&[a, b, c]);
755
756 assert_eq!(layout.len(), 3);
757 assert_eq!(layout[0], TargetSlot::Value(a));
758 assert_eq!(layout[1], TargetSlot::Value(b));
759 assert_eq!(layout[2], TargetSlot::Value(c));
760 }
761
762 #[test]
763 fn test_shuffle_complex_rearrangement() {
764 let v0 = ValueId::from_usize(0);
765 let v1 = ValueId::from_usize(1);
766 let v2 = ValueId::from_usize(2);
767
768 let source = make_model(&[Some(v0), Some(v1), Some(v2)]);
770 let target = [TargetSlot::Value(v2), TargetSlot::Value(v0), TargetSlot::Value(v1)];
772
773 let result = StackShuffler::new(&source, &target).shuffle();
774
775 assert!(result.swap_count > 0);
777 }
778
779 #[test]
780 fn test_block_stack_layout_basic() {
781 let v0 = ValueId::from_usize(0);
782 let v1 = ValueId::from_usize(1);
783
784 let layout = BlockStackLayout::from_values([v0, v1]);
785 assert_eq!(layout.depth(), 2);
786 assert_eq!(layout.get(0), Some(v0));
787 assert_eq!(layout.get(1), Some(v1));
788 assert!(layout.contains(v0));
789 assert!(layout.contains(v1));
790 assert_eq!(layout.find(v0), Some(0));
791 assert_eq!(layout.find(v1), Some(1));
792 }
793
794 #[test]
795 fn test_block_stack_layout_to_target() {
796 let v0 = ValueId::from_usize(0);
797 let v1 = ValueId::from_usize(1);
798
799 let layout = BlockStackLayout::from_values([v0, v1]);
800 let target = layout.to_target_layout();
801
802 assert_eq!(target.len(), 2);
803 assert_eq!(target[0], TargetSlot::Value(v0));
804 assert_eq!(target[1], TargetSlot::Value(v1));
805 }
806
807 #[test]
808 fn test_combine_stack_layouts_single() {
809 let v0 = ValueId::from_usize(0);
810 let v1 = ValueId::from_usize(1);
811
812 let layout = BlockStackLayout::from_values([v0, v1]);
813 let combined = combine_stack_layouts(std::slice::from_ref(&layout));
814
815 assert!(combined.is_some());
816 assert_eq!(combined.unwrap(), layout);
817 }
818
819 #[test]
820 fn test_combine_stack_layouts_identical() {
821 let v0 = ValueId::from_usize(0);
822 let v1 = ValueId::from_usize(1);
823
824 let layout1 = BlockStackLayout::from_values([v0, v1]);
825 let layout2 = BlockStackLayout::from_values([v0, v1]);
826 let combined = combine_stack_layouts(&[layout1, layout2]);
827
828 assert!(combined.is_some());
829 let result = combined.unwrap();
830 assert_eq!(result.get(0), Some(v0));
831 assert_eq!(result.get(1), Some(v1));
832 }
833
834 #[test]
835 fn test_combine_stack_layouts_different_values() {
836 let v0 = ValueId::from_usize(0);
837 let v1 = ValueId::from_usize(1);
838 let v2 = ValueId::from_usize(2);
839
840 let layout1 = BlockStackLayout::from_values([v0, v1]);
844 let layout2 = BlockStackLayout::from_values([v0, v2]);
845 let combined = combine_stack_layouts(&[layout1, layout2]);
846
847 assert!(combined.is_some());
848 let result = combined.unwrap();
849 assert_eq!(result.get(0), Some(v0)); }
851
852 #[test]
853 fn test_estimate_shuffle_cost_identical() {
854 let v0 = ValueId::from_usize(0);
855 let v1 = ValueId::from_usize(1);
856
857 let layout = BlockStackLayout::from_values([v0, v1]);
858 let cost = estimate_shuffle_cost(&layout, &layout);
859
860 assert_eq!(cost, 0);
861 }
862
863 #[test]
864 fn test_estimate_shuffle_cost_swap() {
865 let v0 = ValueId::from_usize(0);
866 let v1 = ValueId::from_usize(1);
867
868 let source = BlockStackLayout::from_values([v0, v1]);
869 let target = BlockStackLayout::from_values([v1, v0]);
870 let cost = estimate_shuffle_cost(&source, &target);
871
872 assert!(cost > 0);
874 }
875}