use super::mips_instr_info::MipsInstrInfo;
#[cfg(test)]
use super::mips_instr_info::MipsOpcode;
#[cfg(test)]
use super::mips_register_info::*;
use super::mips_register_info::{MIPS_FPR_BASE, MIPS_GPR_BASE};
use crate::codegen::*;
use crate::module::Module;
pub struct MipsAsmPrinter {
pub output: String,
pub is_64bit: bool,
pub instr_info: MipsInstrInfo,
}
impl MipsAsmPrinter {
pub fn new(is_64bit: bool) -> Self {
MipsAsmPrinter {
output: String::new(),
is_64bit,
instr_info: MipsInstrInfo::new(),
}
}
pub fn print_function(&mut self, mf: &MachineFunction) {
self.print_prologue(mf);
for bb in &mf.blocks {
self.print_basic_block(bb);
}
self.print_epilogue(mf);
}
pub fn print_module(&mut self, module: &Module) {
if self.is_64bit {
self.output.push_str("\t.set\tmips64\n");
} else {
self.output.push_str("\t.set\tmips32\n");
}
self.output.push_str("\t.set\tnoreorder\n");
self.output.push_str("\t.set\tnomacro\n");
if !module.source_filename.is_empty() {
self.output
.push_str(&format!("\t.file\t\"{}\"\n", module.source_filename));
}
self.output.push_str("\t.text\n");
}
pub fn print_prologue(&mut self, mf: &MachineFunction) {
let name = &mf.name;
self.output.push_str(&format!("\t.globl\t{}\n", name));
self.output.push_str(&format!("\t.ent\t{}\n", name));
self.output.push_str(&format!("{}:\n", name));
}
pub fn print_epilogue(&mut self, mf: &MachineFunction) {
let name = &mf.name;
self.output.push_str(&format!("\t.end\t{}\n", name));
}
pub fn print_basic_block(&mut self, bb: &MachineBasicBlock) {
if !bb.name.is_empty() {
self.output.push_str(&format!(".LBB_{}:\n", bb.name));
}
for mi in &bb.instructions {
self.print_instruction(mi);
}
}
pub fn print_instruction(&mut self, mi: &MachineInstr) {
let mnemonic = self.get_mnemonic(mi.opcode);
if mnemonic.is_empty() || mnemonic == "INVALID" {
return;
}
let mut op_strs: Vec<String> = Vec::new();
let len = mi.operands.len();
let mut skip_last_two = false;
let is_mem_op = matches!(
mnemonic.as_str(),
"lw" | "sw"
| "lb"
| "sb"
| "lh"
| "sh"
| "lbu"
| "lhu"
| "ld"
| "sd"
| "lwc1"
| "swc1"
| "ldc1"
| "sdc1"
);
if len >= 3 && is_mem_op {
let second_last = &mi.operands[len - 2];
let last = &mi.operands[len - 1];
let base_is_reg = matches!(
second_last,
MachineOperand::PhysReg(_) | MachineOperand::Reg(_)
);
let last_is_imm = matches!(last, MachineOperand::Imm(_));
if base_is_reg && last_is_imm {
skip_last_two = true;
for j in 0..len - 2 {
op_strs.push(self.print_operand(&mi.operands[j]));
}
let base_name = match second_last {
MachineOperand::PhysReg(r) => self.format_reg(*r as u16),
MachineOperand::Reg(vr) => format!("%vreg{}", vr),
_ => unreachable!(),
};
let offset = match last {
MachineOperand::Imm(imm) => *imm,
_ => unreachable!(),
};
op_strs.push(format!("{}({})", offset, base_name));
}
}
if !skip_last_two {
for op in &mi.operands {
let s = self.print_operand(op);
if !s.is_empty() {
op_strs.push(s);
}
}
}
if op_strs.is_empty() {
self.output.push_str(&format!("\t{}\n", mnemonic));
} else {
self.output
.push_str(&format!("\t{}\t{}\n", mnemonic, op_strs.join(", ")));
}
}
pub fn print_operand(&self, op: &MachineOperand) -> String {
match op {
MachineOperand::Reg(vr) => {
format!("%vreg{}", vr)
}
MachineOperand::PhysReg(reg) => self.format_reg(*reg as u16),
MachineOperand::Imm(imm) => self.format_imm(*imm),
MachineOperand::Label(label) => {
format!(".LBB_{}", label)
}
MachineOperand::Global(name) => name.clone(),
}
}
pub fn get_mnemonic(&self, opcode: u32) -> String {
self.get_mnemonic_fallback(opcode)
}
fn get_mnemonic_fallback(&self, opcode: u32) -> String {
match opcode {
0 => "add".to_string(),
1 => "addu".to_string(),
2 => "sub".to_string(),
3 => "subu".to_string(),
4 => "and".to_string(),
5 => "or".to_string(),
6 => "xor".to_string(),
7 => "nor".to_string(),
8 => "slt".to_string(),
9 => "sltu".to_string(),
10 => "sll".to_string(),
11 => "srl".to_string(),
12 => "sra".to_string(),
13 => "sllv".to_string(),
14 => "srlv".to_string(),
15 => "srav".to_string(),
16 => "mult".to_string(),
17 => "multu".to_string(),
18 => "div".to_string(),
19 => "divu".to_string(),
20 => "mfhi".to_string(),
21 => "mflo".to_string(),
22 => "mthi".to_string(),
23 => "mtlo".to_string(),
24 => "jr".to_string(),
25 => "jalr".to_string(),
26 => "addi".to_string(),
27 => "addiu".to_string(),
28 => "andi".to_string(),
29 => "ori".to_string(),
30 => "xori".to_string(),
31 => "slti".to_string(),
32 => "sltiu".to_string(),
33 => "lw".to_string(),
34 => "lh".to_string(),
35 => "lhu".to_string(),
36 => "lb".to_string(),
37 => "lbu".to_string(),
38 => "sw".to_string(),
39 => "sh".to_string(),
40 => "sb".to_string(),
41 => "lui".to_string(),
42 => "beq".to_string(),
43 => "bne".to_string(),
44 => "blez".to_string(),
45 => "bgtz".to_string(),
46 => "bltz".to_string(),
47 => "bgez".to_string(),
48 => "j".to_string(),
49 => "jal".to_string(),
50 => "add.s".to_string(),
51 => "sub.s".to_string(),
52 => "mul.s".to_string(),
53 => "div.s".to_string(),
54 => "mov.s".to_string(),
55 => "neg.s".to_string(),
56 => "abs.s".to_string(),
57 => "sqrt.s".to_string(),
58 => "cvt.s.w".to_string(),
59 => "cvt.w.s".to_string(),
60 => "c.eq.s".to_string(),
61 => "c.lt.s".to_string(),
62 => "c.le.s".to_string(),
63 => "add.d".to_string(),
64 => "sub.d".to_string(),
65 => "mul.d".to_string(),
66 => "div.d".to_string(),
67 => "mov.d".to_string(),
68 => "neg.d".to_string(),
69 => "abs.d".to_string(),
70 => "sqrt.d".to_string(),
71 => "cvt.d.w".to_string(),
72 => "cvt.w.d".to_string(),
73 => "cvt.s.d".to_string(),
74 => "cvt.d.s".to_string(),
75 => "c.eq.d".to_string(),
76 => "c.lt.d".to_string(),
77 => "c.le.d".to_string(),
78 => "lwc1".to_string(),
79 => "swc1".to_string(),
80 => "ldc1".to_string(),
81 => "sdc1".to_string(),
82 => "dadd".to_string(),
83 => "daddu".to_string(),
84 => "dsub".to_string(),
85 => "dsubu".to_string(),
86 => "daddiu".to_string(),
87 => "dsll".to_string(),
88 => "dsrl".to_string(),
89 => "dsra".to_string(),
90 => "dsllv".to_string(),
91 => "dsrlv".to_string(),
92 => "dsrav".to_string(),
93 => "dmult".to_string(),
94 => "dmultu".to_string(),
95 => "ddiv".to_string(),
96 => "ddivu".to_string(),
97 => "ld".to_string(),
98 => "sd".to_string(),
99 => "ldl".to_string(),
100 => "ldr".to_string(),
101 => "sdl".to_string(),
102 => "sdr".to_string(),
103 => "nop".to_string(),
104 => "move".to_string(),
105 => "li".to_string(),
106 => "la".to_string(),
107 => "b".to_string(),
108 => "bge".to_string(),
109 => "blt".to_string(),
110 => "bgt".to_string(),
111 => "ble".to_string(),
112 => "beqz".to_string(),
113 => "bnez".to_string(),
114 => "neg".to_string(),
115 => "not".to_string(),
116 => "abs".to_string(),
117 => "syscall".to_string(),
118 => "break".to_string(),
119 => "sync".to_string(),
120 => "mfc0".to_string(),
121 => "mtc0".to_string(),
122 => "eret".to_string(),
123 => "wait".to_string(),
_ => "INVALID".to_string(),
}
}
pub fn format_reg(&self, reg_id: u16) -> String {
if reg_id >= MIPS_GPR_BASE && reg_id < MIPS_GPR_BASE + 32 {
let idx = reg_id - MIPS_GPR_BASE;
match idx {
0 => "$zero".into(),
1 => "$at".into(),
2 => "$v0".into(),
3 => "$v1".into(),
4 => "$a0".into(),
5 => "$a1".into(),
6 => "$a2".into(),
7 => "$a3".into(),
8 => "$t0".into(),
9 => "$t1".into(),
10 => "$t2".into(),
11 => "$t3".into(),
12 => "$t4".into(),
13 => "$t5".into(),
14 => "$t6".into(),
15 => "$t7".into(),
16 => "$s0".into(),
17 => "$s1".into(),
18 => "$s2".into(),
19 => "$s3".into(),
20 => "$s4".into(),
21 => "$s5".into(),
22 => "$s6".into(),
23 => "$s7".into(),
24 => "$t8".into(),
25 => "$t9".into(),
26 => "$k0".into(),
27 => "$k1".into(),
28 => "$gp".into(),
29 => "$sp".into(),
30 => "$fp".into(),
31 => "$ra".into(),
_ => format!("${}", idx),
}
} else if reg_id >= MIPS_FPR_BASE && reg_id < MIPS_FPR_BASE + 32 {
let idx = reg_id - MIPS_FPR_BASE;
format!("$f{}", idx)
} else if reg_id == 4032 {
"$hi".into()
} else if reg_id == 4033 {
"$lo".into()
} else if reg_id == 4034 {
"$pc".into()
} else {
format!("${}", reg_id)
}
}
pub fn format_imm(&self, imm: i64) -> String {
if imm >= -32768 && imm <= 32767 {
format!("{}", imm)
} else if imm >= 0 && imm <= 65535 {
format!("0x{:X}", imm)
} else {
format!("{}", imm)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_printer() {
let printer = MipsAsmPrinter::new(false);
assert!(!printer.is_64bit);
assert!(printer.output.is_empty());
}
#[test]
fn test_format_gpr() {
let printer = MipsAsmPrinter::new(false);
assert_eq!(printer.format_reg(ZERO), "$zero");
assert_eq!(printer.format_reg(AT), "$at");
assert_eq!(printer.format_reg(V0), "$v0");
assert_eq!(printer.format_reg(A0), "$a0");
assert_eq!(printer.format_reg(SP), "$sp");
assert_eq!(printer.format_reg(RA), "$ra");
assert_eq!(printer.format_reg(FP), "$fp");
assert_eq!(printer.format_reg(GP), "$gp");
assert_eq!(printer.format_reg(T0), "$t0");
assert_eq!(printer.format_reg(S0), "$s0");
}
#[test]
fn test_format_fpr() {
let printer = MipsAsmPrinter::new(false);
assert_eq!(printer.format_reg(F0), "$f0");
assert_eq!(printer.format_reg(F12), "$f12");
assert_eq!(printer.format_reg(F31), "$f31");
}
#[test]
fn test_format_special_regs() {
let printer = MipsAsmPrinter::new(false);
assert_eq!(printer.format_reg(HI), "$hi");
assert_eq!(printer.format_reg(LO), "$lo");
assert_eq!(printer.format_reg(PC), "$pc");
}
#[test]
fn test_format_imm() {
let printer = MipsAsmPrinter::new(false);
assert_eq!(printer.format_imm(0), "0");
assert_eq!(printer.format_imm(42), "42");
assert_eq!(printer.format_imm(-1), "-1");
assert_eq!(printer.format_imm(-32), "-32");
}
#[test]
fn test_get_mnemonic() {
let printer = MipsAsmPrinter::new(false);
assert_eq!(printer.get_mnemonic(0), "add");
assert_eq!(printer.get_mnemonic(MipsOpcode::ADDIU as u32), "addiu");
assert_eq!(printer.get_mnemonic(MipsOpcode::LW as u32), "lw");
assert_eq!(printer.get_mnemonic(MipsOpcode::SW as u32), "sw");
assert_eq!(printer.get_mnemonic(MipsOpcode::BEQ as u32), "beq");
assert_eq!(printer.get_mnemonic(MipsOpcode::J as u32), "j");
assert_eq!(printer.get_mnemonic(MipsOpcode::JAL as u32), "jal");
assert_eq!(printer.get_mnemonic(MipsOpcode::JR as u32), "jr");
assert_eq!(printer.get_mnemonic(MipsOpcode::NOP as u32), "nop");
assert_eq!(printer.get_mnemonic(MipsOpcode::LI as u32), "li");
}
#[test]
fn test_print_prologue() {
let mut printer = MipsAsmPrinter::new(false);
let mf = MachineFunction::new("main");
printer.print_prologue(&mf);
assert!(printer.output.contains(".globl\tmain"));
assert!(printer.output.contains(".ent\tmain"));
assert!(printer.output.contains("main:\n"));
}
#[test]
fn test_print_epilogue() {
let mut printer = MipsAsmPrinter::new(false);
let mf = MachineFunction::new("test");
printer.print_epilogue(&mf);
assert!(printer.output.contains(".end\ttest"));
}
#[test]
fn test_print_add_instruction() {
let mut printer = MipsAsmPrinter::new(false);
let mut mi = MachineInstr::new(MipsOpcode::ADD as u32);
mi.operands.push(MachineOperand::PhysReg(V0 as u32));
mi.operands.push(MachineOperand::PhysReg(A0 as u32));
mi.operands.push(MachineOperand::PhysReg(A1 as u32));
printer.print_instruction(&mi);
assert!(printer.output.contains("add\t$v0, $a0, $a1"));
}
#[test]
fn test_print_lw_instruction() {
let mut printer = MipsAsmPrinter::new(false);
let mut mi = MachineInstr::new(MipsOpcode::LW as u32);
mi.operands.push(MachineOperand::PhysReg(V0 as u32));
mi.operands.push(MachineOperand::PhysReg(SP as u32));
mi.push_imm(0);
printer.print_instruction(&mi);
assert!(printer.output.contains("lw\t$v0, 0($sp)"));
}
#[test]
fn test_print_sw_instruction() {
let mut printer = MipsAsmPrinter::new(false);
let mut mi = MachineInstr::new(MipsOpcode::SW as u32);
mi.operands.push(MachineOperand::PhysReg(RA as u32));
mi.operands.push(MachineOperand::PhysReg(SP as u32));
mi.push_imm(28);
printer.print_instruction(&mi);
assert!(printer.output.contains("sw\t$ra, 28($sp)"));
}
#[test]
fn test_print_jr_instruction() {
let mut printer = MipsAsmPrinter::new(false);
let mut mi = MachineInstr::new(MipsOpcode::JR as u32);
mi.operands.push(MachineOperand::PhysReg(RA as u32));
printer.print_instruction(&mi);
assert!(printer.output.contains("jr\t$ra"));
}
#[test]
fn test_print_function() {
let mut printer = MipsAsmPrinter::new(false);
let mut mf = MachineFunction::new("test_func");
let mut bb = MachineBasicBlock {
name: "entry".into(),
instructions: Vec::new(),
successors: Vec::new(),
};
let mut mi = MachineInstr::new(MipsOpcode::ADDIU as u32);
mi.operands.push(MachineOperand::PhysReg(SP as u32));
mi.operands.push(MachineOperand::PhysReg(SP as u32));
mi.push_imm(-32);
bb.instructions.push(mi);
let mut mi2 = MachineInstr::new(MipsOpcode::SW as u32);
mi2.operands.push(MachineOperand::PhysReg(RA as u32));
mi2.operands.push(MachineOperand::PhysReg(SP as u32));
mi2.push_imm(28);
bb.instructions.push(mi2);
let mut mi3 = MachineInstr::new(MipsOpcode::LI as u32);
mi3.operands.push(MachineOperand::PhysReg(V0 as u32));
mi3.push_imm(42);
bb.instructions.push(mi3);
let mut mi4 = MachineInstr::new(MipsOpcode::JR as u32);
mi4.operands.push(MachineOperand::PhysReg(RA as u32));
bb.instructions.push(mi4);
mf.push_block(bb);
printer.print_function(&mf);
let output = &printer.output;
assert!(output.contains("test_func:"));
assert!(output.contains("addiu\t$sp, $sp, -32"));
assert!(output.contains("sw\t$ra, 28($sp)"));
assert!(output.contains("li\t$v0, 42"));
assert!(output.contains("jr\t$ra"));
assert!(output.contains(".end\ttest_func"));
}
#[test]
fn test_print_fpu_instruction() {
let mut printer = MipsAsmPrinter::new(false);
let mut mi = MachineInstr::new(MipsOpcode::ADD_S as u32);
mi.operands.push(MachineOperand::PhysReg(F0 as u32));
mi.operands.push(MachineOperand::PhysReg(F1 as u32));
mi.operands.push(MachineOperand::PhysReg(F2 as u32));
printer.print_instruction(&mi);
assert!(printer.output.contains("add.s\t$f0, $f1, $f2"));
}
#[test]
fn test_invalid_mnemonic_skipped() {
let mut printer = MipsAsmPrinter::new(false);
let mi = MachineInstr::new(99999); printer.print_instruction(&mi);
assert!(!printer.output.contains("INVALID"));
}
}