llvm-native-core 0.1.4

LLVM-native core semantic engine — IR, CodeGen, X86 MC, Clang frontend pipeline
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
//! X86 Register Scavenger — emergency register scavenging for situations where
//! all allocatable registers are live. The scavenger finds a free register by
//! spilling a live register to an emergency spill slot, using it, and restoring
//! the spilled value afterward. Handles prologue/epilogue scavenging and
//! callee-saved register scavenging.
//!
//! ## Scavenging Algorithm
//!
//! ```text
//!   All registers live at point P
//!//!   ┌────────▼────────┐
//!   │  Find victim reg │  ← choose cheapest register to spill
//!   └────────┬────────┘
//!//!   ┌────────▼────────┐
//!   │  Spill victim to │  ← store victim → emergency spill slot
//!   │  emergency slot  │
//!   └────────┬────────┘
//!//!   ┌────────▼────────┐
//!   │  Use scavenged   │  ← the instruction that needed the register
//!   │  register        │
//!   └────────┬────────┘
//!//!   ┌────────▼────────┐
//!   │  Restore victim  │  ← load victim ← emergency spill slot
//!   │  from slot       │
//!   └─────────────────┘
//! ```
//!
//! Clean-room behavioral reconstruction from:
//! - Intel® 64 and IA-32 Architectures Software Developer's Manual
//!   (Vol 1, Ch 3.4: General-Purpose Registers)
//! - AMD64 Architecture Programmer's Manual (Vol 1, Ch 4: Register Organization)
//! - System V Application Binary Interface: AMD64 Architecture Processor Supplement
//! - Register allocation spill code literature (Chaitin 1982, Briggs 1994)
//!
//! Zero LLVM source code consultation. All behavior reconstructed from
//! published specifications and black-box oracle interrogation.

use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
use crate::x86::x86_register_info::{
    self, RegClass, X86RegisterInfo,
    RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI,
    R8, R9, R10, R11, R12, R13, R14, R15,
    EAX, ECX, EDX, ESI, EDI,
    XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7,
    XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15,
    GPR64, GPR32, GPR16, GPR8, XMM, YMM, ZMM, KMASK,
};
use std::collections::{HashMap, HashSet, VecDeque};

// ============================================================================
// Constants
// ============================================================================

/// Sentinel: no register.
pub const NO_REG: u32 = u32::MAX;

/// Number of emergency spill slots reserved per register class.
pub const NUM_EMERGENCY_SLOTS: usize = 2;

/// Maximum number of scavenge attempts per function.
pub const MAX_SCAVENGE_ATTEMPTS: usize = 1000;

/// Default emergency spill slot size for GPR (8 bytes).
pub const EMERGENCY_SLOT_SIZE_GPR: u32 = 8;

/// Default emergency spill slot size for XMM (16 bytes).
pub const EMERGENCY_SLOT_SIZE_XMM: u32 = 16;

/// Default emergency spill slot size for YMM (32 bytes).
pub const EMERGENCY_SLOT_SIZE_YMM: u32 = 32;

/// Default emergency spill slot size for ZMM (64 bytes).
pub const EMERGENCY_SLOT_SIZE_ZMM: u32 = 64;

// ============================================================================
// Emergency Spill Slot Management
// ============================================================================

/// An emergency spill slot for the scavenger.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmergencySpillSlot {
    /// Frame index (unique ID).
    pub frame_index: i32,
    /// Offset from the frame base pointer.
    pub offset: i32,
    /// Size of the slot in bytes.
    pub size: u32,
    /// Required alignment.
    pub alignment: u32,
    /// The register class this slot is sized for.
    pub reg_class: RegClass,
    /// Whether this slot is currently in use.
    pub in_use: bool,
    /// The register that was spilled into this slot.
    pub spilled_reg: u32,
}

impl EmergencySpillSlot {
    pub fn new(frame_index: i32, offset: i32, reg_class: RegClass) -> Self {
        let (size, alignment) = match reg_class {
            GPR64 | GPR32 | GPR16 | GPR8 => (EMERGENCY_SLOT_SIZE_GPR, EMERGENCY_SLOT_SIZE_GPR),
            XMM => (EMERGENCY_SLOT_SIZE_XMM, EMERGENCY_SLOT_SIZE_XMM),
            YMM => (EMERGENCY_SLOT_SIZE_YMM, EMERGENCY_SLOT_SIZE_YMM),
            ZMM => (EMERGENCY_SLOT_SIZE_ZMM, EMERGENCY_SLOT_SIZE_ZMM),
            KMASK => (8, 8),
            _ => (EMERGENCY_SLOT_SIZE_GPR, EMERGENCY_SLOT_SIZE_GPR),
        };
        EmergencySpillSlot {
            frame_index,
            offset,
            size,
            alignment,
            reg_class,
            in_use: false,
            spilled_reg: NO_REG,
        }
    }

    /// Mark this slot as occupied by the given register.
    pub fn occupy(&mut self, reg: u32) {
        self.in_use = true;
        self.spilled_reg = reg;
    }

    /// Mark this slot as free.
    pub fn free(&mut self) {
        self.in_use = false;
        self.spilled_reg = NO_REG;
    }
}

// ============================================================================
// Scavenger State
// ============================================================================

/// Tracks the state of all physical registers at a given program point.
#[derive(Debug, Clone)]
pub struct RegisterState {
    /// Registers that are currently free (available for use).
    pub free: HashSet<u32>,
    /// Registers currently in use (live).
    pub used: HashSet<u32>,
    /// Register that is reserved (cannot be scavenged).
    pub reserved: HashSet<u32>,
}

impl RegisterState {
    pub fn new() -> Self {
        RegisterState {
            free: HashSet::new(),
            used: HashSet::new(),
            reserved: HashSet::new(),
        }
    }

    /// Mark a register as free.
    pub fn add_free(&mut self, reg: u32) {
        self.free.insert(reg);
        self.used.remove(&reg);
    }

    /// Mark a register as used.
    pub fn add_used(&mut self, reg: u32) {
        self.used.insert(reg);
        self.free.remove(&reg);
    }

    /// Mark a register as reserved (e.g., RSP, RBP).
    pub fn add_reserved(&mut self, reg: u32) {
        self.reserved.insert(reg);
        self.free.remove(&reg);
        self.used.remove(&reg);
    }

    /// Check if a register is available.
    pub fn is_available(&self, reg: u32) -> bool {
        self.free.contains(&reg)
    }

    /// Check if a register is currently in use.
    pub fn is_used(&self, reg: u32) -> bool {
        self.used.contains(&reg) || self.reserved.contains(&reg)
    }

    /// Check if a register can be scavenged (not reserved).
    pub fn is_scavengeable(&self, reg: u32) -> bool {
        !self.reserved.contains(&reg) && self.used.contains(&reg)
    }

    /// Find a free register from a given set of candidates.
    pub fn find_free_in(&self, candidates: &[u32]) -> Option<u32> {
        candidates.iter().find(|r| self.free.contains(r)).copied()
    }

    /// Find the cheapest used register to spill (lowest spill cost).
    pub fn find_cheapest_victim(
        &self,
        candidates: &[u32],
        spill_costs: &HashMap<u32, f64>,
    ) -> Option<u32> {
        candidates
            .iter()
            .filter(|r| self.is_scavengeable(**r))
            .min_by(|a, b| {
                let cost_a = spill_costs.get(a).unwrap_or(&1.0);
                let cost_b = spill_costs.get(b).unwrap_or(&1.0);
                cost_a.partial_cmp(cost_b).unwrap_or(std::cmp::Ordering::Equal)
            })
            .copied()
    }
}

impl Default for RegisterState {
    fn default() -> Self {
        Self::new()
    }
}

// ============================================================================
// Scavenge Result
// ============================================================================

/// The result of a register scavenge operation.
#[derive(Debug, Clone)]
pub enum ScavengeResult {
    /// A free register was found without spilling.
    FoundFree(u32),
    /// A register was scavenged by spilling another register.
    Scavenged {
        /// The scavenged register (now free).
        reg: u32,
        /// The register that was spilled to make room.
        victim: u32,
        /// The spill slot used.
        slot: EmergencySpillSlot,
    },
    /// No register could be scavenged.
    Failed,
}

// ============================================================================
// Instruction Position Tracking
// ============================================================================

/// A position in the instruction stream.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InstrPosition {
    /// Basic block index.
    pub block_index: usize,
    /// Instruction index within the block.
    pub instr_index: usize,
}

impl InstrPosition {
    pub fn new(block_index: usize, instr_index: usize) -> Self {
        InstrPosition { block_index, instr_index }
    }
}

// ============================================================================
// Scavenger Statistics
// ============================================================================

/// Statistics collected by the register scavenger.
#[derive(Debug, Clone, Default)]
pub struct ScavengeStats {
    /// Number of successful scavenges (free register found).
    pub free_reg_hits: usize,
    /// Number of scavenges requiring a spill.
    pub spill_scavenges: usize,
    /// Number of failed scavenge attempts.
    pub failures: usize,
    /// Number of callee-saved registers scavenged.
    pub callee_saved_scavenges: usize,
    /// Number of prologue scavenges.
    pub prologue_scavenges: usize,
    /// Number of epilogue scavenges.
    pub epilogue_scavenges: usize,
    /// Total spill instructions inserted.
    pub spills_inserted: usize,
    /// Total restore instructions inserted.
    pub restores_inserted: usize,
    /// Maximum register pressure observed.
    pub max_pressure: usize,
}

// ============================================================================
// X86RegScavenger — main register scavenger
// ============================================================================

/// Emergency register scavenger for x86/x86-64. When all allocatable registers
/// are live at a program point, the scavenger spills a register to an emergency
/// spill slot, uses it for the required operation, and restores the spilled
/// value afterward.
pub struct X86RegScavenger {
    /// Register information.
    pub reg_info: X86RegisterInfo,
    /// Current register state.
    pub reg_state: RegisterState,
    /// Emergency spill slots (one per class).
    pub emergency_slots: Vec<EmergencySpillSlot>,
    /// Callee-saved registers (can be scavenged in emergencies).
    pub callee_saved_regs: HashSet<u32>,
    /// Caller-saved registers (preferred scavenge targets).
    pub caller_saved_regs: HashSet<u32>,
    /// Spill cost estimates for each register.
    pub spill_costs: HashMap<u32, f64>,
    /// Map from instruction position to register state.
    pub state_at_point: HashMap<InstrPosition, RegisterState>,
    /// Statistics.
    pub stats: ScavengeStats,
    /// Whether scavenging is allowed in the prologue.
    pub allow_prologue_scavenge: bool,
    /// Whether scavenging is allowed in the epilogue.
    pub allow_epilogue_scavenge: bool,
    /// Whether to prefer callee-saved regs as victims.
    pub prefer_callee_saved_victims: bool,
}

impl X86RegScavenger {
    /// Create a new register scavenger for x86-64.
    pub fn new(reg_info: X86RegisterInfo) -> Self {
        let mut scavenger = X86RegScavenger {
            reg_info,
            reg_state: RegisterState::new(),
            emergency_slots: Vec::new(),
            callee_saved_regs: HashSet::new(),
            caller_saved_regs: HashSet::new(),
            spill_costs: HashMap::new(),
            state_at_point: HashMap::new(),
            stats: ScavengeStats::default(),
            allow_prologue_scavenge: true,
            allow_epilogue_scavenge: true,
            prefer_callee_saved_victims: false,
        };

        scavenger.init_default_state();
        scavenger
    }

    /// Initialize the default register state for x86-64 System V.
    fn init_default_state(&mut self) {
        // Reserved registers: RSP and RBP are always reserved.
        self.reg_state.add_reserved(RSP);
        self.reg_state.add_reserved(RBP);

        // Caller-saved (scratch) registers — preferred for scavenging.
        let caller_saved: &[u32] = &[
            RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11,
        ];
        for &reg in caller_saved {
            self.caller_saved_regs.insert(reg);
            self.reg_state.add_free(reg);
        }

        // Callee-saved registers — available as last-resort victims.
        let callee_saved: &[u32] = &[
            RBX, R12, R13, R14, R15,
        ];
        for &reg in callee_saved {
            self.callee_saved_regs.insert(reg);
            self.reg_state.add_free(reg);
        }

        // XMM registers — all available.
        let xmm_regs: &[u32] = &[
            XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7,
            XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15,
        ];
        for &reg in xmm_regs {
            self.reg_state.add_free(reg);
        }

        // Set default spill costs: caller-saved are cheaper to spill.
        for &reg in caller_saved {
            self.spill_costs.insert(reg, 1.0);
        }
        for &reg in callee_saved {
            self.spill_costs.insert(reg, 10.0); // expensive to spill
        }
        for &reg in xmm_regs {
            self.spill_costs.insert(reg, 2.0);
        }
    }

    /// Add an emergency spill slot.
    pub fn add_emergency_slot(&mut self, slot: EmergencySpillSlot) {
        self.emergency_slots.push(slot);
    }

    /// Create default emergency spill slots for all register classes.
    pub fn create_default_slots(&mut self, base_frame_index: i32, base_offset: i32) {
        let mut offset = base_offset;
        let mut idx = base_frame_index;

        // One emergency slot per register class.
        let classes: &[(RegClass, u32, u32)] = &[
            (GPR64, EMERGENCY_SLOT_SIZE_GPR, EMERGENCY_SLOT_SIZE_GPR),
            (XMM, EMERGENCY_SLOT_SIZE_XMM, EMERGENCY_SLOT_SIZE_XMM),
            (YMM, EMERGENCY_SLOT_SIZE_YMM, EMERGENCY_SLOT_SIZE_YMM),
            (ZMM, EMERGENCY_SLOT_SIZE_ZMM, EMERGENCY_SLOT_SIZE_ZMM),
        ];

        for &(class, size, align) in classes {
            // Align the offset.
            offset = (offset + align as i32 - 1) & !(align as i32 - 1);
            let slot = EmergencySpillSlot {
                frame_index: idx,
                offset,
                size,
                alignment: align,
                reg_class: class,
                in_use: false,
                spilled_reg: NO_REG,
            };
            self.emergency_slots.push(slot);
            offset += size as i32;
            idx += 1;
        }
    }

    /// Find a free emergency spill slot for the given register class.
    fn find_free_slot(&mut self, class: RegClass) -> Option<&mut EmergencySpillSlot> {
        self.emergency_slots
            .iter_mut()
            .find(|s| s.reg_class == class && !s.in_use)
    }

    /// Find an emergency spill slot (possibly evicting an existing entry).
    fn find_slot_for(&mut self, preg: u32, class: RegClass) -> Option<&mut EmergencySpillSlot> {
        // First try to find a free slot.
        if let Some(pos) = self.emergency_slots.iter().position(|s| s.reg_class == class && !s.in_use) {
            return Some(&mut self.emergency_slots[pos]);
        }
        // If all slots of this class are in use, try to re-use a slot
        // (the register was already spilled and restored).
        if let Some(pos) = self.emergency_slots.iter().position(|s| s.reg_class == class && s.spilled_reg == preg) {
            return Some(&mut self.emergency_slots[pos]);
        }
        None
    }

    /// Compute register state at a given program point by scanning backwards
    /// from the start of the block to the given instruction index.
    pub fn compute_state_at_point(
        &mut self,
        block: &MachineBasicBlock,
        instr_index: usize,
    ) -> RegisterState {
        let mut state = self.reg_state.clone();

        // Forward scan: for each instruction up to instr_index,
        // definitions free registers, uses occupy them.
        for (i, instr) in block.instructions.iter().enumerate() {
            if i > instr_index {
                break;
            }
            self.update_state_for_instr(&mut state, instr);
        }

        state
    }

    /// Update the register state to reflect one instruction.
    fn update_state_for_instr(&self, state: &mut RegisterState, instr: &MachineInstr) {
        for operand in &instr.operands {
            match operand {
                MachineOperand::PhysReg(p) => {
                    // Usage: the register is now live (used).
                    if !state.reserved.contains(p) {
                        state.add_used(*p);
                    }
                }
                MachineOperand::Reg(_) => {
                    // Virtual register — skip (not our concern).
                }
                _ => {}
            }
        }

        // If this instruction defines a register (first operand is typically def),
        // mark it as free after the definition point.
        // In a full implementation, we'd track def/use more precisely.
    }

    /// Track defs and uses to update state accurately.
    fn track_defs_and_uses(
        &self,
        state: &mut RegisterState,
        defs: &[u32],
        uses: &[u32],
    ) {
        // After the instruction: defs become available (free).
        for &def in defs {
            if !state.reserved.contains(&def) {
                state.add_free(def);
            }
        }

        // Uses keep the register occupied.
        for &use_reg in uses {
            if !state.reserved.contains(&use_reg) {
                state.add_used(use_reg);
            }
        }
    }

    /// Main scavenge operation: find a free register at the given position.
    /// If no register is free, spill one and return it.
    pub fn scavenge_register(
        &mut self,
        block: &MachineBasicBlock,
        instr_index: usize,
        required_class: RegClass,
    ) -> ScavengeResult {
        // Compute the live register state at this point.
        let state = self.compute_state_at_point(block, instr_index);

        let pos = InstrPosition::new(0, instr_index); // block_index approximated
        self.state_at_point.insert(pos, state.clone());

        // Get the candidate registers for this class.
        let candidates = self.get_candidates_for_class(required_class);

        // Step 1: Try to find a free register.
        if let Some(free_reg) = state.find_free_in(&candidates) {
            self.stats.free_reg_hits += 1;
            return ScavengeResult::FoundFree(free_reg);
        }

        // Step 2: No free register — must scavenge by spilling.
        self.stats.spill_scavenges += 1;

        // Find the cheapest victim among used registers.
        let victim = match state.find_cheapest_victim(&candidates, &self.spill_costs) {
            Some(v) => v,
            None => {
                // Try callee-saved registers.
                let callee_saved: Vec<u32> = self.callee_saved_regs
                    .iter()
                    .filter(|r| candidates.contains(r))
                    .copied()
                    .collect();
                match state.find_cheapest_victim(&callee_saved, &self.spill_costs) {
                    Some(v) => {
                        self.stats.callee_saved_scavenges += 1;
                        v
                    }
                    None => return ScavengeResult::Failed,
                }
            }
        };

        // Find an emergency spill slot.
        let slot = match self.find_slot_for(victim, required_class) {
            Some(s) => s,
            None => return ScavengeResult::Failed,
        };

        // Occupy the slot.
        slot.occupy(victim);

        ScavengeResult::Scavenged {
            reg: victim,
            victim,
            slot: *slot,
        }
    }

    /// Get the candidate register list for a given register class.
    fn get_candidates_for_class(&self, class: RegClass) -> Vec<u32> {
        match class {
            GPR64 | GPR32 | GPR16 | GPR8 => {
                let mut regs: Vec<u32> = vec![
                    RAX, RCX, RDX, RBX, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15,
                ];
                // Remove reserved regs.
                regs.retain(|r| !self.reg_state.reserved.contains(r));
                regs
            }
            XMM => {
                let mut regs: Vec<u32> = (0..16).map(|i| XMM0 + i as u32).collect();
                regs.retain(|r| !self.reg_state.reserved.contains(r));
                regs
            }
            YMM => {
                // YMM is a super-set of XMM; use higher regs first.
                let mut regs: Vec<u32> = (0..16).map(|i| XMM0 + i as u32 + 32).collect();
                regs.retain(|r| !self.reg_state.reserved.contains(r));
                regs
            }
            ZMM => {
                let mut regs: Vec<u32> = (0..32).map(|i| XMM0 + i as u32 + 64).collect();
                regs.retain(|r| !self.reg_state.reserved.contains(r));
                regs
            }
            _ => Vec::new(),
        }
    }

    /// Generate spill instructions: store a register to its emergency slot.
    pub fn generate_spill(
        &self,
        reg: u32,
        slot: &EmergencySpillSlot,
        class: RegClass,
    ) -> MachineInstr {
        // For GPR: mov [rbp + offset], reg
        // For XMM: vmovaps [rbp + offset], xmm
        let opcode = match class {
            GPR64 => 0x89,  // MOV64mr
            GPR32 => 0x89,  // MOV32mr
            XMM => 0x29,    // VMOVAPSmr
            YMM => 0x29,    // VMOVAPSYmr
            ZMM => 0x7F,    // VMOVAPSZmr
            _ => 0x89,
        };

        let mut instr = MachineInstr::new(opcode);
        // Memory operand: [RBP + offset]
        instr.operands.push(MachineOperand::PhysReg(RBP));
        instr.operands.push(MachineOperand::Imm(slot.offset as i64));
        // Source register.
        instr.operands.push(MachineOperand::PhysReg(reg));

        instr
    }

    /// Generate restore instructions: load a register from its emergency slot.
    pub fn generate_restore(
        &self,
        reg: u32,
        slot: &EmergencySpillSlot,
        class: RegClass,
    ) -> MachineInstr {
        // For GPR: mov reg, [rbp + offset]
        // For XMM: vmovaps xmm, [rbp + offset]
        let opcode = match class {
            GPR64 => 0x8B,  // MOV64rm
            GPR32 => 0x8B,  // MOV32rm
            XMM => 0x28,    // VMOVAPSrm
            YMM => 0x28,    // VMOVAPSYrm
            ZMM => 0x6F,    // VMOVAPSZrm
            _ => 0x8B,
        };

        let mut instr = MachineInstr::new(opcode);
        // Destination register.
        instr.operands.push(MachineOperand::PhysReg(reg));
        // Memory operand: [RBP + offset]
        instr.operands.push(MachineOperand::PhysReg(RBP));
        instr.operands.push(MachineOperand::Imm(slot.offset as i64));

        instr
    }

    /// Scan through a function and scavenge registers where needed.
    /// Returns the number of scavenges performed.
    pub fn scavenge_function(&mut self, mf: &mut MachineFunction) -> Result<usize, String> {
        let mut scavenge_count = 0usize;

        for block in &mut mf.blocks {
            let mut insertions: Vec<(usize, MachineInstr, MachineInstr, MachineInstr)> = Vec::new();

            for (i, instr) in block.instructions.iter().enumerate() {
                // Check if the instruction needs a register we don't have.
                let needed = self.check_register_needs(&block, i, instr);
                if let Some(required_class) = needed {
                    if scavenge_count >= MAX_SCAVENGE_ATTEMPTS {
                        return Err(format!(
                            "Exceeded maximum scavenge attempts ({})",
                            MAX_SCAVENGE_ATTEMPTS
                        ));
                    }

                    let result = self.scavenge_register(&block, i, required_class);
                    match result {
                        ScavengeResult::Scavenged { reg, victim, slot } => {
                            let spill = self.generate_spill(victim, &slot, required_class);
                            let restore = self.generate_restore(victim, &slot, required_class);
                            // insert spill before, restore after
                            let nop = MachineInstr::new(0x90); // placeholder marker
                            insertions.push((i, spill, nop, restore));
                            self.stats.spills_inserted += 1;
                            self.stats.restores_inserted += 1;
                            scavenge_count += 1;
                        }
                        ScavengeResult::FoundFree(_) => {
                            // No insertion needed; register is free.
                        }
                        ScavengeResult::Failed => {
                            return Err(format!(
                                "Failed to scavenge a {} register at instruction {} in block '{}'",
                                format!("{:?}", required_class),
                                i,
                                block.name
                            ));
                        }
                    }
                }
            }

            // Apply insertions in reverse order to preserve indices.
            for (idx, spill, _nop, restore) in insertions.into_iter().rev() {
                block.instructions.insert(idx, restore);
                block.instructions.insert(idx, spill);
            }
        }

        Ok(scavenge_count)
    }

    /// Check if an instruction requires a register that might not be available.
    fn check_register_needs(
        &self,
        block: &MachineBasicBlock,
        instr_index: usize,
        instr: &MachineInstr,
    ) -> Option<RegClass> {
        // In a full implementation, this would analyze the instruction's
        // operand constraints and compare against the live register state.
        // For now, we check if there are virtual register operands that
        // haven't been assigned physical registers.

        for operand in &instr.operands {
            if let MachineOperand::Reg(_vreg) = operand {
                // This virtual register should have been assigned a physical
                // register by now. If not, it needs scavenging.
                let state = self.compute_state_at_point(block, instr_index);
                if state.free.is_empty() {
                    return Some(GPR64); // Default to GPR64
                }
            }
        }

        None
    }

    /// Scavenge specifically in the prologue (before function body).
    pub fn scavenge_prologue(&mut self, block: &mut MachineBasicBlock) -> Result<usize, String> {
        if !self.allow_prologue_scavenge {
            return Ok(0);
        }

        let mut count = 0usize;

        // In the prologue, we may need to scavenge for callee-saved register
        // saves when all registers are needed.
        let callee_saved: Vec<u32> = self.callee_saved_regs.iter().copied().collect();

        for &reg in &callee_saved {
            if self.reg_state.is_used(reg) {
                // Need to spill something to save this register.
                let result = self.scavenge_register(block, 0, GPR64);
                match result {
                    ScavengeResult::Scavenged { .. } | ScavengeResult::FoundFree(_) => {
                        self.stats.prologue_scavenges += 1;
                        count += 1;
                    }
                    ScavengeResult::Failed => {
                        return Err(format!(
                            "Failed to scavenge in prologue for register {}",
                            reg
                        ));
                    }
                }
            }
        }

        Ok(count)
    }

    /// Scavenge in the epilogue (after function body, before return).
    pub fn scavenge_epilogue(&mut self, block: &mut MachineBasicBlock) -> Result<usize, String> {
        if !self.allow_epilogue_scavenge {
            return Ok(0);
        }

        let mut count = 0usize;

        // In the epilogue, we restore callee-saved registers.
        // If a register we need is occupied, scavenge it.
        let callee_saved: Vec<u32> = self.callee_saved_regs.iter().copied().collect();

        for &reg in &callee_saved {
            if self.reg_state.is_used(reg) && !self.reg_state.is_available(reg) {
                let slot = match self.find_free_slot(GPR64) {
                    Some(s) => s.clone(),
                    None => continue,
                };

                let spill = self.generate_spill(reg, &slot, GPR64);
                let restore = self.generate_restore(reg, &slot, GPR64);

                // Insert spill before the last instruction (the return).
                let len = block.instructions.len();
                if len >= 1 {
                    block.instructions.insert(len - 1, restore);
                    block.instructions.insert(len - 1, spill);
                    self.stats.epilogue_scavenges += 1;
                    self.stats.spills_inserted += 1;
                    self.stats.restores_inserted += 1;
                    count += 1;
                }
            }
        }

        Ok(count)
    }

    /// Reset the scavenger state for a new function.
    pub fn reset(&mut self) {
        self.reg_state = RegisterState::new();
        self.init_default_state();
        self.state_at_point.clear();
        for slot in &mut self.emergency_slots {
            slot.free();
        }
    }

    /// Get the current register pressure (count of used registers).
    pub fn current_pressure(&self) -> usize {
        self.reg_state.used.len()
    }

    /// Get the maximum register pressure observed.
    pub fn max_pressure(&self) -> usize {
        self.stats.max_pressure
    }

    /// Record the current pressure (update max if higher).
    pub fn record_pressure(&mut self) {
        let pressure = self.current_pressure();
        if pressure > self.stats.max_pressure {
            self.stats.max_pressure = pressure;
        }
    }

    /// Mark a set of registers as defs (they become free after the instruction).
    pub fn mark_defs(&mut self, regs: &[u32]) {
        for &reg in regs {
            if !self.reg_state.reserved.contains(&reg) {
                self.reg_state.add_free(reg);
            }
        }
    }

    /// Mark a set of registers as uses (they become occupied).
    pub fn mark_uses(&mut self, regs: &[u32]) {
        for &reg in regs {
            if !self.reg_state.reserved.contains(&reg) {
                self.reg_state.add_used(reg);
            }
        }
    }

    /// Forward a register state through one instruction, returning the new state.
    pub fn forward_state(
        &self,
        state: &RegisterState,
        instr: &MachineInstr,
    ) -> RegisterState {
        let mut new_state = state.clone();
        self.update_state_for_instr(&mut new_state, instr);
        new_state
    }
}

// ============================================================================
// Scavenge Sequence Builder
// ============================================================================

/// Builds a complete scavenge sequence: spill → use → restore.
#[derive(Debug, Clone)]
pub struct ScavengeSequence {
    /// The register being scavenged.
    pub scavenged_reg: u32,
    /// The victim register that was spilled.
    pub victim_reg: u32,
    /// Spill instruction (store victim to slot).
    pub spill_instr: MachineInstr,
    /// The instruction that uses the scavenged register.
    pub user_instr: MachineInstr,
    /// Restore instruction (load victim from slot).
    pub restore_instr: MachineInstr,
    /// The emergency spill slot used.
    pub slot: EmergencySpillSlot,
}

impl ScavengeSequence {
    /// Expand the sequence into a vec of instructions: [spill, user, restore].
    pub fn expand(&self) -> Vec<MachineInstr> {
        vec![
            self.spill_instr.clone(),
            self.user_instr.clone(),
            self.restore_instr.clone(),
        ]
    }

    /// Total number of extra instructions added (spill + restore).
    pub fn overhead(&self) -> usize {
        2
    }
}

/// Build a scavenge sequence from a scavenge result.
pub fn build_scavenge_sequence(
    scavenger: &X86RegScavenger,
    result: &ScavengeResult,
    user_instr: MachineInstr,
    class: RegClass,
) -> Option<ScavengeSequence> {
    match result {
        ScavengeResult::Scavenged { reg, victim, slot } => {
            let spill = scavenger.generate_spill(*victim, slot, class);
            let restore = scavenger.generate_restore(*victim, slot, class);
            Some(ScavengeSequence {
                scavenged_reg: *reg,
                victim_reg: *victim,
                spill_instr: spill,
                user_instr,
                restore_instr: restore,
                slot: *slot,
            })
        }
        ScavengeResult::FoundFree(reg) => {
            // No spill needed; just use the register.
            Some(ScavengeSequence {
                scavenged_reg: *reg,
                victim_reg: NO_REG,
                spill_instr: MachineInstr::new(0x90), // NOP
                user_instr,
                restore_instr: MachineInstr::new(0x90), // NOP
                slot: EmergencySpillSlot::new(-1, 0, class),
            })
        }
        ScavengeResult::Failed => None,
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    fn make_scavenger() -> X86RegScavenger {
        let reg_info = X86RegisterInfo;
        let mut scavenger = X86RegScavenger::new(reg_info);
        scavenger.create_default_slots(0, -16);
        scavenger
    }

    #[test]
    fn test_initial_state() {
        let scavenger = make_scavenger();
        // RSP and RBP are reserved.
        assert!(scavenger.reg_state.is_used(RSP));
        assert!(scavenger.reg_state.is_used(RBP));
        // RAX should be free initially.
        assert!(scavenger.reg_state.is_available(RAX));
    }

    #[test]
    fn test_default_slots_created() {
        let mut scavenger = make_scavenger();
        scavenger.create_default_slots(0, 0);
        assert!(!scavenger.emergency_slots.is_empty());

        // Should have slots for GPR, XMM, YMM, ZMM
        let has_gpr = scavenger.emergency_slots.iter().any(|s| s.reg_class == GPR64);
        let has_xmm = scavenger.emergency_slots.iter().any(|s| s.reg_class == XMM);
        assert!(has_gpr);
        assert!(has_xmm);
    }

    #[test]
    fn test_find_free_register() {
        let mut scavenger = make_scavenger();

        let block = MachineBasicBlock::new(0, "test");
        // With default state, RAX should be free.
        let result = scavenger.scavenge_register(&block, 0, GPR64);
        match result {
            ScavengeResult::FoundFree(reg) => {
                assert!(scavenger.reg_state.is_available(reg));
            }
            _ => panic!("Expected FoundFree"),
        }
    }

    #[test]
    fn test_scavenge_when_all_used() {
        let mut scavenger = make_scavenger();

        // Mark all GPRs as used.
        let all_gprs: &[u32] = &[
            RAX, RCX, RDX, RBX, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15,
        ];
        for &reg in all_gprs {
            scavenger.reg_state.add_used(reg);
        }

        let block = MachineBasicBlock::new(0, "test");
        let result = scavenger.scavenge_register(&block, 0, GPR64);

        match result {
            ScavengeResult::Scavenged { reg: _, victim, slot: _ } => {
                // The victim should be one of the cheapest to spill.
                assert!(scavenger.reg_state.is_used(victim) || scavenger.caller_saved_regs.contains(&victim));
            }
            ScavengeResult::FoundFree(_) => {
                // Also acceptable if caller-saved are free.
            }
            ScavengeResult::Failed => {
                // May fail if no slots are available.
            }
        }
    }

    #[test]
    fn test_generate_spill_restore() {
        let scavenger = make_scavenger();
        let slot = EmergencySpillSlot::new(0, -8, GPR64);

        let spill = scavenger.generate_spill(RAX, &slot, GPR64);
        assert_eq!(spill.opcode, 0x89); // MOV64mr

        let restore = scavenger.generate_restore(RAX, &slot, GPR64);
        assert_eq!(restore.opcode, 0x8B); // MOV64rm
    }

    #[test]
    fn test_emergency_slot_occupy_free() {
        let mut slot = EmergencySpillSlot::new(0, -8, GPR64);
        assert!(!slot.in_use);

        slot.occupy(RAX);
        assert!(slot.in_use);
        assert_eq!(slot.spilled_reg, RAX);

        slot.free();
        assert!(!slot.in_use);
        assert_eq!(slot.spilled_reg, NO_REG);
    }

    #[test]
    fn test_callee_saved_are_registered() {
        let scavenger = make_scavenger();
        assert!(scavenger.callee_saved_regs.contains(&RBX));
        assert!(scavenger.callee_saved_regs.contains(&R12));
        assert!(scavenger.callee_saved_regs.contains(&R13));
        assert!(scavenger.callee_saved_regs.contains(&R14));
        assert!(scavenger.callee_saved_regs.contains(&R15));
    }

    #[test]
    fn test_caller_saved_are_cheaper() {
        let scavenger = make_scavenger();
        let rax_cost = scavenger.spill_costs.get(&RAX).unwrap_or(&0.0);
        let rbx_cost = scavenger.spill_costs.get(&RBX).unwrap_or(&0.0);
        // Caller-saved (RAX) should be cheaper than callee-saved (RBX).
        assert!(rax_cost < rbx_cost,
            "Expected RAX spill cost ({}) < RBX spill cost ({})", rax_cost, rbx_cost);
    }

    #[test]
    fn test_reset_clears_state() {
        let mut scavenger = make_scavenger();

        // Mark some regs as used.
        scavenger.reg_state.add_used(RAX);
        scavenger.reg_state.add_used(RBX);
        assert_eq!(scavenger.current_pressure(), 2);

        scavenger.reset();
        assert_eq!(scavenger.current_pressure(), 0);
    }

    #[test]
    fn test_max_pressure_tracking() {
        let mut scavenger = make_scavenger();

        scavenger.reg_state.add_used(RAX);
        scavenger.record_pressure();
        assert_eq!(scavenger.max_pressure(), 1);

        scavenger.reg_state.add_used(RBX);
        scavenger.reg_state.add_used(RCX);
        scavenger.record_pressure();
        assert_eq!(scavenger.max_pressure(), 3);
    }

    #[test]
    fn test_scavenge_sequence_builder() {
        let scavenger = make_scavenger();
        let slot = EmergencySpillSlot::new(0, -8, GPR64);
        let result = ScavengeResult::Scavenged {
            reg: RAX,
            victim: RCX,
            slot,
        };
        let user = MachineInstr::new(0x01); // ADD

        let seq = build_scavenge_sequence(&scavenger, &result, user, GPR64);
        assert!(seq.is_some());
        let seq = seq.unwrap();
        assert_eq!(seq.overhead(), 2);
        assert_eq!(seq.expand().len(), 3);
    }

    #[test]
    fn test_find_cheapest_victim() {
        let scavenger = make_scavenger();
        let state = &scavenger.reg_state;

        // With just reserved registers used, there are no victims.
        // Let's add RCX and RBX as used.
        let mut test_state = state.clone();
        test_state.add_used(RCX);
        test_state.add_used(RBX);

        let candidates: &[u32] = &[RCX, RBX];
        let victim = test_state.find_cheapest_victim(candidates, &scavenger.spill_costs);
        // RCX is caller-saved (cheaper spilling), RBX is callee-saved.
        assert_eq!(victim, Some(RCX));
    }

    #[test]
    fn test_slot_find_for_reg() {
        let mut scavenger = make_scavenger();
        scavenger.create_default_slots(0, 0);

        let slot = scavenger.find_slot_for(RAX, GPR64);
        assert!(slot.is_some());
    }
}