llvm-native-core 0.1.9

LLVM-native core semantic engine — IR, CodeGen, X86 MC, Clang frontend pipeline
//! Lanai Assembly Printer — formats machine instructions as
//! Lanai assembly text.
//!
//! Clean-room behavioral reconstruction from the Lanai ISA
//! specification. Zero LLVM source code consultation.
//!
//! This module prints complete functions, basic blocks, and
//! individual instructions in Lanai assembly syntax.

use super::lanai_instr_info::LanaiInstrInfo;
use super::lanai_register_info::LanaiRegisterInfo;
use crate::codegen::*;
use crate::module::Module;

// ---------------------------------------------------------------------------
// LanaiAsmPrinter
// ---------------------------------------------------------------------------

/// Lanai assembly printer: formats machine instructions into
/// Lanai assembly text with proper mnemonics, register names,
/// and operand formatting.
///
/// # Output format
/// ```asm
///     .text
///     .globl  my_function
///     .type   my_function,@function
/// my_function:
///     add     r2, r3, r4
///     st      r5, r2
///     ld      r6, r5
///     mov     rr1, r6
///     ret
///     .size   my_function, .-my_function
/// ```
pub struct LanaiAsmPrinter {
    /// Accumulated assembly output text.
    pub output: String,
    /// Instruction info for mnemonic lookup.
    pub instr_info: LanaiInstrInfo,
}

impl LanaiAsmPrinter {
    pub fn new() -> Self {
        LanaiAsmPrinter {
            output: String::with_capacity(4096),
            instr_info: LanaiInstrInfo::new(),
        }
    }

    /// Print a complete function with prologue, body, and epilogue.
    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);
    }

    /// Print module-level directives.
    pub fn print_module(&mut self, module: &Module) {
        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");
    }

    /// Emit function prologue.
    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));
    }

    /// Emit function epilogue.
    pub fn print_epilogue(&mut self, mf: &MachineFunction) {
        let name = &mf.name;
        self.output
            .push_str(&format!("\t.size\t{}, .-{}\n", name, name));
    }

    /// Emit a basic block.
    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);
        }
    }

    /// Emit a single machine instruction as Lanai assembly text.
    pub fn print_instruction(&mut self, mi: &MachineInstr) {
        let mnemonic = self.get_mnemonic_by_opcode(mi.opcode);
        if mnemonic.is_empty() || mnemonic == "INVALID" {
            return;
        }

        // Handle zero-operand instructions
        if mi.operands.is_empty() {
            let mn = mnemonic.as_str();
            match mn {
                "nop" => {
                    self.output.push_str("\tnop\n");
                    return;
                }
                "ret" => {
                    self.output.push_str("\tret\n");
                    return;
                }
                _ => {
                    self.output.push_str(&format!("\t{}\n", mn));
                    return;
                }
            }
        }

        // Format operands
        let mut op_strs: Vec<String> = Vec::new();
        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(", ")));
        }
    }

    /// Format a single machine operand.
    pub fn print_operand(&self, op: &MachineOperand) -> String {
        match op {
            MachineOperand::Reg(vr) => {
                format!("r{}", vr)
            }
            MachineOperand::PhysReg(reg) => LanaiRegisterInfo::get_asm_name(*reg as u16),
            MachineOperand::Imm(imm) => format!("{}", imm),
            MachineOperand::Label(label) => format!(".LBB_{}", label),
            MachineOperand::Global(name) => name.clone(),
        }
    }

    // ------------------------------------------------------------------
    // Mnemonic lookup
    // ------------------------------------------------------------------

    fn get_mnemonic_by_opcode(&self, opcode_val: u32) -> String {
        match opcode_val {
            22000 => "add".into(),
            22001 => "addc".into(),
            22002 => "sub".into(),
            22003 => "subb".into(),
            22004 => "and".into(),
            22005 => "or".into(),
            22006 => "xor".into(),
            22007 => "sh".into(),
            22008 => "sel".into(),
            22010 => "ld".into(),
            22011 => "st".into(),
            22012 => "ldh".into(),
            22013 => "sth".into(),
            22014 => "ldb".into(),
            22015 => "stb".into(),
            22020 => "br".into(),
            22021 => "brcc".into(),
            22022 => "brc".into(),
            22023 => "sa".into(),
            22024 => "bt".into(),
            22030 => "popc".into(),
            22031 => "leadz".into(),
            22032 => "trailz".into(),
            22040 => "add_i".into(),
            22041 => "and_i".into(),
            22042 => "or_i".into(),
            22043 => "xor_i".into(),
            22044 => "sh_i".into(),
            22045 => "ld_i".into(),
            22046 => "ldh_i".into(),
            22047 => "ldb_i".into(),
            22048 => "st_i".into(),
            22049 => "sth_i".into(),
            22050 => "stb_i".into(),
            22060 => "nop".into(),
            22061 => "mov".into(),
            22062 => "li".into(),
            22063 => "j".into(),
            22064 => "jal".into(),
            22065 => "jalr".into(),
            22066 => "ret".into(),
            _ => "INVALID".into(),
        }
    }

    pub fn clear(&mut self) {
        self.output.clear();
    }
}

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

    #[test]
    fn test_printer_new() {
        let printer = LanaiAsmPrinter::new();
        assert!(printer.output.is_empty());
    }

    #[test]
    fn test_print_operand_imm() {
        let printer = LanaiAsmPrinter::new();
        assert_eq!(printer.print_operand(&MachineOperand::Imm(42)), "42");
    }

    #[test]
    fn test_print_operand_label() {
        let printer = LanaiAsmPrinter::new();
        assert_eq!(
            printer.print_operand(&MachineOperand::Label("loop".to_string())),
            ".LBB_loop"
        );
    }

    #[test]
    fn test_get_mnemonic() {
        let printer = LanaiAsmPrinter::new();
        assert_eq!(printer.get_mnemonic_by_opcode(22000), "add");
        assert_eq!(printer.get_mnemonic_by_opcode(22066), "ret");
        assert_eq!(printer.get_mnemonic_by_opcode(22063), "j");
        assert_eq!(printer.get_mnemonic_by_opcode(99999), "INVALID");
    }

    #[test]
    fn test_print_ret_instruction() {
        let mut printer = LanaiAsmPrinter::new();
        let mi = MachineInstr::new(22066); // ret
        printer.print_instruction(&mi);
        assert!(printer.output.contains("ret\n"));
    }
}