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}