use asm_rs::{assemble, Arch, AsmError, Assembler, OptLevel, ResourceLimits};
#[test]
#[cfg(feature = "x86")]
fn x86_32_inc_dec_short_forms() {
let mut asm = Assembler::new(Arch::X86);
asm.emit("inc eax\ndec ebx").unwrap();
let result = asm.finish().unwrap();
assert_eq!(result.bytes(), &[0x40, 0x4B]);
}
#[test]
#[cfg(feature = "x86_64")]
fn x86_64_zero_idiom_requires_aggressive_opt_level() {
let mut asm = Assembler::new(Arch::X86_64);
asm.emit("mov ecx, 0").unwrap();
assert_eq!(asm.finish().unwrap().bytes(), &[0xB9, 0, 0, 0, 0]);
let mut asm = Assembler::new(Arch::X86_64);
asm.optimize(OptLevel::Aggressive);
asm.emit("mov ecx, 0").unwrap();
assert_eq!(asm.finish().unwrap().bytes(), &[0x31, 0xC9]);
}
#[test]
#[cfg(feature = "riscv")]
fn riscv_jal_backward_sets_sign_bit() {
let code = assemble("target:\nnop\nnop\njal ra, target", Arch::Rv64).unwrap();
let word = u32::from_le_bytes([code[8], code[9], code[10], code[11]]);
let imm = ((word >> 31) & 1) << 20
| ((word >> 21) & 0x3FF) << 1
| ((word >> 20) & 1) << 11
| ((word >> 12) & 0xFF) << 12;
let disp = ((imm << 11) as i32) >> 11;
assert_eq!(disp, -8, "jal encoded {word:#010x}");
}
#[test]
#[cfg(feature = "arm")]
fn thumb_bl_far_target_encodes_i1_i2() {
let target: i64 = 0x0080_0000; let mut asm = Assembler::new(Arch::Thumb);
asm.define_external("far", target as u64);
asm.emit("bl far").unwrap();
let code = asm.finish().unwrap().into_bytes();
let hw1 = u16::from_le_bytes([code[0], code[1]]);
let hw2 = u16::from_le_bytes([code[2], code[3]]);
let s = u32::from((hw1 >> 10) & 1);
let imm10 = u32::from(hw1 & 0x3FF);
let j1 = u32::from((hw2 >> 13) & 1);
let j2 = u32::from((hw2 >> 11) & 1);
let imm11 = u32::from(hw2 & 0x7FF);
let i1 = !(j1 ^ s) & 1;
let i2 = !(j2 ^ s) & 1;
let raw = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1);
let disp = ((raw as i32) << 7) >> 7;
assert_eq!(i64::from(disp), target - 4);
}
#[test]
#[cfg(feature = "riscv")]
fn riscv_auipc_jalr_rejects_out_of_range_target() {
let mut asm = Assembler::new(Arch::Rv64);
asm.define_external("far", 0xFFFF_FFFF_0000_0000);
asm.emit("call far").unwrap();
assert!(matches!(
asm.finish(),
Err(AsmError::BranchOutOfRange { .. })
));
}
#[test]
#[cfg(feature = "x86_64")]
fn org_padding_is_bounded_before_allocation() {
let mut asm = Assembler::new(Arch::X86_64);
asm.emit("nop").unwrap();
asm.emit(".org 0xFFFFFFFFFFF").unwrap();
assert!(matches!(
asm.finish(),
Err(AsmError::ResourceLimitExceeded { .. })
));
let mut asm = Assembler::new(Arch::X86_64);
asm.emit("nop").unwrap();
asm.emit(".align 0x20000000").unwrap();
assert!(matches!(
asm.finish(),
Err(AsmError::ResourceLimitExceeded { .. })
));
}
#[test]
#[cfg(feature = "x86_64")]
fn preprocessor_expansion_size_is_bounded() {
let body = "nop\n".repeat(2000); let src = format!(".rept 50000\n{body}.endr\n");
let mut asm = Assembler::new(Arch::X86_64);
asm.limits(ResourceLimits {
max_expanded_bytes: 1024 * 1024,
..Default::default()
});
assert!(matches!(
asm.emit(&src),
Err(AsmError::ResourceLimitExceeded { .. })
));
}
#[test]
#[cfg(all(feature = "arm", feature = "aarch64"))]
fn ual_hash_immediate_prefix_is_accepted() {
assert_eq!(
assemble("mov x0, #1", Arch::Aarch64).unwrap(),
assemble("mov x0, 1", Arch::Aarch64).unwrap()
);
assert_eq!(
assemble("ldr x0, [x1, #16]", Arch::Aarch64).unwrap(),
assemble("ldr x0, [x1, 16]", Arch::Aarch64).unwrap()
);
assert_eq!(
assemble("add r0, r1, #8", Arch::Arm).unwrap(),
assemble("add r0, r1, 8", Arch::Arm).unwrap()
);
assert_eq!(
assemble("movs r0, #1", Arch::Thumb).unwrap(),
assemble("movs r0, 1", Arch::Thumb).unwrap()
);
assert_eq!(
assemble("mov x0, #1 @ set to one", Arch::Aarch64).unwrap(),
assemble("mov x0, #1", Arch::Aarch64).unwrap()
);
assert_eq!(
assemble("mov x0, #1 // set to one", Arch::Aarch64).unwrap(),
assemble("mov x0, #1", Arch::Aarch64).unwrap()
);
assert_eq!(
assemble("nop # comment", Arch::X86_64).unwrap(),
assemble("nop", Arch::X86_64).unwrap()
);
}
#[test]
#[cfg(feature = "aarch64")]
fn aarch64_sp_uses_encoding_that_can_name_it() {
assert_eq!(
assemble("add sp, sp, x0", Arch::Aarch64).unwrap(),
0x8B2063FFu32.to_le_bytes()
);
assert_eq!(
assemble("sub sp, sp, x8", Arch::Aarch64).unwrap(),
0xCB2863FFu32.to_le_bytes()
);
assert_eq!(
assemble("add x0, sp, x1", Arch::Aarch64).unwrap(),
0x8B2163E0u32.to_le_bytes()
);
assert_eq!(
assemble("add sp, x1, x2, lsl #2", Arch::Aarch64).unwrap(),
0x8B22683Fu32.to_le_bytes()
);
assert_eq!(
assemble("mov sp, x0", Arch::Aarch64).unwrap(),
0x9100001Fu32.to_le_bytes()
);
assert_eq!(
assemble("mov x0, sp", Arch::Aarch64).unwrap(),
0x910003E0u32.to_le_bytes()
);
assert_eq!(
assemble("mov x0, x1", Arch::Aarch64).unwrap(),
0xAA0103E0u32.to_le_bytes()
);
assert_eq!(
assemble("mov xzr, x0", Arch::Aarch64).unwrap(),
0xAA0003FFu32.to_le_bytes()
);
assert!(assemble("adds sp, sp, x1", Arch::Aarch64).is_err());
}
#[test]
#[cfg(feature = "arm")]
fn register_ranges_expand_in_lists() {
assert_eq!(
assemble("push {r0-r7}", Arch::Thumb).unwrap(),
assemble("push {r0, r1, r2, r3, r4, r5, r6, r7}", Arch::Thumb).unwrap()
);
assert_eq!(
assemble("stmdb sp!, {r4-r11, lr}", Arch::Arm).unwrap(),
assemble(
"stmdb sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}",
Arch::Arm
)
.unwrap()
);
assert!(assemble("push {r4-r0}", Arch::Arm).is_err());
}
#[test]
#[cfg(feature = "arm")]
fn arm_barrel_shift_operands() {
assert_eq!(
assemble("add r0, r1, r2, lsl #3", Arch::Arm).unwrap(),
0xE081_0182u32.to_le_bytes()
);
assert_eq!(
assemble("add r0, r1, r2, lsl r3", Arch::Arm).unwrap(),
0xE081_0312u32.to_le_bytes()
);
assert_eq!(
assemble("lsl r0, r1, #4", Arch::Arm).unwrap(),
assemble("mov r0, r1, lsl #4", Arch::Arm).unwrap()
);
assert_eq!(
assemble("rrx r0, r1", Arch::Arm).unwrap(),
assemble("mov r0, r1, rrx", Arch::Arm).unwrap()
);
assert!(assemble("add r0, r1, r2, lsl", Arch::Arm).is_err());
assert!(assemble("add r0, r1, r2, lsl #32", Arch::Arm).is_err());
}
#[test]
fn misaligned_branch_targets_are_rejected() {
let cases: &[(Arch, &str, u64, u8)] = &[
#[cfg(feature = "aarch64")]
(Arch::Aarch64, "b target", 0x4000_0131, 4),
#[cfg(feature = "aarch64")]
(Arch::Aarch64, "bl target", 0x4000_0002, 4),
#[cfg(feature = "aarch64")]
(Arch::Aarch64, "b.eq target", 0x4000_0002, 4),
#[cfg(feature = "arm")]
(Arch::Arm, "b target", 0x4000_0002, 4),
#[cfg(feature = "arm")]
(Arch::Thumb, "bl target", 0x4000_0001, 2),
#[cfg(feature = "riscv")]
(Arch::Rv64, "jal ra, target", 0x4000_0001, 2),
#[cfg(feature = "riscv")]
(Arch::Rv64, "beq a0, a1, target", 0x4000_0001, 2),
];
for &(arch, src, target, alignment) in cases {
let mut asm = Assembler::new(arch);
asm.base_address(0x4000_0000);
asm.define_external("target", target);
asm.emit(src).unwrap();
match asm.finish() {
Err(AsmError::MisalignedBranchTarget { alignment: a, .. }) => {
assert_eq!(a, alignment, "{arch:?} `{src}`");
}
other => panic!("{arch:?} `{src}` to {target:#x} should be rejected, got {other:?}"),
}
}
#[cfg(feature = "aarch64")]
{
let mut asm = Assembler::new(Arch::Aarch64);
asm.base_address(0x4000_0000);
asm.define_external("target", 0x4000_0100);
asm.emit("b target").unwrap();
assert_eq!(asm.finish().unwrap().len(), 4);
}
}
#[test]
#[cfg(feature = "arm")]
fn extreme_label_address_does_not_overflow() {
let mut asm = Assembler::new(Arch::Thumb);
asm.base_address(0x4000_0000);
asm.define_external("target", 0x7FFF_FFFF_D800_FFC0);
asm.emit("b target").unwrap();
assert!(asm.finish().is_err());
}
#[test]
#[cfg(feature = "x86_64")]
fn too_many_operands_is_an_error_not_a_panic() {
let err = assemble("add rax, rbx, rcx, rdx, r8, r9, r10", Arch::X86_64).unwrap_err();
assert!(matches!(err, AsmError::InvalidOperands { .. }), "{err}");
assert!(assemble("vpternlogd zmm0, zmm1, zmm2, 5", Arch::X86_64).is_ok());
}
#[test]
#[cfg(feature = "riscv")]
fn missing_operands_are_diagnosed_not_panicked() {
for src in ["la", "la a0", "jal", "beq a0"] {
assert!(
assemble(src, Arch::Rv64).is_err(),
"`{src}` should be rejected"
);
}
}
#[test]
#[cfg(feature = "x86_64")]
fn macro_recursion_limit_errors_without_stack_overflow() {
let mut src = String::from(".macro recurse\nrecurse\n.endm\n");
src.push_str("recurse\n");
let mut asm = Assembler::new(Arch::X86_64);
assert!(matches!(
asm.emit(&src),
Err(AsmError::ResourceLimitExceeded { .. })
));
}
#[test]
#[cfg(feature = "x86_64")]
fn rip_relative_displacement_from_instruction_end() {
let mut asm = Assembler::new(Arch::X86_64);
asm.emit("lea rax, [rip + 0]\nnop").unwrap();
let result = asm.finish().unwrap();
assert_eq!(
&result.bytes()[..7],
&[0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00]
);
}
#[test]
#[cfg(feature = "arm")]
fn arm32_nop_is_mov_r0_r0() {
let code = assemble("nop", Arch::Arm).unwrap();
assert_eq!(code, &[0x00, 0x00, 0xA0, 0xE1]);
}