code wiki / (root) / nxasm_arm64_enc.nx

nxasm_arm64_enc.nx source

↩ module page · 227 lines · 11887 B

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