use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
use crate::module::Module;
#[allow(unused_imports)]
use crate::x86::x86_register_info::*;
use crate::x86::x86_subtarget::X86Subtarget;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsmSyntax {
ATT,
Intel,
}
impl Default for AsmSyntax {
fn default() -> Self {
AsmSyntax::ATT
}
}
pub struct X86AsmPrinter {
pub output: String,
pub syntax: AsmSyntax,
pub subtarget: X86Subtarget,
}
impl X86AsmPrinter {
pub fn new(subtarget: X86Subtarget, syntax: AsmSyntax) -> Self {
Self {
output: String::new(),
syntax,
subtarget,
}
}
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_prologue(&mut self, mf: &MachineFunction) {
let name = &mf.name;
self.output.push_str(&format!(".globl {}\n", name));
self.output
.push_str(&format!(".type {}, @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!(".size {}, .-{}\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() {
return;
}
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);
}
}
let instr_line = if op_strs.is_empty() {
format!("\t{}\n", mnemonic)
} else {
match self.syntax {
AsmSyntax::ATT => {
format!("\t{}\t{}\n", mnemonic, op_strs.join(", "))
}
AsmSyntax::Intel => {
format!("\t{}\t{}\n", mnemonic, op_strs.join(", "))
}
}
};
self.output.push_str(&instr_line);
}
pub fn print_operand(&self, op: &MachineOperand) -> String {
match op {
MachineOperand::Reg(vr) => {
match self.syntax {
AsmSyntax::ATT => format!("%vreg{}", vr),
AsmSyntax::Intel => format!("vreg{}", vr),
}
}
MachineOperand::PhysReg(reg) => match self.syntax {
AsmSyntax::ATT => self.format_reg_att(*reg as u16),
AsmSyntax::Intel => self.format_reg_intel(*reg as u16),
},
MachineOperand::Imm(imm) => self.format_immediate(*imm),
MachineOperand::Label(label) => format!(".LBB_{}", label),
MachineOperand::Global(name) => name.clone(),
}
}
pub fn format_reg_att(&self, reg: u16) -> String {
let name = self.lookup_reg_name(reg);
match self.syntax {
AsmSyntax::ATT => format!("%{}", name),
AsmSyntax::Intel => name.to_string(),
}
}
pub fn format_reg_intel(&self, reg: u16) -> String {
self.lookup_reg_name(reg).to_string()
}
fn lookup_reg_name(&self, reg: u16) -> &'static str {
match reg {
0 => "rax",
1 => "rcx",
2 => "rdx",
3 => "rbx",
4 => "rsp",
5 => "rbp",
6 => "rsi",
7 => "rdi",
8 => "r8",
9 => "r9",
10 => "r10",
11 => "r11",
12 => "r12",
13 => "r13",
14 => "r14",
15 => "r15",
16 => "eax",
17 => "ecx",
18 => "edx",
19 => "ebx",
20 => "esp",
21 => "ebp",
22 => "esi",
23 => "edi",
24 => "r8d",
25 => "r9d",
26 => "r10d",
27 => "r11d",
28 => "r12d",
29 => "r13d",
30 => "r14d",
31 => "r15d",
32 => "ax",
33 => "cx",
34 => "dx",
35 => "bx",
36 => "sp",
37 => "bp",
38 => "si",
39 => "di",
40 => "r8w",
41 => "r9w",
42 => "r10w",
43 => "r11w",
44 => "r12w",
45 => "r13w",
46 => "r14w",
47 => "r15w",
48 => "al",
49 => "cl",
50 => "dl",
51 => "bl",
52 => "spl",
53 => "bpl",
54 => "sil",
55 => "dil",
56 => "r8b",
57 => "r9b",
58 => "r10b",
59 => "r11b",
60 => "r12b",
61 => "r13b",
62 => "r14b",
63 => "r15b",
64 => "ah",
65 => "ch",
66 => "dh",
67 => "bh",
98 => "xmm0",
99 => "xmm1",
100 => "xmm2",
101 => "xmm3",
102 => "xmm4",
103 => "xmm5",
104 => "xmm6",
105 => "xmm7",
106 => "xmm8",
107 => "xmm9",
108 => "xmm10",
109 => "xmm11",
110 => "xmm12",
111 => "xmm13",
112 => "xmm14",
113 => "xmm15",
130 => "ymm0",
131 => "ymm1",
132 => "ymm2",
133 => "ymm3",
134 => "ymm4",
135 => "ymm5",
136 => "ymm6",
137 => "ymm7",
138 => "ymm8",
139 => "ymm9",
140 => "ymm10",
141 => "ymm11",
142 => "ymm12",
143 => "ymm13",
144 => "ymm14",
145 => "ymm15",
223 => "rflags",
224 => "rip",
_ => "unknown",
}
}
pub fn format_immediate(&self, imm: i64) -> String {
match self.syntax {
AsmSyntax::ATT => format!("${}", imm),
AsmSyntax::Intel => format!("{}", imm),
}
}
pub fn get_mnemonic(&self, opcode: u32) -> String {
match opcode {
0 => "nop".into(),
1 => "mov".into(),
2 => "add".into(),
3 => "sub".into(),
4 => "mul".into(),
5 => "div".into(),
6 => "and".into(),
7 => "or".into(),
8 => "xor".into(),
9 => "shl".into(),
10 => "shr".into(),
11 => "push".into(),
12 => "pop".into(),
13 => "call".into(),
14 => "ret".into(),
15 => "jmp".into(),
16 => "je".into(),
17 => "jne".into(),
18 => "cmp".into(),
19 => "lea".into(),
20 => "inc".into(),
21 => "dec".into(),
22 => "not".into(),
23 => "neg".into(),
24 => "movsx".into(),
25 => "movzx".into(),
26 => "movabs".into(),
27 => "cmovo".into(),
28 => "cmovno".into(),
29 => "cmovb".into(),
30 => "cmovae".into(),
31 => "cmove".into(),
32 => "cmovne".into(),
33 => "cmovbe".into(),
34 => "cmova".into(),
35 => "cmovs".into(),
36 => "cmovns".into(),
37 => "cmovp".into(),
38 => "cmovnp".into(),
39 => "cmovl".into(),
40 => "cmovge".into(),
41 => "cmovle".into(),
42 => "cmovg".into(),
43 => "xchg".into(),
44 => "pushf".into(),
45 => "popf".into(),
46 => "adc".into(),
47 => "sbb".into(),
48 => "imul".into(),
49 => "idiv".into(),
50 => "test".into(),
51 => "sar".into(),
52 => "rol".into(),
53 => "ror".into(),
54 => "rcl".into(),
55 => "rcr".into(),
56 => "shld".into(),
57 => "shrd".into(),
58 => "jo".into(),
59 => "jno".into(),
60 => "jb".into(),
61 => "jae".into(),
62 => "jbe".into(),
63 => "ja".into(),
64 => "js".into(),
65 => "jns".into(),
66 => "jp".into(),
67 => "jnp".into(),
68 => "jl".into(),
69 => "jge".into(),
70 => "jle".into(),
71 => "jg".into(),
72 => "loop".into(),
73 => "loope".into(),
74 => "loopne".into(),
75 => "int".into(),
76 => "seto".into(),
77 => "setno".into(),
78 => "setb".into(),
79 => "setae".into(),
80 => "sete".into(),
81 => "setne".into(),
82 => "setbe".into(),
83 => "seta".into(),
84 => "sets".into(),
85 => "setns".into(),
86 => "setp".into(),
87 => "setnp".into(),
88 => "setl".into(),
89 => "setge".into(),
90 => "setle".into(),
91 => "setg".into(),
1000 => "int3".into(),
1001 => "ud2".into(),
_ => {
if opcode >= 92 && opcode <= 114 {
let names = [
"movsb", "movsw", "movsd", "movsq", "cmpsb", "cmpsw", "cmpsd", "cmpsq",
"stosb", "stosw", "stosd", "stosq", "lodsb", "lodsw", "lodsd", "lodsq",
"scasb", "scasw", "scasd", "scasq", "rep", "repe", "repne",
];
names
.get(opcode as usize - 92)
.unwrap_or(&"unknown")
.to_string()
} else if opcode >= 115 && opcode <= 142 {
let names = [
"movss",
"movsd",
"addss",
"addsd",
"subss",
"subsd",
"mulss",
"mulsd",
"divss",
"divsd",
"sqrtss",
"sqrtsd",
"minss",
"minsd",
"maxss",
"maxsd",
"cmpss",
"cmpsd",
"cvtsi2ss",
"cvtsi2sd",
"cvtss2si",
"cvtsd2si",
"cvttss2si",
"cvttsd2si",
"andps",
"andnps",
"orps",
"xorps",
];
names
.get(opcode as usize - 115)
.unwrap_or(&"unknown")
.to_string()
} else if opcode == 1000 + 18 {
"cqo".into()
} else {
format!("opcode_{}", opcode)
}
}
}
}
pub fn print_module(&mut self, module: &Module) -> String {
self.output.clear();
self.output
.push_str(&format!("\t.file\t\"{}\"\n", module.source_filename));
self.output.push_str("\t.text\n");
for func in &module.functions {
let f = func.borrow();
self.output.push_str(&format!("\n# Function: {}\n", f.name));
}
self.output.clone()
}
}
impl Default for X86AsmPrinter {
fn default() -> Self {
Self::new(
X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""),
AsmSyntax::default(),
)
}
}
impl X86AsmPrinter {
pub fn print_memory_operand_intel(
&self,
base: Option<u16>,
index: Option<u16>,
scale: u8,
displacement: i64,
rip_relative: bool,
segment: Option<u16>,
) -> String {
let seg_part = if let Some(seg_reg) = segment {
format!("{}:", self.lookup_reg_name(seg_reg))
} else {
String::new()
};
if rip_relative {
if displacement == 0 {
return format!("{}[rip]", seg_part);
}
let sign = if displacement >= 0 { "+" } else { "" };
return format!("{}[rip{}{:#x}]", seg_part, sign, displacement);
}
let mut parts: Vec<String> = Vec::new();
if let Some(b) = base {
parts.push(self.lookup_reg_name(b).to_string());
}
if let Some(idx) = index {
let idx_name = self.lookup_reg_name(idx);
if scale > 1 {
parts.push(format!("{}*{}", idx_name, scale));
} else {
parts.push(idx_name.to_string());
}
}
let base_str = parts.join("+");
if displacement == 0 && !base_str.is_empty() {
format!("{}[{}]", seg_part, base_str)
} else if displacement > 0 {
if base_str.is_empty() {
format!("{}[{:#x}]", seg_part, displacement)
} else {
format!("{}[{}+{:#x}]", seg_part, base_str, displacement)
}
} else {
if base_str.is_empty() {
format!("{}[-{:#x}]", seg_part, -displacement)
} else {
format!("{}[{}-{:#x}]", seg_part, base_str, -displacement)
}
}
}
pub fn print_memory_operand_att(
&self,
base: Option<u16>,
index: Option<u16>,
scale: u8,
displacement: i64,
rip_relative: bool,
segment: Option<u16>,
) -> String {
let seg_part = if let Some(seg_reg) = segment {
format!("%{}:", self.lookup_reg_name(seg_reg))
} else {
String::new()
};
let disp_str = if displacement != 0 {
format!("{}", displacement)
} else {
String::new()
};
let base_str = if rip_relative {
"%rip".to_string()
} else if let Some(b) = base {
format!("%{}", self.lookup_reg_name(b))
} else {
String::new()
};
let mut index_scale_str = String::new();
if let Some(idx) = index {
index_scale_str.push_str(&format!(",%{}", self.lookup_reg_name(idx)));
if scale > 1 {
index_scale_str.push_str(&format!(",{}", scale));
}
}
format!("{}{}({}{})", seg_part, disp_str, base_str, index_scale_str)
}
pub fn print_register_with_suffix(&self, reg: u16) -> String {
self.format_reg_att(reg)
}
pub fn print_avx512_decorators(
&self,
mask_reg: Option<u8>,
zeroing: bool,
broadcast: Option<u8>,
rounding: Option<&str>,
) -> String {
let mut decorators = String::new();
if let Some(mask) = mask_reg {
if mask != 0 {
decorators.push_str(&format!("{{k{}}}", mask));
}
}
if zeroing {
decorators.push_str("{z}");
}
if let Some(bc) = broadcast {
match bc {
2 => decorators.push_str("{1to2}"),
4 => decorators.push_str("{1to4}"),
8 => decorators.push_str("{1to8}"),
16 => decorators.push_str("{1to16}"),
_ => {}
}
}
if let Some(round) = rounding {
decorators.push_str(&format!("{{{}}}", round));
}
decorators
}
pub fn emit_directive_section(&mut self, name: &str, flags: &str) {
self.output
.push_str(&format!(".section {},\"{}\"\n", name, flags));
}
pub fn emit_directive_text(&mut self) {
self.output.push_str(".text\n");
}
pub fn emit_directive_data(&mut self) {
self.output.push_str(".data\n");
}
pub fn emit_directive_bss(&mut self) {
self.output.push_str(".bss\n");
}
pub fn emit_directive_globl(&mut self, name: &str) {
self.output.push_str(&format!(".globl {}\n", name));
}
pub fn emit_directive_local(&mut self, name: &str) {
self.output.push_str(&format!(".local {}\n", name));
}
pub fn emit_directive_weak(&mut self, name: &str) {
self.output.push_str(&format!(".weak {}\n", name));
}
pub fn emit_directive_align(&mut self, alignment: u32) {
self.output.push_str(&format!(".align {}\n", alignment));
}
pub fn emit_directive_p2align(&mut self, n: u32) {
self.output.push_str(&format!(".p2align {}\n", n));
}
pub fn emit_directive_size(&mut self, name: &str, size_expr: &str) {
self.output
.push_str(&format!(".size {}, {}\n", name, size_expr));
}
pub fn emit_directive_type(&mut self, name: &str, ty: &str) {
self.output.push_str(&format!(".type {}, {}\n", name, ty));
}
pub fn emit_cfi_startproc(&mut self) {
self.output.push_str(".cfi_startproc\n");
}
pub fn emit_cfi_endproc(&mut self) {
self.output.push_str(".cfi_endproc\n");
}
pub fn emit_cfi_def_cfa_offset(&mut self, offset: i32) {
self.output
.push_str(&format!(".cfi_def_cfa_offset {}\n", offset));
}
pub fn emit_cfi_offset(&mut self, reg: u16, offset: i32) {
let reg_name = self.lookup_reg_name(reg);
self.output
.push_str(&format!(".cfi_offset {}, {}\n", reg_name, offset));
}
pub fn emit_directive_zero(&mut self, size: u64) {
self.output.push_str(&format!(".zero {}\n", size));
}
pub fn emit_directive_skip(&mut self, size: u64) {
self.output.push_str(&format!(".skip {}\n", size));
}
pub fn emit_label(&mut self, label: &str) {
self.output.push_str(&format!("{}:\n", label));
}
pub fn emit_func_label(&mut self, name: &str, suffix: &str) {
self.output
.push_str(&format!(".Lfunc_begin{}_{}:\n", suffix, name));
}
pub fn emit_bb_label(&mut self, label: &str) {
self.output.push_str(&format!(".LBB_{}:\n", label));
}
pub fn emit_tmp_label(&mut self, label: &str) {
self.output.push_str(&format!(".Ltmp{}:\n", label));
}
pub fn emit_constant_quad(&mut self, value: i64) {
self.output.push_str(&format!("\t.quad\t{}\n", value));
}
pub fn emit_constant_long(&mut self, value: i32) {
self.output.push_str(&format!("\t.long\t{}\n", value));
}
pub fn emit_constant_short(&mut self, value: i16) {
self.output.push_str(&format!("\t.short\t{}\n", value));
}
pub fn emit_constant_byte(&mut self, value: u8) {
self.output.push_str(&format!("\t.byte\t{}\n", value));
}
pub fn emit_constant_pool(&mut self, values: &[i64]) {
if values.is_empty() {
return;
}
self.output.push_str("\t.section\t.rodata\n");
self.output.push_str("\t.align\t8\n");
self.emit_label(".LCPI0_0");
for &val in values {
self.emit_constant_quad(val);
}
self.output.push_str("\t.text\n");
}
pub fn emit_jump_table(&mut self, targets: &[&str]) {
if targets.is_empty() {
return;
}
self.output.push_str("\t.section\t.rodata\n");
self.output.push_str("\t.align\t4\n");
self.emit_label(".LJTI0_0");
for target in targets {
self.output.push_str(&format!("\t.long\t{}\n", target));
}
self.output.push_str("\t.text\n");
}
pub fn emit_comment(&mut self, comment: &str) {
self.output.push_str(&format!("\t# {}\n", comment));
}
pub fn emit_newline(&mut self) {
self.output.push('\n');
}
pub fn format_instruction_with_decorators(
&self,
mnemonic: &str,
operands: &[String],
decorators: &str,
) -> String {
let mnemonic_part = if decorators.is_empty() {
mnemonic.to_string()
} else {
format!("{}{}", mnemonic, decorators)
};
if operands.is_empty() {
format!("\t{}\n", mnemonic_part)
} else {
format!("\t{}\t{}\n", mnemonic_part, operands.join(", "))
}
}
pub fn print_full_module(&mut self, module: &Module) -> String {
self.output.clear();
self.emit_comment(&format!("Assembly output for: {}", module.source_filename));
self.output
.push_str(&format!("\t.file\t\"{}\"\n", module.source_filename));
self.emit_directive_text();
self.emit_newline();
for func in &module.functions {
let f = func.borrow();
self.emit_comment(&format!("Function: {}", f.name));
self.emit_directive_globl(&f.name);
self.emit_directive_type(&f.name, "@function");
self.emit_label(&f.name);
self.emit_cfi_startproc();
self.emit_comment("function body");
self.emit_cfi_endproc();
self.emit_directive_size(&f.name, &format!(".-{}", f.name));
self.emit_newline();
}
self.output.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::*;
use crate::x86::x86_instr_info::X86Opcode;
fn make_test_mf(name: &str) -> MachineFunction {
MachineFunction::new(name)
}
#[test]
fn test_asm_syntax_default() {
assert_eq!(AsmSyntax::default(), AsmSyntax::ATT);
}
#[test]
fn test_asm_syntax_values() {
let att = AsmSyntax::ATT;
let intel = AsmSyntax::Intel;
assert_ne!(att, intel);
assert_eq!(att, AsmSyntax::ATT);
}
#[test]
fn test_format_reg_att_rax() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_reg_att(RAX), "%rax");
assert_eq!(printer.format_reg_att(RCX), "%rcx");
assert_eq!(printer.format_reg_att(RSP), "%rsp");
}
#[test]
fn test_format_reg_att_eax() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_reg_att(EAX), "%eax");
assert_eq!(printer.format_reg_att(EBP), "%ebp");
}
#[test]
fn test_format_reg_att_8bit() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_reg_att(AL), "%al");
assert_eq!(printer.format_reg_att(AH), "%ah");
}
#[test]
fn test_format_reg_intel() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_reg_intel(RAX), "rax");
assert_eq!(printer.format_reg_intel(EAX), "eax");
assert_eq!(printer.format_reg_intel(R8), "r8");
assert_eq!(printer.format_reg_intel(R8D), "r8d");
}
#[test]
fn test_format_intel_syntax_registers() {
let mut printer = X86AsmPrinter::default();
printer.syntax = AsmSyntax::Intel;
assert_eq!(printer.format_reg_intel(RAX), "rax");
assert_eq!(printer.format_reg_intel(RDI), "rdi");
assert_eq!(printer.format_reg_intel(R12), "r12");
}
#[test]
fn test_format_xmm_registers() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_reg_att(XMM0), "%xmm0");
assert_eq!(printer.format_reg_att(XMM15), "%xmm15");
}
#[test]
fn test_format_immediate_att() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_immediate(42), "$42");
assert_eq!(printer.format_immediate(-1), "$-1");
assert_eq!(printer.format_immediate(0), "$0");
}
#[test]
fn test_format_immediate_intel() {
let mut printer = X86AsmPrinter::default();
printer.syntax = AsmSyntax::Intel;
assert_eq!(printer.format_immediate(42), "42");
assert_eq!(printer.format_immediate(-1), "-1");
}
#[test]
fn test_get_mnemonic_basic() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::MOV as u32), "mov");
assert_eq!(printer.get_mnemonic(X86Opcode::ADD as u32), "add");
assert_eq!(printer.get_mnemonic(X86Opcode::SUB as u32), "sub");
assert_eq!(printer.get_mnemonic(X86Opcode::RET as u32), "ret");
assert_eq!(printer.get_mnemonic(X86Opcode::JMP as u32), "jmp");
assert_eq!(printer.get_mnemonic(X86Opcode::CALL as u32), "call");
}
#[test]
fn test_get_mnemonic_conditional() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::JE as u32), "je");
assert_eq!(printer.get_mnemonic(X86Opcode::JNE as u32), "jne");
assert_eq!(printer.get_mnemonic(X86Opcode::JG as u32), "jg");
assert_eq!(printer.get_mnemonic(X86Opcode::JL as u32), "jl");
}
#[test]
fn test_get_mnemonic_setcc() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::SETE as u32), "sete");
assert_eq!(printer.get_mnemonic(X86Opcode::SETNE as u32), "setne");
assert_eq!(printer.get_mnemonic(X86Opcode::SETL as u32), "setl");
}
#[test]
fn test_get_mnemonic_cmov() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::CMOVE as u32), "cmove");
assert_eq!(printer.get_mnemonic(X86Opcode::CMOVNE as u32), "cmovne");
assert_eq!(printer.get_mnemonic(X86Opcode::CMOVG as u32), "cmovg");
}
#[test]
fn test_get_mnemonic_unknown() {
let printer = X86AsmPrinter::default();
let mnemonic = printer.get_mnemonic(9999);
assert!(mnemonic.starts_with("opcode_"));
}
#[test]
fn test_print_operand_reg() {
let printer = X86AsmPrinter::default();
let op = MachineOperand::Reg(3);
assert_eq!(printer.print_operand(&op), "%vreg3");
}
#[test]
fn test_print_operand_phys_reg() {
let printer = X86AsmPrinter::default();
let op = MachineOperand::PhysReg(RAX as u32);
assert_eq!(printer.print_operand(&op), "%rax");
}
#[test]
fn test_print_operand_imm() {
let printer = X86AsmPrinter::default();
let op = MachineOperand::Imm(42);
assert_eq!(printer.print_operand(&op), "$42");
}
#[test]
fn test_print_operand_label() {
let printer = X86AsmPrinter::default();
let op = MachineOperand::Label("entry".into());
assert_eq!(printer.print_operand(&op), ".LBB_entry");
}
#[test]
fn test_print_operand_global() {
let printer = X86AsmPrinter::default();
let op = MachineOperand::Global("printf".into());
assert_eq!(printer.print_operand(&op), "printf");
}
#[test]
fn test_print_instruction_mov_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::MOV as u32).with_def(0);
mi.operands.push(MachineOperand::PhysReg(RAX as u32));
mi.push_imm(42);
printer.print_instruction(&mi);
assert!(printer.output.contains("mov"));
assert!(printer.output.contains("$42"));
}
#[test]
fn test_print_instruction_add_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::ADD as u32).with_def(1);
mi.push_reg(1);
mi.push_reg(2);
printer.print_instruction(&mi);
assert!(printer.output.contains("add"));
assert!(printer.output.contains("%vreg"));
}
#[test]
fn test_print_instruction_ret_att() {
let mut printer = X86AsmPrinter::default();
let mi = MachineInstr::new(X86Opcode::RET as u32);
printer.print_instruction(&mi);
assert!(printer.output.contains("ret"));
}
#[test]
fn test_print_instruction_jmp_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::JMP as u32);
mi.push_label("target");
printer.print_instruction(&mi);
assert!(printer.output.contains("jmp"));
assert!(printer.output.contains(".LBB_target"));
}
#[test]
fn test_print_instruction_call_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::CALL as u32);
mi.operands.push(MachineOperand::Global("foo".into()));
printer.print_instruction(&mi);
assert!(printer.output.contains("call"));
assert!(printer.output.contains("foo"));
}
#[test]
fn test_print_instruction_mov_intel() {
let mut printer = X86AsmPrinter::new(
X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""),
AsmSyntax::Intel,
);
let mut mi = MachineInstr::new(X86Opcode::MOV as u32).with_def(0);
mi.operands.push(MachineOperand::PhysReg(RAX as u32));
mi.push_imm(42);
printer.print_instruction(&mi);
assert!(printer.output.contains("mov"));
assert!(printer.output.contains("42"));
assert!(!printer.output.contains("$42"));
}
#[test]
fn test_print_instruction_intel_registers() {
let mut printer = X86AsmPrinter::new(
X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""),
AsmSyntax::Intel,
);
let mut mi = MachineInstr::new(X86Opcode::ADD as u32).with_def(1);
mi.operands.push(MachineOperand::PhysReg(EAX as u32));
mi.operands.push(MachineOperand::PhysReg(EBX as u32));
printer.print_instruction(&mi);
assert!(!printer.output.contains("%eax"));
assert!(printer.output.contains("eax"));
assert!(printer.output.contains("ebx"));
}
#[test]
fn test_print_prologue() {
let mut printer = X86AsmPrinter::default();
let mf = make_test_mf("my_func");
printer.print_prologue(&mf);
assert!(printer.output.contains(".globl my_func"));
assert!(printer.output.contains(".type my_func, @function"));
assert!(printer.output.contains("my_func:"));
}
#[test]
fn test_print_epilogue() {
let mut printer = X86AsmPrinter::default();
let mf = make_test_mf("my_func");
printer.print_epilogue(&mf);
assert!(printer.output.contains(".size my_func, .-my_func"));
}
#[test]
fn test_print_basic_block() {
let mut printer = X86AsmPrinter::default();
let mut bb = MachineBasicBlock {
name: "entry".into(),
instructions: Vec::new(),
successors: Vec::new(),
};
bb.instructions
.push(MachineInstr::new(X86Opcode::RET as u32));
printer.print_basic_block(&bb);
assert!(printer.output.contains(".LBB_entry:"));
assert!(printer.output.contains("ret"));
}
#[test]
fn test_print_full_function() {
let mut printer = X86AsmPrinter::default();
let mut mf = make_test_mf("test_func");
let mut bb = MachineBasicBlock {
name: "entry".into(),
instructions: Vec::new(),
successors: Vec::new(),
};
let mut push_rbp = MachineInstr::new(X86Opcode::PUSH as u32);
push_rbp.operands.push(MachineOperand::PhysReg(RBP as u32));
bb.instructions.push(push_rbp);
let mut mov_fp = MachineInstr::new(X86Opcode::MOV as u32).with_def(0);
mov_fp.operands.push(MachineOperand::PhysReg(RBP as u32));
mov_fp.operands.push(MachineOperand::PhysReg(RSP as u32));
bb.instructions.push(mov_fp);
let mut mov_imm = MachineInstr::new(X86Opcode::MOV as u32).with_def(1);
mov_imm.operands.push(MachineOperand::PhysReg(EAX as u32));
mov_imm.push_imm(42);
bb.instructions.push(mov_imm);
let mut pop_rbp = MachineInstr::new(X86Opcode::POP as u32);
pop_rbp.operands.push(MachineOperand::PhysReg(RBP as u32));
bb.instructions.push(pop_rbp);
bb.instructions
.push(MachineInstr::new(X86Opcode::RET as u32));
mf.push_block(bb);
printer.print_function(&mf);
let out = &printer.output;
assert!(out.contains(".globl test_func"));
assert!(out.contains("test_func:"));
assert!(out.contains("push"));
assert!(out.contains("mov"));
assert!(out.contains("$42"));
assert!(out.contains("pop"));
assert!(out.contains("ret"));
assert!(out.contains(".size test_func, .-test_func"));
}
#[test]
fn test_print_module() {
let mut printer = X86AsmPrinter::default();
let mut module = Module::new("test");
module.source_filename = "test.ll".into();
let out = printer.print_module(&module);
assert!(out.contains(".file"));
assert!(out.contains("test.ll"));
assert!(out.contains(".text"));
}
#[test]
fn test_default_printer_is_att() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.syntax, AsmSyntax::ATT);
assert!(printer.output.is_empty());
}
#[test]
fn test_new_with_syntax() {
let printer = X86AsmPrinter::new(
X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""),
AsmSyntax::Intel,
);
assert_eq!(printer.syntax, AsmSyntax::Intel);
}
#[test]
fn test_empty_instruction() {
let mut printer = X86AsmPrinter::default();
let mi = MachineInstr::new(99999); printer.print_instruction(&mi);
assert!(printer.output.contains("opcode_99999"));
}
#[test]
fn test_instruction_with_no_operands() {
let mut printer = X86AsmPrinter::default();
let mi = MachineInstr::new(X86Opcode::NOP as u32);
printer.print_instruction(&mi);
assert!(printer.output.contains("nop"));
}
#[test]
fn test_multiple_instructions() {
let mut printer = X86AsmPrinter::default();
let mut bb = MachineBasicBlock {
name: String::new(),
instructions: Vec::new(),
successors: Vec::new(),
};
bb.instructions
.push(MachineInstr::new(X86Opcode::PUSH as u32));
bb.instructions
.push(MachineInstr::new(X86Opcode::POP as u32));
bb.instructions
.push(MachineInstr::new(X86Opcode::RET as u32));
printer.print_basic_block(&bb);
let lines: Vec<&str> = printer.output.lines().collect();
assert!(
lines.len() >= 3,
"Expected at least 3 lines, got: {:?}",
lines
);
}
#[test]
fn test_print_instruction_cmp_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::CMP as u32);
mi.push_reg(1);
mi.push_reg(2);
printer.print_instruction(&mi);
assert!(printer.output.contains("cmp"));
}
#[test]
fn test_print_instruction_lea_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::LEA as u32).with_def(2);
mi.push_reg(0);
mi.push_imm(8);
printer.print_instruction(&mi);
assert!(printer.output.contains("lea"));
}
#[test]
fn test_print_instruction_test_att() {
let mut printer = X86AsmPrinter::default();
let mut mi = MachineInstr::new(X86Opcode::TEST as u32);
mi.push_reg(3);
mi.push_imm(1);
printer.print_instruction(&mi);
assert!(printer.output.contains("test"));
}
#[test]
fn test_format_ymm_intel() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.format_reg_intel(YMM0), "ymm0");
assert_eq!(printer.format_reg_intel(YMM7), "ymm7");
}
#[test]
fn test_rint_sext_movsx() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::MOVSX as u32), "movsx");
assert_eq!(printer.get_mnemonic(X86Opcode::MOVZX as u32), "movzx");
}
#[test]
fn test_rint_logic_mnemonics() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::AND as u32), "and");
assert_eq!(printer.get_mnemonic(X86Opcode::OR as u32), "or");
assert_eq!(printer.get_mnemonic(X86Opcode::XOR as u32), "xor");
assert_eq!(printer.get_mnemonic(X86Opcode::NOT as u32), "not");
}
#[test]
fn test_rint_shift_mnemonics() {
let printer = X86AsmPrinter::default();
assert_eq!(printer.get_mnemonic(X86Opcode::SHL as u32), "shl");
assert_eq!(printer.get_mnemonic(X86Opcode::SHR as u32), "shr");
assert_eq!(printer.get_mnemonic(X86Opcode::SAR as u32), "sar");
}
#[test]
fn test_print_memory_operand_intel_basic() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_intel(Some(RAX), None, 1, 0, false, None);
assert_eq!(result, "[rax]");
}
#[test]
fn test_print_memory_operand_intel_with_disp() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_intel(Some(RBP), None, 1, -8, false, None);
assert!(result.contains("rbp"));
assert!(result.contains("0x8"));
}
#[test]
fn test_print_memory_operand_intel_with_index() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_intel(Some(RBX), Some(RAX), 4, 16, false, None);
assert!(result.contains("rbx"));
assert!(result.contains("rax*4"));
assert!(result.contains("0x10"));
}
#[test]
fn test_print_memory_operand_intel_rip_relative() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_intel(None, None, 1, 0x1234, true, None);
assert!(result.contains("rip"));
assert!(result.contains("0x1234"));
}
#[test]
fn test_print_memory_operand_intel_rip_no_disp() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_intel(None, None, 1, 0, true, None);
assert_eq!(result, "[rip]");
}
#[test]
fn test_print_memory_operand_intel_negative_disp() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_intel(Some(RSP), None, 1, -16, false, None);
assert!(result.contains("rsp"));
assert!(result.contains("0x10"));
}
#[test]
fn test_print_memory_operand_att_basic() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_att(Some(RAX), None, 1, 0, false, None);
assert_eq!(result, "(%rax)");
}
#[test]
fn test_print_memory_operand_att_with_disp() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_att(Some(RBP), None, 1, -8, false, None);
assert!(result.contains("-8"));
assert!(result.contains("%rbp"));
}
#[test]
fn test_print_memory_operand_att_with_index_scale() {
let printer = X86AsmPrinter::default();
let result = printer.print_memory_operand_att(Some(RBX), Some(RSI), 4, 8, false, None);
assert!(result.contains("%rbx"));
assert!(result.contains("%rsi"));
assert!(result.contains(",4"));
}
#[test]
fn test_avx512_decorators_mask() {
let printer = X86AsmPrinter::default();
let dec = printer.print_avx512_decorators(Some(3), false, None, None);
assert_eq!(dec, "{k3}");
}
#[test]
fn test_avx512_decorators_mask_zeroing() {
let printer = X86AsmPrinter::default();
let dec = printer.print_avx512_decorators(Some(1), true, None, None);
assert!(dec.contains("{k1}"));
assert!(dec.contains("{z}"));
}
#[test]
fn test_avx512_decorators_broadcast() {
let printer = X86AsmPrinter::default();
let dec = printer.print_avx512_decorators(Some(2), false, Some(4), None);
assert!(dec.contains("{k2}"));
assert!(dec.contains("{1to4}"));
}
#[test]
fn test_avx512_decorators_rounding() {
let printer = X86AsmPrinter::default();
let dec = printer.print_avx512_decorators(None, false, None, Some("rn-sae"));
assert_eq!(dec, "{rn-sae}");
}
#[test]
fn test_avx512_decorators_empty() {
let printer = X86AsmPrinter::default();
let dec = printer.print_avx512_decorators(None, false, None, None);
assert!(dec.is_empty());
}
#[test]
fn test_avx512_decorators_k0_omitted() {
let printer = X86AsmPrinter::default();
let dec = printer.print_avx512_decorators(Some(0), false, None, None);
assert!(dec.is_empty());
}
#[test]
fn test_emit_directive_section() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_section("my_data", "aw");
assert!(printer.output.contains(".section"));
assert!(printer.output.contains("my_data"));
}
#[test]
fn test_emit_directive_text() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_text();
assert_eq!(printer.output.trim(), ".text");
}
#[test]
fn test_emit_directive_data() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_data();
assert_eq!(printer.output.trim(), ".data");
}
#[test]
fn test_emit_directive_globl() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_globl("main");
assert!(printer.output.contains(".globl main"));
}
#[test]
fn test_emit_directive_local() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_local("helper");
assert!(printer.output.contains(".local helper"));
}
#[test]
fn test_emit_directive_weak() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_weak("sym");
assert!(printer.output.contains(".weak sym"));
}
#[test]
fn test_emit_directive_align() {
let mut printer = X86AsmPrinter::default();
printer.emit_directive_align(8);
assert!(printer.output.contains(".align 8"));
}
#[test]
fn test_emit_cfi_directives() {
let mut printer = X86AsmPrinter::default();
printer.emit_cfi_startproc();
printer.emit_cfi_def_cfa_offset(16);
printer.emit_cfi_offset(RBP, -16);
printer.emit_cfi_endproc();
assert!(printer.output.contains(".cfi_startproc"));
assert!(printer.output.contains(".cfi_def_cfa_offset 16"));
assert!(printer.output.contains(".cfi_endproc"));
}
#[test]
fn test_emit_label() {
let mut printer = X86AsmPrinter::default();
printer.emit_label("my_func");
assert_eq!(printer.output.trim(), "my_func:");
}
#[test]
fn test_emit_func_label() {
let mut printer = X86AsmPrinter::default();
printer.emit_func_label("main", "0");
assert!(printer.output.contains(".Lfunc_begin0_main:"));
}
#[test]
fn test_emit_bb_label() {
let mut printer = X86AsmPrinter::default();
printer.emit_bb_label("entry");
assert!(printer.output.contains(".LBB_entry:"));
}
#[test]
fn test_emit_tmp_label() {
let mut printer = X86AsmPrinter::default();
printer.emit_tmp_label("0");
assert!(printer.output.contains(".Ltmp0:"));
}
#[test]
fn test_emit_constant_quad() {
let mut printer = X86AsmPrinter::default();
printer.emit_constant_quad(0xDEADBEEF);
assert!(printer.output.contains(".quad"));
assert!(printer.output.contains("3735928559")); }
#[test]
fn test_emit_constant_long() {
let mut printer = X86AsmPrinter::default();
printer.emit_constant_long(42);
assert!(printer.output.contains(".long"));
}
#[test]
fn test_emit_constant_short() {
let mut printer = X86AsmPrinter::default();
printer.emit_constant_short(1024);
assert!(printer.output.contains(".short"));
}
#[test]
fn test_emit_constant_byte() {
let mut printer = X86AsmPrinter::default();
printer.emit_constant_byte(0x90);
assert!(printer.output.contains(".byte"));
}
#[test]
fn test_emit_constant_pool() {
let mut printer = X86AsmPrinter::default();
printer.emit_constant_pool(&[1, 2, 3, 4]);
assert!(printer.output.contains(".rodata"));
assert!(printer.output.contains(".LCPI0_0"));
assert!(printer.output.contains(".quad"));
}
#[test]
fn test_emit_constant_pool_empty() {
let mut printer = X86AsmPrinter::default();
printer.emit_constant_pool(&[]);
assert!(printer.output.is_empty());
}
#[test]
fn test_emit_jump_table() {
let mut printer = X86AsmPrinter::default();
printer.emit_jump_table(&[".LBB0_1", ".LBB0_2", ".LBB0_3"]);
assert!(printer.output.contains(".LJTI0_0"));
assert!(printer.output.contains(".LBB0_1"));
assert!(printer.output.contains(".LBB0_2"));
assert!(printer.output.contains(".LBB0_3"));
}
#[test]
fn test_format_instruction_with_decorators() {
let printer = X86AsmPrinter::default();
let result = printer.format_instruction_with_decorators(
"vaddps",
&["%zmm0".into(), "%zmm1".into(), "%zmm2".into()],
"{k1}{z}",
);
assert!(result.contains("vaddps{k1}{z}"));
assert!(result.contains("%zmm0"));
assert!(result.contains("%zmm1"));
assert!(result.contains("%zmm2"));
}
#[test]
fn test_format_instruction_no_decorators() {
let printer = X86AsmPrinter::default();
let result =
printer.format_instruction_with_decorators("add", &["%rax".into(), "%rcx".into()], "");
assert!(result.contains("add"));
assert!(!result.contains("{"));
}
#[test]
fn test_emit_comment() {
let mut printer = X86AsmPrinter::default();
printer.emit_comment("end of function");
assert!(printer.output.contains("# end of function"));
}
}