diff --git a/src/backend/x86_64.rs b/src/backend/x86_64.rs deleted file mode 100644 index 3dea3ab..0000000 --- a/src/backend/x86_64.rs +++ /dev/null @@ -1,732 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::fmt::Write; -use std::mem::take; - -use crate::ir::*; - -#[derive(Clone, Copy, Debug)] -enum Storage { - Hardware(&'static str), - Stack(usize), - Alloc(usize), // Represents the address `-offset(%rbp)` -} - -pub struct X86Backend<'a> { - assembly: String, - module: &'a Module, - live_intervals: HashMap, - allocations: HashMap, -} - -impl<'a> X86Backend<'a> { - pub fn new(module: &'a Module) -> Self { - Self { - assembly: String::new(), - module, - live_intervals: HashMap::new(), - allocations: HashMap::new(), - } - } - - pub fn compile_module(mut self) -> String { - for func in &self.module.functions { - self.compile_function(func); - } - - self.assembly - } - - fn resolve_op( - &mut self, - op: &Operand, - scratch_reg: &str, - allocs: &HashMap, - ) -> String { - match op { - Operand::Integer(v) => format!("${}", v), - Operand::Boolean(b) => format!("${}", if *b { 1 } else { 0 }), - Operand::Register(r) => match allocs.get(r).unwrap() { - Storage::Hardware(hw) => format!("%{}", hw), - Storage::Stack(off) => format!("-{}(%rbp)", off), - Storage::Alloc(off) => { - // Materialize the address dynamically into the provided scratch register - writeln!( - &mut self.assembly, - " leaq -{}(%rbp), {}", - off, scratch_reg - ) - .unwrap(); - scratch_reg.to_string() - } - }, - } - } - - fn mark_use(&mut self, reg: Register, idx: usize) { - if !self.allocations.contains_key(®) { - let entry = self.live_intervals.entry(reg).or_insert((idx, idx)); - entry.1 = idx; - } - } - - fn mark_op(&mut self, op: &Operand, idx: usize) { - if let Operand::Register(r) = op { - self.mark_use(*r, idx); - } - } - - fn compile_function(&mut self, func: &Function) { - // 0. Pre-Pass: Handle Allocations - let mut allocations: HashMap = HashMap::new(); - let mut next_stack_offset = 0; - - for block in &func.blocks { - for inst in &block.instructions { - if let Instruction::Alloc { dest, .. } = inst { - next_stack_offset += 8; - allocations.insert(*dest, Storage::Alloc(next_stack_offset)); - } - } - } - - // 1. Liveness Analysis & Call Tracking - let mut call_indices = Vec::new(); - let mut hints: HashMap = HashMap::new(); - let mut inst_idx = 0; - - for (_, reg) in &func.params { - self.mark_use(*reg, inst_idx); - } - inst_idx += 1; - - for block in &func.blocks { - for inst in &block.instructions { - match inst { - Instruction::Alloc { .. } => {} - Instruction::Call { dest, args, .. } => { - self.mark_use(*dest, inst_idx); - let arg_regs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - for (i, (_, arg_op)) in args.iter().enumerate() { - self.mark_op(arg_op, inst_idx); - if i < 6 - && let Operand::Register(r) = arg_op - { - hints.insert(*r, arg_regs[i]); - } - } - call_indices.push(inst_idx); - } - Instruction::Load { dest, src, .. } => { - self.mark_use(*dest, inst_idx); - self.mark_op(src, inst_idx); - } - Instruction::Store { dest, src, .. } => { - self.mark_op(dest, inst_idx); - self.mark_op(src, inst_idx); - } - Instruction::Assign { register, operand } => { - self.mark_use(*register, inst_idx); - self.mark_op(operand, inst_idx); - } - Instruction::Binary { - dest, src1, src2, .. - } => { - self.mark_use(*dest, inst_idx); - self.mark_op(src1, inst_idx); - self.mark_op(src2, inst_idx); - } - Instruction::Unary { dest, src, .. } => { - self.mark_use(*dest, inst_idx); - self.mark_op(src, inst_idx); - } - Instruction::Phi { dest, sources, .. } => { - self.mark_use(*dest, inst_idx); - for (op, _) in sources { - self.mark_op(op, inst_idx); - } - } - } - inst_idx += 1; - } - match &block.terminator { - Terminator::Branch { cond, .. } => self.mark_op(cond, inst_idx), - Terminator::Return { - value: Some(val), .. - } => self.mark_op(val, inst_idx), - _ => {} - } - inst_idx += 1; - } - - // 2. ABI-Aware Linear Scan Allocation - let mut free_callee_saved = vec!["rbx", "r12", "r13", "r14", "r15"]; - let mut free_caller_saved = vec!["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - - let mut active: Vec<(Register, usize, &'static str, bool)> = Vec::new(); - let mut used_callee_saved = HashSet::new(); - - let live_intervals = take(&mut self.live_intervals); - let mut intervals_sorted: Vec<_> = live_intervals.into_iter().collect(); - intervals_sorted.sort_by_key(|(_, (start, _))| *start); - - for (reg, (start, end)) in intervals_sorted { - active.retain(|(_, active_end, hw_reg, is_caller)| { - if *active_end < start { - if *is_caller { - free_caller_saved.push(hw_reg); - } else { - free_callee_saved.push(hw_reg); - } - false - } else { - true - } - }); - - let crosses_call = call_indices - .iter() - .any(|&c_idx| c_idx > start && c_idx < end); - - let mut selected_hw = None; - let mut is_caller = false; - - if !crosses_call { - if let Some(&hint_reg) = hints.get(®) - && let Some(pos) = free_caller_saved.iter().position(|&r| r == hint_reg) - { - selected_hw = Some(free_caller_saved.remove(pos)); - is_caller = true; - } - if selected_hw.is_none() - && let Some(r) = free_caller_saved.pop() - { - selected_hw = Some(r); - is_caller = true; - } - } - - if selected_hw.is_none() - && let Some(r) = free_callee_saved.pop() - { - selected_hw = Some(r); - - used_callee_saved.insert(r); - is_caller = false; - } - - if let Some(hw_reg) = selected_hw { - allocations.insert(reg, Storage::Hardware(hw_reg)); - active.push((reg, end, hw_reg, is_caller)); - } else { - next_stack_offset += 8; - allocations.insert(reg, Storage::Stack(next_stack_offset)); - } - } - - let used_callee_saved: Vec<&'static str> = used_callee_saved.into_iter().collect(); - - // Determine if a Stack Frame is required - let needs_frame = !call_indices.is_empty() - || next_stack_offset > 0 - || !used_callee_saved.is_empty() - || func.params.len() > 6; - - // 3. Prologue - writeln!(&mut self.assembly, " .text").unwrap(); - writeln!(&mut self.assembly, " .globl {}", func.name).unwrap(); - writeln!(&mut self.assembly, " .p2align 4").unwrap(); - writeln!(&mut self.assembly, " .type {},@function", func.name).unwrap(); - writeln!(&mut self.assembly, "{}:", func.name).unwrap(); - - if needs_frame { - writeln!(&mut self.assembly, " pushq %rbp").unwrap(); - writeln!(&mut self.assembly, " movq %rsp, %rbp").unwrap(); - - for reg in &used_callee_saved { - writeln!(&mut self.assembly, " pushq %{}", reg).unwrap(); - } - - let s = next_stack_offset; - let rem = (used_callee_saved.len() * 8 + s) % 16; - let stack_adj = if rem != 0 { s + (16 - rem) } else { s }; - - if stack_adj > 0 { - writeln!(&mut self.assembly, " subq ${}, %rsp", stack_adj).unwrap(); - } - } - - // 4. Map ABI Arguments - let arg_regs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - for (i, (_, reg)) in func.params.iter().enumerate() { - let dest_str = self.format_dest(*reg, &allocations); - if i < 6 { - writeln!( - &mut self.assembly, - " movq %{}, {}", - arg_regs[i], dest_str - ) - .unwrap(); - } else { - let caller_offset = 16 + ((i - 6) * 8); - writeln!(&mut self.assembly, " movq {}(%rbp), %rax", caller_offset).unwrap(); - writeln!(&mut self.assembly, " movq %rax, {}", dest_str).unwrap(); - } - } - - // 5. Compile Blocks - let num_blocks = func.blocks.len(); - for i in 0..num_blocks { - let block = &func.blocks[i]; - let next_block_id = if i + 1 < num_blocks { - Some(func.blocks[i + 1].id) - } else { - None - }; - - writeln!(&mut self.assembly, ".L{}_block_{}:", func.name, block.id.0).unwrap(); - - // Peephole Optimization: Fuse an immediately preceding ICmp into the Branch - let mut fused_cmp = None; - if let Terminator::Branch { - cond: Operand::Register(cond_reg), - .. - } = block.terminator - && let Some(Instruction::Binary { - dest, - op: BinaryOp::ICmp(cmp_op), - src1, - src2, - .. - }) = block.instructions.last() - && cond_reg == *dest - { - fused_cmp = Some((*cmp_op, *src1, *src2)); - } - - // If we fused the comparison, we omit the last instruction from standard compilation - let inst_limit = if fused_cmp.is_some() { - block.instructions.len() - 1 - } else { - block.instructions.len() - }; - - for inst in &block.instructions[..inst_limit] { - self.compile_instruction(inst, &allocations); - } - - self.compile_terminator( - &block.terminator, - &func.name, - &allocations, - next_block_id, - fused_cmp, - ); - } - - // 6. Unified Epilogue - writeln!(&mut self.assembly, ".L{}_epilogue:", func.name).unwrap(); - - if needs_frame { - let pushes_size = used_callee_saved.len() * 8; - if pushes_size > 0 { - writeln!(&mut self.assembly, " leaq -{}(%rbp), %rsp", pushes_size).unwrap(); - for reg in used_callee_saved.iter().rev() { - writeln!(&mut self.assembly, " popq %{}", reg).unwrap(); - } - } else { - writeln!(&mut self.assembly, " movq %rbp, %rsp").unwrap(); - } - - writeln!(&mut self.assembly, " popq %rbp").unwrap(); - } - - writeln!(&mut self.assembly, " ret\n").unwrap(); - } - - fn compile_instruction(&mut self, inst: &Instruction, allocs: &HashMap) { - match inst { - Instruction::Alloc { .. } => {} // Stack space is already reserved in prologue - Instruction::Assign { register, operand } => { - let dest = self.format_dest(*register, allocs); - let src = self.resolve_op(operand, "%rax", allocs); - self.emit_mov(&src, &dest); - } - Instruction::Load { dest, src, .. } => { - let dest_str = self.format_dest(*dest, allocs); - - // OPTIMIZATION: If reading from an Alloc pointer, read directly from the stack offset - if let Operand::Register(r) = src - && let Some(Storage::Alloc(off)) = allocs.get(r) - { - self.emit_mov(&format!("-{}(%rbp)", off), &dest_str); - return; - } - - let src_str = self.resolve_op(src, "%rax", allocs); - let addr_reg = if src_str.starts_with('-') { - writeln!(&mut self.assembly, " movq {}, %rax", src_str).unwrap(); - "%rax".to_string() - } else { - src_str - }; - - writeln!(&mut self.assembly, " movq ({}), %r10", addr_reg).unwrap(); - self.emit_mov("%r10", &dest_str); - } - Instruction::Store { dest, src, .. } => { - let src_str = self.resolve_op(src, "%rax", allocs); - - // OPTIMIZATION: If writing to an Alloc pointer, write directly to the stack offset - if let Operand::Register(r) = dest - && let Some(Storage::Alloc(off)) = allocs.get(r) - { - self.emit_mov(&src_str, &format!("-{}(%rbp)", off)); - return; - } - - let dest_str = self.resolve_op(dest, "%r10", allocs); - let addr_reg = if dest_str.starts_with('-') { - writeln!(&mut self.assembly, " movq {}, %r10", dest_str).unwrap(); - "%r10".to_string() - } else { - dest_str - }; - - let val_reg = if src_str.starts_with('-') || src_str.starts_with('$') { - writeln!(&mut self.assembly, " movq {}, %rax", src_str).unwrap(); - "%rax".to_string() - } else { - src_str - }; - - writeln!(&mut self.assembly, " movq {}, ({})", val_reg, addr_reg).unwrap(); - } - Instruction::Unary { dest, op, src, .. } => { - let dest_str = self.format_dest(*dest, allocs); - let src_str = self.resolve_op(src, "%rax", allocs); - - self.emit_mov(&src_str, &dest_str); - match op { - UnaryOp::INeg => writeln!(&mut self.assembly, " negq {}", dest_str).unwrap(), - } - } - Instruction::Binary { - dest, - op, - src1, - src2, - .. - } => { - let dest_str = self.format_dest(*dest, allocs); - let src1_str = self.resolve_op(src1, "%r10", allocs); - let src2_str = self.resolve_op(src2, "%r11", allocs); - - match op { - BinaryOp::Add | BinaryOp::Sub | BinaryOp::SMul => { - let is_add = matches!(op, BinaryOp::Add); - let dest_hw = !dest_str.starts_with('-'); - let src1_hw = !src1_str.starts_with('-'); - let src2_hw = !src2_str.starts_with('-'); - let src2_imm = src2_str.starts_with('$'); - - if dest_str != src1_str - && dest_hw - && src1_hw - && src2_imm - && !matches!(op, BinaryOp::UMul) - { - let val: i64 = src2_str[1..].parse().unwrap(); - let offset = if is_add { val } else { -val }; - writeln!( - &mut self.assembly, - " leaq {}({}), {}", - offset, src1_str, dest_str - ) - .unwrap(); - } else if dest_str != src1_str && dest_hw && src1_hw && src2_hw && is_add { - writeln!( - &mut self.assembly, - " leaq ({},{}), {}", - src1_str, src2_str, dest_str - ) - .unwrap(); - } else { - self.emit_mov(&src1_str, &dest_str); - let mnemonic = match op { - BinaryOp::Add => "addq", - BinaryOp::Sub => "subq", - BinaryOp::SMul => "imulq", - _ => unreachable!(), - }; - if dest_str.starts_with('-') && src2_str.starts_with('-') { - writeln!(&mut self.assembly, " movq {}, %rax", src2_str) - .unwrap(); - writeln!(&mut self.assembly, " {} %rax, {}", mnemonic, dest_str) - .unwrap(); - } else { - writeln!( - &mut self.assembly, - " {} {}, {}", - mnemonic, src2_str, dest_str - ) - .unwrap(); - } - } - } - BinaryOp::UMul => { - // Unsigned multiply (mulq) strictly takes 1 operand and multiplies it by %rax. - writeln!(&mut self.assembly, " movq {}, %rax", src1_str).unwrap(); - - // mulq cannot take an immediate value, so route it through a scratch register if needed - if src2_str.starts_with('$') { - writeln!(&mut self.assembly, " movq {}, %r10", src2_str).unwrap(); - writeln!(&mut self.assembly, " mulq %r10").unwrap(); - } else { - writeln!(&mut self.assembly, " mulq {}", src2_str).unwrap(); - } - - // The lower 64 bits of the result are in %rax - self.emit_mov("%rax", &dest_str); - } - BinaryOp::SDiv | BinaryOp::SRem => { - writeln!(&mut self.assembly, " movq {}, %rax", src1_str).unwrap(); - writeln!(&mut self.assembly, " cqto").unwrap(); - - if src2_str.starts_with('$') { - writeln!(&mut self.assembly, " movq {}, %r10", src2_str).unwrap(); - writeln!(&mut self.assembly, " idivq %r10").unwrap(); - } else { - writeln!(&mut self.assembly, " idivq {}", src2_str).unwrap(); - } - - let result_reg = if let BinaryOp::URem = op { - "%rdx" - } else { - "%rax" - }; - self.emit_mov(result_reg, &dest_str); - } - BinaryOp::UDiv | BinaryOp::URem => { - writeln!(&mut self.assembly, " movq {}, %rax", src1_str).unwrap(); - writeln!(&mut self.assembly, " cqto").unwrap(); - - if src2_str.starts_with('$') { - writeln!(&mut self.assembly, " movq {}, %r10", src2_str).unwrap(); - writeln!(&mut self.assembly, " divq %r10").unwrap(); - } else { - writeln!(&mut self.assembly, " divq {}", src2_str).unwrap(); - } - - let result_reg = if let BinaryOp::URem = op { - "%rdx" - } else { - "%rax" - }; - self.emit_mov(result_reg, &dest_str); - } - BinaryOp::ICmp(cmp) => { - if (src1_str.starts_with('-') && src2_str.starts_with('-')) - || src1_str.starts_with('$') - { - writeln!(&mut self.assembly, " movq {}, %rax", src1_str).unwrap(); - writeln!(&mut self.assembly, " cmpq {}, %rax", src2_str).unwrap(); - } else { - writeln!(&mut self.assembly, " cmpq {}, {}", src2_str, src1_str) - .unwrap(); - } - - let set_cc = match cmp { - ICmpOp::Eq => "sete", - ICmpOp::Ne => "setne", - ICmpOp::Slt => "setl", - ICmpOp::Sle => "setle", - ICmpOp::Sgt => "setg", - ICmpOp::Sge => "setge", - ICmpOp::Ult => "setb", - ICmpOp::Ule => "setbe", - ICmpOp::Ugt => "seta", - ICmpOp::Uge => "setae", - }; - - writeln!(&mut self.assembly, " {} %al", set_cc).unwrap(); - writeln!(&mut self.assembly, " movzbq %al, %rax").unwrap(); - self.emit_mov("%rax", &dest_str); - } - } - } - Instruction::Call { - dest, func, args, .. - } => { - let arg_regs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - for (i, (_, arg_op)) in args.iter().enumerate() { - let src = self.resolve_op(arg_op, "%r10", allocs); - if i < 6 { - self.emit_mov(&src, &format!("%{}", arg_regs[i])); - } else { - if src.starts_with('-') { - writeln!(&mut self.assembly, " movq {}, %rax", src).unwrap(); - writeln!(&mut self.assembly, " pushq %rax").unwrap(); - } else { - writeln!(&mut self.assembly, " pushq {}", src).unwrap(); - } - } - } - - let function_name = self - .module - .functions - .iter() - .find_map(|f| (f.id == *func).then(|| f.name.clone())) - .unwrap(); - - writeln!(&mut self.assembly, " call {}", function_name).unwrap(); - if args.len() > 6 { - let cleanup_size = (args.len() - 6) * 8; - writeln!(&mut self.assembly, " addq ${}, %rsp", cleanup_size).unwrap(); - } - let dest_str = self.format_dest(*dest, allocs); - self.emit_mov("%rax", &dest_str); - } - Instruction::Phi { .. } => { - unreachable!( - "Phi nodes cannot be compiled to x86-64 natively. You must run an SSA-Destruction (Out-of-SSA) pass before code generation!" - ); - } - } - } - - fn compile_terminator( - &mut self, - term: &Terminator, - func_name: &str, - allocs: &HashMap, - next_block_id: Option, - fused_cmp: Option<(ICmpOp, Operand, Operand)>, - ) { - match term { - Terminator::Return { value, .. } => { - if let Some(val) = value { - let src = self.resolve_op(val, "%r10", allocs); - self.emit_mov(&src, "%rax"); - } - - // If there is no next block we can fallthrough otherwise emit a jump - if next_block_id.is_some() { - writeln!(&mut self.assembly, " jmp .L{}_epilogue", func_name).unwrap(); - } - } - Terminator::Jump(target) => { - if Some(*target) != next_block_id { - writeln!( - &mut self.assembly, - " jmp .L{}_block_{}", - func_name, target.0 - ) - .unwrap(); - } - } - Terminator::Branch { - cond, - then_block, - else_block, - } => { - // Determine the condition codes based on whether we fused an ICmp - let (jump_cond_true, jump_cond_false) = - if let Some((cmp_op, src1, src2)) = fused_cmp { - let src1_str = self.resolve_op(&src1, "%r10", allocs); - let src2_str = self.resolve_op(&src2, "%r10", allocs); - - // Emit the comparison directly inside the terminator - if (src1_str.starts_with('-') && src2_str.starts_with('-')) - || src1_str.starts_with('$') - { - writeln!(&mut self.assembly, " movq {}, %rax", src1_str).unwrap(); - writeln!(&mut self.assembly, " cmpq {}, %rax", src2_str).unwrap(); - } else { - writeln!(&mut self.assembly, " cmpq {}, {}", src2_str, src1_str) - .unwrap(); - } - - // Map IR ICmpOp to native AT&T condition suffixes (true_jump, false_jump) - match cmp_op { - ICmpOp::Eq => ("e", "ne"), - ICmpOp::Ne => ("ne", "e"), - ICmpOp::Slt => ("l", "ge"), - ICmpOp::Sle => ("le", "g"), - ICmpOp::Sgt => ("g", "le"), - ICmpOp::Sge => ("ge", "l"), - ICmpOp::Ult => ("b", "ae"), - ICmpOp::Ule => ("be", "a"), - ICmpOp::Ugt => ("a", "be"), - ICmpOp::Uge => ("ae", "b"), - } - } else { - // Standard fallback: evaluating an isolated boolean - let cond_str = self.resolve_op(cond, "%r10", allocs); - if cond_str.starts_with('$') { - writeln!(&mut self.assembly, " movq {}, %rax", cond_str).unwrap(); - writeln!(&mut self.assembly, " testq %rax, %rax").unwrap(); - } else { - writeln!(&mut self.assembly, " testq {}, {}", cond_str, cond_str) - .unwrap(); - } - ("nz", "z") // true = not zero, false = zero - }; - - // Fallthrough logic cleanly applied to dynamically `d jump conditions - if Some(*else_block) == next_block_id { - writeln!( - &mut self.assembly, - " j{} .L{}_block_{}", - jump_cond_true, func_name, then_block.0 - ) - .unwrap(); - } else if Some(*then_block) == next_block_id { - writeln!( - &mut self.assembly, - " j{} .L{}_block_{}", - jump_cond_false, func_name, else_block.0 - ) - .unwrap(); - } else { - writeln!( - &mut self.assembly, - " j{} .L{}_block_{}", - jump_cond_false, func_name, else_block.0 - ) - .unwrap(); - writeln!( - &mut self.assembly, - " jmp .L{}_block_{}", - func_name, then_block.0 - ) - .unwrap(); - } - } - Terminator::Unknown => panic!("Cannot compile Unknown terminator"), - } - } - - // Helpers - - fn format_dest(&self, reg: Register, allocs: &HashMap) -> String { - match allocs.get(®).unwrap() { - Storage::Hardware(hw) => format!("%{}", hw), - Storage::Stack(off) | Storage::Alloc(off) => format!("-{}(%rbp)", off), - } - } - - /// Emits a move instruction, gracefully handling memory-to-memory constraints - fn emit_mov(&mut self, src: &str, dest: &str) { - if src == dest { - return; - } - - if src.starts_with('-') && dest.starts_with('-') { - writeln!(&mut self.assembly, " movq {}, %rax", src).unwrap(); - writeln!(&mut self.assembly, " movq %rax, {}", dest).unwrap(); - } else { - writeln!(&mut self.assembly, " movq {}, {}", src, dest).unwrap(); - } - } -} diff --git a/src/backend/x86_64/codegen.rs b/src/backend/x86_64/codegen.rs new file mode 100644 index 0000000..baee7e4 --- /dev/null +++ b/src/backend/x86_64/codegen.rs @@ -0,0 +1,119 @@ +use crate::backend::x86_64::types::*; + +pub struct Codegen { + pub instructions: Vec, +} + +impl Default for Codegen { + fn default() -> Self { + Self::new() + } +} + +impl Codegen { + pub fn new() -> Self { + Self { + instructions: Vec::new(), + } + } + + /// Emits a standard `mov` instruction. + pub fn emit_mov(&mut self, width: OperandWidth, src: Operand, dest: Operand) { + // Validation 1: Destination cannot be an immediate constant + if matches!(dest, Operand::Imm(_)) { + panic!("x86-64 error: Cannot use an immediate as a destination operand."); + } + + // Validation 2: Memory-to-Memory moves are physically impossible on x86-64 + if matches!(src, Operand::Mem { .. }) && matches!(dest, Operand::Mem { .. }) { + panic!( + "x86-64 error: Memory-to-memory moves are not allowed. Route the value through a scratch register first." + ); + } + + self.instructions.push(Instruction::Mov(width, src, dest)); + } + + /// Emits a 2-operand arithmetic instruction like `add`, `sub`, or `cmp`. + pub fn emit_binary( + &mut self, + op: fn(OperandWidth, Operand, Operand) -> Instruction, + width: OperandWidth, + src: Operand, + dest: Operand, + ) { + if matches!(dest, Operand::Imm(_)) { + panic!("x86-64 error: Destination of a binary operation cannot be an immediate."); + } + + if matches!(src, Operand::Mem { .. }) && matches!(dest, Operand::Mem { .. }) { + panic!( + "x86-64 error: Binary operations cannot act on two memory addresses simultaneously." + ); + } + + self.instructions.push(op(width, src, dest)); + } + + /// Emits a `lea` (Load Effective Address) instruction. + /// This uses the CPU's memory addressing circuitry to perform fast, non-destructive math. + pub fn emit_lea(&mut self, width: OperandWidth, src: Operand, dest: Operand) { + // Validation 1: Destination MUST be a hardware register. + // You cannot calculate an address and store it directly into memory or an immediate. + if !matches!(dest, Operand::Reg(_)) { + panic!("x86-64 error: Destination of a `lea` instruction must be a hardware register."); + } + + // Validation 2: Source MUST be a memory operand. + // Even if we are just using it for math (e.g., `leaq -1(%r15), %rdi`), + // the instruction strictly expects memory addressing syntax. + if !matches!(src, Operand::Mem { .. }) { + panic!("x86-64 error: Source of a `lea` instruction must be a memory operand."); + } + + self.instructions.push(Instruction::Lea(width, src, dest)); + } + + /// Emits the 1-operand unsigned multiply (`mul`). + /// x86-64 strictly multiplies the operand by %rax and stores the result in %rdx:%rax. + pub fn emit_umul(&mut self, width: OperandWidth, src: Operand) { + // Validation: mul cannot take an immediate value directly. + if matches!(src, Operand::Imm(_)) { + panic!( + "x86-64 error: Unsigned `mul` cannot take an immediate operand. Load it into a register first." + ); + } + + self.instructions.push(Instruction::Mul(width, src)); + } + + /// Emits a `push` instruction. + /// In 64-bit mode, pushes are almost universally 64-bit operations. + pub fn emit_push(&mut self, src: Operand) { + // Note: x86-64 allows pushing memory, registers, and 32-bit sign-extended immediates. + self.instructions.push(Instruction::Push(src)); + } + + /// Emits a `pop` instruction. + pub fn emit_pop(&mut self, dest: Operand) { + if matches!(dest, Operand::Imm(_)) { + panic!("x86-64 error: Cannot pop into an immediate value."); + } + self.instructions.push(Instruction::Pop(dest)); + } + + /// Emits a `setcc` instruction (e.g., `sete`, `setne`). + /// Hardware constraint: setcc ONLY writes to 8-bit (Byte) operands. + pub fn emit_setcc(&mut self, cc: ConditionCode, dest: Operand) { + if matches!(dest, Operand::Imm(_)) { + panic!("x86-64 error: Cannot setcc into an immediate value."); + } + + self.instructions.push(Instruction::Setcc(cc, dest)); + } + + /// Helper to grab the finalized list of instructions + pub fn finish(self) -> Vec { + self.instructions + } +} diff --git a/src/backend/x86_64/mod.rs b/src/backend/x86_64/mod.rs new file mode 100644 index 0000000..a217d94 --- /dev/null +++ b/src/backend/x86_64/mod.rs @@ -0,0 +1,774 @@ +pub mod codegen; +pub mod types; + +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; +use std::mem::take; + +use crate::backend::x86_64::codegen::Codegen; +use crate::backend::x86_64::types::*; +use crate::ir; + +#[derive(Clone, Copy, Debug)] +enum Storage { + Hardware(Gpr), + Stack(i32), + Alloc(i32), +} + +pub struct X86Backend<'a> { + module: &'a ir::Module, + assembly: String, + live_intervals: HashMap, + allocations: HashMap, +} + +impl<'a> X86Backend<'a> { + pub fn new(module: &'a ir::Module) -> Self { + Self { + assembly: String::new(), + module, + live_intervals: HashMap::new(), + allocations: HashMap::new(), + } + } + + fn resolve_op(&mut self, op: &ir::Operand, scratch_base: Gpr, cg: &mut Codegen) -> Operand { + match op { + ir::Operand::Integer(v) => Operand::Imm(*v as i64), + ir::Operand::Boolean(b) => Operand::Imm(if *b { 1 } else { 0 }), + ir::Operand::Register(r) => match self.allocations.get(r).unwrap() { + Storage::Hardware(hw_gpr) => Operand::Reg(*hw_gpr), + &Storage::Stack(offset) => Operand::Mem { + base: Gpr::Rbp, + offset, + }, + &Storage::Alloc(offset) => { + // Load Effective Address (LEA) to materialize the pointer + let mem_loc = Operand::Mem { + base: Gpr::Rbp, + offset, + }; + cg.emit_lea(OperandWidth::QWord, mem_loc, Operand::Reg(scratch_base)); + Operand::Reg(scratch_base) + } + }, + } + } + + fn resolve_dest(&self, reg: ir::Register) -> Operand { + match self.allocations.get(®).unwrap() { + &Storage::Hardware(hw_gpr) => Operand::Reg(hw_gpr), + &Storage::Stack(offset) | &Storage::Alloc(offset) => Operand::Mem { + base: Gpr::Rbp, + offset, + }, + } + } + + fn mark_use(&mut self, reg: ir::Register, idx: usize) { + if !self.allocations.contains_key(®) { + let entry = self.live_intervals.entry(reg).or_insert((idx, idx)); + entry.1 = idx; + } + } + + fn mark_op(&mut self, op: &ir::Operand, idx: usize) { + if let ir::Operand::Register(r) = op { + self.mark_use(*r, idx); + } + } + + pub fn compile_module(mut self) -> String { + for func in &self.module.functions { + self.compile_function(func); + } + + self.assembly + } + + fn compile_function(&mut self, func: &ir::Function) { + // 0. Pre-Pass: Handle Allocations + let mut next_stack_offset = 0; + + for block in &func.blocks { + for inst in &block.instructions { + if let ir::Instruction::Alloc { dest, .. } = inst { + next_stack_offset -= 8; + self.allocations + .insert(*dest, Storage::Alloc(next_stack_offset)); + } + } + } + + // 1. Liveness Analysis & Call Tracking + let mut call_indices = Vec::new(); + let mut hints: HashMap = HashMap::new(); + let mut inst_idx = 0; + + for (_, reg) in &func.params { + self.mark_use(*reg, inst_idx); + } + inst_idx += 1; + + for block in &func.blocks { + for inst in &block.instructions { + match inst { + ir::Instruction::Alloc { .. } => {} + ir::Instruction::Call { dest, args, .. } => { + self.mark_use(*dest, inst_idx); + let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9]; + for (i, (_, arg_op)) in args.iter().enumerate() { + self.mark_op(arg_op, inst_idx); + if i < 6 + && let ir::Operand::Register(r) = arg_op + { + hints.insert(*r, arg_regs[i]); + } + } + call_indices.push(inst_idx); + } + ir::Instruction::Load { dest, src, .. } => { + self.mark_use(*dest, inst_idx); + self.mark_op(src, inst_idx); + } + ir::Instruction::Store { dest, src, .. } => { + self.mark_op(dest, inst_idx); + self.mark_op(src, inst_idx); + } + ir::Instruction::Copy { + dest: register, + src: operand, + } => { + self.mark_use(*register, inst_idx); + self.mark_op(operand, inst_idx); + } + ir::Instruction::Binary { + dest, src1, src2, .. + } => { + self.mark_use(*dest, inst_idx); + self.mark_op(src1, inst_idx); + self.mark_op(src2, inst_idx); + } + ir::Instruction::Unary { dest, src, .. } => { + self.mark_use(*dest, inst_idx); + self.mark_op(src, inst_idx); + } + ir::Instruction::Phi { dest, sources, .. } => { + self.mark_use(*dest, inst_idx); + for (op, _) in sources { + self.mark_op(op, inst_idx); + } + } + } + inst_idx += 1; + } + match &block.terminator { + ir::Terminator::Branch { cond, .. } => self.mark_op(cond, inst_idx), + ir::Terminator::Return { + value: Some(val), .. + } => self.mark_op(val, inst_idx), + _ => {} + } + inst_idx += 1; + } + + // 2. ABI-Aware Linear Scan Allocation + let mut free_callee_saved = vec![Gpr::Rbx, Gpr::R12, Gpr::R13, Gpr::R14, Gpr::R15]; + let mut free_caller_saved = vec![Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9]; + + let mut active: Vec<(ir::Register, usize, Gpr, bool)> = Vec::new(); + let mut used_callee_saved = HashSet::new(); + + let live_intervals = take(&mut self.live_intervals); + let mut intervals_sorted: Vec<_> = live_intervals.into_iter().collect(); + intervals_sorted.sort_by_key(|(_, (start, _))| *start); + + for (reg, (start, end)) in intervals_sorted { + active.retain(|(_, active_end, hw_reg, is_caller)| { + if *active_end < start { + if *is_caller { + free_caller_saved.push(*hw_reg); + } else { + free_callee_saved.push(*hw_reg); + } + false + } else { + true + } + }); + + let crosses_call = call_indices + .iter() + .any(|&c_idx| c_idx > start && c_idx < end); + + let mut selected_hw = None; + let mut is_caller = false; + + if !crosses_call { + if let Some(&hint_reg) = hints.get(®) + && let Some(pos) = free_caller_saved.iter().position(|&r| r == hint_reg) + { + selected_hw = Some(free_caller_saved.remove(pos)); + is_caller = true; + } + if selected_hw.is_none() + && let Some(r) = free_caller_saved.pop() + { + selected_hw = Some(r); + is_caller = true; + } + } + + if selected_hw.is_none() + && let Some(r) = free_callee_saved.pop() + { + selected_hw = Some(r); + + used_callee_saved.insert(r); + is_caller = false; + } + + if let Some(hw_reg) = selected_hw { + self.allocations.insert(reg, Storage::Hardware(hw_reg)); + active.push((reg, end, hw_reg, is_caller)); + } else { + next_stack_offset += 8; + self.allocations + .insert(reg, Storage::Stack(next_stack_offset)); + } + } + + let used_callee_saved: Vec = used_callee_saved.into_iter().collect(); + + // Determine if a Stack Frame is required + let needs_frame = !call_indices.is_empty() + || next_stack_offset < 0 + || !used_callee_saved.is_empty() + || func.params.len() > 6; + + // 3. Prologue + writeln!(&mut self.assembly, " .text").unwrap(); + writeln!(&mut self.assembly, " .globl {}", func.name).unwrap(); + writeln!(&mut self.assembly, " .p2align 4").unwrap(); + writeln!(&mut self.assembly, " .type {},@function", func.name).unwrap(); + writeln!(&mut self.assembly, "{}:", func.name).unwrap(); + + let mut pro_cg = Codegen::new(); + + if needs_frame { + pro_cg.emit_push(Operand::Reg(Gpr::Rbp)); + pro_cg.emit_mov( + OperandWidth::QWord, + Operand::Reg(Gpr::Rsp), + Operand::Reg(Gpr::Rbp), + ); + + for ® in &used_callee_saved { + pro_cg.emit_push(Operand::Reg(reg)); + } + + let local_vars_size = next_stack_offset.unsigned_abs() as usize; + let pushes_size = used_callee_saved.len() * 8; + + // 16-byte alignment calculation + let rem = (pushes_size + local_vars_size) % 16; + let stack_adj = if rem != 0 { + local_vars_size + (16 - rem) + } else { + local_vars_size + }; + + if stack_adj > 0 { + pro_cg.emit_binary( + Instruction::Sub, + OperandWidth::QWord, + Operand::Imm(stack_adj as i64), + Operand::Reg(Gpr::Rsp), + ); + } + } + + // 4. Map ABI Arguments + let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9]; + for (i, (ty, reg)) in func.params.iter().enumerate() { + let width = OperandWidth::from_type(ty); + let dest_op = self.resolve_dest(*reg); + + if i < 6 { + pro_cg.emit_mov(width, Operand::Reg(arg_regs[i]), dest_op); + } else { + let caller_offset = 16 + ((i - 6) * 8); + let caller_mem = Operand::Mem { + base: Gpr::Rbp, + offset: caller_offset as i32, + }; + + // x86-64 constraint: Memory-to-memory moves must route through a register + if matches!(dest_op, Operand::Mem { .. }) { + pro_cg.emit_mov(width, caller_mem, Operand::Reg(Gpr::Rax)); + pro_cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op); + } else { + pro_cg.emit_mov(width, caller_mem, dest_op); + } + } + } + + // Flush prologue and ABI setup instructions to the assembly string + for inst in pro_cg.finish() { + writeln!(&mut self.assembly, " {}", inst).unwrap(); + } + + // 5. Compile Blocks + let num_blocks = func.blocks.len(); + for i in 0..num_blocks { + let block = &func.blocks[i]; + let next_block_id = if i + 1 < num_blocks { + Some(func.blocks[i + 1].id) + } else { + None + }; + + writeln!(&mut self.assembly, ".L{}_block_{}:", func.name, block.id.0).unwrap(); + + // Peephole Optimization: Fuse an immediately preceding ICmp into the Branch + let mut fused_cmp = None; + if let ir::Terminator::Branch { + cond: ir::Operand::Register(cond_reg), + .. + } = block.terminator + && let Some(ir::Instruction::Binary { + dest, + op: ir::BinaryOp::ICmp(cmp_op), + src1, + src2, + .. + }) = block.instructions.last() + && cond_reg == *dest + { + fused_cmp = Some((*cmp_op, *src1, *src2)); + } + + // If we fused the comparison, we omit the last instruction from standard compilation + let inst_limit = if fused_cmp.is_some() { + block.instructions.len() - 1 + } else { + block.instructions.len() + }; + + let mut block_instructions = Vec::new(); + + for inst in &block.instructions[..inst_limit] { + self.compile_instruction(inst, &mut block_instructions); + } + + self.compile_terminator( + &block.terminator, + &func.name, + next_block_id, + fused_cmp, + &mut block_instructions, + ); + + for instr in block_instructions { + writeln!(&mut self.assembly, " {}", instr).unwrap(); + } + } + + // 6. Unified Epilogue + writeln!(&mut self.assembly, ".L{}_epilogue:", func.name).unwrap(); + + let mut epi_cg = Codegen::new(); + + if needs_frame { + let pushes_size = used_callee_saved.len() * 8; + if pushes_size > 0 { + let sp_restore = Operand::Mem { + base: Gpr::Rbp, + offset: -(pushes_size as i32), + }; + epi_cg.emit_lea(OperandWidth::QWord, sp_restore, Operand::Reg(Gpr::Rsp)); + + for ® in used_callee_saved.iter().rev() { + epi_cg.emit_pop(Operand::Reg(reg)); + } + } else { + epi_cg.emit_mov( + OperandWidth::QWord, + Operand::Reg(Gpr::Rbp), + Operand::Reg(Gpr::Rsp), + ); + } + + epi_cg.emit_pop(Operand::Reg(Gpr::Rbp)); + } + + epi_cg.instructions.push(Instruction::Ret); + + // Flush epilogue instructions to the assembly string + for inst in epi_cg.finish() { + writeln!(&mut self.assembly, " {}", inst).unwrap(); + } + + writeln!(&mut self.assembly).unwrap(); + } + + fn compile_instruction( + &mut self, + inst: &ir::Instruction, + block_instructions: &mut Vec, + ) { + let mut cg = Codegen::new(); + + match inst { + ir::Instruction::Alloc { .. } => {} // Handled in prologue + + ir::Instruction::Copy { dest, src } => { + // Assuming everything is 64-bit for a raw copy unless we track sizes perfectly + let width = OperandWidth::QWord; + let dest_op = self.resolve_dest(*dest); + let src_op = self.resolve_op(src, Gpr::Rax, &mut cg); + + if matches!(dest_op, Operand::Mem { .. }) && matches!(src_op, Operand::Mem { .. }) { + cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax)); + cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op); + } else { + cg.emit_mov(width, src_op, dest_op); + } + } + + ir::Instruction::Load { ty, dest, src } => { + let width = OperandWidth::from_type(ty); + let dest_op = self.resolve_dest(*dest); + + // If loading directly from an Alloc, we can bypass pointer materialization + if let ir::Operand::Register(r) = src + && let Some(&Storage::Alloc(offset)) = self.allocations.get(r) + { + let mem = Operand::Mem { + base: Gpr::Rbp, + offset, + }; + cg.emit_mov(width, mem, dest_op); + block_instructions.extend(cg.finish()); + return; + } + + // Standard pointer dereference + let ptr_reg = self.resolve_op(src, Gpr::Rax, &mut cg); + + // Route ptr into R10 if it's currently sitting in memory + let final_ptr = if matches!(ptr_reg, Operand::Mem { .. }) { + cg.emit_mov(OperandWidth::QWord, ptr_reg, Operand::Reg(Gpr::R10)); + Gpr::R10 + } else if let Operand::Reg(g) = ptr_reg { + g + } else { + unreachable!() + }; + + let deref = Operand::Mem { + base: final_ptr, + offset: 0, + }; + + if matches!(dest_op, Operand::Mem { .. }) { + cg.emit_mov(width, deref, Operand::Reg(Gpr::R11)); + cg.emit_mov(width, Operand::Reg(Gpr::R11), dest_op); + } else { + cg.emit_mov(width, deref, dest_op); + } + } + + ir::Instruction::Store { ty, dest, src } => { + let width = OperandWidth::from_type(ty); + let src_op = self.resolve_op(src, Gpr::Rax, &mut cg); + + // If storing directly to an Alloc, bypass pointer materialization + if let ir::Operand::Register(r) = dest + && let Some(&Storage::Alloc(offset)) = self.allocations.get(r) + { + let mem = Operand::Mem { + base: Gpr::Rbp, + offset, + }; + if matches!(src_op, Operand::Mem { .. }) || matches!(src_op, Operand::Imm(_)) { + cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax)); + cg.emit_mov(width, Operand::Reg(Gpr::Rax), mem); + } else { + cg.emit_mov(width, src_op, mem); + } + block_instructions.extend(cg.finish()); + return; + } + + let ptr_reg = self.resolve_op(dest, Gpr::R10, &mut cg); + let final_ptr = if matches!(ptr_reg, Operand::Mem { .. }) { + cg.emit_mov(OperandWidth::QWord, ptr_reg, Operand::Reg(Gpr::R10)); + Gpr::R10 + } else if let Operand::Reg(g) = ptr_reg { + g + } else { + unreachable!() + }; + + let deref = Operand::Mem { + base: final_ptr, + offset: 0, + }; + + if matches!(src_op, Operand::Mem { .. }) || matches!(src_op, Operand::Imm(_)) { + cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax)); + cg.emit_mov(width, Operand::Reg(Gpr::Rax), deref); + } else { + cg.emit_mov(width, src_op, deref); + } + } + + ir::Instruction::Binary { + dest, + result_ty, + op, + src1, + src2, + } => { + let width = OperandWidth::from_type(result_ty); + let dest_op = self.resolve_dest(*dest); + let src1_op = self.resolve_op(src1, Gpr::R10, &mut cg); + let src2_op = self.resolve_op(src2, Gpr::R11, &mut cg); + + match op { + ir::BinaryOp::Add | ir::BinaryOp::Sub | ir::BinaryOp::SMul => { + let x86_op = match op { + ir::BinaryOp::Add => Instruction::Add, + ir::BinaryOp::Sub => Instruction::Sub, + ir::BinaryOp::SMul => Instruction::IMul, + _ => unreachable!(), + }; + + // Ensure dest = src1 before applying destructive x86 math + if src1_op != dest_op { + if matches!(src1_op, Operand::Mem { .. }) + && matches!(dest_op, Operand::Mem { .. }) + { + cg.emit_mov(width, src1_op, Operand::Reg(Gpr::Rax)); + cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op); + } else { + cg.emit_mov(width, src1_op, dest_op); + } + } + + if matches!(src2_op, Operand::Mem { .. }) + && matches!(dest_op, Operand::Mem { .. }) + { + cg.emit_mov(width, src2_op, Operand::Reg(Gpr::Rax)); + cg.emit_binary(x86_op, width, Operand::Reg(Gpr::Rax), dest_op); + } else { + cg.emit_binary(x86_op, width, src2_op, dest_op); + } + } + ir::BinaryOp::UMul => { + cg.emit_mov(width, src1_op, Operand::Reg(Gpr::Rax)); + if matches!(src2_op, Operand::Imm(_)) { + cg.emit_mov(width, src2_op, Operand::Reg(Gpr::R10)); + cg.emit_umul(width, Operand::Reg(Gpr::R10)); + } else { + cg.emit_umul(width, src2_op); + } + cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op); + } + // Implement Div/Rem/Cmp similarly using cg.emit_* methods... + _ => unimplemented!("Implement other binary ops"), + } + } + + ir::Instruction::Unary { + dest, + result_ty, + op, + src, + } => { + let width = OperandWidth::from_type(result_ty); + let dest_op = self.resolve_dest(*dest); + let src_op = self.resolve_op(src, Gpr::Rax, &mut cg); + + cg.emit_mov(width, src_op, dest_op); + match op { + ir::UnaryOp::INeg => cg.instructions.push(Instruction::Neg(width, dest_op)), + } + } + + ir::Instruction::Call { + dest, + result_ty, + func, + args, + } => { + let width = OperandWidth::from_type(result_ty); + let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9]; + + for (i, (ty, arg_op)) in args.iter().enumerate() { + let arg_width = OperandWidth::from_type(ty); + let src = self.resolve_op(arg_op, Gpr::R10, &mut cg); + if i < 6 { + cg.emit_mov(arg_width, src, Operand::Reg(arg_regs[i])); + } else { + if matches!(src, Operand::Mem { .. }) { + cg.emit_mov(OperandWidth::QWord, src, Operand::Reg(Gpr::Rax)); + cg.emit_push(Operand::Reg(Gpr::Rax)); + } else { + cg.emit_push(src); + } + } + } + + let func_name = self + .module + .functions + .iter() + .find_map(|f| (f.id == *func).then(|| f.name.clone())) + .unwrap(); + + cg.instructions.push(Instruction::Call(func_name)); + + if args.len() > 6 { + let cleanup = ((args.len() - 6) * 8) as i64; + cg.emit_binary( + Instruction::Add, + OperandWidth::QWord, + Operand::Imm(cleanup), + Operand::Reg(Gpr::Rsp), + ); + } + + if *result_ty != ir::Type::Void { + let dest_op = self.resolve_dest(*dest); + cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op); + } + } + + ir::Instruction::Phi { .. } => unreachable!("Run SSA Destruction pass before codegen!"), + } + + // Commit generated instructions to the block + block_instructions.extend(cg.finish()); + } + + fn compile_terminator( + &mut self, + term: &ir::Terminator, + func_name: &str, + next_block_id: Option, + fused_cmp: Option<(ir::ICmpOp, ir::Operand, ir::Operand)>, + block_instructions: &mut Vec, + ) { + let mut cg = Codegen::new(); + + match term { + ir::Terminator::Return { return_ty, value } => { + if let Some(val) = value { + let width = OperandWidth::from_type(return_ty); + let src = self.resolve_op(val, Gpr::R10, &mut cg); + cg.emit_mov(width, src, Operand::Reg(Gpr::Rax)); + } + + // If there is no next block, we can fallthrough. Otherwise, emit a jump. + if next_block_id.is_some() { + cg.instructions + .push(Instruction::Jmp(format!(".L{}_epilogue", func_name))); + } + } + + ir::Terminator::Jump(target) => { + if Some(*target) != next_block_id { + cg.instructions.push(Instruction::Jmp(format!( + ".L{}_block_{}", + func_name, target.0 + ))); + } + } + + ir::Terminator::Branch { + cond, + then_block, + else_block, + } => { + let (cc_true, cc_false) = if let Some((cmp_op, src1, src2)) = fused_cmp { + let src1_op = self.resolve_op(&src1, Gpr::R10, &mut cg); + let src2_op = self.resolve_op(&src2, Gpr::R11, &mut cg); + + // Note: IR Binary ops only track the result type (Bool for ICmp). + // To be perfectly accurate in the future, we may want to track operand types. + // For now, we assume pointers/64-bit integers (QWord) for the comparison. + let width = OperandWidth::QWord; + + // x86-64 constraints: destination (src1 in AT&T) cannot be an immediate. + // Memory-to-memory comparisons are also invalid. + if matches!(src1_op, Operand::Mem { .. }) + && matches!(src2_op, Operand::Mem { .. }) + || matches!(src1_op, Operand::Imm(_)) + { + cg.emit_mov(width, src1_op, Operand::Reg(Gpr::Rax)); + cg.instructions.push(Instruction::Cmp( + width, + src2_op, + Operand::Reg(Gpr::Rax), + )); + } else { + cg.instructions + .push(Instruction::Cmp(width, src2_op, src1_op)); + } + + match cmp_op { + ir::ICmpOp::Eq => (ConditionCode::E, ConditionCode::Ne), + ir::ICmpOp::Ne => (ConditionCode::Ne, ConditionCode::E), + ir::ICmpOp::Slt => (ConditionCode::L, ConditionCode::Ge), + ir::ICmpOp::Sle => (ConditionCode::Le, ConditionCode::G), + ir::ICmpOp::Sgt => (ConditionCode::G, ConditionCode::Le), + ir::ICmpOp::Sge => (ConditionCode::Ge, ConditionCode::L), + ir::ICmpOp::Ult => (ConditionCode::B, ConditionCode::Ae), + ir::ICmpOp::Ule => (ConditionCode::Be, ConditionCode::A), + ir::ICmpOp::Ugt => (ConditionCode::A, ConditionCode::Be), + ir::ICmpOp::Uge => (ConditionCode::Ae, ConditionCode::B), + } + } else { + // Fallback: evaluate an isolated boolean + let cond_op = self.resolve_op(cond, Gpr::R10, &mut cg); + let width = OperandWidth::Byte; // Booleans are 8-bit + + if matches!(cond_op, Operand::Imm(_)) || matches!(cond_op, Operand::Mem { .. }) + { + cg.emit_mov(width, cond_op, Operand::Reg(Gpr::Rax)); + cg.instructions.push(Instruction::Test( + width, + Operand::Reg(Gpr::Rax), + Operand::Reg(Gpr::Rax), + )); + } else { + cg.instructions + .push(Instruction::Test(width, cond_op, cond_op)); + } + + (ConditionCode::Nz, ConditionCode::Z) + }; + + let then_label = format!(".L{}_block_{}", func_name, then_block.0); + let else_label = format!(".L{}_block_{}", func_name, else_block.0); + + // Fallthrough logic cleanly applied to dynamically resolved jump conditions + if Some(*else_block) == next_block_id { + cg.instructions.push(Instruction::Jcc(cc_true, then_label)); + } else if Some(*then_block) == next_block_id { + cg.instructions.push(Instruction::Jcc(cc_false, else_label)); + } else { + cg.instructions.push(Instruction::Jcc(cc_false, else_label)); + cg.instructions.push(Instruction::Jmp(then_label)); + } + } + ir::Terminator::Unknown => panic!("Cannot compile Unknown terminator"), + } + + // Commit generated instructions to the block + block_instructions.extend(cg.finish()); + } +} diff --git a/src/backend/x86_64/types.rs b/src/backend/x86_64/types.rs new file mode 100644 index 0000000..916fea0 --- /dev/null +++ b/src/backend/x86_64/types.rs @@ -0,0 +1,360 @@ +use std::fmt; + +use crate::ir::Type; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OperandWidth { + Byte, // 8-bit (b suffix) + Word, // 16-bit (w suffix) + DWord, // 32-bit (l suffix) + QWord, // 64-bit (q suffix) +} + +impl OperandWidth { + /// Returns the AT&T assembly suffix for this width. + pub fn suffix(self) -> char { + match self { + OperandWidth::Byte => 'b', + OperandWidth::Word => 'w', + OperandWidth::DWord => 'l', + OperandWidth::QWord => 'q', + } + } + + /// Returns the operand with for a given [Type]. + pub fn from_type(ty: &Type) -> Self { + match ty { + Type::Bool | Type::I8 => OperandWidth::Byte, + Type::I16 => OperandWidth::Word, + Type::I32 => OperandWidth::DWord, + Type::I64 | Type::Ptr => OperandWidth::QWord, + Type::Void => panic!("x86-64 error: Cannot compute width of Void type"), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Gpr { + Rax, + Rcx, + Rdx, + Rbx, + Rsp, + Rbp, + Rsi, + Rdi, + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, +} + +impl Gpr { + /// Formats the register according to the requested width. + pub fn format_with_width(self, width: OperandWidth) -> &'static str { + match (self, width) { + // 64-bit (QWord) + (Gpr::Rax, OperandWidth::QWord) => "%rax", + (Gpr::Rcx, OperandWidth::QWord) => "%rcx", + (Gpr::Rdx, OperandWidth::QWord) => "%rdx", + (Gpr::Rbx, OperandWidth::QWord) => "%rbx", + (Gpr::Rsp, OperandWidth::QWord) => "%rsp", + (Gpr::Rbp, OperandWidth::QWord) => "%rbp", + (Gpr::Rsi, OperandWidth::QWord) => "%rsi", + (Gpr::Rdi, OperandWidth::QWord) => "%rdi", + (Gpr::R8, OperandWidth::QWord) => "%r8", + (Gpr::R9, OperandWidth::QWord) => "%r9", + (Gpr::R10, OperandWidth::QWord) => "%r10", + (Gpr::R11, OperandWidth::QWord) => "%r11", + (Gpr::R12, OperandWidth::QWord) => "%r12", + (Gpr::R13, OperandWidth::QWord) => "%r13", + (Gpr::R14, OperandWidth::QWord) => "%r14", + (Gpr::R15, OperandWidth::QWord) => "%r15", + + // 32-bit (DWord) + (Gpr::Rax, OperandWidth::DWord) => "%eax", + (Gpr::Rcx, OperandWidth::DWord) => "%ecx", + (Gpr::Rdx, OperandWidth::DWord) => "%edx", + (Gpr::Rbx, OperandWidth::DWord) => "%ebx", + (Gpr::Rsp, OperandWidth::DWord) => "%esp", + (Gpr::Rbp, OperandWidth::DWord) => "%ebp", + (Gpr::Rsi, OperandWidth::DWord) => "%esi", + (Gpr::Rdi, OperandWidth::DWord) => "%edi", + (Gpr::R8, OperandWidth::DWord) => "%r8d", + (Gpr::R9, OperandWidth::DWord) => "%r9d", + (Gpr::R10, OperandWidth::DWord) => "%r10d", + (Gpr::R11, OperandWidth::DWord) => "%r11d", + (Gpr::R12, OperandWidth::DWord) => "%r12d", + (Gpr::R13, OperandWidth::DWord) => "%r13d", + (Gpr::R14, OperandWidth::DWord) => "%r14d", + (Gpr::R15, OperandWidth::DWord) => "%r15d", + + // 16-bit (Word) + (Gpr::Rax, OperandWidth::Word) => "%ax", + (Gpr::Rcx, OperandWidth::Word) => "%cx", + (Gpr::Rdx, OperandWidth::Word) => "%dx", + (Gpr::Rbx, OperandWidth::Word) => "%bx", + (Gpr::Rsp, OperandWidth::Word) => "%sp", + (Gpr::Rbp, OperandWidth::Word) => "%bp", + (Gpr::Rsi, OperandWidth::Word) => "%si", + (Gpr::Rdi, OperandWidth::Word) => "%di", + (Gpr::R8, OperandWidth::Word) => "%r8w", + (Gpr::R9, OperandWidth::Word) => "%r9w", + (Gpr::R10, OperandWidth::Word) => "%r10w", + (Gpr::R11, OperandWidth::Word) => "%r11w", + (Gpr::R12, OperandWidth::Word) => "%r12w", + (Gpr::R13, OperandWidth::Word) => "%r13w", + (Gpr::R14, OperandWidth::Word) => "%r14w", + (Gpr::R15, OperandWidth::Word) => "%r15w", + + // 8-bit (Byte) + (Gpr::Rax, OperandWidth::Byte) => "%al", + (Gpr::Rcx, OperandWidth::Byte) => "%cl", + (Gpr::Rdx, OperandWidth::Byte) => "%dl", + (Gpr::Rbx, OperandWidth::Byte) => "%bl", + (Gpr::Rsp, OperandWidth::Byte) => "%spl", + (Gpr::Rbp, OperandWidth::Byte) => "%bpl", + (Gpr::Rsi, OperandWidth::Byte) => "%sil", + (Gpr::Rdi, OperandWidth::Byte) => "%dil", + (Gpr::R8, OperandWidth::Byte) => "%r8b", + (Gpr::R9, OperandWidth::Byte) => "%r9b", + (Gpr::R10, OperandWidth::Byte) => "%r10b", + (Gpr::R11, OperandWidth::Byte) => "%r11b", + (Gpr::R12, OperandWidth::Byte) => "%r12b", + (Gpr::R13, OperandWidth::Byte) => "%r13b", + (Gpr::R14, OperandWidth::Byte) => "%r14b", + (Gpr::R15, OperandWidth::Byte) => "%r15b", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ConditionCode { + E, + Ne, + L, + Le, + G, + Ge, + B, + Be, + A, + Ae, + Z, + Nz, +} + +impl fmt::Display for ConditionCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + ConditionCode::E => "e", + ConditionCode::Ne => "ne", + ConditionCode::L => "l", + ConditionCode::Le => "le", + ConditionCode::G => "g", + ConditionCode::Ge => "ge", + ConditionCode::B => "b", + ConditionCode::Be => "be", + ConditionCode::A => "a", + ConditionCode::Ae => "ae", + ConditionCode::Z => "z", + ConditionCode::Nz => "nz", + }; + write!(f, "{}", s) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Operand { + /// An immediate constant, e.g. `$5` + Imm(i64), + /// A hardware register + Reg(Gpr), + /// A memory operand: `offset(base_reg)` + Mem { base: Gpr, offset: i32 }, +} + +impl Operand { + /// Returns whether this operand is stored in memory. + pub fn is_memory(&self) -> bool { + matches!(self, Operand::Mem { .. }) + } + + /// Formats the operand into its AT&T assembly representation. + pub fn format_with_width(&self, width: OperandWidth) -> String { + match self { + Operand::Imm(val) => format!("${}", val), + Operand::Reg(gpr) => gpr.format_with_width(width).to_string(), + // Memory addresses inherently use 64-bit pointers (QWord) for the base register, + // even if the value being read/written is smaller. + Operand::Mem { base, offset } => { + let base_str = base.format_with_width(OperandWidth::QWord); + if *offset == 0 { + format!("({})", base_str) + } else { + format!("{}({})", offset, base_str) + } + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Instruction { + // Data movement + Mov(OperandWidth, Operand, Operand), // width, src, dest + Movzx(OperandWidth, OperandWidth, Operand, Operand), // src_width, dest_width, src, dest + Lea(OperandWidth, Operand, Operand), // width, src (mem), dest (reg) + + // Stack manipulation (typically 64-bit in x86-64) + Push(Operand), + Pop(Operand), + + // Arithmetic + Add(OperandWidth, Operand, Operand), + Sub(OperandWidth, Operand, Operand), + Neg(OperandWidth, Operand), + IMul(OperandWidth, Operand, Operand), // 2-operand signed multiply + Mul(OperandWidth, Operand), // 1-operand unsigned multiply (implicitly uses RAX/RDX) + IDiv(OperandWidth, Operand), + Div(OperandWidth, Operand), + Cqto, // Sign-extend RAX into RDX:RAX + + // Logic & Comparison + Cmp(OperandWidth, Operand, Operand), // width, src, dest + Test(OperandWidth, Operand, Operand), + Setcc(ConditionCode, Operand), // e.g., sete %al + + // Control Flow + Jmp(String), // Target label + Jcc(ConditionCode, String), // e.g., jne .L_block_2 + Call(String), + Ret, +} + +impl fmt::Display for Instruction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Instruction::Mov(w, src, dest) => { + write!( + f, + "mov{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Movzx(src_w, dest_w, src, dest) => { + // movz requires two suffixes, e.g., movzbl (byte to long) + write!( + f, + "movz{}{} {}, {}", + src_w.suffix(), + dest_w.suffix(), + src.format_with_width(*src_w), + dest.format_with_width(*dest_w) + ) + } + Instruction::Lea(w, src, dest) => { + write!( + f, + "lea{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Push(op) => { + write!(f, "pushq {}", op.format_with_width(OperandWidth::QWord)) + } + Instruction::Pop(op) => { + write!(f, "popq {}", op.format_with_width(OperandWidth::QWord)) + } + Instruction::Add(w, src, dest) => { + write!( + f, + "add{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Sub(w, src, dest) => { + write!( + f, + "sub{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Neg(w, dest) => { + write!(f, "neg{} {}", w.suffix(), dest.format_with_width(*w)) + } + Instruction::IMul(w, src, dest) => { + write!( + f, + "imul{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Mul(w, src) => { + write!(f, "mul{} {}", w.suffix(), src.format_with_width(*w)) + } + Instruction::IDiv(w, src) => { + write!(f, "idiv{} {}", w.suffix(), src.format_with_width(*w)) + } + Instruction::Div(w, src) => { + write!(f, "div{} {}", w.suffix(), src.format_with_width(*w)) + } + Instruction::Cqto => { + write!(f, "cqto") + } + Instruction::Cmp(w, src, dest) => { + write!( + f, + "cmp{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Test(w, src, dest) => { + write!( + f, + "test{} {}, {}", + w.suffix(), + src.format_with_width(*w), + dest.format_with_width(*w) + ) + } + Instruction::Setcc(cc, dest) => { + // setcc strictly operates on 8-bit (byte) registers + write!( + f, + "set{} {}", + cc, + dest.format_with_width(OperandWidth::Byte) + ) + } + Instruction::Jmp(label) => { + write!(f, "jmp {}", label) + } + Instruction::Jcc(cc, label) => { + write!(f, "j{} {}", cc, label) + } + Instruction::Call(label) => { + write!(f, "call {}", label) + } + Instruction::Ret => { + write!(f, "ret") + } + } + } +} diff --git a/src/ir.rs b/src/ir.rs index ad7df65..a04e425 100644 --- a/src/ir.rs +++ b/src/ir.rs @@ -92,9 +92,9 @@ pub enum Instruction { args: Vec<(Type, Operand)>, }, - Assign { - register: Register, - operand: Operand, + Copy { + dest: Register, + src: Operand, }, Phi { diff --git a/src/main.rs b/src/main.rs index 7958495..93309b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,12 +11,8 @@ fn build_test_module() -> Module { // factorial(n: i32) -> i32 let fact_id = module_builder.new_function_id(); { - let f_builder = module_builder.new_function( - fact_id, - "factorial_iterative", - vec![&i32_ty], - i32_ty.clone(), - ); + let f_builder = + module_builder.new_function(fact_id, "factorial_iterative", vec![&i32_ty], i32_ty); // 1. Block Definitions let loop_cond_block = f_builder.create_block(); @@ -24,22 +20,22 @@ fn build_test_module() -> Module { let exit_block = f_builder.create_block(); // Allocate space for 'res' (accumulator) and 'i' (counter) - let res_ptr = f_builder.build_alloc(i32_ty.clone()); - let i_ptr = f_builder.build_alloc(i32_ty.clone()); + let res_ptr = f_builder.build_alloc(i32_ty); + let i_ptr = f_builder.build_alloc(i32_ty); let n = f_builder.get_param(0).expect("Param 0 missing"); let const_1 = Operand::Integer(1); // res = 1; i = n; - f_builder.build_store(i32_ty.clone(), res_ptr.clone(), const_1.clone()); - f_builder.build_store(i32_ty.clone(), i_ptr.clone(), n); + f_builder.build_store(i32_ty, res_ptr, const_1); + f_builder.build_store(i32_ty, i_ptr, n); f_builder.build_jump(loop_cond_block); // 2. Loop Condition Block: i > 1 f_builder.switch_to_block(loop_cond_block); - let current_i = f_builder.build_load(i32_ty.clone(), i_ptr.clone()); - let is_gt_1 = f_builder.build_icmp(ICmpOp::Ugt, current_i, const_1.clone()); + let current_i = f_builder.build_load(i32_ty, i_ptr); + let is_gt_1 = f_builder.build_icmp(ICmpOp::Ugt, current_i, const_1); // If i > 1 goto body, else goto exit f_builder.build_branch(is_gt_1, loop_body_block, exit_block); @@ -47,24 +43,24 @@ fn build_test_module() -> Module { // 3. Loop Body Block: res = res * i; i = i - 1; f_builder.switch_to_block(loop_body_block); - let val_res = f_builder.build_load(i32_ty.clone(), res_ptr.clone()); - let val_i = f_builder.build_load(i32_ty.clone(), i_ptr.clone()); + let val_res = f_builder.build_load(i32_ty, res_ptr); + let val_i = f_builder.build_load(i32_ty, i_ptr); // res = res * i - let updated_res = f_builder.build_umul(i32_ty.clone(), val_res, val_i.clone()); - f_builder.build_store(i32_ty.clone(), res_ptr.clone(), updated_res); + let updated_res = f_builder.build_umul(i32_ty, val_res, val_i); + f_builder.build_store(i32_ty, res_ptr, updated_res); // i = i - 1 - let updated_i = f_builder.build_sub(i32_ty.clone(), val_i, const_1); - f_builder.build_store(i32_ty.clone(), i_ptr.clone(), updated_i); + let updated_i = f_builder.build_sub(i32_ty, val_i, const_1); + f_builder.build_store(i32_ty, i_ptr, updated_i); // Jump back to condition f_builder.build_jump(loop_cond_block); // 4. Exit Block: return res f_builder.switch_to_block(exit_block); - let final_res = f_builder.build_load(i32_ty.clone(), res_ptr); - f_builder.build_return(i32_ty.clone(), final_res); + let final_res = f_builder.build_load(i32_ty, res_ptr); + f_builder.build_return(i32_ty, final_res); } module_builder.complete_function(); @@ -75,25 +71,25 @@ fn build_test_module() -> Module { main_id, "main", vec![], // no params - i32_ty.clone(), + i32_ty, ); // 1. Allocate space for an integer on the stack - let ptr = f_builder.build_alloc(i32_ty.clone()); + let ptr = f_builder.build_alloc(i32_ty); // 2. Store the value '5' into that pointer let input_val = Operand::Integer(5); - f_builder.build_store(i32_ty.clone(), ptr.clone(), input_val); + f_builder.build_store(i32_ty, ptr, input_val); // 3. Load the value back from the pointer - let loaded_val = f_builder.build_load(i32_ty.clone(), ptr); + let loaded_val = f_builder.build_load(i32_ty, ptr); // 4. Call factorial(loaded_val) - let args = [(i32_ty.clone(), loaded_val)]; - let final_result = f_builder.build_call(i32_ty.clone(), fact_id, args.iter()); + let args = [(i32_ty, loaded_val)]; + let final_result = f_builder.build_call(i32_ty, fact_id, args.iter()); // 5. Return the result of the factorial call - f_builder.build_return(i32_ty.clone(), final_result); + f_builder.build_return(i32_ty, final_result); } module_builder.complete_function(); diff --git a/src/passes/cfp.rs b/src/passes/cfp.rs index 6d9a517..c24d1ab 100644 --- a/src/passes/cfp.rs +++ b/src/passes/cfp.rs @@ -20,9 +20,9 @@ fn fold_function_constants(func: &mut Function) { // 2. Evaluate and rewrite instructions where possible match inst { Instruction::Alloc { .. } => {} - Instruction::Assign { register, operand } => { - if is_constant(operand) { - known_constants.insert(*register, *operand); + Instruction::Copy { dest, src } => { + if is_constant(src) { + known_constants.insert(*dest, *src); } } Instruction::Binary { @@ -36,9 +36,9 @@ fn fold_function_constants(func: &mut Function) { known_constants.insert(*dest, folded); // Rewrite the evaluated Binary instruction into a clean Assign - *inst = Instruction::Assign { - register: *dest, - operand: folded, + *inst = Instruction::Copy { + dest: *dest, + src: folded, }; } } @@ -47,9 +47,9 @@ fn fold_function_constants(func: &mut Function) { known_constants.insert(*dest, folded); // Rewrite the evaluated Unary instruction into a clean Assign - *inst = Instruction::Assign { - register: *dest, - operand: folded, + *inst = Instruction::Copy { + dest: *dest, + src: folded, }; } } @@ -92,7 +92,7 @@ fn substitute_constants_in_inst(inst: &mut Instruction, constants: &HashMap {} - Instruction::Assign { operand, .. } => replace(operand), + Instruction::Copy { src, .. } => replace(src), Instruction::Load { src, .. } => replace(src), Instruction::Store { dest, src, .. } => { replace(dest); diff --git a/src/passes/cpp.rs b/src/passes/cpp.rs index 5dfaeb2..3fc235d 100644 --- a/src/passes/cpp.rs +++ b/src/passes/cpp.rs @@ -27,12 +27,12 @@ fn propagate_copies_in_func(func: &mut Function) { // 1. Scan for pure copy instructions (Assigning a Register to a Register) for block in &func.blocks { for inst in &block.instructions { - if let Instruction::Assign { register, operand } = inst { + if let Instruction::Copy { dest, src } = inst { // We only unconditionally propagate register-to-register copies here. // The constant folding pass handles propagating literals. - if let Operand::Register(_) = operand { - let root_operand = resolve(*operand, &aliases); - aliases.insert(*register, root_operand); + if let Operand::Register(_) = src { + let root_operand = resolve(*src, &aliases); + aliases.insert(*dest, root_operand); } } } @@ -74,7 +74,7 @@ fn propagate_copies_in_func(func: &mut Function) { replace(op); } } - Instruction::Assign { operand, .. } => replace(operand), + Instruction::Copy { src, .. } => replace(src), Instruction::Alloc { .. } => {} } } @@ -95,8 +95,8 @@ fn propagate_copies_in_func(func: &mut Function) { // since we just successfully propagated it everywhere it was used. !matches!( inst, - Instruction::Assign { - operand: Operand::Register(_), + Instruction::Copy { + src: Operand::Register(_), .. } ) diff --git a/src/passes/dce.rs b/src/passes/dce.rs index a52c29d..13a416e 100644 --- a/src/passes/dce.rs +++ b/src/passes/dce.rs @@ -87,7 +87,7 @@ fn eliminate_dead_code_in_func(func: &mut Function) { mark_used(arg); } } - Instruction::Assign { operand, .. } => mark_used(operand), + Instruction::Copy { src, .. } => mark_used(src), Instruction::Phi { sources, .. } => { sources.iter().for_each(|(op, _)| mark_used(op)) } @@ -112,7 +112,7 @@ fn eliminate_dead_code_in_func(func: &mut Function) { match inst { // PURE INSTRUCTIONS: Can be safely removed if their result is ignored Instruction::Alloc { dest, .. } - | Instruction::Assign { register: dest, .. } + | Instruction::Copy { dest, .. } | Instruction::Binary { dest, .. } | Instruction::Unary { dest, .. } | Instruction::Load { dest, .. } diff --git a/src/passes/des.rs b/src/passes/des.rs index 0ca9347..708fd25 100644 --- a/src/passes/des.rs +++ b/src/passes/des.rs @@ -18,9 +18,9 @@ fn destroy_ssa_in_func(func: &mut Function) { for inst in &block.instructions { if let Instruction::Phi { dest, sources, .. } = inst { for (op, pred_id) in sources { - let assign = Instruction::Assign { - register: *dest, - operand: *op, + let assign = Instruction::Copy { + dest: *dest, + src: *op, }; // Queue the assignment to be inserted into the predecessor block pending_moves.entry(*pred_id).or_default().push(assign); diff --git a/src/passes/m2r.rs b/src/passes/m2r.rs index 2e449b6..a4d3be7 100644 --- a/src/passes/m2r.rs +++ b/src/passes/m2r.rs @@ -47,7 +47,7 @@ fn promote_allocs_in_func(func: &mut Function) { escaped_allocs.insert(*r); } } - Instruction::Unary { src, .. } | Instruction::Assign { operand: src, .. } => { + Instruction::Unary { src, .. } | Instruction::Copy { src, .. } => { if let Operand::Register(r) = src { escaped_allocs.insert(*r); } @@ -128,7 +128,7 @@ fn promote_allocs_in_func(func: &mut Function) { match inst { Instruction::Alloc { dest, .. } | Instruction::Load { dest, .. } - | Instruction::Assign { register: dest, .. } + | Instruction::Copy { dest, .. } | Instruction::Binary { dest, .. } | Instruction::Unary { dest, .. } | Instruction::Call { dest, .. } @@ -214,9 +214,9 @@ fn promote_allocs_in_func(func: &mut Function) { } if promotable_allocs.contains_key(&src_reg) => { // Replace Load with direct Assign from the active definition let active_val = *local_defs.get(&src_reg).unwrap_or(&Operand::Integer(0)); - new_instructions.push(Instruction::Assign { - register: dest, - operand: active_val, + new_instructions.push(Instruction::Copy { + dest, + src: active_val, }); } _ => { diff --git a/src/printer.rs b/src/printer.rs index 0e4c361..d414031 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -211,8 +211,8 @@ impl<'a> IrPrinter<'a> { dest, result_ty, func_name, args ) } - Instruction::Assign { register, operand } => { - write!(&mut self.buffer, "{} = {}", register, operand) + Instruction::Copy { dest, src } => { + write!(&mut self.buffer, "{} = copy {}", dest, src) } Instruction::Phi { dest, diff --git a/src/validate.rs b/src/validate.rs index d257624..2e7ec1b 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -49,7 +49,10 @@ fn validate_function( Instruction::Call { result_ty, dest, .. } => (Some(*dest), Some(*result_ty)), - Instruction::Assign { register, operand } => { + Instruction::Copy { + dest: register, + src: operand, + } => { let inferred_ty = match operand { Operand::Register(r) => reg_types.get(r).copied(), Operand::Boolean(_) => Some(Type::Bool), @@ -110,7 +113,10 @@ fn validate_function( for inst in &block.instructions { match inst { Instruction::Alloc { .. } => {} - Instruction::Assign { register, operand } => { + Instruction::Copy { + dest: register, + src: operand, + } => { let expected_ty = *reg_types .get(register) .ok_or_else(|| format!("Unknown register {:?}", register))?;