use std::collections::BTreeMap; use crate::{backend::{arm_instr::{ARMInstr, AddInstr, BInstr, BlInstr, CmpInstr, ConditionCode, LoadInstr, LoadPseudoInstr, MoveInstr, MulInstr, PopInstr, PushInstr, RegisterOrImm, RsbInstr, SDivInstr, StoreInstr, SubInstr}, register_allocator::{REG_R0, REG_R1, REG_R2, REG_R3, Register, RegisterAlloc, RegisterAllocator}, types::ARMAsmVar}, ir::types::{Function, IRInstr, MoveRValue, Variable, VariableOrIntLit, VariableType}}; use crate::ir::types::BinaryOp as IRBinaryOp; use crate::ir::types::CmpOp as IRCmpOp; use crate::ir::types::UnaryOp as IRUnaryOp; pub const ARM_STACK_ALIGNMENT: usize = 8; pub struct Generator { instrs: Vec, var_inited: Vec, var_uninited: Vec, register_allocator: RegisterAllocator, } const DEFAULT_VAR_ALIGN: usize = 4; const ARG_REGS: [Register; 4] = [REG_R0, REG_R1, REG_R2, REG_R3]; fn load_variable(variable: Variable, reg_allocator: &mut RegisterAllocator, var_index_to_stack_offset: &BTreeMap, instrs: &mut Vec) -> RegisterAlloc { match variable.var_type { VariableType::Global => { let var_alloc = reg_allocator.alloc(variable).expect("Ran out of registers"); let var_reg = var_alloc.reg; // if !var_alloc.is_reused { let address_alloc = reg_allocator.alloc_any().expect("Ran out of registers"); instrs.push(LoadPseudoInstr::new(address_alloc.reg, format!("global_var_{}", variable.index))); instrs.push(LoadInstr::new(var_reg, address_alloc.reg, None)); // } var_alloc }, VariableType::ParamTemp(param_index) => { if param_index < ARG_REGS.len() { let reg = ARG_REGS[param_index]; let var_alloc = reg_allocator.alloc_reg(reg).expect("Ran out of registers"); var_alloc } else { todo!("More than 4 parameters not supported yet"); } }, _ => { let stack_offset = var_index_to_stack_offset.get(&variable.index).expect("Variable not declared"); let var_alloc = reg_allocator.alloc(variable).expect("Ran out of registers"); // if !var_alloc.is_reused { instrs.push(LoadInstr::new_stack(var_alloc.reg, *stack_offset as i32)); // } var_alloc } } } fn save_variable(variable: Variable, reg: Register, reg_allocator: &mut RegisterAllocator, var_index_to_stack_offset: &BTreeMap, instrs: &mut Vec) { match variable.var_type { VariableType::Global => { let address_alloc = reg_allocator.alloc_any().expect("Ran out of registers"); instrs.push(LoadPseudoInstr::new(address_alloc.reg, format!("global_var_{}", variable.index))); instrs.push(StoreInstr::new(reg, address_alloc.reg, None)); }, VariableType::ParamTemp(_) => { todo!() }, _ => { let stack_offset = var_index_to_stack_offset.get(&variable.index).expect("Variable not declared"); instrs.push(StoreInstr::new_stack(reg, *stack_offset as i32)); } } } impl Generator { pub fn new() -> Self { Self { instrs: Vec::new(), var_inited: Vec::new(), var_uninited: Vec::new(), register_allocator: RegisterAllocator::new(), } } pub fn emit(&mut self, ir_instrs: Vec) { for ir_instr in ir_instrs { match ir_instr { IRInstr::DefineFunc(func, args, body) => self.emit_func_def(func, args, body), IRInstr::Declare(var) => self.emit_global_decl(var), _ => unreachable!(), } } } pub fn to_text(&self) -> String { let mut text = String::new(); text.push_str(".arch armv7ve\n.arm\n.fpu vfpv4\n"); for var in &self.var_uninited { text.push_str(&format!(".comm {}, {}, {}\n", var.name, var.size, var.align)); } for var in &self.var_inited { text.push_str(&format!(".data\n.align {}\n.global {}\n.type {}, @object\n:{}\n", var.align, var.name, var.name, var.name)); text.push_str(&format!(".word 0\n")); } text.push_str(".text\n"); for instr in &self.instrs { text.push_str(&format!("{}\n", instr)); } text } fn emit_global_decl(&mut self, var: Variable) { self.var_uninited.push(ARMAsmVar { name: format!("global_var_{}", var.index), size: var.data_type.size_in_bytes(), align: DEFAULT_VAR_ALIGN, }); } fn emit_func_def(&mut self, func: Function, _args: Vec, body: Vec) { self.instrs.push(ARMInstr::FunctionHead(func.name.clone(), 4)); // Assuming 4-byte alignment for simplicity self.instrs.push(PushInstr::new_push_fp_lr()); self.instrs.push(MoveInstr::new_sp_to_fp()); self.emit_func(&func.name, body); } fn emit_func(&mut self, func_name: &str, instrs: Vec) { let mut encounter_entry = false; let mut stack_size_needed = 0; let mut var_index_to_stack_offset = BTreeMap::new(); for ir_instr in instrs { match ir_instr { IRInstr::Binary(dest, left, op, right) => self.emit_binary(dest, left, op, right, &var_index_to_stack_offset), IRInstr::Exit(v) => { if let Some(v) = v { let ret_alloc = self.register_allocator.alloc_reg(REG_R0).expect("Ran out of registers"); let ret_reg = ret_alloc.reg; let v_alloc = load_variable(v, &mut self.register_allocator, &var_index_to_stack_offset, &mut self.instrs); self.instrs.push(MoveInstr::new_uncond(ret_reg, RegisterOrImm::Reg(v_alloc.reg))); } self.instrs.push(MoveInstr::new_fp_to_sp()); self.instrs.push(PopInstr::new_pop_fp_pc()); }, IRInstr::FuncCall(func, args, ret) => self.emit_func_call(func, args, ret, &var_index_to_stack_offset), IRInstr::Move(dest, src) => self.emit_move(dest, src, &var_index_to_stack_offset), IRInstr::Declare(variable) => { assert!(!encounter_entry, "Variable declarations must come before entry instruction"); let size = variable.data_type.size_in_bytes(); stack_size_needed = (stack_size_needed + size).next_multiple_of(ARM_STACK_ALIGNMENT); var_index_to_stack_offset.insert(variable.index, stack_size_needed); }, IRInstr::Entry => { assert!(!encounter_entry, "Multiple entry instructions are not allowed"); encounter_entry = true; self.instrs.push(SubInstr::new_sp(stack_size_needed as i32)); }, IRInstr::DefineFunc(_, _, _) => unreachable!(), IRInstr::Cmp(variable, left, cmp_op, right) => self.emit_cmp(variable, left, cmp_op, right, &var_index_to_stack_offset), IRInstr::Unary(variable, unary_op, variable1) => self.emit_unary(variable, unary_op, variable1, &var_index_to_stack_offset), IRInstr::Goto(index) => self.instrs.push(BInstr::new(format!("label_{}_{}", func_name, index))), IRInstr::CondGoto(variable, true_label_index, false_label_index) => { let variable_alloc = load_variable(variable, &mut self.register_allocator, &var_index_to_stack_offset, &mut self.instrs); let variable_reg = variable_alloc.reg; self.instrs.push(CmpInstr::new(variable_reg, RegisterOrImm::Imm(0))); self.instrs.push(BInstr::new_cond(ConditionCode::Ne, format!("label_{}_{}", func_name, true_label_index))); self.instrs.push(BInstr::new(format!("label_{}_{}", func_name, false_label_index))); }, IRInstr::Label(index) => self.instrs.push(ARMInstr::Label(format!("label_{}_{}", func_name, index))), } } } fn emit_unary(&mut self, dest: Variable, unary_op: IRUnaryOp, variable: Variable, var_index_to_stack_offset: &BTreeMap) { let variable_alloc = load_variable(variable, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers"); match unary_op { IRUnaryOp::Neg => { self.instrs.push(RsbInstr::new(dest_alloc.reg, variable_alloc.reg, RegisterOrImm::Imm(0))); }, } save_variable(dest, dest_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); } fn emit_cmp(&mut self, variable: Variable, left: VariableOrIntLit, cmp_op: IRCmpOp, right: VariableOrIntLit, var_index_to_stack_offset: &BTreeMap) { let left_alloc = match left { VariableOrIntLit::Var(var) => { load_variable(var, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs) }, VariableOrIntLit::IntLit(lit) => { let alloc = self.register_allocator.alloc_any().expect("Ran out of registers"); self.instrs.push(MoveInstr::new_uncond(alloc.reg, RegisterOrImm::Imm(lit))); alloc }, }; // TODO: check left == right let right_alloc = match right { VariableOrIntLit::Var(var) => { load_variable(var, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs) }, VariableOrIntLit::IntLit(lit) => { let alloc = self.register_allocator.alloc_any().expect("Ran out of registers"); self.instrs.push(MoveInstr::new_uncond(alloc.reg, RegisterOrImm::Imm(lit))); alloc }, }; let variable_alloc = self.register_allocator.alloc(variable).expect("Ran out of registers"); let variable_reg = variable_alloc.reg; self.instrs.push(CmpInstr::new(left_alloc.reg, RegisterOrImm::Reg(right_alloc.reg))); self.instrs.push(MoveInstr::new_uncond(variable_reg, RegisterOrImm::Imm(0))); self.instrs.push(MoveInstr::new_cond(cmp_op.into(), variable_reg, RegisterOrImm::Imm(1))); save_variable(variable, variable_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); } fn emit_func_call(&mut self, func: Function, args: Vec, ret: Option, var_index_to_stack_offset: &BTreeMap) { self.instrs.push(PushInstr::new_push_caller_save()); if args.len() > 4 { todo!("More than 4 arguments not supported yet"); } let mut arg_reg_allocs = Vec::new(); for (i, arg) in args.into_iter().enumerate() { arg_reg_allocs.push(self.register_allocator.alloc_reg(ARG_REGS[i]).expect("Ran out of registers")); let arg_alloc = load_variable(arg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); self.instrs.push(MoveInstr::new_uncond(ARG_REGS[i], RegisterOrImm::Reg(arg_alloc.reg))); } self.instrs.push(BlInstr::new(func.name.clone())); if let Some(ret) = ret { save_variable(ret, REG_R0, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); } self.instrs.push(PopInstr::new_pop_caller_save()); } fn emit_move(&mut self, dest: Variable, src: MoveRValue, var_index_to_stack_offset: &BTreeMap) { let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers"); let dest_register = dest_alloc.reg; match src { MoveRValue::Var(variable) => { let src_alloc = load_variable(variable, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); self.instrs.push(MoveInstr::new_uncond(dest_register, RegisterOrImm::Reg(src_alloc.reg))); }, MoveRValue::ConstInt(literal_int) => self.instrs.push(MoveInstr::new_uncond(dest_register, RegisterOrImm::Imm(literal_int))), }; save_variable(dest, dest_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); } fn emit_binary(&mut self, dest: Variable, left: Variable, op: IRBinaryOp, right: Variable, var_index_to_stack_offset: &BTreeMap) { let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers"); let dest_reg = dest_alloc.reg; // should consider left == right let left_alloc = load_variable(left, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); let (_right_alloc, right_reg) = if left != right { let right_alloc = load_variable(right, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); let right_reg = right_alloc.reg; (Some(right_alloc), right_reg) } else { (None, left_alloc.reg) }; match op { IRBinaryOp::Add => { self.instrs.push(AddInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg))); }, IRBinaryOp::Sub => { self.instrs.push(SubInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg))); }, IRBinaryOp::Mul => { self.instrs.push(MulInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg))); }, IRBinaryOp::Div => { self.instrs.push(SDivInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg))); }, IRBinaryOp::Mod => { let temp_alloc = self.register_allocator.alloc_any().expect("Ran out of registers"); let temp_reg = temp_alloc.reg; self.instrs.push(SDivInstr::new(temp_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(MulInstr::new(temp_reg, temp_reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(SubInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(temp_reg))); }, } save_variable(dest, dest_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs); } }