mod common;
use common::*;
#[test]
fn data_directives_respect_width_and_endianness() {
assert_eq!(text(".byte 1, 2, 0xff"), vec![1, 2, 0xff]);
assert_eq!(text(".short 0x1234"), vec![0x34, 0x12]);
assert_eq!(text(".long 0x11223344"), vec![0x44, 0x33, 0x22, 0x11]);
assert_eq!(
text(".quad 0x1122334455667788"),
vec![0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11]
);
assert_eq!(text(".word 1").len(), 2);
assert_eq!(text(".byte -1"), vec![0xff]);
assert_eq!(text(".long -1"), vec![0xff; 4]);
}
#[test]
fn string_directives() {
assert_eq!(text(r#".ascii "hi""#), b"hi".to_vec());
assert_eq!(text(r#".asciz "hi""#), b"hi\0".to_vec());
assert_eq!(text(r#".string "a\nb""#), b"a\nb\0".to_vec());
assert_eq!(text(r#".ascii "a", "b""#), b"ab".to_vec());
}
#[test]
fn leb128_encoding() {
assert_eq!(text(".uleb128 0"), vec![0]);
assert_eq!(text(".uleb128 624485"), vec![0xe5, 0x8e, 0x26]);
assert_eq!(text(".sleb128 -2"), vec![0x7e]);
assert_eq!(text(".sleb128 -127"), vec![0x81, 0x7f]);
}
#[test]
fn space_fill_and_zero() {
assert_eq!(text(".space 4"), vec![0; 4]);
assert_eq!(text(".space 3, 0xaa"), vec![0xaa; 3]);
assert_eq!(text(".zero 2"), vec![0, 0]);
assert_eq!(
text(".fill 3, 2, 0x4142"),
vec![0x42, 0x41, 0x42, 0x41, 0x42, 0x41]
);
assert_eq!(text(".fill 4"), vec![0; 4]);
}
#[test]
fn alignment() {
assert_eq!(
text(".byte 1\n.p2align 3, 0xcc\n.byte 2"),
vec![1, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 2]
);
assert_eq!(text(".byte 1\n.align 2, 0\n.byte 2"), vec![1, 0, 2]);
assert_eq!(text(".short 1\n.balign 2, 0\n.byte 2"), vec![1, 0, 2]);
assert_eq!(text(".byte 1\n.balign 8, 0, 2\n.byte 2"), vec![1, 2]);
assert_eq!(
text(".byte 1\n.balign 4, 0, 8\n.byte 2"),
vec![1, 0, 0, 0, 2]
);
let asm = assemble(".data\n.byte 1\n.balign 4\n.byte 2");
assert_eq!(section(&asm, ".data"), vec![1, 0, 0, 0, 2]);
}
#[test]
fn executable_sections_pad_with_no_ops() {
let out = text("nop\n.balign 8\nret");
assert_eq!(out.len(), 9);
assert_eq!(out[0], 0x90);
assert_eq!(out[8], 0xc3);
assert!(
matches!(out[1], 0x90 | 0x66 | 0x0f),
"padding should start a no-op: {out:02x?}"
);
}
#[test]
fn org_moves_the_location_counter() {
assert_eq!(text(".byte 1\n.org 4\n.byte 2"), vec![1, 0, 0, 0, 2]);
assert_eq!(
text(".byte 1\n.org 4, 0xff\n.byte 2"),
vec![1, 0xff, 0xff, 0xff, 2]
);
assert_eq!(text(".byte 1\n. = . + 3\n.byte 2"), vec![1, 0, 0, 0, 2]);
assert!(errors(".byte 1\n.byte 2\n.org 1").contains("cannot move backwards"));
}
#[test]
fn set_and_expressions() {
assert_eq!(text(".set n, 4\n.byte n, n*n"), vec![4, 16]);
assert_eq!(text(".equ n, 1+2*3\n.byte n"), vec![7]);
assert_eq!(text("n = 5\n.byte n"), vec![5]);
assert_eq!(text(".set n, 1\n.byte n\n.set n, 2\n.byte n"), vec![1, 2]);
assert!(errors("foo: nop\nfoo: nop").contains("already defined"));
assert!(errors(".equiv n, 1\n.equiv n, 2").contains("already defined"));
assert!(errors(".set a, b\n.set b, a\n.byte a").contains("circular"));
}
#[test]
fn label_arithmetic() {
assert_eq!(text("a: nop\nnop\nb:\n.byte b - a"), vec![0x90, 0x90, 2]);
assert_eq!(text("a:\n.long . - a\n"), vec![0, 0, 0, 0]);
assert_eq!(text("nop\na:\n.byte a"), vec![0x90, 0]);
let asm = assemble_flat("nop\na:\n.byte a", 0);
assert_eq!(section(&asm, ".text"), vec![0x90, 1]);
}
#[test]
fn numeric_local_labels() {
assert_eq!(text("1: nop\n.byte 1b"), vec![0x90, 0]);
assert_eq!(text(".byte 1f\nnop\n1: nop"), vec![0, 0x90, 0x90]);
let asm = assemble_flat(".byte 1f\nnop\n1: nop", 0);
assert_eq!(section(&asm, ".text"), vec![2, 0x90, 0x90]);
assert_eq!(
text("1: nop\n.byte 1b\n1: nop\n.byte 1b"),
vec![0x90, 0, 0x90, 0]
);
let asm = assemble_flat("1: nop\n.byte 1b\n1: nop\n.byte 1b", 0);
assert_eq!(section(&asm, ".text"), vec![0x90, 0, 0x90, 2]);
assert!(errors(".byte 1b").contains("no previous local label"));
assert!(errors("jmp 1f").contains("no local label"));
}
#[test]
fn conditional_assembly() {
assert_eq!(text(".if 1\n.byte 1\n.else\n.byte 2\n.endif"), vec![1]);
assert_eq!(text(".if 0\n.byte 1\n.else\n.byte 2\n.endif"), vec![2]);
assert_eq!(text(".set x,1\n.ifdef x\n.byte 1\n.endif"), vec![1]);
assert_eq!(text(".ifndef nope\n.byte 1\n.endif"), vec![1]);
assert_eq!(
text(".if 0\n.byte 1\n.elseif 1\n.byte 2\n.else\n.byte 3\n.endif"),
vec![2]
);
assert_eq!(text(".if 1\n.byte 1\n.elseif 1\n.byte 2\n.endif"), vec![1]);
assert_eq!(
text(
".if 0\n .if 1\n .byte 1\n .else\n .byte 2\n .endif\n.else\n .byte 3\n.endif"
),
vec![3]
);
assert!(errors(".if 1\n.byte 1").contains("unterminated"));
assert!(errors(".endif").contains("without a matching"));
assert!(errors(".if 1\n.else\n.else\n.endif").contains("duplicate `.else`"));
}
#[test]
fn sections() {
let asm = assemble(".text\nnop\n.data\n.byte 1\n.text\nret\n");
assert_eq!(section(&asm, ".text"), vec![0x90, 0xc3]);
assert_eq!(section(&asm, ".data"), vec![1]);
let asm = assemble("nop\n.pushsection .data\n.byte 1\n.popsection\nret\n");
assert_eq!(section(&asm, ".text"), vec![0x90, 0xc3]);
assert_eq!(section(&asm, ".data"), vec![1]);
let asm = assemble("nop\n.data\n.byte 1\n.previous\nret\n");
assert_eq!(section(&asm, ".text"), vec![0x90, 0xc3]);
let asm = assemble(".section .mine,\"ax\",@progbits\nnop\n");
let s = asm
.sections
.iter()
.find(|s| asm.interner.get(s.name) == ".mine")
.unwrap();
assert!(s.flags.alloc && s.flags.exec && !s.flags.write);
}
#[test]
fn bss_rejects_data() {
assert!(errors(".bss\n.byte 1").contains("allocates no file space"));
let asm = assemble(".bss\n.space 16");
let s = asm
.sections
.iter()
.find(|s| asm.interner.get(s.name) == ".bss")
.unwrap();
assert_eq!(s.size, 16);
}
#[test]
fn diagnostics_point_at_the_problem() {
let e = errors("movq %rax");
assert!(e.contains("operand"), "{e}");
assert!(e.contains("test.s:1:1"), "{e}");
let e = errors("movq %nosuch, %rax");
assert!(e.contains("unknown register `%nosuch`"), "{e}");
let e = errors("frobnicate %rax");
assert!(e.contains("unknown instruction `frobnicate`"), "{e}");
let e = errors(".nosuchdirective");
assert!(e.contains("unknown directive"), "{e}");
let e = errors(".byte 300");
assert!(e.contains("does not fit in 1 byte"), "{e}");
let e = errors(".arch nosucharch");
assert!(
e.contains("unknown architecture") && e.contains("x86-64"),
"{e}"
);
}
#[test]
fn diagnostics_survive_after_the_first_error() {
let e = errors("frobnicate\nzimzam\n.byte 300\n");
assert!(e.contains("frobnicate"), "{e}");
assert!(e.contains("zimzam"), "{e}");
assert!(e.contains("does not fit"), "{e}");
}
#[test]
fn symbol_attributes() {
use rsasm::symbol::{Binding, SymType, Visibility};
let asm = assemble(
".globl g\n.weak w\n.hidden h\n.type f, @function\n\
g: nop\nw: nop\nh: nop\nf: nop\n.size f, 1\n",
);
let find = |n: &str| {
asm.symbols
.iter()
.find(|(_, s)| asm.interner.get(s.name) == n)
.map(|(_, s)| s)
.unwrap_or_else(|| panic!("no symbol `{n}`"))
};
assert_eq!(find("g").binding, Binding::Global);
assert_eq!(find("w").binding, Binding::Weak);
assert_eq!(find("h").visibility, Visibility::Hidden);
assert_eq!(find("f").ty, SymType::Func);
}
#[test]
fn error_and_warning_directives() {
assert!(errors(r#".error "boom""#).contains("boom"));
let asm = assemble(r#".warning "careful""#);
assert!(!asm.diags.has_errors());
assert!(asm.diags.render(&asm.sm, false).contains("careful"));
}