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}