llvm-native-core 0.1.9

LLVM-native core semantic engine — IR, CodeGen, X86 MC, Clang frontend pipeline
//! MSP430 Assembly Printer — formats machine instructions as
//! MSP430 assembly text.
//!
//! Clean-room reconstruction from the MSP430 Assembly Language Tools
//! User's Guide. Zero LLVM source consultation.

use super::msp430_instr_info::Msp430InstrInfo;
use crate::codegen::*;
use crate::module::Module;

pub struct Msp430AsmPrinter {
    pub output: String,
    pub instr_info: Msp430InstrInfo,
}

impl Msp430AsmPrinter {
    pub fn new() -> Self {
        Msp430AsmPrinter {
            output: String::new(),
            instr_info: Msp430InstrInfo::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) {
        self.output.push_str("\t.text\n");
        self.output.push_str("\t.code\t16\n");
        if !module.source_filename.is_empty() {
            self.output
                .push_str(&format!("\t.file\t\"{}\"\n", module.source_filename));
        }
    }

    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.type\t{}, @function\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.size\t{}, .-{}\n", name, 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 op_strs: Vec<String> = mi
            .operands
            .iter()
            .map(|op| self.print_operand(op))
            .filter(|s| !s.is_empty())
            .collect();

        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) => format!("#{}", imm),
            MachineOperand::Label(label) => format!(".LBB_{}", label),
            MachineOperand::Global(name) => name.clone(),
        }
    }

    fn format_reg(&self, reg_id: u16) -> String {
        match reg_id {
            0 => "PC".into(),
            1 => "SP".into(),
            2 => "SR".into(),
            3 => "CG2".into(),
            _ if reg_id >= 4 && reg_id <= 15 => format!("R{}", reg_id),
            _ => format!("R{}", reg_id),
        }
    }

    pub fn get_mnemonic(&self, opcode: u32) -> String {
        match opcode {
            0 => "rrc".to_string(),
            1 => "swpb".to_string(),
            2 => "rra".to_string(),
            3 => "sxt".to_string(),
            4 => "push".to_string(),
            5 => "pop".to_string(),
            6 => "call".to_string(),
            7 => "reti".to_string(),
            10 => "mov".to_string(),
            11 => "add".to_string(),
            12 => "addc".to_string(),
            13 => "subc".to_string(),
            14 => "sub".to_string(),
            15 => "cmp".to_string(),
            16 => "dadd".to_string(),
            17 => "bit".to_string(),
            18 => "bic".to_string(),
            19 => "bis".to_string(),
            20 => "xor".to_string(),
            21 => "and".to_string(),
            30 => "jne".to_string(),
            31 => "jeq".to_string(),
            32 => "jnc".to_string(),
            33 => "jc".to_string(),
            34 => "jn".to_string(),
            35 => "jge".to_string(),
            36 => "jl".to_string(),
            37 => "jmp".to_string(),
            40 => "clr".to_string(),
            41 => "clrc".to_string(),
            42 => "setc".to_string(),
            43 => "clrz".to_string(),
            44 => "setz".to_string(),
            45 => "clrn".to_string(),
            46 => "setn".to_string(),
            47 => "dint".to_string(),
            48 => "eint".to_string(),
            49 => "nop".to_string(),
            50 => "br".to_string(),
            51 => "ret".to_string(),
            52 => "tst".to_string(),
            53 => "inv".to_string(),
            54 => "rla".to_string(),
            55 => "rlc".to_string(),
            56 => "adc".to_string(),
            57 => "dadc".to_string(),
            60 => "mova".to_string(),
            61 => "cmpa".to_string(),
            62 => "adda".to_string(),
            63 => "suba".to_string(),
            64 => "bra".to_string(),
            65 => "reta".to_string(),
            66 => "pushm".to_string(),
            67 => "popm".to_string(),
            68 => "calla".to_string(),
            _ => "INVALID".to_string(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mnemonic_lookup() {
        let printer = Msp430AsmPrinter::new();
        assert_eq!(printer.get_mnemonic(10), "mov");
        assert_eq!(printer.get_mnemonic(11), "add");
        assert_eq!(printer.get_mnemonic(14), "sub");
        assert_eq!(printer.get_mnemonic(19), "bis");
        assert_eq!(printer.get_mnemonic(20), "xor");
        assert_eq!(printer.get_mnemonic(21), "and");
        assert_eq!(printer.get_mnemonic(30), "jne");
        assert_eq!(printer.get_mnemonic(37), "jmp");
        assert_eq!(printer.get_mnemonic(49), "nop");
        assert_eq!(printer.get_mnemonic(51), "ret");
    }

    #[test]
    fn test_format_reg() {
        let printer = Msp430AsmPrinter::new();
        assert_eq!(printer.format_reg(0), "PC");
        assert_eq!(printer.format_reg(1), "SP");
        assert_eq!(printer.format_reg(4), "R4");
        assert_eq!(printer.format_reg(15), "R15");
    }

    #[test]
    fn test_print_nop() {
        let mut printer = Msp430AsmPrinter::new();
        let mi = MachineInstr::new(49);
        printer.print_instruction(&mi);
        assert!(printer.output.contains("nop"));
    }

    #[test]
    fn test_print_ret() {
        let mut printer = Msp430AsmPrinter::new();
        let mi = MachineInstr::new(51);
        printer.print_instruction(&mi);
        assert!(printer.output.contains("ret"));
    }
}