use std::fmt::Write;
use crate::codegen::asm_ast::{
AsmBinaryOp, AsmProgram, AsmStaticConstant, AsmStaticVar, AsmType, AsmUnaryOp, CondCode,
Instruction, Operand, Reg, StaticInit,
};
use crate::error::{CompileError, Result};
pub fn emit(program: &AsmProgram) -> Result<String> {
let mut out = String::new();
for func in &program.functions {
emit_function(&mut out, func)?;
}
for var in &program.static_vars {
emit_static_var(&mut out, var)?;
}
for constant in &program.static_constants {
emit_static_constant(&mut out, constant)?;
}
writeln!(out, " .section .ferrugo_sig,\"a\",@note")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .byte 0x46,0x45,0x52,0x52,0x55,0x47,0x4f")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .byte 0x04,0x00").map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .section .note.GNU-stack,\"\",@progbits")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
Ok(out)
}
fn emit_function(out: &mut String, func: &crate::codegen::asm_ast::AsmFunction) -> Result<()> {
if func.global {
writeln!(out, " .globl {}", func.name)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
writeln!(out, "{}:", func.name).map_err(|e| CompileError::EmitError(e.to_string()))?;
for instr in &func.instructions {
emit_instruction(out, instr)?;
}
Ok(())
}
fn static_init_size(init: &StaticInit, asm_type: &AsmType) -> usize {
match init {
StaticInit::IntInit(_) => match asm_type {
AsmType::Byte => 1,
AsmType::Word => 2,
AsmType::Longword => 4,
_ => 8,
},
StaticInit::FloatInit(_) => 4,
StaticInit::DoubleInit(_) => 8,
StaticInit::ZeroInit(n) => *n,
StaticInit::StringInit(_, n) => *n,
StaticInit::ByteArrayInit(bytes) => bytes.len(),
StaticInit::PointerArrayInit(labels) => labels.len() * 8,
StaticInit::ArrayInit(elems) => elems.iter().map(|e| static_init_size(e, asm_type)).sum(),
}
}
fn is_static_init_zero(init: &StaticInit) -> bool {
match init {
StaticInit::IntInit(v) => *v == 0,
StaticInit::DoubleInit(_) => false,
StaticInit::ZeroInit(_) => true,
StaticInit::ByteArrayInit(bytes) => bytes.iter().all(|&b| b == 0),
StaticInit::ArrayInit(elems) => elems.iter().all(is_static_init_zero),
_ => false,
}
}
fn emit_static_var(out: &mut String, var: &AsmStaticVar) -> Result<()> {
let (align, size) = match var.init {
StaticInit::ZeroInit(n) => (16, n), StaticInit::PointerArrayInit(ref labels) => (8, labels.len() * 8),
StaticInit::ArrayInit(ref elems) => {
if let Some(ref vt) = var.var_type {
(vt.alignment().max(16), vt.size())
} else {
let total_size: usize = elems
.iter()
.map(|e| static_init_size(e, &var.asm_type))
.sum();
let elem_align = match var.asm_type {
AsmType::Byte => 1,
AsmType::Word => 2,
AsmType::Longword | AsmType::Float => 4,
AsmType::Quadword | AsmType::Double => 8,
};
(std::cmp::max(elem_align, 16), total_size)
}
}
_ => match var.asm_type {
AsmType::Byte => (1, 1),
AsmType::Word => (2, 2),
AsmType::Longword | AsmType::Float => (4, 4),
AsmType::Quadword | AsmType::Double => (8, 8),
},
};
if var.global {
writeln!(out, " .globl {}", var.name)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
let is_zero = match &var.init {
StaticInit::IntInit(v) => *v == 0,
StaticInit::FloatInit(_) => false,
StaticInit::DoubleInit(_) => false,
StaticInit::ZeroInit(_) => true,
StaticInit::StringInit(_, _) => false,
StaticInit::ByteArrayInit(_) => false,
StaticInit::PointerArrayInit(_) => false,
StaticInit::ArrayInit(elems) => elems.iter().all(is_static_init_zero),
};
if !is_zero {
writeln!(out, " .data").map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .align {align}").map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, "{}:", var.name).map_err(|e| CompileError::EmitError(e.to_string()))?;
match &var.init {
StaticInit::IntInit(v) => {
let directive = match var.asm_type {
AsmType::Byte => "byte",
AsmType::Word => "word",
AsmType::Longword => "long",
_ => "quad",
};
writeln!(out, " .{directive} {v}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::FloatInit(v) => {
writeln!(out, " .long {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::DoubleInit(v) => {
writeln!(out, " .quad {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::ZeroInit(_) => unreachable!(),
StaticInit::StringInit(content, _) => {
writeln!(out, " .asciz \"{}\"", escape_string_for_asm(content))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::ByteArrayInit(bytes) => {
let byte_strs: Vec<String> = bytes.iter().map(|b| format!("0x{b:02x}")).collect();
writeln!(out, " .byte {}", byte_strs.join(", "))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::PointerArrayInit(labels) => {
for label in labels {
writeln!(out, " .quad {label}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
StaticInit::ArrayInit(elems) => {
emit_array_init_data(out, elems, &var.asm_type)?;
}
}
} else {
writeln!(out, " .bss").map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .align {align}").map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, "{}:", var.name).map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .zero {size}").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Ok(())
}
fn emit_array_init_data(out: &mut String, elems: &[StaticInit], asm_type: &AsmType) -> Result<()> {
for elem in elems {
match elem {
StaticInit::IntInit(v) => {
let directive = match asm_type {
AsmType::Byte => "byte",
AsmType::Word => "word",
AsmType::Longword => "long",
_ => "quad",
};
writeln!(out, " .{directive} {v}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::FloatInit(v) => {
writeln!(out, " .long {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::DoubleInit(v) => {
writeln!(out, " .quad {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::ZeroInit(n) => {
writeln!(out, " .zero {n}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::PointerArrayInit(labels) => {
for label in labels {
writeln!(out, " .quad {label}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
StaticInit::ByteArrayInit(bytes) => {
if !bytes.is_empty() {
let byte_strs: Vec<String> =
bytes.iter().map(|b| format!("0x{b:02x}")).collect();
writeln!(out, " .byte {}", byte_strs.join(", "))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
StaticInit::ArrayInit(inner) => {
emit_array_init_data(out, inner, asm_type)?;
}
StaticInit::StringInit(content, _) => {
writeln!(out, " .asciz \"{}\"", escape_string_for_asm(content))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
}
Ok(())
}
fn emit_static_constant(out: &mut String, constant: &AsmStaticConstant) -> Result<()> {
writeln!(out, " .section .rodata").map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " .align {}", constant.alignment)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, "{}:", constant.name).map_err(|e| CompileError::EmitError(e.to_string()))?;
match &constant.init {
StaticInit::IntInit(v) => {
writeln!(out, " .quad {v}").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::FloatInit(v) => {
writeln!(out, " .long {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
if constant.alignment >= 16 {
writeln!(out, " .zero 12")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
StaticInit::DoubleInit(v) => {
writeln!(out, " .quad {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::ZeroInit(n) => {
writeln!(out, " .zero {n}").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::StringInit(content, _) => {
writeln!(out, " .asciz \"{}\"", escape_string_for_asm(content))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::ByteArrayInit(bytes) => {
let byte_strs: Vec<String> = bytes.iter().map(|b| format!("0x{b:02x}")).collect();
writeln!(out, " .byte {}", byte_strs.join(", "))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::PointerArrayInit(labels) => {
for label in labels {
writeln!(out, " .quad {label}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
StaticInit::ArrayInit(elems) => {
for elem in elems {
match elem {
StaticInit::IntInit(v) => {
writeln!(out, " .quad {v}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::FloatInit(v) => {
writeln!(out, " .long {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::DoubleInit(v) => {
writeln!(out, " .quad {}", v.to_bits())
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
StaticInit::ZeroInit(n) => {
writeln!(out, " .zero {n}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
_ => unreachable!("unsupported element type in array initializer"),
}
}
}
}
Ok(())
}
fn emit_instruction(out: &mut String, instr: &Instruction) -> Result<()> {
match instr {
Instruction::Mov { asm_type, src, dst } => {
if *asm_type == AsmType::Double {
writeln!(
out,
" movsd {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else if *asm_type == AsmType::Float {
writeln!(
out,
" movss {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else {
let suffix = type_suffix(asm_type);
writeln!(
out,
" mov{suffix} {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
Instruction::Unary {
asm_type,
op,
operand,
} => {
let base = match op {
AsmUnaryOp::Neg => "neg",
AsmUnaryOp::Not => "not",
};
let suffix = type_suffix(asm_type);
writeln!(
out,
" {base}{suffix} {}",
format_operand_typed(operand, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cmp { asm_type, src, dst } => {
if *asm_type == AsmType::Double {
writeln!(
out,
" comisd {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else if *asm_type == AsmType::Float {
writeln!(
out,
" comiss {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else {
let suffix = type_suffix(asm_type);
writeln!(
out,
" cmp{suffix} {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
Instruction::SetCC { condition, operand } => {
let suffix = format_condition(condition);
writeln!(out, " set{} {}", suffix, format_operand_byte(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Binary {
asm_type,
op,
src,
dst,
} => {
if *asm_type == AsmType::Double {
let mnemonic = match op {
AsmBinaryOp::Add => "addsd",
AsmBinaryOp::Sub => "subsd",
AsmBinaryOp::Mult => "mulsd",
AsmBinaryOp::DivDouble => "divsd",
AsmBinaryOp::Xor => "xorpd",
AsmBinaryOp::And
| AsmBinaryOp::Or
| AsmBinaryOp::BitXor
| AsmBinaryOp::Sal
| AsmBinaryOp::Sar
| AsmBinaryOp::Shr => unreachable!("bitwise/shift ops not for Double type"),
};
writeln!(
out,
" {mnemonic} {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else if *asm_type == AsmType::Float {
let mnemonic = match op {
AsmBinaryOp::Add => "addss",
AsmBinaryOp::Sub => "subss",
AsmBinaryOp::Mult => "mulss",
AsmBinaryOp::DivDouble => "divss",
AsmBinaryOp::Xor => "xorps",
AsmBinaryOp::And
| AsmBinaryOp::Or
| AsmBinaryOp::BitXor
| AsmBinaryOp::Sal
| AsmBinaryOp::Sar
| AsmBinaryOp::Shr => unreachable!("bitwise/shift ops not for Float type"),
};
writeln!(
out,
" {mnemonic} {}, {}",
format_operand_typed(src, asm_type),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else {
let is_shift = matches!(op, AsmBinaryOp::Sal | AsmBinaryOp::Sar | AsmBinaryOp::Shr);
let base = match op {
AsmBinaryOp::Add => "add",
AsmBinaryOp::Sub => "sub",
AsmBinaryOp::Mult => "imul",
AsmBinaryOp::And => "and",
AsmBinaryOp::Or => "or",
AsmBinaryOp::BitXor => "xor",
AsmBinaryOp::Sal => "sal",
AsmBinaryOp::Sar => "sar",
AsmBinaryOp::Shr => "shr",
AsmBinaryOp::DivDouble => unreachable!("DivDouble only for Double type"),
AsmBinaryOp::Xor => unreachable!("Xor only for Double type"),
};
let suffix = type_suffix(asm_type);
let src_str = if is_shift {
format_operand_byte(src)
} else {
format_operand_typed(src, asm_type)
};
writeln!(
out,
" {base}{suffix} {}, {}",
src_str,
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
Instruction::Idiv { asm_type, operand } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" idiv{suffix} {}",
format_operand_typed(operand, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Div { asm_type, operand } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" div{suffix} {}",
format_operand_typed(operand, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::SignExtend(asm_type) => {
let mnemonic = match asm_type {
AsmType::Longword => "cdq",
AsmType::Quadword => "cqo",
AsmType::Byte | AsmType::Word | AsmType::Double | AsmType::Float => {
unreachable!("SignExtend not applicable to {:?}", asm_type)
}
};
writeln!(out, " {mnemonic}").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Movsx { src, dst } => {
writeln!(
out,
" movslq {}, {}",
format_operand(src),
format_operand_quad(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Truncate { src, dst } => {
writeln!(
out,
" movl {}, {}",
format_operand(src),
format_operand(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::MovZeroExtend { src, dst } => {
writeln!(
out,
" movl {}, {}",
format_operand(src),
format_operand(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::MovsxByte { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" movsb{suffix} {}, {}",
format_operand_byte(src),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::MovZeroExtendByte { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" movzb{suffix} {}, {}",
format_operand_byte(src),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::MovsxWord { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" movsw{suffix} {}, {}",
format_operand_word(src),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::MovZeroExtendWord { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" movzw{suffix} {}, {}",
format_operand_word(src),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Push(operand) => {
if let Operand::Register(reg) = operand {
if is_xmm_register(reg) {
writeln!(out, " subq $8, %rsp")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " movsd {}, (%rsp)", format_register_xmm(reg))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else {
writeln!(out, " push {}", format_operand_quad(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
} else {
writeln!(out, " push {}", format_operand_quad(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
Instruction::Pop(operand) => {
if let Operand::Register(reg) = operand {
if is_xmm_register(reg) {
writeln!(out, " movsd (%rsp), {}", format_register_xmm(reg))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
writeln!(out, " addq $8, %rsp")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
} else {
writeln!(out, " pop {}", format_operand_quad(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
} else {
writeln!(out, " pop {}", format_operand_quad(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
Instruction::Jmp(label) => {
writeln!(out, " jmp {label}").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::JmpCC(condition, label) => {
let suffix = format_condition(condition);
writeln!(out, " j{suffix} {label}")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Label(label) => {
writeln!(out, "{label}:").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::AllocateStack(size) => {
writeln!(out, " subq ${size}, %rsp")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::DeallocateStack(size) => {
writeln!(out, " addq ${size}, %rsp")
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Call(name) => {
writeln!(out, " call {name}").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cvtsi2sd { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" cvtsi2sd{suffix} {}, {}",
format_operand_typed(src, asm_type),
format_operand_xmm(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cvttsd2si { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" cvttsd2si{suffix} {}, {}",
format_operand_xmm(src),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cvtsi2ss { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" cvtsi2ss{suffix} {}, {}",
format_operand_typed(src, asm_type),
format_operand_xmm(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cvttss2si { asm_type, src, dst } => {
let suffix = type_suffix(asm_type);
writeln!(
out,
" cvttss2si{suffix} {}, {}",
format_operand_xmm(src),
format_operand_typed(dst, asm_type)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cvtss2sd { src, dst } => {
writeln!(
out,
" cvtss2sd {}, {}",
format_operand_xmm(src),
format_operand_xmm(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Cvtsd2ss { src, dst } => {
writeln!(
out,
" cvtsd2ss {}, {}",
format_operand_xmm(src),
format_operand_xmm(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Lea { src, dst } => {
writeln!(
out,
" leaq {}, {}",
format_operand_quad(src),
format_operand_quad(dst)
)
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::Ret => {
writeln!(out, " ret").map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::JmpIndirect(operand, _) => {
writeln!(out, " jmp *{}", format_operand_quad(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::CallIndirect(operand) => {
writeln!(out, " call *{}", format_operand_quad(operand))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
Instruction::RawBytes(bytes) => {
let byte_strs: Vec<String> = bytes.iter().map(|b| format!("0x{b:02x}")).collect();
writeln!(out, " .byte {}", byte_strs.join(", "))
.map_err(|e| CompileError::EmitError(e.to_string()))?;
}
}
Ok(())
}
fn type_suffix(asm_type: &AsmType) -> &'static str {
match asm_type {
AsmType::Byte => "b",
AsmType::Word => "w",
AsmType::Longword => "l",
AsmType::Quadword => "q",
AsmType::Double => "sd",
AsmType::Float => "ss",
}
}
fn format_operand_typed(operand: &Operand, asm_type: &AsmType) -> String {
match asm_type {
AsmType::Byte => format_operand_byte(operand),
AsmType::Word => format_operand_word(operand),
AsmType::Longword => format_operand(operand),
AsmType::Quadword => format_operand_quad(operand),
AsmType::Double => format_operand_xmm(operand),
AsmType::Float => format_operand_xmm(operand),
}
}
fn format_operand(operand: &Operand) -> String {
match operand {
Operand::Imm(value) => format!("${value}"),
Operand::Register(reg) => format_register(reg).to_string(),
Operand::Pseudo(name) => panic!("BUG: Pseudo '{}' survived to emission", name),
Operand::Stack(offset) => format!("{offset}(%rbp)"),
Operand::Data(name) => format!("{name}(%rip)"),
Operand::Memory(reg) => format!("({})", format_register_quad(reg)),
Operand::MemoryOffset(reg, offset) => format!("{offset}({})", format_register_quad(reg)),
}
}
fn format_operand_byte(operand: &Operand) -> String {
match operand {
Operand::Imm(value) => format!("${value}"),
Operand::Register(reg) => format_register_byte(reg).to_string(),
Operand::Pseudo(name) => panic!("BUG: Pseudo '{}' survived to emission", name),
Operand::Stack(offset) => format!("{offset}(%rbp)"),
Operand::Data(name) => format!("{name}(%rip)"),
Operand::Memory(reg) => format!("({})", format_register_quad(reg)),
Operand::MemoryOffset(reg, offset) => format!("{offset}({})", format_register_quad(reg)),
}
}
fn format_operand_word(operand: &Operand) -> String {
match operand {
Operand::Imm(value) => format!("${value}"),
Operand::Register(reg) => format_register_word(reg).to_string(),
Operand::Pseudo(name) => panic!("BUG: Pseudo '{}' survived to emission", name),
Operand::Stack(offset) => format!("{offset}(%rbp)"),
Operand::Data(name) => format!("{name}(%rip)"),
Operand::Memory(reg) => format!("({})", format_register_quad(reg)),
Operand::MemoryOffset(reg, offset) => format!("{offset}({})", format_register_quad(reg)),
}
}
fn format_operand_quad(operand: &Operand) -> String {
match operand {
Operand::Imm(value) => format!("${value}"),
Operand::Register(reg) => format_register_quad(reg).to_string(),
Operand::Pseudo(name) => panic!("BUG: Pseudo '{}' survived to emission", name),
Operand::Stack(offset) => format!("{offset}(%rbp)"),
Operand::Data(name) => format!("{name}(%rip)"),
Operand::Memory(reg) => format!("({})", format_register_quad(reg)),
Operand::MemoryOffset(reg, offset) => format!("{offset}({})", format_register_quad(reg)),
}
}
fn format_operand_xmm(operand: &Operand) -> String {
match operand {
Operand::Imm(value) => format!("${value}"),
Operand::Register(reg) => format_register_xmm(reg).to_string(),
Operand::Pseudo(name) => panic!("BUG: Pseudo '{}' survived to emission", name),
Operand::Stack(offset) => format!("{offset}(%rbp)"),
Operand::Data(name) => format!("{name}(%rip)"),
Operand::Memory(reg) => format!("({})", format_register_quad(reg)),
Operand::MemoryOffset(reg, offset) => format!("{offset}({})", format_register_quad(reg)),
}
}
fn format_register_xmm(reg: &Reg) -> &'static str {
match reg {
Reg::XMM0 => "%xmm0",
Reg::XMM1 => "%xmm1",
Reg::XMM2 => "%xmm2",
Reg::XMM3 => "%xmm3",
Reg::XMM4 => "%xmm4",
Reg::XMM5 => "%xmm5",
Reg::XMM6 => "%xmm6",
Reg::XMM7 => "%xmm7",
Reg::XMM8 => "%xmm8",
Reg::XMM9 => "%xmm9",
Reg::XMM10 => "%xmm10",
Reg::XMM11 => "%xmm11",
Reg::XMM12 => "%xmm12",
Reg::XMM13 => "%xmm13",
Reg::XMM14 => "%xmm14",
Reg::XMM15 => "%xmm15",
_ => format_register_quad(reg),
}
}
fn format_register_quad(reg: &Reg) -> &'static str {
match reg {
Reg::AX => "%rax",
Reg::BX => "%rbx",
Reg::CX => "%rcx",
Reg::DX => "%rdx",
Reg::DI => "%rdi",
Reg::SI => "%rsi",
Reg::R8 => "%r8",
Reg::R9 => "%r9",
Reg::R10 => "%r10",
Reg::R11 => "%r11",
Reg::R12 => "%r12",
Reg::R13 => "%r13",
Reg::R14 => "%r14",
Reg::R15 => "%r15",
Reg::SP => "%rsp",
Reg::BP => "%rbp",
Reg::XMM0
| Reg::XMM1
| Reg::XMM2
| Reg::XMM3
| Reg::XMM4
| Reg::XMM5
| Reg::XMM6
| Reg::XMM7
| Reg::XMM8
| Reg::XMM9
| Reg::XMM10
| Reg::XMM11
| Reg::XMM12
| Reg::XMM13
| Reg::XMM14
| Reg::XMM15 => format_register_xmm(reg),
}
}
fn format_register(reg: &Reg) -> &'static str {
match reg {
Reg::AX => "%eax",
Reg::BX => "%ebx",
Reg::CX => "%ecx",
Reg::DX => "%edx",
Reg::DI => "%edi",
Reg::SI => "%esi",
Reg::R8 => "%r8d",
Reg::R9 => "%r9d",
Reg::R10 => "%r10d",
Reg::R11 => "%r11d",
Reg::R12 => "%r12d",
Reg::R13 => "%r13d",
Reg::R14 => "%r14d",
Reg::R15 => "%r15d",
Reg::SP => "%esp",
Reg::BP => "%ebp",
Reg::XMM0
| Reg::XMM1
| Reg::XMM2
| Reg::XMM3
| Reg::XMM4
| Reg::XMM5
| Reg::XMM6
| Reg::XMM7
| Reg::XMM8
| Reg::XMM9
| Reg::XMM10
| Reg::XMM11
| Reg::XMM12
| Reg::XMM13
| Reg::XMM14
| Reg::XMM15 => format_register_xmm(reg),
}
}
fn format_register_byte(reg: &Reg) -> &'static str {
match reg {
Reg::AX => "%al",
Reg::BX => "%bl",
Reg::CX => "%cl",
Reg::DX => "%dl",
Reg::DI => "%dil",
Reg::SI => "%sil",
Reg::R8 => "%r8b",
Reg::R9 => "%r9b",
Reg::R10 => "%r10b",
Reg::R11 => "%r11b",
Reg::R12 => "%r12b",
Reg::R13 => "%r13b",
Reg::R14 => "%r14b",
Reg::R15 => "%r15b",
Reg::SP => "%spl",
Reg::BP => "%bpl",
Reg::XMM0
| Reg::XMM1
| Reg::XMM2
| Reg::XMM3
| Reg::XMM4
| Reg::XMM5
| Reg::XMM6
| Reg::XMM7
| Reg::XMM8
| Reg::XMM9
| Reg::XMM10
| Reg::XMM11
| Reg::XMM12
| Reg::XMM13
| Reg::XMM14
| Reg::XMM15 => format_register_xmm(reg),
}
}
fn format_register_word(reg: &Reg) -> &'static str {
match reg {
Reg::AX => "%ax",
Reg::BX => "%bx",
Reg::CX => "%cx",
Reg::DX => "%dx",
Reg::DI => "%di",
Reg::SI => "%si",
Reg::R8 => "%r8w",
Reg::R9 => "%r9w",
Reg::R10 => "%r10w",
Reg::R11 => "%r11w",
Reg::R12 => "%r12w",
Reg::R13 => "%r13w",
Reg::R14 => "%r14w",
Reg::R15 => "%r15w",
Reg::SP => "%sp",
Reg::BP => "%bp",
Reg::XMM0
| Reg::XMM1
| Reg::XMM2
| Reg::XMM3
| Reg::XMM4
| Reg::XMM5
| Reg::XMM6
| Reg::XMM7
| Reg::XMM8
| Reg::XMM9
| Reg::XMM10
| Reg::XMM11
| Reg::XMM12
| Reg::XMM13
| Reg::XMM14
| Reg::XMM15 => format_register_xmm(reg),
}
}
fn is_xmm_register(reg: &Reg) -> bool {
matches!(
reg,
Reg::XMM0
| Reg::XMM1
| Reg::XMM2
| Reg::XMM3
| Reg::XMM4
| Reg::XMM5
| Reg::XMM6
| Reg::XMM7
| Reg::XMM8
| Reg::XMM9
| Reg::XMM10
| Reg::XMM11
| Reg::XMM12
| Reg::XMM13
| Reg::XMM14
| Reg::XMM15
)
}
fn format_condition(cc: &CondCode) -> &'static str {
match cc {
CondCode::E => "e",
CondCode::NE => "ne",
CondCode::L => "l",
CondCode::LE => "le",
CondCode::G => "g",
CondCode::GE => "ge",
CondCode::A => "a",
CondCode::AE => "ae",
CondCode::B => "b",
CondCode::BE => "be",
CondCode::P => "p",
CondCode::NP => "np",
}
}
fn escape_string_for_asm(s: &str) -> String {
let mut result = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'\\' => result.push_str("\\\\"),
'"' => result.push_str("\\\""),
'\n' => result.push_str("\\n"),
'\t' => result.push_str("\\t"),
'\r' => result.push_str("\\r"),
'\0' => result.push_str("\\0"),
'\x07' => result.push_str("\\a"),
'\x08' => result.push_str("\\b"),
'\x0c' => result.push_str("\\f"),
'\x0b' => result.push_str("\\v"),
c => result.push(c),
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::asm_ast::*;
fn test_program(instructions: Vec<Instruction>) -> AsmProgram {
AsmProgram {
functions: vec![AsmFunction {
name: "main".to_string(),
instructions,
global: true,
}],
static_vars: vec![],
static_constants: vec![],
}
}
const LW: AsmType = AsmType::Longword;
#[test]
fn emit_return_constant() {
let program = test_program(vec![
Instruction::Push(Operand::Register(Reg::BP)),
Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::SP),
dst: Operand::Register(Reg::BP),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(2),
dst: Operand::Register(Reg::AX),
},
Instruction::Pop(Operand::Register(Reg::BP)),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
let expected = " .globl main\nmain:\n push %rbp\n movq %rsp, %rbp\n movl $2, %eax\n pop %rbp\n ret\n .section .ferrugo_sig,\"a\",@note\n .byte 0x46,0x45,0x52,0x52,0x55,0x47,0x4f\n .byte 0x04,0x00\n .section .note.GNU-stack,\"\",@progbits\n";
assert_eq!(asm, expected);
}
#[test]
fn emit_negation() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(5),
dst: Operand::Register(Reg::AX),
},
Instruction::Unary {
asm_type: LW,
op: AsmUnaryOp::Neg,
operand: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("negl %eax"));
}
#[test]
fn emit_complement() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::Unary {
asm_type: LW,
op: AsmUnaryOp::Not,
operand: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("notl %eax"));
}
#[test]
fn emit_logical_not() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(1),
dst: Operand::Register(Reg::AX),
},
Instruction::Cmp {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::SetCC {
condition: CondCode::E,
operand: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("cmpl $0, %eax"));
assert!(asm.contains("sete %al")); }
#[test]
fn emit_addition() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(1),
dst: Operand::Register(Reg::AX),
},
Instruction::Push(Operand::Register(Reg::AX)),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(2),
dst: Operand::Register(Reg::AX),
},
Instruction::Pop(Operand::Register(Reg::CX)),
Instruction::Binary {
asm_type: LW,
op: AsmBinaryOp::Add,
src: Operand::Register(Reg::CX),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("push %rax"));
assert!(asm.contains("pop %rcx"));
assert!(asm.contains("addl %ecx, %eax"));
}
#[test]
fn emit_division() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(7),
dst: Operand::Register(Reg::AX),
},
Instruction::Push(Operand::Register(Reg::AX)),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(2),
dst: Operand::Register(Reg::AX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Register(Reg::AX),
dst: Operand::Register(Reg::CX),
},
Instruction::Pop(Operand::Register(Reg::AX)),
Instruction::SignExtend(LW),
Instruction::Idiv {
asm_type: LW,
operand: Operand::Register(Reg::CX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("movl %eax, %ecx"));
assert!(asm.contains("cdq"));
assert!(asm.contains("idivl %ecx"));
}
#[test]
fn emit_less_than() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(1),
dst: Operand::Register(Reg::AX),
},
Instruction::Push(Operand::Register(Reg::AX)),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(2),
dst: Operand::Register(Reg::AX),
},
Instruction::Pop(Operand::Register(Reg::CX)),
Instruction::Cmp {
asm_type: LW,
src: Operand::Register(Reg::AX),
dst: Operand::Register(Reg::CX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::SetCC {
condition: CondCode::L,
operand: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("cmpl %eax, %ecx"));
assert!(asm.contains("setl %al"));
}
#[test]
fn emit_logical_and() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(1),
dst: Operand::Register(Reg::AX),
},
Instruction::Cmp {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::JmpCC(CondCode::E, ".Land_false0".to_string()),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(2),
dst: Operand::Register(Reg::AX),
},
Instruction::Cmp {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::JmpCC(CondCode::E, ".Land_false0".to_string()),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(1),
dst: Operand::Register(Reg::AX),
},
Instruction::Jmp(".Land_end0".to_string()),
Instruction::Label(".Land_false0".to_string()),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::Label(".Land_end0".to_string()),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("je .Land_false0"));
assert!(asm.contains("jmp .Land_end0"));
assert!(asm.contains(".Land_false0:"));
assert!(asm.contains(".Land_end0:"));
}
#[test]
fn emit_logical_or() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::Cmp {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::JmpCC(CondCode::NE, ".Lor_true0".to_string()),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(3),
dst: Operand::Register(Reg::AX),
},
Instruction::Cmp {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::JmpCC(CondCode::NE, ".Lor_true0".to_string()),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::AX),
},
Instruction::Jmp(".Lor_end0".to_string()),
Instruction::Label(".Lor_true0".to_string()),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(1),
dst: Operand::Register(Reg::AX),
},
Instruction::Label(".Lor_end0".to_string()),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("jne .Lor_true0"));
assert!(asm.contains("jmp .Lor_end0"));
assert!(asm.contains(".Lor_true0:"));
assert!(asm.contains(".Lor_end0:"));
}
#[test]
fn emit_remainder() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(7),
dst: Operand::Register(Reg::AX),
},
Instruction::Push(Operand::Register(Reg::AX)),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(2),
dst: Operand::Register(Reg::AX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Register(Reg::AX),
dst: Operand::Register(Reg::CX),
},
Instruction::Pop(Operand::Register(Reg::AX)),
Instruction::SignExtend(LW),
Instruction::Idiv {
asm_type: LW,
operand: Operand::Register(Reg::CX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Register(Reg::DX),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("idivl %ecx"));
assert!(asm.contains("movl %edx, %eax"));
}
#[test]
fn emit_allocate_stack_and_stack_operand() {
let program = test_program(vec![
Instruction::Push(Operand::Register(Reg::BP)),
Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::SP),
dst: Operand::Register(Reg::BP),
},
Instruction::AllocateStack(16),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(5),
dst: Operand::Register(Reg::AX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Register(Reg::AX),
dst: Operand::Stack(-4),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Stack(-4),
dst: Operand::Register(Reg::AX),
},
Instruction::DeallocateStack(16),
Instruction::Pop(Operand::Register(Reg::BP)),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("subq $16, %rsp"));
assert!(asm.contains("movl %eax, -4(%rbp)"));
assert!(asm.contains("movl -4(%rbp), %eax"));
assert!(asm.contains("push %rbp"));
assert!(asm.contains("movq %rsp, %rbp"));
assert!(asm.contains("addq $16, %rsp"));
assert!(asm.contains("pop %rbp"));
}
#[test]
fn emit_var_declaration_full() {
let program = test_program(vec![
Instruction::Push(Operand::Register(Reg::BP)),
Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::SP),
dst: Operand::Register(Reg::BP),
},
Instruction::AllocateStack(16),
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(5),
dst: Operand::Register(Reg::AX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Register(Reg::AX),
dst: Operand::Stack(-4),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Stack(-4),
dst: Operand::Register(Reg::AX),
},
Instruction::DeallocateStack(16),
Instruction::Pop(Operand::Register(Reg::BP)),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
let expected = " .globl main\nmain:\n push %rbp\n movq %rsp, %rbp\n subq $16, %rsp\n movl $5, %eax\n movl %eax, -4(%rbp)\n movl -4(%rbp), %eax\n addq $16, %rsp\n pop %rbp\n ret\n .section .ferrugo_sig,\"a\",@note\n .byte 0x46,0x45,0x52,0x52,0x55,0x47,0x4f\n .byte 0x04,0x00\n .section .note.GNU-stack,\"\",@progbits\n";
assert_eq!(asm, expected);
}
#[test]
fn emit_static_function_no_globl() {
let program = AsmProgram {
functions: vec![AsmFunction {
name: "helper".to_string(),
instructions: vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(42),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
],
global: false,
}],
static_vars: vec![],
static_constants: vec![],
};
let asm = emit(&program).unwrap();
assert!(!asm.contains(".globl helper"));
assert!(asm.contains("helper:"));
assert!(asm.contains("movl $42, %eax"));
}
#[test]
fn emit_initialized_static_var() {
let program = AsmProgram {
functions: vec![],
static_vars: vec![AsmStaticVar {
name: "x".to_string(),
global: true,
init: StaticInit::IntInit(5),
asm_type: LW,
var_type: None,
}],
static_constants: vec![],
};
let asm = emit(&program).unwrap();
assert!(asm.contains(" .globl x"));
assert!(asm.contains(" .data"));
assert!(asm.contains(" .align 4"));
assert!(asm.contains("x:"));
assert!(asm.contains(" .long 5"));
}
#[test]
fn emit_zero_initialized_static_var() {
let program = AsmProgram {
functions: vec![],
static_vars: vec![AsmStaticVar {
name: "y".to_string(),
global: true,
init: StaticInit::IntInit(0),
asm_type: LW,
var_type: None,
}],
static_constants: vec![],
};
let asm = emit(&program).unwrap();
assert!(asm.contains(" .globl y"));
assert!(asm.contains(" .bss"));
assert!(asm.contains(" .align 4"));
assert!(asm.contains("y:"));
assert!(asm.contains(" .zero 4"));
}
#[test]
fn emit_static_internal_linkage_var() {
let program = AsmProgram {
functions: vec![],
static_vars: vec![AsmStaticVar {
name: "c.0".to_string(),
global: false,
init: StaticInit::IntInit(0),
asm_type: LW,
var_type: None,
}],
static_constants: vec![],
};
let asm = emit(&program).unwrap();
assert!(!asm.contains(".globl c.0"));
assert!(asm.contains("c.0:"));
assert!(asm.contains(" .zero 4"));
}
#[test]
fn emit_data_operand() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Data("x".to_string()),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("movl x(%rip), %eax"));
}
#[test]
fn emit_mov_quadword() {
let program = test_program(vec![
Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Imm(2147483648),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("movq $2147483648, %rax"));
}
#[test]
fn emit_movsx() {
let program = test_program(vec![
Instruction::Movsx {
src: Operand::Register(Reg::AX),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("movslq %eax, %rax"));
}
#[test]
fn emit_truncate() {
let program = test_program(vec![
Instruction::Truncate {
src: Operand::Register(Reg::AX),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("movl %eax, %eax"));
}
#[test]
fn emit_sign_extend_quadword() {
let program = test_program(vec![
Instruction::SignExtend(AsmType::Quadword),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("cqo"));
}
#[test]
fn emit_unsigned_division() {
let program = test_program(vec![
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(7),
dst: Operand::Register(Reg::AX),
},
Instruction::Mov {
asm_type: LW,
src: Operand::Imm(0),
dst: Operand::Register(Reg::DX),
},
Instruction::Div {
asm_type: LW,
operand: Operand::Register(Reg::CX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("divl %ecx"));
}
#[test]
fn emit_unsigned_division_quadword() {
let program = test_program(vec![
Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Imm(0),
dst: Operand::Register(Reg::DX),
},
Instruction::Div {
asm_type: AsmType::Quadword,
operand: Operand::Register(Reg::CX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("divq %rcx"));
}
#[test]
fn emit_zero_extend() {
let program = test_program(vec![
Instruction::MovZeroExtend {
src: Operand::Register(Reg::AX),
dst: Operand::Register(Reg::AX),
},
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("movl %eax, %eax"));
}
#[test]
fn emit_unsigned_condition_codes() {
let program = test_program(vec![
Instruction::SetCC {
condition: CondCode::A,
operand: Operand::Register(Reg::AX),
},
Instruction::SetCC {
condition: CondCode::AE,
operand: Operand::Register(Reg::AX),
},
Instruction::SetCC {
condition: CondCode::B,
operand: Operand::Register(Reg::AX),
},
Instruction::SetCC {
condition: CondCode::BE,
operand: Operand::Register(Reg::AX),
},
Instruction::JmpCC(CondCode::A, ".Ltest".to_string()),
Instruction::JmpCC(CondCode::B, ".Ltest2".to_string()),
Instruction::Ret,
]);
let asm = emit(&program).unwrap();
assert!(asm.contains("seta %al"));
assert!(asm.contains("setae %al"));
assert!(asm.contains("setb %al"));
assert!(asm.contains("setbe %al"));
assert!(asm.contains("ja .Ltest"));
assert!(asm.contains("jb .Ltest2"));
}
#[test]
fn emit_quadword_static_var() {
let program = AsmProgram {
functions: vec![],
static_vars: vec![AsmStaticVar {
name: "big".to_string(),
global: true,
init: StaticInit::IntInit(4294967296),
asm_type: AsmType::Quadword,
var_type: None,
}],
static_constants: vec![],
};
let asm = emit(&program).unwrap();
assert!(asm.contains(" .align 8"));
assert!(asm.contains(" .quad 4294967296"));
}
}