Skip to main content

llvm_codegen/
isel.rs

1//! Machine IR types and instruction-selection backend trait.
2//!
3//! The machine IR (`MachineFunction`, `MInstr`, …) is target-independent.
4//! Target backends implement [`IselBackend`] to lower LLVM IR to machine IR.
5
6use llvm_ir::{Context, Function, Module};
7use std::collections::HashMap;
8
9// ── indices ────────────────────────────────────────────────────────────────
10
11/// Virtual register (unlimited supply, created during instruction selection).
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct VReg(pub u32);
14
15/// Physical register (target-specific numbering).
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub struct PReg(pub u8);
18
19/// Opaque machine opcode (each target provides its own constants).
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub struct MOpcode(pub u32);
22
23/// Source-level debug location carried with machine instructions.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct DebugLoc {
26    /// Public API for `line`.
27    pub line: u32,
28    /// Public API for `column`.
29    pub column: u32,
30}
31
32// ── machine operand ────────────────────────────────────────────────────────
33
34/// An operand in a machine instruction.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub enum MOperand {
37    /// Virtual register (pre-allocation).
38    VReg(VReg),
39    /// Physical register (post-allocation or ABI-fixed).
40    PReg(PReg),
41    /// Immediate integer constant.
42    Imm(i64),
43    /// Branch target: index into `MachineFunction::blocks`.
44    Block(usize),
45}
46
47// ── machine instruction ────────────────────────────────────────────────────
48
49/// A single machine instruction.
50#[derive(Clone, Debug)]
51pub struct MInstr {
52    /// Target-specific opcode.
53    pub opcode: MOpcode,
54    /// Output (destination) virtual register, if any.
55    pub dst: Option<VReg>,
56    /// Input operands (source registers, immediates, branch targets).
57    pub operands: Vec<MOperand>,
58    /// Physical registers that must hold specific values before this instruction
59    /// (e.g. argument registers at a call site).
60    pub phys_uses: Vec<PReg>,
61    /// Physical registers whose values are destroyed by this instruction
62    /// (e.g. caller-saved regs clobbered by a call).
63    pub clobbers: Vec<PReg>,
64    /// Source debug location associated with this machine instruction.
65    pub debug_loc: Option<DebugLoc>,
66}
67
68impl MInstr {
69    /// Public API for `new`.
70    pub fn new(opcode: MOpcode) -> Self {
71        // `Self` variant.
72        Self {
73            opcode,
74            // `dst` field.
75            dst: None,
76            // `operands` field.
77            operands: Vec::new(),
78            // `phys_uses` field.
79            phys_uses: Vec::new(),
80            // `clobbers` field.
81            clobbers: Vec::new(),
82            // `debug_loc` field.
83            debug_loc: None,
84        }
85    }
86
87    /// Public API for `with_dst`.
88    pub fn with_dst(mut self, dst: VReg) -> Self {
89        self.dst = Some(dst);
90        self
91    }
92    /// Public API for `with_vreg`.
93    pub fn with_vreg(mut self, r: VReg) -> Self {
94        self.operands.push(MOperand::VReg(r));
95        self
96    }
97    /// Public API for `with_preg`.
98    pub fn with_preg(mut self, r: PReg) -> Self {
99        self.operands.push(MOperand::PReg(r));
100        self
101    }
102    /// Public API for `with_imm`.
103    pub fn with_imm(mut self, imm: i64) -> Self {
104        self.operands.push(MOperand::Imm(imm));
105        self
106    }
107    /// Public API for `with_block`.
108    pub fn with_block(mut self, b: usize) -> Self {
109        self.operands.push(MOperand::Block(b));
110        self
111    }
112}
113
114// ── machine basic block ────────────────────────────────────────────────────
115
116/// A sequence of machine instructions corresponding to one IR basic block.
117#[derive(Clone, Debug, Default)]
118pub struct MachineBlock {
119    /// Label derived from the IR block name (or function name for entry).
120    pub label: String,
121    /// Instructions in emission order.
122    pub instrs: Vec<MInstr>,
123}
124
125// ── machine function ───────────────────────────────────────────────────────
126
127/// Machine-level representation of a function, ready for register allocation
128/// and code emission.
129#[derive(Clone, Debug)]
130pub struct MachineFunction {
131    /// Name of the function.
132    pub name: String,
133    /// Basic blocks in layout order (block 0 is the entry).
134    pub blocks: Vec<MachineBlock>,
135    /// Counter for allocating fresh virtual registers.
136    pub(crate) next_vreg: u32,
137    /// Physical registers available for allocation (set by the target).
138    pub allocatable_pregs: Vec<PReg>,
139    /// Callee-saved physical registers (set by the target).
140    pub callee_saved_pregs: Vec<PReg>,
141    /// Frame size in bytes (spill slots only; set by insert_spill_reloads).
142    pub frame_size: u32,
143    /// Map from spilled VReg → frame slot index (0-based; emitter converts to byte offset).
144    pub spill_slots: HashMap<VReg, u32>,
145    /// Callee-saved physical registers actually used by this function (populated by apply_allocation).
146    pub used_callee_saved: Vec<PReg>,
147    /// Counter for frame slot allocation.
148    next_slot: u32,
149    /// Source filename used for debug line tables.
150    pub debug_source: Option<String>,
151    /// First source line observed from IR `!dbg` metadata.
152    pub debug_line_start: Option<u32>,
153    /// Debug location automatically attached by [`MachineFunction::push`].
154    pub current_debug_loc: Option<DebugLoc>,
155}
156
157impl MachineFunction {
158    /// Public API for `new`.
159    pub fn new(name: String) -> Self {
160        Self {
161            name,
162            blocks: Vec::new(),
163            next_vreg: 0,
164            allocatable_pregs: Vec::new(),
165            callee_saved_pregs: Vec::new(),
166            frame_size: 0,
167            spill_slots: HashMap::new(),
168            used_callee_saved: Vec::new(),
169            next_slot: 0,
170            debug_source: None,
171            debug_line_start: None,
172            current_debug_loc: None,
173        }
174    }
175
176    /// Allocate a fresh virtual register.
177    pub fn fresh_vreg(&mut self) -> VReg {
178        let id = self.next_vreg;
179        self.next_vreg += 1;
180        VReg(id)
181    }
182
183    /// Allocate a fresh frame slot for a spilled VReg and return its index.
184    ///
185    /// Slot 0 is the first 8-byte slot below the frame pointer.  The emitter
186    /// converts a slot index `n` to a byte offset (e.g. x86: `-(n+1)*8` from
187    /// RBP; AArch64: `(n+2)*8` above the saved FP/LR pair).
188    pub fn alloc_spill_slot(&mut self, vreg: VReg) -> u32 {
189        if let Some(&existing) = self.spill_slots.get(&vreg) {
190            return existing;
191        }
192        let slot = self.next_slot;
193        self.next_slot += 1;
194        self.spill_slots.insert(vreg, slot);
195        // Update frame_size: each slot is 8 bytes.
196        self.frame_size = self.next_slot * 8;
197        slot
198    }
199
200    /// Append a new empty machine block and return its index.
201    pub fn add_block(&mut self, label: impl Into<String>) -> usize {
202        let idx = self.blocks.len();
203        self.blocks.push(MachineBlock {
204            label: label.into(),
205            instrs: Vec::new(),
206        });
207        idx
208    }
209
210    /// Append `instr` to block `block_idx`.
211    pub fn push(&mut self, block_idx: usize, mut instr: MInstr) {
212        if instr.debug_loc.is_none() {
213            instr.debug_loc = self.current_debug_loc;
214        }
215        self.blocks[block_idx].instrs.push(instr);
216    }
217}
218
219// ── IselBackend trait ──────────────────────────────────────────────────────
220
221/// Implemented by each target to lower LLVM IR functions to machine IR.
222pub trait IselBackend {
223    /// Lower a single IR function to a [`MachineFunction`].
224    fn lower_function(
225        &mut self,
226        ctx: &Context,
227        module: &Module,
228        func: &Function,
229    ) -> MachineFunction;
230}
231
232// ── tests ──────────────────────────────────────────────────────────────────
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn machine_function_fresh_vreg() {
240        let mut mf = MachineFunction::new("f".into());
241        let v0 = mf.fresh_vreg();
242        let v1 = mf.fresh_vreg();
243        assert_eq!(v0, VReg(0));
244        assert_eq!(v1, VReg(1));
245    }
246
247    #[test]
248    fn machine_function_add_block() {
249        let mut mf = MachineFunction::new("f".into());
250        let b0 = mf.add_block("entry");
251        let b1 = mf.add_block("exit");
252        assert_eq!(b0, 0);
253        assert_eq!(b1, 1);
254        assert_eq!(mf.blocks[0].label, "entry");
255    }
256
257    #[test]
258    fn minstr_builder() {
259        let v = VReg(0);
260        let p = PReg(1);
261        let mi = MInstr::new(MOpcode(42))
262            .with_dst(v)
263            .with_vreg(v)
264            .with_preg(p)
265            .with_imm(-7)
266            .with_block(3);
267        assert_eq!(mi.dst, Some(v));
268        assert_eq!(mi.operands.len(), 4);
269        assert_eq!(mi.operands[0], MOperand::VReg(v));
270        assert_eq!(mi.operands[1], MOperand::PReg(p));
271        assert_eq!(mi.operands[2], MOperand::Imm(-7));
272        assert_eq!(mi.operands[3], MOperand::Block(3));
273    }
274}