use std::io::Write;
use std::sync::{Arc, Mutex};
use nixvm::abi::Arch;
use nixvm::fs::MountTable;
use nixvm::kernel::Kernel;
use nixvm::loader::{ProcessSpec, load_static};
use nixvm::vcpu::Backend;
use nixvm::vcpu::GuestMemory;
use nixvm::vcpu::interp::InterpBackend;
use nixvm::vcpu::mem::PAGE_SIZE;
const EHDR_LEN: usize = 64;
const PHDR_LEN: usize = 56;
const EM_AARCH64: u16 = 183;
const BODY_OFF: u64 = (EHDR_LEN + PHDR_LEN) as u64;
const NR_EXIT: u32 = 93;
const NR_MMAP: u32 = 222;
const COND_EQ: u32 = 0b0000;
const COND_MI: u32 = 0b0100;
const COND_PL: u32 = 0b0101;
fn movz(rd: u32, imm16: u32) -> u32 {
0xD280_0000 | (imm16 << 5) | rd
}
fn movk(rd: u32, imm16: u32, hw: u32) -> u32 {
0xF280_0000 | (hw << 21) | (imm16 << 5) | rd
}
fn svc0() -> u32 {
0xD400_0001
}
fn mov_imm32(rd: u32, val: u32) -> Vec<u32> {
let lo = val & 0xffff;
let hi = (val >> 16) & 0xffff;
let mut words = vec![movz(rd, lo)];
if hi != 0 {
words.push(movk(rd, hi, 1));
}
words
}
fn mov_imm64(rd: u32, val: u64) -> [u32; 4] {
[
movz(rd, (val & 0xffff) as u32),
movk(rd, ((val >> 16) & 0xffff) as u32, 1),
movk(rd, ((val >> 32) & 0xffff) as u32, 2),
movk(rd, ((val >> 48) & 0xffff) as u32, 3),
]
}
fn mov_addr(rd: u32, val: u32) -> [u32; 2] {
[movz(rd, val & 0xffff), movk(rd, (val >> 16) & 0xffff, 1)]
}
fn strb(rt: u32, rn: u32, imm12: u32) -> u32 {
0x3900_0000 | (imm12 << 10) | (rn << 5) | rt
}
fn ldrb(rt: u32, rn: u32, imm12: u32) -> u32 {
0x3940_0000 | (imm12 << 10) | (rn << 5) | rt
}
fn ldrh(rt: u32, rn: u32, imm12: u32) -> u32 {
0x7940_0000 | (imm12 << 10) | (rn << 5) | rt
}
fn ldr_w(rt: u32, rn: u32, imm12: u32) -> u32 {
0xB940_0000 | (imm12 << 10) | (rn << 5) | rt
}
fn ldr_x(rt: u32, rn: u32, imm12: u32) -> u32 {
0xF940_0000 | (imm12 << 10) | (rn << 5) | rt
}
fn mov_reg(rd: u32, rm: u32) -> u32 {
0xAA00_03E0 | (rm << 16) | rd
}
fn add_reg(rd: u32, rn: u32, rm: u32) -> u32 {
0x8B00_0000 | (rm << 16) | (rn << 5) | rd
}
fn sub_reg(rd: u32, rn: u32, rm: u32) -> u32 {
0xCB00_0000 | (rm << 16) | (rn << 5) | rd
}
fn subs_imm(rd: u32, rn: u32, imm12: u32) -> u32 {
0xF100_0000 | (imm12 << 10) | (rn << 5) | rd
}
fn and_reg(rd: u32, rn: u32, rm: u32) -> u32 {
0x8A00_0000 | (rm << 16) | (rn << 5) | rd
}
fn orr_reg(rd: u32, rn: u32, rm: u32) -> u32 {
0xAA00_0000 | (rm << 16) | (rn << 5) | rd
}
fn eor_reg(rd: u32, rn: u32, rm: u32) -> u32 {
0xCA00_0000 | (rm << 16) | (rn << 5) | rd
}
fn mul(rd: u32, rn: u32, rm: u32) -> u32 {
0x9B00_7C00 | (rm << 16) | (rn << 5) | rd
}
fn udiv(rd: u32, rn: u32, rm: u32) -> u32 {
0x9AC0_0800 | (rm << 16) | (rn << 5) | rd
}
fn lslv(rd: u32, rn: u32, rm: u32) -> u32 {
0x9AC0_2000 | (rm << 16) | (rn << 5) | rd
}
fn lsrv(rd: u32, rn: u32, rm: u32) -> u32 {
0x9AC0_2400 | (rm << 16) | (rn << 5) | rd
}
fn cbnz(rt: u32, byte_offset: i32) -> u32 {
let imm19 = ((byte_offset / 4) as u32) & 0x7_ffff;
0xB500_0000 | (imm19 << 5) | rt
}
fn sbfx(rd: u32, rn: u32, lsb: u32, width: u32) -> u32 {
0x9340_0000 | (lsb << 16) | ((lsb + width - 1) << 10) | (rn << 5) | rd
}
fn ubfx(rd: u32, rn: u32, lsb: u32, width: u32) -> u32 {
0xD340_0000 | (lsb << 16) | ((lsb + width - 1) << 10) | (rn << 5) | rd
}
fn csel(rd: u32, rn: u32, rm: u32, cond: u32) -> u32 {
0x9A80_0000 | (rm << 16) | (cond << 12) | (rn << 5) | rd
}
fn csinc(rd: u32, rn: u32, rm: u32, cond: u32) -> u32 {
0x9A80_0400 | (rm << 16) | (cond << 12) | (rn << 5) | rd
}
fn ccmp_imm(rn: u32, imm5: u32, nzcv: u32, cond: u32) -> u32 {
0xFA40_0800 | (imm5 << 16) | (cond << 12) | (rn << 5) | nzcv
}
fn scvtf_d(rd: u32, rn: u32) -> u32 {
0x9E62_0000 | (rn << 5) | rd
}
fn fcvtzs_xd(rd: u32, rn: u32) -> u32 {
0x9E78_0000 | (rn << 5) | rd
}
fn fadd_d(rd: u32, rn: u32, rm: u32) -> u32 {
0x1E60_2800 | (rm << 16) | (rn << 5) | rd
}
fn fmul_d(rd: u32, rn: u32, rm: u32) -> u32 {
0x1E60_0800 | (rm << 16) | (rn << 5) | rd
}
fn fdiv_d(rd: u32, rn: u32, rm: u32) -> u32 {
0x1E60_1800 | (rm << 16) | (rn << 5) | rd
}
fn fsqrt_d(rd: u32, rn: u32) -> u32 {
0x1E61_C000 | (rn << 5) | rd
}
fn dup_4s(rd: u32, rn: u32) -> u32 {
0x4E04_0C00 | (rn << 5) | rd
}
fn ins_s(rd: u32, index: u32, rn: u32) -> u32 {
let imm5 = ((index << 3) | 0b100) & 0x1f;
0x4E00_1C00 | (imm5 << 16) | (rn << 5) | rd
}
fn movi_4s(rd: u32, imm8: u32) -> u32 {
0x4F00_0400 | (((imm8 >> 5) & 0x7) << 16) | ((imm8 & 0x1f) << 5) | rd
}
fn add_4s(rd: u32, rn: u32, rm: u32) -> u32 {
0x4EA0_8400 | (rm << 16) | (rn << 5) | rd
}
fn mul_4s(rd: u32, rn: u32, rm: u32) -> u32 {
0x4EA0_9C00 | (rm << 16) | (rn << 5) | rd
}
fn addv_4s(rd: u32, rn: u32) -> u32 {
0x4EB1_B800 | (rn << 5) | rd
}
fn umov_s(rd: u32, rn: u32, index: u32) -> u32 {
let imm5 = ((index << 3) | 0b100) & 0x1f;
0x0E00_3C00 | (imm5 << 16) | (rn << 5) | rd
}
fn ldadd_w(rs: u32, rt: u32, rn: u32) -> u32 {
0xB820_0000 | (rs << 16) | (rn << 5) | rt
}
fn cas_w(rs: u32, rt: u32, rn: u32) -> u32 {
0x88A0_7C00 | (rs << 16) | (rn << 5) | rt
}
#[derive(Clone)]
struct SharedBuf(Arc<Mutex<Vec<u8>>>);
impl Write for SharedBuf {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn build_elf(vaddr: u64, body: &[u8]) -> Vec<u8> {
let mut f = vec![0u8; EHDR_LEN + PHDR_LEN];
f[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
f[4] = 2; f[5] = 1; f[6] = 1;
f[16..18].copy_from_slice(&2u16.to_le_bytes()); f[18..20].copy_from_slice(&EM_AARCH64.to_le_bytes());
f[20..24].copy_from_slice(&1u32.to_le_bytes());
f[24..32].copy_from_slice(&(vaddr + BODY_OFF).to_le_bytes()); f[32..40].copy_from_slice(&(EHDR_LEN as u64).to_le_bytes()); f[52..54].copy_from_slice(&(EHDR_LEN as u16).to_le_bytes());
f[54..56].copy_from_slice(&(PHDR_LEN as u16).to_le_bytes());
f[56..58].copy_from_slice(&1u16.to_le_bytes());
let total = BODY_OFF + body.len() as u64;
let p = EHDR_LEN;
f[p..p + 4].copy_from_slice(&1u32.to_le_bytes()); f[p + 4..p + 8].copy_from_slice(&7u32.to_le_bytes()); f[p + 16..p + 24].copy_from_slice(&vaddr.to_le_bytes()); f[p + 24..p + 32].copy_from_slice(&vaddr.to_le_bytes());
f[p + 32..p + 40].copy_from_slice(&total.to_le_bytes()); f[p + 40..p + 48].copy_from_slice(&total.to_le_bytes()); f[p + 48..p + 56].copy_from_slice(&PAGE_SIZE.to_le_bytes());
f.extend_from_slice(body);
f
}
fn words_to_bytes(words: &[u32]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(words.len() * 4);
for w in words {
bytes.extend_from_slice(&w.to_le_bytes());
}
bytes
}
fn run_program(vaddr: u64, body: &[u8]) -> (i32, Vec<u8>, Kernel) {
let elf = build_elf(vaddr, body);
let mut mem = GuestMemory::new(vaddr, 256 * PAGE_SIZE);
let spec = ProcessSpec {
argv: vec!["prog".into()],
envp: vec![],
};
let img = load_static(&mut mem, &elf, &spec).unwrap();
let backend = InterpBackend::new(Arch::Aarch64).unwrap();
let vcpu = backend.new_vcpu(img.entry, img.stack_pointer).unwrap();
let captured = Arc::new(Mutex::new(Vec::new()));
let mut kernel = Kernel::new(Arch::Aarch64, MountTable::new());
kernel.set_stdout(Box::new(SharedBuf(captured.clone())));
let code = kernel.run(vcpu, mem).unwrap();
let out = captured.lock().unwrap().clone();
(code, out, kernel)
}
#[test]
fn integer_chain_with_summation_loop_exit_code() {
let vaddr = 0x1_0000u64;
let mut code = Vec::new();
code.push(movz(0, 0)); code.push(movz(1, 10)); let loop_start = code.len(); code.push(add_reg(0, 0, 1)); code.push(subs_imm(1, 1, 1)); let cbnz_idx = code.len();
code.push(0); let back_off = ((loop_start as i64 - cbnz_idx as i64) * 4) as i32;
code[cbnz_idx] = cbnz(1, back_off);
code.extend(mov_imm32(2, 6));
code.push(sub_reg(0, 0, 2)); code.extend(mov_imm32(2, 2));
code.push(mul(0, 0, 2)); code.extend(mov_imm32(2, 7));
code.push(udiv(0, 0, 2)); code.extend(mov_imm32(2, 31));
code.push(and_reg(0, 0, 2)); code.extend(mov_imm32(2, 64));
code.push(orr_reg(0, 0, 2)); code.extend(mov_imm32(2, 8));
code.push(eor_reg(0, 0, 2)); code.extend(mov_imm32(2, 1));
code.push(lslv(0, 0, 2)); code.extend(mov_imm32(2, 3));
code.push(lsrv(0, 0, 2));
code.extend(mov_imm32(8, NR_EXIT));
code.push(svc0());
let body = words_to_bytes(&code);
let (exit_code, out, kernel) = run_program(vaddr, &body);
assert_eq!(
exit_code, 17,
"loop must sum 1..=10 to 55, then the SUB/MUL/UDIV/AND/ORR/EOR/LSL/LSR chain must fold it to 17"
);
assert!(out.is_empty());
assert!(
kernel.unsupported().is_empty(),
"{:?}",
kernel.unsupported()
);
}
#[test]
fn bitfield_extract_and_conditional_chain_exit_code() {
let vaddr = 0x1_0000u64;
let mut code = Vec::new();
code.extend(mov_imm64(1, 0x1234_5678_9abc_def0));
code.push(ubfx(0, 1, 4, 8)); code.push(sbfx(2, 1, 4, 8)); code.push(subs_imm(31, 0, 239)); code.push(ccmp_imm(2, 17, 0, COND_EQ)); code.extend(mov_imm32(9, 200)); code.extend(mov_imm32(10, 10)); code.push(csel(3, 9, 10, COND_MI)); code.push(csinc(4, 9, 10, COND_PL)); code.push(sub_reg(0, 3, 4));
code.extend(mov_imm32(8, NR_EXIT));
code.push(svc0());
let body = words_to_bytes(&code);
let (exit_code, out, kernel) = run_program(vaddr, &body);
assert_eq!(
exit_code, 189,
"UBFX/SBFX extraction feeding CMP/CCMP/CSEL/CSINC must fold to 189"
);
assert!(out.is_empty());
assert!(
kernel.unsupported().is_empty(),
"{:?}",
kernel.unsupported()
);
}
#[test]
fn scalar_fp_sqrt_chain_exit_code() {
let vaddr = 0x1_0000u64;
let mut code = Vec::new();
code.extend(mov_imm32(1, 16));
code.push(scvtf_d(0, 1)); code.push(fsqrt_d(1, 0)); code.extend(mov_imm32(2, 3));
code.push(scvtf_d(2, 2)); code.push(fadd_d(3, 1, 2)); code.extend(mov_imm32(4, 21));
code.push(scvtf_d(4, 4)); code.push(fdiv_d(5, 4, 3)); code.push(fmul_d(6, 5, 1)); code.push(fcvtzs_xd(0, 6));
code.extend(mov_imm32(8, NR_EXIT));
code.push(svc0());
let body = words_to_bytes(&code);
let (exit_code, out, kernel) = run_program(vaddr, &body);
assert_eq!(
exit_code, 12,
"sqrt(16)+3=7, 21/7=3, 3*4=12 must round-trip through SCVTF/FSQRT/FADD/FDIV/FMUL/FCVTZS"
);
assert!(out.is_empty());
assert!(
kernel.unsupported().is_empty(),
"{:?}",
kernel.unsupported()
);
}
#[test]
fn neon_build_and_reduce_exit_code() {
let vaddr = 0x1_0000u64;
let mut code = Vec::new();
code.extend(mov_imm32(1, 5));
code.push(dup_4s(0, 1)); code.extend(mov_imm32(2, 3));
code.push(dup_4s(1, 2)); code.push(mul_4s(2, 0, 1)); code.extend(mov_imm32(3, 39));
code.push(ins_s(2, 0, 3)); code.push(movi_4s(5, 1)); code.push(add_4s(2, 2, 5)); code.push(addv_4s(4, 2)); code.push(umov_s(0, 4, 0));
code.extend(mov_imm32(8, NR_EXIT));
code.push(svc0());
let body = words_to_bytes(&code);
let (exit_code, out, kernel) = run_program(vaddr, &body);
assert_eq!(
exit_code, 88,
"DUP/MUL/INS/MOVI/ADD (vector) reduced via ADDV/UMOV must yield 88"
);
assert!(out.is_empty());
assert!(
kernel.unsupported().is_empty(),
"{:?}",
kernel.unsupported()
);
}
#[test]
fn lse_atomics_ldadd_then_cas_exit_code() {
fn build(word_addr: u32) -> Vec<u32> {
let mut code = Vec::new();
code.extend(mov_addr(0, word_addr)); code.extend(mov_imm32(1, 5)); code.push(ldadd_w(1, 2, 0)); code.extend(mov_imm32(4, 15)); code.extend(mov_imm32(5, 77)); code.push(cas_w(4, 5, 0)); code.push(ldr_w(6, 0, 0)); code.push(mov_reg(0, 6)); code.extend(mov_imm32(8, NR_EXIT));
code.push(svc0());
code
}
let vaddr = 0x1_0000u64;
let code_words = build(0).len() as u64;
let word_addr = u32::try_from(vaddr + BODY_OFF + code_words * 4).unwrap();
let instrs = build(word_addr);
assert_eq!(
instrs.len() as u64,
code_words,
"two-pass build must be length-stable"
);
let mut body = words_to_bytes(&instrs);
body.extend_from_slice(&10u32.to_le_bytes());
let (exit_code, out, kernel) = run_program(vaddr, &body);
assert_eq!(
exit_code, 77,
"LDADD(10,+5)=15 then CAS(==15 -> 77) must leave 77 in memory"
);
assert!(out.is_empty());
assert!(
kernel.unsupported().is_empty(),
"{:?}",
kernel.unsupported()
);
}
#[test]
fn mmap_pattern_multi_width_load_exit_code() {
const MAP_ANONYMOUS: u32 = 0x20;
const PROT_RW: u32 = 0x3; let vaddr = 0x1_0000u64;
let mut code = Vec::new();
code.extend(mov_imm32(0, 0)); code.extend(mov_imm32(1, PAGE_SIZE as u32)); code.extend(mov_imm32(2, PROT_RW));
code.extend(mov_imm32(3, MAP_ANONYMOUS));
code.extend(mov_imm32(4, 0)); code.extend(mov_imm32(5, 0)); code.extend(mov_imm32(8, NR_MMAP));
code.push(svc0()); code.push(mov_reg(9, 0));
for off in 0u32..8 {
code.extend(mov_imm32(2, 0x10 + off));
code.push(strb(2, 9, off));
}
code.push(ldr_x(1, 9, 0)); code.push(ldrh(2, 9, 1)); code.push(ldrb(3, 9, 5)); code.push(eor_reg(4, 1, 2));
code.push(eor_reg(0, 4, 3));
code.extend(mov_imm32(8, NR_EXIT));
code.push(svc0());
let body = words_to_bytes(&code);
let elf = build_elf(vaddr, &body);
let mut mem = GuestMemory::new(vaddr, 256 * PAGE_SIZE);
let spec = ProcessSpec {
argv: vec!["prog".into()],
envp: vec![],
};
let img = load_static(&mut mem, &elf, &spec).unwrap();
let backend = InterpBackend::new(Arch::Aarch64).unwrap();
let vcpu = backend.new_vcpu(img.entry, img.stack_pointer).unwrap();
let captured = Arc::new(Mutex::new(Vec::new()));
let mut kernel = Kernel::new(Arch::Aarch64, MountTable::new());
kernel.set_stdout(Box::new(SharedBuf(captured.clone())));
kernel.set_mmap_area(img.stack_bottom, img.program_break);
let exit_code = kernel.run(vcpu, mem).unwrap();
assert_eq!(
exit_code, 23,
"LDR x/LDRH/LDRB reading the same stored bytes at three widths, EORed, must yield 23"
);
assert!(captured.lock().unwrap().is_empty());
assert!(
kernel.unsupported().is_empty(),
"{:?}",
kernel.unsupported()
);
}