code wiki / (root) / nxasm_arm64_enc.nx

nxasm_arm64_enc.nx source

↩ module page · 224 lines · 11597 B

1// nxasm_arm64_enc.nx -- sovereign aarch64 (A64) instruction encoder + 2// ELF writer, written in NishiLang. The aarch64 sibling of 3// nxasm_x86_enc.nx (x86_64) and asm_enc.nx (RV64). 4// 5// All A64 instructions are fixed 32-bit, emitted little-endian. Pure 6// bit-manipulation -- no syscalls in the encode functions (arch-neutral, 7// KAT-able under any lane). Each encoder returns the 32-bit word; the 8// ELF writer + a64_put_u32le handle emission. 9// 10// Validated byte-exact vs the ARM A64 ISA (Arm ARM DDI 0487) -- the spec 11// is the oracle (per the externals-as-oracle cardinal); golden words are 12// cross-checked against canonical forms (mov x0,x1=0xAA0103E0, 13// mul x2,x0,x1=0x9B017C02, sdiv x0,x1,x2=0x9AC20C20, ldr x0,[x1,#8]=0xF9400420). 14// 15// genealogy_id: arm_a64_isa_ddi0487 + osdev_elf 16// lineage_id: nishi_sovereign_aarch64_encoder_m6 17// license_tier: ORIGINAL 18 19const A64_XZR: i64 = 31 20const EM_AARCH64: i64 = 183 // 0xB7 21const A64_BASE: i64 = 0x10000 22const A64_HDRLEN: i64 = 120 // 64 ehdr + 56 one phdr 23 24// ---- little-endian word emit ---- 25func a64_put_u32le(out: *u8, o: i64, w: i64) -> i64 { 26 out[o] = w & 0xff 27 out[o + 1] = (w >> 8) & 0xff 28 out[o + 2] = (w >> 16) & 0xff 29 out[o + 3] = (w >> 24) & 0xff 30 return o + 4 31} 32func a64_put_u64le(out: *u8, o: i64, v: i64) -> i64 { 33 var i: i64 = 0 34 while i < 8 { out[o + i] = (v >> (i * 8)) & 0xff; i = i + 1 } 35 return o + 8 36} 37 38// ---- MOV-wide immediate family (MOVZ/MOVN/MOVK), 16-bit lane model ---- 39func a64_enc_movz(sf: i64, Rd: i64, imm16: i64, hw: i64) -> i64 { 40 return (sf << 31) | (2 << 29) | (0x25 << 23) | ((hw & 3) << 21) | ((imm16 & 0xFFFF) << 5) | (Rd & 0x1F) 41} 42func a64_enc_movn(sf: i64, Rd: i64, imm16: i64, hw: i64) -> i64 { 43 return (sf << 31) | (0 << 29) | (0x25 << 23) | ((hw & 3) << 21) | ((imm16 & 0xFFFF) << 5) | (Rd & 0x1F) 44} 45func a64_enc_movk(sf: i64, Rd: i64, imm16: i64, hw: i64) -> i64 { 46 return (sf << 31) | (3 << 29) | (0x25 << 23) | ((hw & 3) << 21) | ((imm16 & 0xFFFF) << 5) | (Rd & 0x1F) 47} 48 49// Materialize a 64-bit immediate into Rd: MOVZ lane0, then MOVK any 50// nonzero high lane (always-correct additive chain). Returns new offset. 51func a64_mov_imm64(out: *u8, o: i64, Rd: i64, imm: i64) -> i64 { 52 var p: i64 = a64_put_u32le(out, o, a64_enc_movz(1, Rd, imm & 0xFFFF, 0)) 53 var lane: i64 = 1 54 while lane < 4 { 55 let part: i64 = (imm >> (lane * 16)) & 0xFFFF 56 if part != 0 { p = a64_put_u32le(out, p, a64_enc_movk(1, Rd, part, lane)) } 57 lane = lane + 1 58 } 59 return p 60} 61 62// ---- ADD/SUB immediate (Rn 31 = SP) ---- 63func a64_enc_add_imm(sf: i64, Rd: i64, Rn: i64, imm12: i64, sh: i64) -> i64 { 64 return (sf << 31) | (0x22 << 23) | ((sh & 1) << 22) | ((imm12 & 0xFFF) << 10) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 65} 66func a64_enc_sub_imm(sf: i64, Rd: i64, Rn: i64, imm12: i64, sh: i64) -> i64 { 67 return a64_enc_add_imm(sf, Rd, Rn, imm12, sh) | (1 << 30) 68} 69func a64_enc_subs_imm(sf: i64, Rd: i64, Rn: i64, imm12: i64, sh: i64) -> i64 { 70 return a64_enc_sub_imm(sf, Rd, Rn, imm12, sh) | (1 << 29) 71} 72func a64_enc_cmp_imm(sf: i64, Rn: i64, imm12: i64) -> i64 { 73 return a64_enc_subs_imm(sf, 31, Rn, imm12, 0) 74} 75 76// ---- ADD/SUB shifted-register ---- 77func a64_enc_add_shifted(sf: i64, Rd: i64, Rn: i64, Rm: i64, shift: i64, imm6: i64) -> i64 { 78 return (sf << 31) | (0x0B << 24) | ((shift & 3) << 22) | ((Rm & 0x1F) << 16) | ((imm6 & 0x3F) << 10) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 79} 80func a64_enc_sub_shifted(sf: i64, Rd: i64, Rn: i64, Rm: i64, shift: i64, imm6: i64) -> i64 { 81 return a64_enc_add_shifted(sf, Rd, Rn, Rm, shift, imm6) | (1 << 30) 82} 83func a64_enc_subs_shifted(sf: i64, Rd: i64, Rn: i64, Rm: i64) -> i64 { 84 return a64_enc_sub_shifted(sf, Rd, Rn, Rm, 0, 0) | (1 << 29) 85} 86func a64_enc_cmp_reg(sf: i64, Rn: i64, Rm: i64) -> i64 { 87 return a64_enc_subs_shifted(sf, 31, Rn, Rm) 88} 89 90// ---- Logical shifted-register (AND/ORR/EOR); MOV reg = ORR Rn=XZR ---- 91func a64_enc_orr_shifted(sf: i64, Rd: i64, Rn: i64, Rm: i64, shift: i64, imm6: i64) -> i64 { 92 return (sf << 31) | (1 << 29) | (0x0A << 24) | ((shift & 3) << 22) | ((Rm & 0x1F) << 16) | ((imm6 & 0x3F) << 10) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 93} 94func a64_enc_and_shifted(sf: i64, Rd: i64, Rn: i64, Rm: i64) -> i64 { 95 return (sf << 31) | (0 << 29) | (0x0A << 24) | ((Rm & 0x1F) << 16) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 96} 97func a64_enc_eor_shifted(sf: i64, Rd: i64, Rn: i64, Rm: i64) -> i64 { 98 return (sf << 31) | (2 << 29) | (0x0A << 24) | ((Rm & 0x1F) << 16) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 99} 100func a64_enc_mov_reg(sf: i64, Rd: i64, Rm: i64) -> i64 { 101 return a64_enc_orr_shifted(sf, Rd, 31, Rm, 0, 0) 102} 103 104// ---- MADD/MUL + SDIV/UDIV ---- 105func a64_enc_madd(sf: i64, Rd: i64, Rn: i64, Rm: i64, Ra: i64) -> i64 { 106 return (sf << 31) | (0x1B << 24) | ((Rm & 0x1F) << 16) | ((Ra & 0x1F) << 10) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 107} 108func a64_enc_mul(sf: i64, Rd: i64, Rn: i64, Rm: i64) -> i64 { 109 return a64_enc_madd(sf, Rd, Rn, Rm, 31) 110} 111// is_signed=1 -> SDIV, 0 -> UDIV 112func a64_enc_div(sf: i64, Rd: i64, Rn: i64, Rm: i64, is_signed: i64) -> i64 { 113 return (sf << 31) | (0x0D6 << 21) | ((Rm & 0x1F) << 16) | (1 << 11) | ((is_signed & 1) << 10) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 114} 115 116// ---- CSINC (conditional select increment) -- DDI0602 C6.2.79: 117// sf 0 S=0 11010100 Rm cond o2=1(bit10) Rn Rd -> base 0x1A800400. 118// CSET Rd,cc is the alias CSINC Rd, XZR, XZR, invert(cc) (invert = cc ^ 1). ---- 119func a64_enc_csinc(sf: i64, Rd: i64, Rn: i64, Rm: i64, cond: i64) -> i64 { 120 return (sf << 31) | 0x1A800400 | ((Rm & 0x1F) << 16) | ((cond & 0xF) << 12) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 121} 122func a64_enc_cset(sf: i64, Rd: i64, cond: i64) -> i64 { 123 return a64_enc_csinc(sf, Rd, 31, 31, cond ^ 1) 124} 125 126// ---- Variable shifts (data-processing 2-source) -- DDI0602 C6.2.208/213/32: 127// sf 0 S=0 11010110 Rm 0010 op2(bits 11:10) Rn Rd -> base 0x1AC02000. 128// op2: 0=LSLV 1=LSRV 2=ASRV 3=RORV. Shift amount = Rm mod 64. ---- 129func a64_enc_shiftv(sf: i64, Rd: i64, Rn: i64, Rm: i64, op2: i64) -> i64 { 130 return (sf << 31) | 0x1AC02000 | ((Rm & 0x1F) << 16) | ((op2 & 3) << 10) | ((Rn & 0x1F) << 5) | (Rd & 0x1F) 131} 132 133// ---- LDR/STR (register offset) -- DDI0602 C6.2.188/365, option=011 (LSL) S=0: 134// size 111 V=0 00 opc(L at bit22) 1 Rm option=011 S=0 10 Rn Rt -> ea = Xn + Xm. 135// 64-bit words: STR 0xF8206800, LDR 0xF8606800. This is the form nxc2 emits for 136// large-frame slots (`mov x16, #-off; str Xt, [x29, x16]`). ---- 137func a64_enc_ldst_reg(size: i64, L: i64, Rt: i64, Rn: i64, Rm: i64) -> i64 { 138 return ((size & 3) << 30) | 0x38206800 | ((L & 1) << 22) | ((Rm & 0x1F) << 16) | ((Rn & 0x1F) << 5) | (Rt & 0x1F) 139} 140 141// ---- LDR/STR unsigned-offset (size 3=64-bit; imm12 in scaled units) ---- 142func a64_enc_ldr_uimm(size: i64, Rt: i64, Rn: i64, imm12: i64) -> i64 { 143 return (size << 30) | (7 << 27) | (1 << 24) | (1 << 22) | ((imm12 & 0xFFF) << 10) | ((Rn & 0x1F) << 5) | (Rt & 0x1F) 144} 145func a64_enc_str_uimm(size: i64, Rt: i64, Rn: i64, imm12: i64) -> i64 { 146 return (size << 30) | (7 << 27) | (1 << 24) | ((imm12 & 0xFFF) << 10) | ((Rn & 0x1F) << 5) | (Rt & 0x1F) 147} 148 149// ---- STUR/LDUR: unscaled signed-offset (simm9), 64-bit. These are what nxc2's 150// AArch64 backend emits for frame slots (e.g. `stur x0, [x29, #-144]`). Decode 151// matches nx_emu_arm64 (mask 0x3B200C00 == 0x38000000; size=3 -> base 0xF8000000; 152// opc 0=store/1=load; imm9 at bits 20:12; Rn 9:5; Rt 4:0). L: 0=STUR, 1=LDUR. ---- 153func a64_enc_sturldur(L: i64, Rt: i64, Rn: i64, imm: i64) -> i64 { 154 return 0xF8000000 | ((L & 1) << 22) | ((imm & 0x1FF) << 12) | ((Rn & 0x1F) << 5) | (Rt & 0x1F) 155} 156func a64_enc_stur(Rt: i64, Rn: i64, imm: i64) -> i64 { return a64_enc_sturldur(0, Rt, Rn, imm) } 157func a64_enc_ldur(Rt: i64, Rn: i64, imm: i64) -> i64 { return a64_enc_sturldur(1, Rt, Rn, imm) } 158 159// ---- STP/LDP: load/store register PAIR, 64-bit GPR. nxc2 frame prologue/epilogue 160// (`stp x29,x30,[sp,#-16]!`, `ldp x29,x30,[sp],#16`). Decode matches nx_emu_arm64 161// (mask 0xFC000000 == 0xA8000000; idx<<23 = 1 post / 2 signed-offset / 3 pre-index; 162// L<<22 = 0 store / 1 load; imm7 = byteoffset/8 signed at 21:15; Rt2 14:10). ---- 163func a64_enc_ldstp(idx: i64, L: i64, Rt: i64, Rt2: i64, Rn: i64, imm: i64) -> i64 { 164 let imm7: i64 = (imm / 8) & 0x7F 165 return 0xA8000000 | ((idx & 7) << 23) | ((L & 1) << 22) | (imm7 << 15) | ((Rt2 & 0x1F) << 10) | ((Rn & 0x1F) << 5) | (Rt & 0x1F) 166} 167func a64_enc_stp_pre(Rt: i64, Rt2: i64, Rn: i64, imm: i64) -> i64 { return a64_enc_ldstp(3, 0, Rt, Rt2, Rn, imm) } 168func a64_enc_ldp_post(Rt: i64, Rt2: i64, Rn: i64, imm: i64) -> i64 { return a64_enc_ldstp(1, 1, Rt, Rt2, Rn, imm) } 169func a64_enc_stp_off(Rt: i64, Rt2: i64, Rn: i64, imm: i64) -> i64 { return a64_enc_ldstp(2, 0, Rt, Rt2, Rn, imm) } 170func a64_enc_ldp_off(Rt: i64, Rt2: i64, Rn: i64, imm: i64) -> i64 { return a64_enc_ldstp(2, 1, Rt, Rt2, Rn, imm) } 171 172// ---- branches ---- 173func a64_enc_b(off: i64) -> i64 { 174 return (5 << 26) | ((off >> 2) & 0x03FFFFFF) 175} 176func a64_enc_bl(off: i64) -> i64 { 177 return (0x25 << 26) | ((off >> 2) & 0x03FFFFFF) 178} 179func a64_enc_bcond(cond: i64, off: i64) -> i64 { 180 return (0x54 << 24) | (((off >> 2) & 0x7FFFF) << 5) | (cond & 0xF) 181} 182func a64_enc_cbz(sf: i64, Rt: i64, off: i64) -> i64 { 183 return (sf << 31) | (0x1A << 25) | (((off >> 2) & 0x7FFFF) << 5) | (Rt & 0x1F) 184} 185func a64_enc_cbnz(sf: i64, Rt: i64, off: i64) -> i64 { 186 return a64_enc_cbz(sf, Rt, off) | (1 << 24) 187} 188func a64_enc_ret() -> i64 { return 0xD65F03C0 } 189func a64_enc_br(Rn: i64) -> i64 { return 0xD61F0000 | ((Rn & 0x1F) << 5) } 190func a64_enc_blr(Rn: i64) -> i64 { return 0xD63F0000 | ((Rn & 0x1F) << 5) } 191func a64_enc_svc0() -> i64 { return 0xD4000001 } 192 193// ---- minimal static aarch64 ELF (ET_EXEC, one R+X PT_LOAD) ---- 194// (Real-kernel-valid; entry = base + 120. NX-EMU loads it; qemu-user's 195// stricter loader is a separate oracle-convenience concern.) 196func a64_build_elf(code: *u8, code_len: i64, out: *u8) -> i64 { 197 out[0] = 0x7F; out[1] = 0x45; out[2] = 0x4C; out[3] = 0x46 198 out[4] = 2; out[5] = 1; out[6] = 1 199 var i: i64 = 7 200 while i < 16 { out[i] = 0; i = i + 1 } 201 var o: i64 = 16 202 o = a64_put_u32le(out, 16, 2 | (EM_AARCH64 << 16)) // e_type=ET_EXEC(2), e_machine=183 203 a64_put_u32le(out, 20, 1) // e_version 204 let total: i64 = A64_HDRLEN + code_len 205 let entry: i64 = A64_BASE + A64_HDRLEN 206 a64_put_u64le(out, 24, entry) // e_entry 207 a64_put_u64le(out, 32, 64) // e_phoff 208 a64_put_u64le(out, 40, 0) // e_shoff 209 a64_put_u32le(out, 48, 0) // e_flags 210 a64_put_u32le(out, 52, 64 | (56 << 16)) // e_ehsize=64, e_phentsize=56 211 a64_put_u32le(out, 56, 1) // e_phnum=1, e_shentsize=0 212 a64_put_u32le(out, 60, 0) // e_shnum=0, e_shstrndx=0 213 a64_put_u32le(out, 64, 1) // p_type=PT_LOAD 214 a64_put_u32le(out, 68, 5) // p_flags=R+X 215 a64_put_u64le(out, 72, 0) // p_offset 216 a64_put_u64le(out, 80, A64_BASE) // p_vaddr 217 a64_put_u64le(out, 88, A64_BASE) // p_paddr 218 a64_put_u64le(out, 96, total) // p_filesz 219 a64_put_u64le(out, 104, total) // p_memsz 220 a64_put_u64le(out, 112, 0x1000) // p_align 221 var k: i64 = 0 222 while k < code_len { out[A64_HDRLEN + k] = code[k]; k = k + 1 } 223 return total 224}