llvm-native-core 0.1.14

LLVM-native core semantic engine — IR, CodeGen, X86 MC, Clang frontend pipeline
//! Hexagon Assembly Printer — formats machine instructions as
//! Qualcomm Hexagon assembly text.
//!
//! Clean-room behavioral reconstruction from the Hexagon V5/V6
//! Programmer's Reference Manual. Zero LLVM source consultation.

use super::hexagon_instr_info::HexagonInstrInfo;
use crate::codegen::*;
use crate::module::Module;

pub struct HexagonAsmPrinter {
    pub output: String,
    pub is_64bit: bool,
    pub instr_info: HexagonInstrInfo,
}

impl HexagonAsmPrinter {
    pub fn new(_is_64bit: bool) -> Self {
        HexagonAsmPrinter {
            output: String::new(),
            is_64bit: false,
            instr_info: HexagonInstrInfo::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");
        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));
        self.output.push_str("\t{\n");
        self.output
            .push_str(&format!("\t\tallocframe(r29, #0):raw\n"));
    }

    pub fn print_epilogue(&mut self, mf: &MachineFunction) {
        let name = &mf.name;
        self.output.push_str("\t\tdeallocframe\n");
        self.output.push_str("\t}\n");
        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));
        }
        self.output.push_str("\t{\n");
        for mi in &bb.instructions {
            self.print_instruction(mi);
        }
        self.output.push_str("\t}\n");
    }

    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(),
            "ldw" | "ldb" | "ldh" | "ldd" | "stw" | "stb" | "sth" | "std"
        );

        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(_)
            );
            if base_is_reg {
                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) => format!("#{}", imm),
                    _ => String::new(),
                };
                op_strs.push(format!("{}({})", base_name, offset));
            }
        }

        if !skip_last_two {
            for op in &mi.operands {
                let s = self.print_operand(op);
                if !s.is_empty() {
                    op_strs.push(s);
                }
            }
        }

        let prefix = if mnemonic.starts_with("if") {
            format!("{} ", mnemonic)
        } else {
            String::new()
        };

        if op_strs.is_empty() {
            self.output
                .push_str(&format!("\t\t{} = {}\n", prefix.trim(), mnemonic));
        } else {
            let ops = op_strs.join(", ");
            self.output
                .push_str(&format!("\t\t{} = {}({})\n", prefix.trim(), mnemonic, ops));
        }
    }

    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 {
        if reg_id < 32 {
            format!("r{}", reg_id)
        } else if reg_id >= 32 && reg_id < 36 {
            format!("p{}", reg_id - 32)
        } else if reg_id >= 40 && reg_id < 72 {
            format!("v{}", reg_id - 40)
        } else {
            format!("r{}", reg_id)
        }
    }

    pub fn get_mnemonic(&self, opcode: u32) -> String {
        match opcode {
            0 => "add".to_string(),
            1 => "sub".to_string(),
            2 => "and".to_string(),
            3 => "or".to_string(),
            4 => "xor".to_string(),
            5 => "neg".to_string(),
            6 => "not".to_string(),
            7 => "combine".to_string(),
            8 => "extract".to_string(),
            9 => "insert".to_string(),
            10 => "sxt".to_string(),
            11 => "zxt".to_string(),
            12 => "abs".to_string(),
            20 => "lsl".to_string(),
            21 => "lsr".to_string(),
            22 => "asr".to_string(),
            23 => "ror".to_string(),
            24 => "rol".to_string(),
            30 => "mpy".to_string(),
            31 => "mpyu".to_string(),
            32 => "mpyi".to_string(),
            33 => "mpyih".to_string(),
            34 => "mpyh".to_string(),
            50 => "ldw".to_string(),
            51 => "ldb".to_string(),
            52 => "ldh".to_string(),
            53 => "ldd".to_string(),
            60 => "stw".to_string(),
            61 => "stb".to_string(),
            62 => "sth".to_string(),
            63 => "std".to_string(),
            64 => "memw".to_string(),
            65 => "memd".to_string(),
            66 => "allocframe".to_string(),
            67 => "deallocframe".to_string(),
            80 => "jump".to_string(),
            81 => "jumpr".to_string(),
            82 => "jumpr".to_string(),
            83 => "call".to_string(),
            84 => "callr".to_string(),
            85 => "jmp".to_string(),
            86 => "jmpr".to_string(),
            100 => "cmpeq".to_string(),
            101 => "cmpgt".to_string(),
            102 => "cmpgtu".to_string(),
            103 => "cmplt".to_string(),
            104 => "cmpltu".to_string(),
            110 => "mux".to_string(),
            120 => "vadd".to_string(),
            121 => "vsub".to_string(),
            122 => "vmpy".to_string(),
            123 => "vmpa".to_string(),
            124 => "vshuff".to_string(),
            125 => "vshuffe".to_string(),
            126 => "vshuffo".to_string(),
            140 => "nop".to_string(),
            141 => "li".to_string(),
            142 => "mov".to_string(),
            _ => "INVALID".to_string(),
        }
    }
}

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

    #[test]
    fn test_mnemonic_lookup() {
        let printer = HexagonAsmPrinter::new(false);
        assert_eq!(printer.get_mnemonic(0), "add");
        assert_eq!(printer.get_mnemonic(1), "sub");
        assert_eq!(printer.get_mnemonic(30), "mpy");
        assert_eq!(printer.get_mnemonic(80), "jump");
        assert_eq!(printer.get_mnemonic(83), "call");
        assert_eq!(printer.get_mnemonic(100), "cmpeq");
        assert_eq!(printer.get_mnemonic(120), "vadd");
        assert_eq!(printer.get_mnemonic(140), "nop");
        assert_eq!(printer.get_mnemonic(142), "mov");
    }

    #[test]
    fn test_format_reg() {
        let printer = HexagonAsmPrinter::new(false);
        assert_eq!(printer.format_reg(0), "r0");
        assert_eq!(printer.format_reg(29), "r29");
        assert_eq!(printer.format_reg(31), "r31");
        assert_eq!(printer.format_reg(32), "p0");
        assert_eq!(printer.format_reg(40), "v0");
    }

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