Skip to main content

lamina_codegen/aarch64/
frame.rs

1//! Stack frame layout for AArch64 code generation.
2//!
3//! This module provides frame mapping for virtual registers to stack slots.
4
5use lamina_mir::{Function, Instruction, Register};
6use std::collections::{HashMap, HashSet};
7
8/// Maps virtual registers to stack slot offsets.
9pub struct FrameMap {
10    pub slots: HashMap<Register, i32>,
11    pub frame_size: i32,
12}
13
14impl FrameMap {
15    /// Creates a frame map from a function, assigning stack slots to all virtual registers.
16    pub fn from_function(f: &Function) -> Self {
17        let mut regs: HashSet<Register> = HashSet::new();
18        for p in &f.sig.params {
19            regs.insert(p.reg.clone());
20        }
21        for b in &f.blocks {
22            for ins in &b.instructions {
23                if let Some(d) = ins.def_reg() {
24                    regs.insert(d.clone());
25                }
26                if let Instruction::StackAddr { slot, .. } = &ins.instr {
27                    regs.insert(Register::Virtual(*slot));
28                }
29                for u in ins.use_regs() {
30                    regs.insert(u.clone());
31                }
32            }
33        }
34
35        let mut reg_vec: Vec<Register> = regs.into_iter().collect();
36        reg_vec.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
37
38        let mut slots = HashMap::new();
39        let mut offset: i32 = -8;
40        for r in reg_vec {
41            if let Register::Virtual(v) = &r {
42                // A register with a reservation addresses the lowest byte of a
43                // multi-slot block, so it sits at the deepest slot of that block.
44                let extra = f.stack_reservations.get(v).copied().unwrap_or(0) as i32;
45                offset -= 8 * extra;
46                if let Some(align) = f.stack_alignments.get(v) {
47                    let align = *align as i32;
48                    let limit = offset - 8 * align;
49                    while offset % align != 0 {
50                        offset -= 8;
51                        assert!(
52                            offset > limit,
53                            "no slot within {align} steps lands on a {align}-byte boundary"
54                        );
55                    }
56                }
57                slots.insert(r.clone(), offset);
58                offset -= 8;
59            }
60        }
61        let mut frame_size = -offset - 8;
62        if frame_size < 0 {
63            frame_size = 0;
64        }
65        frame_size = (frame_size + 15) & !15;
66        Self { slots, frame_size }
67    }
68
69    pub fn recompute_frame_size_from_slots(&mut self) {
70        let mut min_off = 0i32;
71        for &o in self.slots.values() {
72            if o < min_off {
73                min_off = o;
74            }
75        }
76        // The deepest slot starts at `min_off`, so the frame must span all of
77        // it; `-min_off - 8` left that slot below sp.
78        let mut frame_size = (-min_off).max(0);
79        frame_size = (frame_size + 15) & !15;
80        self.frame_size = frame_size;
81    }
82
83    /// Returns the stack slot offset for a register, if it has one.
84    pub fn slot_of(&self, r: &Register) -> Option<i32> {
85        self.slots.get(r).copied()
86    }
87}