nxasm_x86.nx source
↩ module page · 1096 lines · 65145 B
1// nxasm_x86.nx -- sovereign x86_64 assembler (AT&T .s text -> machine
2// code) in NishiLang. The x86_64 sibling of nxasm_v2.nx (RV64).
3//
4// Drives nxasm_x86_enc.nx. Two-pass: pass 1 records label addresses,
5// pass 2 emits bytes resolving call/jmp/jcc displacements. Parses the
6// exact surface nxc2 --target x86_64 emits: directives (.att_syntax/
7// .text/.globl/.type/.size/.section -- all metadata, ignored), labels
8// (`name:` incl. `.Lxxx:`), and AT&T operands (%reg / $imm /
9// disp(%base) / symbol). Replaces GNU `as` on the output path; gas is
10// kept only as a byte-exact oracle (Wheeler/benchmark).
11//
12// genealogy_id: intel_sdm_vol2 + att_syntax + nxasm_v2_two_pass_spine
13// lineage_id: nishi_sovereign_x86_64_assembler_m3
14// license_tier: ORIGINAL
15
16import "nxasm_x86_enc.nx"
17
18// ---- operand record (7-i64 array used as a struct) ----
19const OP_KIND: i64 = 0
20const OP_REG: i64 = 1
21const OP_IMM: i64 = 2
22const OP_BASE: i64 = 3
23const OP_DISP: i64 = 4
24const OP_SYMOFF: i64 = 5
25const OP_SYMLEN: i64 = 6
26const OP_INDEX: i64 = 7 // SIB index reg (-1 = none); op arrays are mmap(72)=9 slots
27const OP_SCALE: i64 = 8 // SIB scale value {1,2,4,8}
28const K_REG: i64 = 0
29const K_IMM: i64 = 1
30const K_MEM: i64 = 2
31const K_XMM: i64 = 3 // an xmm-named register operand (distinct from a GPR so movq can pick the GPR<->xmm SSE form)
32const K_SYM: i64 = 3
33const K_NONE: i64 = 4
34const K_IND: i64 = 5
35const K_RIP: i64 = 6
36
37// 65536: the rebuilt self-host compiler's .s carries 10,674 labels — the old cap of 4096
38// overflowed the label tables SILENTLY and segfaulted (found 2026-06-09 assembling nx_nxc).
39// The add-site now fail-louds at the cap instead of corrupting adjacent mmaps.
40const ASM_MAX_LABELS: i64 = 65536
41
42// ---- char classifiers ----
43func axc_is_space(c: i64) -> i64 {
44 if c == 32 { return 1 }
45 if c == 9 { return 1 }
46 return 0
47}
48func axc_is_digit(c: i64) -> i64 {
49 if c >= 48 { if c <= 57 { return 1 } }
50 return 0
51}
52func axc_is_ident(c: i64) -> i64 {
53 if c >= 97 { if c <= 122 { return 1 } } // a-z
54 if c >= 65 { if c <= 90 { return 1 } } // A-Z
55 if c >= 48 { if c <= 57 { return 1 } } // 0-9
56 if c == 95 { return 1 } // _
57 if c == 46 { return 1 } // .
58 return 0
59}
60
61// ---- length of a null-terminated literal ----
62func axc_cstr_len(s: *u8) -> i64 {
63 var i: i64 = 0
64 while s[i] != 0 { i = i + 1 }
65 return i
66}
67
68// ---- token [off,off+len) in src equals literal lit ? ----
69func axc_tok_is(src: *u8, off: i64, len: i64, lit: *u8) -> i64 {
70 let ll: i64 = axc_cstr_len(lit)
71 if len != ll { return 0 }
72 var i: i64 = 0
73 while i < len {
74 if (src[off + i] & 0xff) != (lit[i] & 0xff) { return 0 }
75 i = i + 1
76 }
77 return 1
78}
79
80// ---- two src-slices equal ? ----
81func axc_slice_eq(src: *u8, a: i64, alen: i64, b: i64, blen: i64) -> i64 {
82 if alen != blen { return 0 }
83 var i: i64 = 0
84 while i < alen {
85 if (src[a + i] & 0xff) != (src[b + i] & 0xff) { return 0 }
86 i = i + 1
87 }
88 return 1
89}
90
91// ---- register name -> encoding number (-1 if unknown) ----
92func axc_reg_num(src: *u8, off: i64, len: i64) -> i64 {
93 if axc_tok_is(src, off, len, "rax") { return 0 }
94 if axc_tok_is(src, off, len, "rcx") { return 1 }
95 if axc_tok_is(src, off, len, "rdx") { return 2 }
96 if axc_tok_is(src, off, len, "rbx") { return 3 }
97 if axc_tok_is(src, off, len, "rsp") { return 4 }
98 if axc_tok_is(src, off, len, "rbp") { return 5 }
99 if axc_tok_is(src, off, len, "rsi") { return 6 }
100 if axc_tok_is(src, off, len, "rdi") { return 7 }
101 if axc_tok_is(src, off, len, "r8") { return 8 }
102 if axc_tok_is(src, off, len, "r9") { return 9 }
103 if axc_tok_is(src, off, len, "r10") { return 10 }
104 if axc_tok_is(src, off, len, "r11") { return 11 }
105 if axc_tok_is(src, off, len, "r12") { return 12 }
106 if axc_tok_is(src, off, len, "r13") { return 13 }
107 if axc_tok_is(src, off, len, "r14") { return 14 }
108 if axc_tok_is(src, off, len, "r15") { return 15 }
109 // 32-bit registers (same numbers; width comes from the opcode --
110 // needed by lzcntl/tzcntl, the compiler's clz32/ctz32 lowering)
111 if axc_tok_is(src, off, len, "eax") { return 0 }
112 if axc_tok_is(src, off, len, "ecx") { return 1 }
113 if axc_tok_is(src, off, len, "edx") { return 2 }
114 if axc_tok_is(src, off, len, "ebx") { return 3 }
115 if axc_tok_is(src, off, len, "esp") { return 4 }
116 if axc_tok_is(src, off, len, "ebp") { return 5 }
117 if axc_tok_is(src, off, len, "esi") { return 6 }
118 if axc_tok_is(src, off, len, "edi") { return 7 }
119 if axc_tok_is(src, off, len, "r8d") { return 8 }
120 if axc_tok_is(src, off, len, "r9d") { return 9 }
121 if axc_tok_is(src, off, len, "r10d") { return 10 }
122 if axc_tok_is(src, off, len, "r11d") { return 11 }
123 if axc_tok_is(src, off, len, "r12d") { return 12 }
124 if axc_tok_is(src, off, len, "r13d") { return 13 }
125 if axc_tok_is(src, off, len, "r14d") { return 14 }
126 if axc_tok_is(src, off, len, "r15d") { return 15 }
127 // byte registers (same numbers; width comes from the opcode)
128 if axc_tok_is(src, off, len, "al") { return 0 }
129 if axc_tok_is(src, off, len, "cl") { return 1 }
130 if axc_tok_is(src, off, len, "dl") { return 2 }
131 if axc_tok_is(src, off, len, "bl") { return 3 }
132 if axc_tok_is(src, off, len, "spl") { return 4 }
133 if axc_tok_is(src, off, len, "bpl") { return 5 }
134 if axc_tok_is(src, off, len, "sil") { return 6 }
135 if axc_tok_is(src, off, len, "dil") { return 7 }
136 // SSE/AVX xmm registers (0..15). Same index space; the SSE/SHA-NI mnemonics
137 // interpret the operand as an xmm -- GPR mnemonics never receive an xmm operand.
138 if axc_tok_is(src, off, len, "xmm0") { return 0 }
139 if axc_tok_is(src, off, len, "xmm1") { return 1 }
140 if axc_tok_is(src, off, len, "xmm2") { return 2 }
141 if axc_tok_is(src, off, len, "xmm3") { return 3 }
142 if axc_tok_is(src, off, len, "xmm4") { return 4 }
143 if axc_tok_is(src, off, len, "xmm5") { return 5 }
144 if axc_tok_is(src, off, len, "xmm6") { return 6 }
145 if axc_tok_is(src, off, len, "xmm7") { return 7 }
146 if axc_tok_is(src, off, len, "xmm8") { return 8 }
147 if axc_tok_is(src, off, len, "xmm9") { return 9 }
148 if axc_tok_is(src, off, len, "xmm10") { return 10 }
149 if axc_tok_is(src, off, len, "xmm11") { return 11 }
150 if axc_tok_is(src, off, len, "xmm12") { return 12 }
151 if axc_tok_is(src, off, len, "xmm13") { return 13 }
152 if axc_tok_is(src, off, len, "xmm14") { return 14 }
153 if axc_tok_is(src, off, len, "xmm15") { return 15 }
154 // AVX/AVX2 ymm registers (0..15; same index space, width from the VEX.L bit).
155 if axc_tok_is(src, off, len, "ymm0") { return 0 }
156 if axc_tok_is(src, off, len, "ymm1") { return 1 }
157 if axc_tok_is(src, off, len, "ymm2") { return 2 }
158 if axc_tok_is(src, off, len, "ymm3") { return 3 }
159 if axc_tok_is(src, off, len, "ymm4") { return 4 }
160 if axc_tok_is(src, off, len, "ymm5") { return 5 }
161 if axc_tok_is(src, off, len, "ymm6") { return 6 }
162 if axc_tok_is(src, off, len, "ymm7") { return 7 }
163 if axc_tok_is(src, off, len, "ymm8") { return 8 }
164 if axc_tok_is(src, off, len, "ymm9") { return 9 }
165 if axc_tok_is(src, off, len, "ymm10") { return 10 }
166 if axc_tok_is(src, off, len, "ymm11") { return 11 }
167 if axc_tok_is(src, off, len, "ymm12") { return 12 }
168 if axc_tok_is(src, off, len, "ymm13") { return 13 }
169 if axc_tok_is(src, off, len, "ymm14") { return 14 }
170 if axc_tok_is(src, off, len, "ymm15") { return 15 }
171 return -1
172}
173
174// ---- parse a signed decimal integer at pos (advances pos) ----
175func axc_parse_int(src: *u8, pos: *i64, le: i64) -> i64 {
176 var p: i64 = pos[0]
177 var neg: i64 = 0
178 if p < le { if src[p] == 45 { neg = 1; p = p + 1 } } // '-'
179 var v: i64 = 0
180 var done: i64 = 0
181 while p < le && done == 0 {
182 let c: i64 = src[p] & 0xff
183 if axc_is_digit(c) == 1 {
184 v = v * 10 + (c - 48)
185 p = p + 1
186 } else {
187 done = 1
188 }
189 }
190 pos[0] = p
191 if neg == 1 { return 0 - v }
192 return v
193}
194
195// ---- parse one operand at pos into op (7-i64 array); advances pos ----
196// Operand kinds: %reg | $imm | disp(%base) memory | bare symbol.
197func axc_parse_operand(src: *u8, pos: *i64, le: i64, op: *i64) -> i64 {
198 var p: i64 = pos[0]
199 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
200 op[OP_KIND] = K_NONE
201 op[OP_INDEX] = 0 - 1 // SIB sentinel: no index unless a ',' is parsed below
202 if p >= le { pos[0] = p; return 0 }
203 let c: i64 = src[p] & 0xff
204 // register: %name
205 if c == 37 { // '%'
206 p = p + 1
207 let s: i64 = p
208 while p < le { if axc_is_ident(src[p]) == 1 { p = p + 1 } else { break } }
209 op[OP_KIND] = K_REG
210 op[OP_REG] = axc_reg_num(src, s, p - s)
211 // xmm-named register -> K_XMM (same 0..15 index; only movq's GPR<->xmm
212 // form needs to tell them apart -- SSE mnemonics read OP_REG directly).
213 if (p - s) >= 3 { if src[s] == (120 as u8) { if src[s+1] == (109 as u8) { if src[s+2] == (109 as u8) { op[OP_KIND] = K_XMM } } } }
214 pos[0] = p
215 return 0
216 }
217 // indirect: *%reg (call/jmp through a register)
218 if c == 42 { // '*'
219 p = p + 1
220 if p < le { if src[p] == 37 { p = p + 1 } } // '%'
221 let s2: i64 = p
222 while p < le { if axc_is_ident(src[p]) == 1 { p = p + 1 } else { break } }
223 op[OP_KIND] = K_IND
224 op[OP_REG] = axc_reg_num(src, s2, p - s2)
225 pos[0] = p
226 return 0
227 }
228 // immediate: $int
229 if c == 36 { // '$'
230 p = p + 1
231 pos[0] = p
232 let v: i64 = axc_parse_int(src, pos, le)
233 op[OP_KIND] = K_IMM
234 op[OP_IMM] = v
235 return 0
236 }
237 // memory: '(' or a signed displacement followed by '('
238 var is_mem: i64 = 0
239 if c == 40 { is_mem = 1 } // '('
240 if c == 45 { is_mem = 1 } // '-'
241 if axc_is_digit(c) == 1 { is_mem = 1 }
242 if is_mem == 1 {
243 var disp: i64 = 0
244 if c != 40 {
245 pos[0] = p
246 disp = axc_parse_int(src, pos, le)
247 p = pos[0]
248 }
249 op[OP_DISP] = disp
250 if p < le { if src[p] == 40 { p = p + 1 } } // '('
251 if p < le { if src[p] == 37 { p = p + 1 } } // '%'
252 let bs: i64 = p
253 while p < le { if axc_is_ident(src[p]) == 1 { p = p + 1 } else { break } }
254 op[OP_BASE] = axc_reg_num(src, bs, p - bs)
255 // SIB: `(%base,%index,scale)` -- a ',' after the base opens the index+scale.
256 // Additive: no existing .s emits this form, so the disp(%base) path above is
257 // byte-identical for all current code.
258 if p < le { if src[p] == 44 { // ','
259 p = p + 1
260 if p < le { if src[p] == 37 { p = p + 1 } } // '%'
261 let is0: i64 = p
262 while p < le { if axc_is_ident(src[p]) == 1 { p = p + 1 } else { break } }
263 op[OP_INDEX] = axc_reg_num(src, is0, p - is0)
264 if p < le { if src[p] == 44 { p = p + 1 } } // ',' before scale
265 var scv: i64 = 0
266 while p < le { if axc_is_digit(src[p] & 0xff) == 1 { scv = scv * 10 + ((src[p] & 0xff) - 48); p = p + 1 } else { break } }
267 op[OP_SCALE] = scv
268 } }
269 if p < le { if src[p] == 41 { p = p + 1 } } // ')'
270 op[OP_KIND] = K_MEM
271 pos[0] = p
272 return 0
273 }
274 // bare symbol (`call main`) OR RIP-relative `LABEL(%rip)`
275 let ss: i64 = p
276 while p < le { if axc_is_ident(src[p]) == 1 { p = p + 1 } else { break } }
277 op[OP_SYMOFF] = ss
278 op[OP_SYMLEN] = p - ss
279 // detect a (%rip) suffix -> RIP-relative memory operand
280 if p < le {
281 if src[p] == 40 { // '('
282 var q: i64 = p + 1
283 if q < le { if src[q] == 37 { q = q + 1 } } // '%'
284 let rs: i64 = q
285 while q < le { if axc_is_ident(src[q]) == 1 { q = q + 1 } else { break } }
286 if axc_tok_is(src, rs, q - rs, "rip") == 1 {
287 if q < le { if src[q] == 41 { q = q + 1 } } // ')'
288 op[OP_KIND] = K_RIP
289 pos[0] = q
290 return 0
291 }
292 }
293 }
294 op[OP_KIND] = K_SYM
295 pos[0] = p
296 return 0
297}
298
299// ---- find a label's address by name slice (-1 if not found) ----
300func axc_label_find(src: *u8, lab_off: *i64, lab_len: *i64, lab_addr: *i64,
301 n_lab: i64, name_off: i64, name_len: i64) -> i64 {
302 var k: i64 = 0
303 while k < n_lab {
304 if axc_slice_eq(src, lab_off[k], lab_len[k], name_off, name_len) == 1 {
305 return lab_addr[k]
306 }
307 k = k + 1
308 }
309 return -1
310}
311
312// ---- O(1) hashed label lookup (PERF FIX 2026-06-09) ----------------
313// The linear axc_label_find above is O(n_lab) string-compares PER
314// REFERENCE; on compiler-scale .s (~75K lines, tens of thousands of
315// labels x as many jmp/jcc/call/lea refs) that is billions of byte
316// compares = minutes of assemble time (the C2-deploy stall). Same
317// disease as the LICM fixpoint: a linear scan inside a hot loop.
318// Open-addressing hash over label INDICES, built ONCE after pass 0;
319// first-insert-wins so duplicate names resolve to the FIRST definition,
320// byte-identical to the linear scan's first-match semantics.
321const ASM_LH_SIZE: i64 = 262144 // 2^18 = 4x ASM_MAX_LABELS load<=25%
322const ASM_LH_MASK: i64 = 262143
323
324func axc_lh_hash(src: *u8, off: i64, len: i64) -> i64 {
325 var h: i64 = 5381
326 var i: i64 = 0
327 while i < len {
328 h = h * 33 + (src[off + i] & 0xff)
329 i = i + 1
330 }
331 if h < 0 { h = 0 - h }
332 return h & ASM_LH_MASK
333}
334
335// Build the index over labels 0..n_lab-1. lh holds label indices, -1 = empty.
336func axc_lh_build(src: *u8, lab_off: *i64, lab_len: *i64, n_lab: i64, lh: *i64) -> i64 {
337 var i: i64 = 0
338 while i < ASM_LH_SIZE { lh[i] = 0 - 1; i = i + 1 }
339 var k: i64 = 0
340 while k < n_lab {
341 var slot: i64 = axc_lh_hash(src, lab_off[k], lab_len[k])
342 var placed: i64 = 0
343 while placed == 0 {
344 let e: i64 = lh[slot]
345 if e < 0 { lh[slot] = k; placed = 1 }
346 else {
347 // duplicate name: keep the FIRST definition (linear-scan parity)
348 if axc_slice_eq(src, lab_off[e], lab_len[e], lab_off[k], lab_len[k]) == 1 { placed = 1 }
349 else { slot = (slot + 1) & ASM_LH_MASK }
350 }
351 }
352 k = k + 1
353 }
354 return 0
355}
356
357func axc_label_find_h(src: *u8, lab_off: *i64, lab_len: *i64, lab_addr: *i64,
358 lh: *i64, name_off: i64, name_len: i64) -> i64 {
359 var slot: i64 = axc_lh_hash(src, name_off, name_len)
360 var hops: i64 = 0
361 while hops < ASM_LH_SIZE {
362 let e: i64 = lh[slot]
363 if e < 0 { return -1 }
364 if axc_slice_eq(src, lab_off[e], lab_len[e], name_off, name_len) == 1 {
365 return lab_addr[e]
366 }
367 slot = (slot + 1) & ASM_LH_MASK
368 hops = hops + 1
369 }
370 return -1
371}
372
373// LOUD-fail resolve for pass 2 (PREVENT): silently encoding a -1 address
374// turns a missing definition into a base-minus-one pointer that segfaults
375// at RUNTIME far from the cause (2026-06-10: ignored .lcomm .Lg472 ->
376// write through 0x400077, a day-long hunt). An assembler must never emit
377// a reference it could not resolve.
378func axc_label_resolve(src: *u8, lab_off: *i64, lab_len: *i64, lab_addr: *i64,
379 lh: *i64, name_off: i64, name_len: i64) -> i64 {
380 let a: i64 = axc_label_find_h(src, lab_off, lab_len, lab_addr, lh, name_off, name_len)
381 if a < 0 {
382 sys_write(2, "nxasm_x86: UNDEFINED label: " as *u8, 28)
383 sys_write(2, ((src as i64) + name_off) as *u8, name_len)
384 sys_write(2, "\n" as *u8, 1)
385 sys_exit(102)
386 }
387 return a
388}
389
390// ---- shared ALU dispatch (reg,reg via MR opcode | imm,reg via ext) ----
391func axc_alu(out: *u8, op0: *i64, op1: *i64, rr_op: i64, imm_ext: i64) -> i64 {
392 if op0[OP_KIND] == K_IMM { return x86_alu_imm(out, 0, imm_ext, op1[OP_REG], op0[OP_IMM]) }
393 return x86_alu_rr(out, 0, rr_op, op1[OP_REG], op0[OP_REG])
394}
395
396// ---- jcc mnemonic -> condition code (-1 if not a jcc) ----
397func axc_jcc_cc(src: *u8, off: i64, len: i64) -> i64 {
398 if axc_tok_is(src, off, len, "je") { return X86_CC_E }
399 if axc_tok_is(src, off, len, "jne") { return X86_CC_NE }
400 if axc_tok_is(src, off, len, "jl") { return X86_CC_L }
401 if axc_tok_is(src, off, len, "jle") { return X86_CC_LE }
402 if axc_tok_is(src, off, len, "jg") { return X86_CC_G }
403 if axc_tok_is(src, off, len, "jge") { return X86_CC_GE }
404 return -1
405}
406
407// ---- setcc mnemonic -> condition code (-1 if not a setcc) ----
408func axc_setcc_cc(src: *u8, off: i64, len: i64) -> i64 {
409 if axc_tok_is(src, off, len, "sete") { return X86_CC_E }
410 if axc_tok_is(src, off, len, "setne") { return X86_CC_NE }
411 if axc_tok_is(src, off, len, "setl") { return X86_CC_L }
412 if axc_tok_is(src, off, len, "setle") { return X86_CC_LE }
413 if axc_tok_is(src, off, len, "setg") { return X86_CC_G }
414 if axc_tok_is(src, off, len, "setge") { return X86_CC_GE }
415 return -1
416}
417
418// ---- cmovcc mnemonic -> condition code (-1 if not a cmov). Full family so
419// the mapper never lags the compiler; S/NS are the G22 bias-select users. ----
420func axc_cmovcc_cc(src: *u8, off: i64, len: i64) -> i64 {
421 if axc_tok_is(src, off, len, "cmove") { return X86_CC_E }
422 if axc_tok_is(src, off, len, "cmovne") { return X86_CC_NE }
423 if axc_tok_is(src, off, len, "cmovl") { return X86_CC_L }
424 if axc_tok_is(src, off, len, "cmovle") { return X86_CC_LE }
425 if axc_tok_is(src, off, len, "cmovg") { return X86_CC_G }
426 if axc_tok_is(src, off, len, "cmovge") { return X86_CC_GE }
427 if axc_tok_is(src, off, len, "cmovs") { return X86_CC_S }
428 if axc_tok_is(src, off, len, "cmovns") { return X86_CC_NS }
429 return -1
430}
431
432// ---- atomic reg,(%base) encoder: [REX.W .R .B] [0F] opcode ModRM(mod=00, reg, base). The compiler emits
433// atomics only against a simple (%r11) base with rax/rcx as the register, so mod=00 rm=base is correct (no
434// SIB/disp -- base is never rsp/rbp/r12/r13). xchgq=87, cmpxchgq=0F B1, xaddq=0F C1. The F0 lock prefix for
435// cmpxchg/xadd is prepended by the assemble loop's `lock` handling (xchg-with-memory is auto-locked).
436func x86_atomic_rm(out: *u8, is0f: i64, opcode: i64, reg: i64, base: i64) -> i64 {
437 var rex: i64 = 0x48
438 if reg >= 8 { rex = rex + 4 }
439 if base >= 8 { rex = rex + 1 }
440 var n: i64 = 0
441 out[n] = rex as u8; n = n + 1
442 if is0f == 1 { out[n] = 0x0f as u8; n = n + 1 }
443 out[n] = opcode as u8; n = n + 1
444 out[n] = ((reg & 7) * 8 + (base & 7)) as u8; n = n + 1
445 return n
446}
447// mfence = 0F AE F0 (full memory fence).
448func x86_mfence_enc(out: *u8) -> i64 { out[0] = 0x0f as u8; out[1] = 0xae as u8; out[2] = 0xf0 as u8; return 3 }
449
450// ---- encode ONE instruction into out[0..]; return byte length ----
451// `cur` = this instruction's address (for rel32 resolution); labels
452// resolved only in pass 2 (rel-independent length means pass-1 rel=0
453// gives the correct length). Returns -1 on an unrecognized mnemonic.
454func axc_emit(out: *u8, src: *u8, mn: i64, mnl: i64, op0: *i64, op1: *i64, op2: *i64,
455 lab_off: *i64, lab_len: *i64, lab_addr: *i64, n_lab: i64,
456 lh: *i64, cur: i64, pass: i64) -> i64 {
457 // First-character dispatch: x86 AT&T mnemonics are partitioned by
458 // their leading byte so a typical instruction pays ~1-6 token
459 // comparisons instead of walking a ~30-long linear chain (the chain
460 // position was measured at ~47% of per-instruction cost). Every
461 // case body below is byte-for-byte the original; only the grouping
462 // changed. Unknown first byte -> -1 (unrecognized), same as before.
463 let c0: i64 = src[mn] & 0xff
464 if c0 == 97 { // 'a'
465 if axc_tok_is(src, mn, mnl, "addq") { return axc_alu(out, op0, op1, X86_OP_ADD, X86_EXT_ADD) }
466 if axc_tok_is(src, mn, mnl, "andq") { return axc_alu(out, op0, op1, X86_OP_AND, X86_EXT_AND) }
467 // scalar single-precision float (SSE): addss F3 0F 58 /r (AT&T src,dst -> ModRM reg=dst rm=src)
468 if axc_tok_is(src, mn, mnl, "addss") { return x86_sse_rr(out, 0, 0xf3, 0, 0x58, op1[OP_REG], op0[OP_REG]) }
469 // scalar DOUBLE-precision (f64): same opcodes as *ss with the F2 prefix (not F3).
470 if axc_tok_is(src, mn, mnl, "addsd") { return x86_sse_rr(out, 0, 0xf2, 0, 0x58, op1[OP_REG], op0[OP_REG]) }
471 // PACKED single-precision (4 x f32): addps 0F 58 -- same as addss WITHOUT the F3 prefix (the SIMD lever).
472 if axc_tok_is(src, mn, mnl, "addps") { return x86_sse_rr(out, 0, 0, 0, 0x58, op1[OP_REG], op0[OP_REG]) }
473 // AES-NI (66 0F 38 xx /r, reg-reg). Hardware AES rounds: aesenc/last + aesdec/last + aesimc.
474 if axc_tok_is(src, mn, mnl, "aesenclast") { return x86_sse_rr(out, 0, 0x66, 0x38, 0xdd, op1[OP_REG], op0[OP_REG]) }
475 if axc_tok_is(src, mn, mnl, "aesenc") { return x86_sse_rr(out, 0, 0x66, 0x38, 0xdc, op1[OP_REG], op0[OP_REG]) }
476 if axc_tok_is(src, mn, mnl, "aesdeclast") { return x86_sse_rr(out, 0, 0x66, 0x38, 0xdf, op1[OP_REG], op0[OP_REG]) }
477 if axc_tok_is(src, mn, mnl, "aesdec") { return x86_sse_rr(out, 0, 0x66, 0x38, 0xde, op1[OP_REG], op0[OP_REG]) }
478 if axc_tok_is(src, mn, mnl, "aesimc") { return x86_sse_rr(out, 0, 0x66, 0x38, 0xdb, op1[OP_REG], op0[OP_REG]) }
479 // ADX add-with-carry (2-operand %src,%dst -> ModRM reg=dst rm=src, like crc32q):
480 // adcx uses the CF chain (66 REX.W 0F38 F6), adox uses the OF chain (F3 REX.W 0F38 F6).
481 // The two independent carry chains let a schoolbook column accumulate both carries in
482 // parallel around flags-free MULX -- the P-256/bignum dual-carry field-mul kernel.
483 if axc_tok_is(src, mn, mnl, "adcx") { return x86_adcx_r64(out, 0, op1[OP_REG], op0[OP_REG]) }
484 if axc_tok_is(src, mn, mnl, "adox") { return x86_adox_r64(out, 0, op1[OP_REG], op0[OP_REG]) }
485 return -1
486 }
487 if c0 == 98 { // 'b'
488 if axc_tok_is(src, mn, mnl, "bswapq") { return x86_bswap(out, 0, op0[OP_REG]) }
489 return -1
490 }
491 if c0 == 99 { // 'c'
492 if axc_tok_is(src, mn, mnl, "cqo") { return x86_cqo(out, 0) }
493 if axc_tok_is(src, mn, mnl, "cqto") { return x86_cqo(out, 0) }
494 if axc_tok_is(src, mn, mnl, "cmpq") { return axc_alu(out, op0, op1, X86_OP_CMP, X86_EXT_CMP) }
495 // atomic compare-and-swap (lock prefix from the assemble loop): cmpxchgq %reg, (%base) = 0F B1 /r
496 if axc_tok_is(src, mn, mnl, "cmpxchgq") { return x86_atomic_rm(out, 1, 0xb1, op0[OP_REG], op1[OP_BASE]) }
497 // scalar single-precision float converts (SSE, REX.W for the r64 operand): cvtsi2ss F3 0F 2A
498 // (r64->xmm), cvttss2si F3 0F 2C (xmm->r64 truncate), cvtss2si F3 0F 2D (xmm->r64 round-to-nearest).
499 if axc_tok_is(src, mn, mnl, "cvtsi2ss") { return x86_sse_rr_w(out, 0, 0xf3, 0, 0x2a, op1[OP_REG], op0[OP_REG]) }
500 if axc_tok_is(src, mn, mnl, "cvttss2si") { return x86_sse_rr_w(out, 0, 0xf3, 0, 0x2c, op1[OP_REG], op0[OP_REG]) }
501 // f64 converts: cvtsi2sd F2 0F 2A (r64->xmm double), cvttsd2si F2 0F 2C (xmm double->r64 truncate).
502 if axc_tok_is(src, mn, mnl, "cvtsi2sd") { return x86_sse_rr_w(out, 0, 0xf2, 0, 0x2a, op1[OP_REG], op0[OP_REG]) }
503 if axc_tok_is(src, mn, mnl, "cvttsd2si") { return x86_sse_rr_w(out, 0, 0xf2, 0, 0x2c, op1[OP_REG], op0[OP_REG]) }
504 if axc_tok_is(src, mn, mnl, "cvtss2si") { return x86_sse_rr_w(out, 0, 0xf3, 0, 0x2d, op1[OP_REG], op0[OP_REG]) }
505 // crc32q %src,%dst -- SSE4.2 CRC-32C accumulate (F2 REX.W 0F 38 F1 /r).
506 // AT&T src,dst -> ModRM reg=dst (accumulator), rm=src (data word).
507 if axc_tok_is(src, mn, mnl, "crc32q") { return x86_crc32_r64(out, 0, op1[OP_REG], op0[OP_REG]) }
508 // cmovcc %src,%dst -- conditional move family (REX.W 0F 40+cc /r).
509 let mcc: i64 = axc_cmovcc_cc(src, mn, mnl)
510 if mcc >= 0 { return x86_cmovcc(out, 0, mcc, op1[OP_REG], op0[OP_REG]) }
511 if axc_tok_is(src, mn, mnl, "call") {
512 if op0[OP_KIND] == K_IND { return x86_call_indirect(out, 0, op0[OP_REG]) }
513 var rel: i64 = 0
514 if pass == 2 { rel = axc_label_resolve(src, lab_off, lab_len, lab_addr, lh, op0[OP_SYMOFF], op0[OP_SYMLEN]) - (cur + 5) }
515 return x86_call_rel32(out, 0, rel)
516 }
517 return -1
518 }
519 if c0 == 100 { // 'd'
520 if axc_tok_is(src, mn, mnl, "decq") { return x86_incdec(out, 0, 1, op0[OP_REG]) }
521 // scalar single-precision float: divss F3 0F 5E /r
522 if axc_tok_is(src, mn, mnl, "divss") { return x86_sse_rr(out, 0, 0xf3, 0, 0x5e, op1[OP_REG], op0[OP_REG]) }
523 if axc_tok_is(src, mn, mnl, "divsd") { return x86_sse_rr(out, 0, 0xf2, 0, 0x5e, op1[OP_REG], op0[OP_REG]) }
524 return -1
525 }
526 if c0 == 105 { // 'i'
527 if axc_tok_is(src, mn, mnl, "idivq") { return x86_idiv(out, 0, op0[OP_REG]) }
528 if axc_tok_is(src, mn, mnl, "imulq") { return x86_imul_rr(out, 0, op1[OP_REG], op0[OP_REG]) }
529 if axc_tok_is(src, mn, mnl, "incq") { return x86_incdec(out, 0, 0, op0[OP_REG]) }
530 return -1
531 }
532 if c0 == 106 { // 'j'
533 if axc_tok_is(src, mn, mnl, "jmp") {
534 if op0[OP_KIND] == K_IND { return x86_jmp_indirect(out, 0, op0[OP_REG]) }
535 var rel: i64 = 0
536 if pass == 2 { rel = axc_label_resolve(src, lab_off, lab_len, lab_addr, lh, op0[OP_SYMOFF], op0[OP_SYMLEN]) - (cur + 5) }
537 return x86_jmp_rel32(out, 0, rel)
538 }
539 let cc: i64 = axc_jcc_cc(src, mn, mnl)
540 if cc >= 0 {
541 var rel: i64 = 0
542 if pass == 2 { rel = axc_label_resolve(src, lab_off, lab_len, lab_addr, lh, op0[OP_SYMOFF], op0[OP_SYMLEN]) - (cur + 6) }
543 return x86_jcc_rel32(out, 0, cc, rel)
544 }
545 return -1
546 }
547 if c0 == 108 { // 'l'
548 if axc_tok_is(src, mn, mnl, "lzcntl") { return x86_lzcnt32(out, 0, op1[OP_REG], op0[OP_REG]) }
549 if axc_tok_is(src, mn, mnl, "leaq") {
550 if op0[OP_KIND] == K_RIP {
551 var d: i64 = 0
552 if pass == 2 { d = axc_label_resolve(src, lab_off, lab_len, lab_addr, lh, op0[OP_SYMOFF], op0[OP_SYMLEN]) - (cur + 7) }
553 return x86_lea_rip(out, 0, op1[OP_REG], d)
554 }
555 // SIB source d(%base,%index,scale) -> lea-strength form (mirrors the
556 // movq K_MEM SIB dispatch below). op0[OP_INDEX] >= 0 signals a scaled index.
557 if op0[OP_INDEX] >= 0 { return x86_lea_sib(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_INDEX], op0[OP_SCALE], op0[OP_DISP]) }
558 return x86_lea(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP])
559 }
560 return -1
561 }
562 if c0 == 109 { // 'm'
563 if axc_tok_is(src, mn, mnl, "movabsq") { return x86_movabs(out, 0, op1[OP_REG], op0[OP_IMM]) }
564 // mulq %reg -- UNSIGNED 64x64 -> rdx:rax (REX.W F7 /4). Single-operand,
565 // like idivq/negq; the compiler emits this for OP_UMULHI (__umulhi64).
566 if axc_tok_is(src, mn, mnl, "mulq") { return x86_mul(out, 0, op0[OP_REG]) }
567 if axc_tok_is(src, mn, mnl, "mfence") { return x86_mfence_enc(out) }
568 // mulx %src,%dstlo,%dsthi -- BMI2 flags-free 64x64->128 (src * implicit RDX; low->dstlo,
569 // high->dsthi; CF/OF untouched). AT&T 3-operand (src,dstlo,dsthi) -> op0=src op1=dstlo
570 // op2=dsthi; encoder x86_mulx_r64(dst_hi,dst_lo,src). VEX.NDD.LZ.F2.0F38.W1 F6 /r.
571 if axc_tok_is(src, mn, mnl, "mulx") { return x86_mulx_r64(out, 0, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
572 // SSE 128-bit moves (xmm,xmm reg-reg + xmm<->m128 load/store).
573 // AT&T src,dst: a memory SOURCE (op0=K_MEM) is a LOAD (opcode 0x6f,
574 // reg=dst xmm); a memory DEST (op1=K_MEM) is a STORE (opcode 0x7f,
575 // reg=src xmm). Same K_MEM branch shape proven by movq (lines below).
576 if axc_tok_is(src, mn, mnl, "movdqa") {
577 if op0[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0x66, 0x6f, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
578 if op1[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0x66, 0x7f, op0[OP_REG], op1[OP_BASE], op1[OP_DISP]) }
579 return x86_sse_rr(out, 0, 0x66, 0, 0x6f, op1[OP_REG], op0[OP_REG])
580 }
581 if axc_tok_is(src, mn, mnl, "movdqu") {
582 if op0[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0xf3, 0x6f, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
583 if op1[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0xf3, 0x7f, op0[OP_REG], op1[OP_BASE], op1[OP_DISP]) }
584 return x86_sse_rr(out, 0, 0xf3, 0, 0x6f, op1[OP_REG], op0[OP_REG])
585 }
586 // scalar single-precision float: mulss F3 0F 59 /r; movss F3 0F 10 (load/reg-reg) / 0F 11 (store).
587 // AT&T src,dst: memory SOURCE (op0=K_MEM) is a LOAD (reg=dst xmm); memory DEST (op1=K_MEM) is a STORE.
588 if axc_tok_is(src, mn, mnl, "mulss") { return x86_sse_rr(out, 0, 0xf3, 0, 0x59, op1[OP_REG], op0[OP_REG]) }
589 if axc_tok_is(src, mn, mnl, "mulsd") { return x86_sse_rr(out, 0, 0xf2, 0, 0x59, op1[OP_REG], op0[OP_REG]) }
590 // PACKED single-precision (4 x f32): mulps 0F 59 -- mulss without the F3 prefix (the SIMD compute lever).
591 if axc_tok_is(src, mn, mnl, "mulps") { return x86_sse_rr(out, 0, 0, 0, 0x59, op1[OP_REG], op0[OP_REG]) }
592 if axc_tok_is(src, mn, mnl, "movss") {
593 if op0[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0xf3, 0x10, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
594 if op1[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0xf3, 0x11, op0[OP_REG], op1[OP_BASE], op1[OP_DISP]) }
595 return x86_sse_rr(out, 0, 0xf3, 0, 0x10, op1[OP_REG], op0[OP_REG])
596 }
597 // scalar double load/store/reg (SSE movsd F2 0F 10/11) -- the xmm-operand form, NOT the string op.
598 if axc_tok_is(src, mn, mnl, "movsd") {
599 if op0[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0xf2, 0x10, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
600 if op1[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0xf2, 0x11, op0[OP_REG], op1[OP_BASE], op1[OP_DISP]) }
601 return x86_sse_rr(out, 0, 0xf2, 0, 0x10, op1[OP_REG], op0[OP_REG])
602 }
603 // PACKED load/store 4 x f32: movups 0F 10 (load/reg-reg) / 0F 11 (store) -- movss without F3, all 4 lanes.
604 if axc_tok_is(src, mn, mnl, "movups") {
605 if op0[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0, 0x10, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
606 if op1[OP_KIND] == K_MEM { return x86_sse_mem(out, 0, 0, 0x11, op0[OP_REG], op1[OP_BASE], op1[OP_DISP]) }
607 return x86_sse_rr(out, 0, 0, 0, 0x10, op1[OP_REG], op0[OP_REG])
608 }
609
610 if axc_tok_is(src, mn, mnl, "movq") {
611 // GPR<->xmm direct moves (SSE2 66 REX.W 0F 6E/7E) -- the f64 fast path
612 // that avoids the red-zone memory shuttle. reg field = the xmm operand
613 // in BOTH directions; the opcode encodes the direction.
614 if op1[OP_KIND] == K_XMM { if op0[OP_KIND] == K_REG { return x86_sse_rr_w(out, 0, 0x66, 0, 0x6e, op1[OP_REG], op0[OP_REG]) } } // movq %gpr,%xmm
615 if op0[OP_KIND] == K_XMM { if op1[OP_KIND] == K_REG { return x86_sse_rr_w(out, 0, 0x66, 0, 0x7e, op0[OP_REG], op1[OP_REG]) } } // movq %xmm,%gpr
616 if op0[OP_KIND] == K_IMM { return x86_mov_imm32(out, 0, op1[OP_REG], op0[OP_IMM]) }
617 if op0[OP_KIND] == K_MEM { if op0[OP_INDEX] >= 0 { return x86_mov_load_sib(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_INDEX], op0[OP_SCALE], op0[OP_DISP]) } }
618 if op1[OP_KIND] == K_MEM { if op1[OP_INDEX] >= 0 { return x86_mov_store_sib(out, 0, op1[OP_BASE], op1[OP_INDEX], op1[OP_SCALE], op1[OP_DISP], op0[OP_REG]) } }
619 if op0[OP_KIND] == K_MEM { return x86_mov_load(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
620 if op1[OP_KIND] == K_MEM { return x86_mov_store(out, 0, op1[OP_BASE], op1[OP_DISP], op0[OP_REG]) }
621 return x86_mov_reg(out, 0, op1[OP_REG], op0[OP_REG])
622 }
623 if axc_tok_is(src, mn, mnl, "movzbq") {
624 if op0[OP_KIND] == K_MEM { return x86_movzbq_mem(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
625 return x86_movzbq(out, 0, op1[OP_REG], op0[OP_REG])
626 }
627 // SIGN-extending subword loads + the movzwq zero-twin (2026-07-10 debt fix; encoders in nxasm_x86_enc)
628 if axc_tok_is(src, mn, mnl, "movsbq") {
629 if op0[OP_KIND] == K_MEM { return x86_movsbq_mem(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
630 return x86_movsbq(out, 0, op1[OP_REG], op0[OP_REG])
631 }
632 if axc_tok_is(src, mn, mnl, "movswq") {
633 if op0[OP_KIND] == K_MEM { return x86_movswq_mem(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
634 return x86_movswq(out, 0, op1[OP_REG], op0[OP_REG])
635 }
636 if axc_tok_is(src, mn, mnl, "movzwq") {
637 if op0[OP_KIND] == K_MEM { return x86_movzwq_mem(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
638 return x86_movzwq(out, 0, op1[OP_REG], op0[OP_REG])
639 }
640 if axc_tok_is(src, mn, mnl, "movslq") {
641 if op0[OP_KIND] == K_MEM { return x86_movslq_mem(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
642 return x86_movslq(out, 0, op1[OP_REG], op0[OP_REG])
643 }
644 if axc_tok_is(src, mn, mnl, "movb") {
645 if op0[OP_KIND] == K_IMM { return x86_movb_imm(out, 0, op1[OP_BASE], op1[OP_DISP], op0[OP_IMM]) }
646 return x86_movb_store(out, 0, op1[OP_BASE], op1[OP_DISP], op0[OP_REG])
647 }
648 if axc_tok_is(src, mn, mnl, "movl") {
649 if op0[OP_KIND] == K_MEM { return x86_movl_load(out, 0, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
650 if op1[OP_KIND] == K_MEM { return x86_movl_store(out, 0, op1[OP_BASE], op1[OP_DISP], op0[OP_REG]) }
651 return x86_movl_rr(out, 0, op1[OP_REG], op0[OP_REG])
652 }
653 return -1
654 }
655 if c0 == 110 { // 'n'
656 if axc_tok_is(src, mn, mnl, "negq") { return x86_neg(out, 0, op0[OP_REG]) }
657 if axc_tok_is(src, mn, mnl, "notq") { return x86_not(out, 0, op0[OP_REG]) }
658 return -1
659 }
660 if c0 == 111 { // 'o'
661 if axc_tok_is(src, mn, mnl, "orq") { return axc_alu(out, op0, op1, X86_OP_OR, X86_EXT_OR) }
662 return -1
663 }
664 if c0 == 112 { // 'p'
665 if axc_tok_is(src, mn, mnl, "pushq") { return x86_push(out, 0, op0[OP_REG]) }
666 if axc_tok_is(src, mn, mnl, "popq") { return x86_pop(out, 0, op0[OP_REG]) }
667 if axc_tok_is(src, mn, mnl, "popcntq") { return x86_popcnt(out, 0, op1[OP_REG], op0[OP_REG]) }
668 // BMI2 parallel bit deposit/extract (VEX 3-operand $src2,$src1,$dst ->
669 // dst=ModRM.reg, src1=VEX.vvvv, src2=ModRM.rm). VEX.LZ.F2/F3.0F38.W1 F5.
670 if axc_tok_is(src, mn, mnl, "pdep") { return x86_pdep_r64(out, 0, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
671 if axc_tok_is(src, mn, mnl, "pext") { return x86_pext_r64(out, 0, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
672 // SSE/SHA-256 packed ops (xmm,xmm; AT&T src,dst -> ModRM reg=dst rm=src)
673 if axc_tok_is(src, mn, mnl, "pshufb") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x00, op1[OP_REG], op0[OP_REG]) }
674 if axc_tok_is(src, mn, mnl, "paddd") { return x86_sse_rr(out, 0, 0x66, 0, 0xfe, op1[OP_REG], op0[OP_REG]) }
675 if axc_tok_is(src, mn, mnl, "punpcklqdq") { return x86_sse_rr(out, 0, 0x66, 0, 0x6c, op1[OP_REG], op0[OP_REG]) }
676 if axc_tok_is(src, mn, mnl, "punpckhqdq") { return x86_sse_rr(out, 0, 0x66, 0, 0x6d, op1[OP_REG], op0[OP_REG]) }
677 if axc_tok_is(src, mn, mnl, "pxor") { return x86_sse_rr(out, 0, 0x66, 0, 0xef, op1[OP_REG], op0[OP_REG]) }
678 // packed-integer bitwise (xmm,xmm reg-reg; opcode twins of the proven pxor path)
679 if axc_tok_is(src, mn, mnl, "por") { return x86_sse_rr(out, 0, 0x66, 0, 0xeb, op1[OP_REG], op0[OP_REG]) }
680 if axc_tok_is(src, mn, mnl, "pand") { return x86_sse_rr(out, 0, 0x66, 0, 0xdb, op1[OP_REG], op0[OP_REG]) }
681 if axc_tok_is(src, mn, mnl, "pandn") { return x86_sse_rr(out, 0, 0x66, 0, 0xdf, op1[OP_REG], op0[OP_REG]) }
682 // packed add across element widths: b=8 w=16 d=32(exists) q=64
683 if axc_tok_is(src, mn, mnl, "paddb") { return x86_sse_rr(out, 0, 0x66, 0, 0xfc, op1[OP_REG], op0[OP_REG]) }
684 if axc_tok_is(src, mn, mnl, "paddw") { return x86_sse_rr(out, 0, 0x66, 0, 0xfd, op1[OP_REG], op0[OP_REG]) }
685 if axc_tok_is(src, mn, mnl, "paddq") { return x86_sse_rr(out, 0, 0x66, 0, 0xd4, op1[OP_REG], op0[OP_REG]) }
686 // packed sub across element widths
687 if axc_tok_is(src, mn, mnl, "psubb") { return x86_sse_rr(out, 0, 0x66, 0, 0xf8, op1[OP_REG], op0[OP_REG]) }
688 if axc_tok_is(src, mn, mnl, "psubw") { return x86_sse_rr(out, 0, 0x66, 0, 0xf9, op1[OP_REG], op0[OP_REG]) }
689 if axc_tok_is(src, mn, mnl, "psubd") { return x86_sse_rr(out, 0, 0x66, 0, 0xfa, op1[OP_REG], op0[OP_REG]) }
690 if axc_tok_is(src, mn, mnl, "psubq") { return x86_sse_rr(out, 0, 0x66, 0, 0xfb, op1[OP_REG], op0[OP_REG]) }
691 // packed shift by immediate ($imm,%xmm; ModRM.reg = /ext): the rotl/rotr legs
692 if axc_tok_is(src, mn, mnl, "pslld") { return x86_sse_shift_imm(out, 0, 0x72, 6, op1[OP_REG], op0[OP_IMM]) }
693 if axc_tok_is(src, mn, mnl, "psrld") { return x86_sse_shift_imm(out, 0, 0x72, 2, op1[OP_REG], op0[OP_IMM]) }
694 if axc_tok_is(src, mn, mnl, "psllq") { return x86_sse_shift_imm(out, 0, 0x73, 6, op1[OP_REG], op0[OP_IMM]) }
695 if axc_tok_is(src, mn, mnl, "psrlq") { return x86_sse_shift_imm(out, 0, 0x73, 2, op1[OP_REG], op0[OP_IMM]) }
696 // packed shuffle dwords/words (3-operand $imm,%src,%dst): 0F 70 /r ib (pfx selects d/lw/hw)
697 if axc_tok_is(src, mn, mnl, "pshufd") { return x86_sse_rri(out, 0, 0x66, 0x70, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
698 if axc_tok_is(src, mn, mnl, "pshuflw") { return x86_sse_rri(out, 0, 0xf2, 0x70, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
699 if axc_tok_is(src, mn, mnl, "pshufhw") { return x86_sse_rri(out, 0, 0xf3, 0x70, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
700 // carry-less multiply (CLMUL): 66 0F 3A 44 /r ib (3-operand $imm,%src,%dst).
701 // x86_sse_rr emits 66 0F 3A 44 ModRM; the trailing imm8 (half-select) goes after.
702 // This is the GHASH/GF(2^128) core -- the fast path for AES-GCM authentication.
703 if axc_tok_is(src, mn, mnl, "pclmulqdq") {
704 let pn: i64 = x86_sse_rr(out, 0, 0x66, 0x3a, 0x44, op2[OP_REG], op1[OP_REG])
705 out[pn] = (op0[OP_IMM] & 0xff) as u8
706 return pn + 1
707 }
708 // packed-int MULTIPLY (SSE2/SSE4.1) -- the compute core of ML/GEMM kernels
709 if axc_tok_is(src, mn, mnl, "pmulld") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x40, op1[OP_REG], op0[OP_REG]) }
710 if axc_tok_is(src, mn, mnl, "pmuludq") { return x86_sse_rr(out, 0, 0x66, 0, 0xf4, op1[OP_REG], op0[OP_REG]) }
711 if axc_tok_is(src, mn, mnl, "pmulhw") { return x86_sse_rr(out, 0, 0x66, 0, 0xe5, op1[OP_REG], op0[OP_REG]) }
712 if axc_tok_is(src, mn, mnl, "pmullw") { return x86_sse_rr(out, 0, 0x66, 0, 0xd5, op1[OP_REG], op0[OP_REG]) }
713 if axc_tok_is(src, mn, mnl, "pmaddwd") { return x86_sse_rr(out, 0, 0x66, 0, 0xf5, op1[OP_REG], op0[OP_REG]) }
714 // packed COMPARE (eq/gt) and MIN/MAX
715 if axc_tok_is(src, mn, mnl, "pcmpeqd") { return x86_sse_rr(out, 0, 0x66, 0, 0x76, op1[OP_REG], op0[OP_REG]) }
716 if axc_tok_is(src, mn, mnl, "pcmpgtd") { return x86_sse_rr(out, 0, 0x66, 0, 0x66, op1[OP_REG], op0[OP_REG]) }
717 if axc_tok_is(src, mn, mnl, "pminsd") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x39, op1[OP_REG], op0[OP_REG]) }
718 if axc_tok_is(src, mn, mnl, "pmaxsd") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x3d, op1[OP_REG], op0[OP_REG]) }
719 if axc_tok_is(src, mn, mnl, "pminub") { return x86_sse_rr(out, 0, 0x66, 0, 0xda, op1[OP_REG], op0[OP_REG]) }
720 if axc_tok_is(src, mn, mnl, "pmaxub") { return x86_sse_rr(out, 0, 0x66, 0, 0xde, op1[OP_REG], op0[OP_REG]) }
721 // packed SATURATING add/sub (b/w)
722 if axc_tok_is(src, mn, mnl, "paddsb") { return x86_sse_rr(out, 0, 0x66, 0, 0xec, op1[OP_REG], op0[OP_REG]) }
723 if axc_tok_is(src, mn, mnl, "paddusb") { return x86_sse_rr(out, 0, 0x66, 0, 0xdc, op1[OP_REG], op0[OP_REG]) }
724 if axc_tok_is(src, mn, mnl, "psubsb") { return x86_sse_rr(out, 0, 0x66, 0, 0xe8, op1[OP_REG], op0[OP_REG]) }
725 if axc_tok_is(src, mn, mnl, "psubusb") { return x86_sse_rr(out, 0, 0x66, 0, 0xd8, op1[OP_REG], op0[OP_REG]) }
726 // PACK (narrow) + UNPACK (interleave) + widening convert
727 if axc_tok_is(src, mn, mnl, "packssdw") { return x86_sse_rr(out, 0, 0x66, 0, 0x6b, op1[OP_REG], op0[OP_REG]) }
728 if axc_tok_is(src, mn, mnl, "packuswb") { return x86_sse_rr(out, 0, 0x66, 0, 0x67, op1[OP_REG], op0[OP_REG]) }
729 if axc_tok_is(src, mn, mnl, "punpckldq") { return x86_sse_rr(out, 0, 0x66, 0, 0x62, op1[OP_REG], op0[OP_REG]) }
730 if axc_tok_is(src, mn, mnl, "punpckhdq") { return x86_sse_rr(out, 0, 0x66, 0, 0x6a, op1[OP_REG], op0[OP_REG]) }
731 if axc_tok_is(src, mn, mnl, "pmovzxbw") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x30, op1[OP_REG], op0[OP_REG]) }
732 // absolute value, horizontal add, sum-of-abs-diff, byte-mask extract
733 if axc_tok_is(src, mn, mnl, "pabsd") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x1e, op1[OP_REG], op0[OP_REG]) }
734 if axc_tok_is(src, mn, mnl, "phaddd") { return x86_sse_rr(out, 0, 0x66, 0x38, 0x02, op1[OP_REG], op0[OP_REG]) }
735 if axc_tok_is(src, mn, mnl, "psadbw") { return x86_sse_rr(out, 0, 0x66, 0, 0xf6, op1[OP_REG], op0[OP_REG]) }
736 if axc_tok_is(src, mn, mnl, "pmovmskb") { return x86_sse_rr(out, 0, 0x66, 0, 0xd7, op1[OP_REG], op0[OP_REG]) }
737 return -1
738 }
739 if c0 == 114 { // 'r'
740 if axc_tok_is(src, mn, mnl, "ret") { return x86_ret(out, 0) }
741 if axc_tok_is(src, mn, mnl, "rdtsc") { return x86_rdtsc(out, 0) }
742 if axc_tok_is(src, mn, mnl, "rolq") {
743 if op0[OP_KIND] == K_IMM { return x86_rot_imm(out, 0, 0, op1[OP_REG], op0[OP_IMM]) }
744 return x86_rot_cl(out, 0, 0, op1[OP_REG])
745 }
746 if axc_tok_is(src, mn, mnl, "rorq") {
747 if op0[OP_KIND] == K_IMM { return x86_rot_imm(out, 0, 1, op1[OP_REG], op0[OP_IMM]) }
748 return x86_rot_cl(out, 0, 1, op1[OP_REG])
749 }
750 return -1
751 }
752 if c0 == 115 { // 's'
753 if axc_tok_is(src, mn, mnl, "syscall") { return x86_syscall(out, 0) }
754 // scalar float subtract + sqrt: subss/subsd F3/F2 0F 5C; sqrtss/sqrtsd F3/F2 0F 51.
755 if axc_tok_is(src, mn, mnl, "subss") { return x86_sse_rr(out, 0, 0xf3, 0, 0x5c, op1[OP_REG], op0[OP_REG]) }
756 if axc_tok_is(src, mn, mnl, "subsd") { return x86_sse_rr(out, 0, 0xf2, 0, 0x5c, op1[OP_REG], op0[OP_REG]) }
757 if axc_tok_is(src, mn, mnl, "sqrtss") { return x86_sse_rr(out, 0, 0xf3, 0, 0x51, op1[OP_REG], op0[OP_REG]) }
758 if axc_tok_is(src, mn, mnl, "sqrtsd") { return x86_sse_rr(out, 0, 0xf2, 0, 0x51, op1[OP_REG], op0[OP_REG]) }
759 // PACKED single-precision shuffle: shufps 0F C6 /r ib (3-operand $imm,%src,%dst) -- the broadcast/horizontal-sum primitive for packed f32 matmul.
760 if axc_tok_is(src, mn, mnl, "shufps") { return x86_sse_rri(out, 0, 0, 0xc6, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
761 // SHA-NI rounds (xmm,xmm; sha256rnds2 also uses xmm0 implicitly as the message)
762 if axc_tok_is(src, mn, mnl, "sha256rnds2") { return x86_sse_rr(out, 0, 0, 0x38, 0xcb, op1[OP_REG], op0[OP_REG]) }
763 if axc_tok_is(src, mn, mnl, "sha256msg1") { return x86_sse_rr(out, 0, 0, 0x38, 0xcc, op1[OP_REG], op0[OP_REG]) }
764 if axc_tok_is(src, mn, mnl, "sha256msg2") { return x86_sse_rr(out, 0, 0, 0x38, 0xcd, op1[OP_REG], op0[OP_REG]) }
765 if axc_tok_is(src, mn, mnl, "subq") { return axc_alu(out, op0, op1, X86_OP_SUB, X86_EXT_SUB) }
766 if axc_tok_is(src, mn, mnl, "shlq") {
767 if op0[OP_KIND] == K_IMM { return x86_shift_imm(out, 0, X86_EXT_SHL, op1[OP_REG], op0[OP_IMM]) }
768 return x86_shift_cl(out, 0, X86_EXT_SHL, op1[OP_REG])
769 }
770 if axc_tok_is(src, mn, mnl, "shrq") {
771 if op0[OP_KIND] == K_IMM { return x86_shift_imm(out, 0, X86_EXT_SHR, op1[OP_REG], op0[OP_IMM]) }
772 return x86_shift_cl(out, 0, X86_EXT_SHR, op1[OP_REG])
773 }
774 if axc_tok_is(src, mn, mnl, "sarq") {
775 if op0[OP_KIND] == K_IMM { return x86_shift_imm(out, 0, X86_EXT_SAR, op1[OP_REG], op0[OP_IMM]) }
776 return x86_shift_cl(out, 0, X86_EXT_SAR, op1[OP_REG])
777 }
778 let sc: i64 = axc_setcc_cc(src, mn, mnl)
779 if sc >= 0 { return x86_setcc(out, 0, sc, op0[OP_REG]) }
780 return -1
781 }
782 if c0 == 116 { // 't'
783 if axc_tok_is(src, mn, mnl, "testq") { return x86_alu_rr(out, 0, X86_OP_TEST, op1[OP_REG], op0[OP_REG]) }
784 if axc_tok_is(src, mn, mnl, "tzcntl") { return x86_tzcnt32(out, 0, op1[OP_REG], op0[OP_REG]) }
785 return -1
786 }
787 if c0 == 118 { // 'v' -- AVX/AVX2 (VEX-encoded, 256-bit ymm)
788 // 256-bit unaligned load/store: VEX.256.F3.0F 6F(load)/7F(store).
789 if axc_tok_is(src, mn, mnl, "vmovdqu") {
790 if op0[OP_KIND] == K_MEM { return x86_vex_rm(out, 0, 2, 1, 0, 1, 0x6f, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) }
791 if op1[OP_KIND] == K_MEM { return x86_vex_rm(out, 0, 2, 1, 0, 1, 0x7f, op0[OP_REG], op1[OP_BASE], op1[OP_DISP]) }
792 return -1
793 }
794 // 256-bit packed-int 3-operand ALU: VEX.256.66.0F <opc> (%src2,%src1,%dst).
795 if axc_tok_is(src, mn, mnl, "vpaddd") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xfe, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
796 if axc_tok_is(src, mn, mnl, "vpaddq") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xd4, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
797 if axc_tok_is(src, mn, mnl, "vpsubd") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xfa, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
798 if axc_tok_is(src, mn, mnl, "vpsubq") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xfb, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
799 if axc_tok_is(src, mn, mnl, "vpxor") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xef, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
800 if axc_tok_is(src, mn, mnl, "vpand") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xdb, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
801 if axc_tok_is(src, mn, mnl, "vpandn") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xdf, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
802 if axc_tok_is(src, mn, mnl, "vpor") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xeb, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
803 if axc_tok_is(src, mn, mnl, "vpshufb"){ return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x00, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
804 // 128-bit broadcast to both ymm lanes (VEX.256.66.0F38.W0 5A /r mem) -- OpenSSL's key-smash + mask-reload primitive
805 if axc_tok_is(src, mn, mnl, "vbroadcasti128") { if op0[OP_KIND] == K_MEM { return x86_vex_rm(out, 0, 1, 2, 0, 1, 0x5a, op1[OP_REG], op0[OP_BASE], op0[OP_DISP]) } return -1 }
806 // 256-bit packed multiply / compare / minmax (3-operand reg; 0F38 map=2 where noted)
807 if axc_tok_is(src, mn, mnl, "vpmulld") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x40, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
808 if axc_tok_is(src, mn, mnl, "vpmuludq") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xf4, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
809 if axc_tok_is(src, mn, mnl, "vpmaddwd") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0xf5, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
810 if axc_tok_is(src, mn, mnl, "vpcmpeqd") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0x76, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
811 if axc_tok_is(src, mn, mnl, "vpcmpgtd") { return x86_vex_rrr(out, 0, 1, 1, 0, 1, 0x66, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
812 if axc_tok_is(src, mn, mnl, "vpminsd") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x39, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
813 if axc_tok_is(src, mn, mnl, "vpmaxsd") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x3d, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
814 if axc_tok_is(src, mn, mnl, "vpermd") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x36, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
815 if axc_tok_is(src, mn, mnl, "vpsllvd") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x47, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
816 if axc_tok_is(src, mn, mnl, "vpsrlvd") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0x45, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
817 if axc_tok_is(src, mn, mnl, "vfmadd231ps") { return x86_vex_rrr(out, 0, 1, 2, 0, 1, 0xb8, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
818 if axc_tok_is(src, mn, mnl, "vmulps") { return x86_vex_rrr(out, 0, 0, 1, 0, 1, 0x59, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
819 if axc_tok_is(src, mn, mnl, "vaddps") { return x86_vex_rrr(out, 0, 0, 1, 0, 1, 0x58, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
820 if axc_tok_is(src, mn, mnl, "vsubps") { return x86_vex_rrr(out, 0, 0, 1, 0, 1, 0x5c, op2[OP_REG], op1[OP_REG], op0[OP_REG]) }
821 // 256-bit shift-by-immediate (NDD: $imm,%src,%dst) -- the AVX2 ARX rotate legs
822 if axc_tok_is(src, mn, mnl, "vpslld") { return x86_vex_shift_imm(out, 0, 1, 1, 1, 0x72, 6, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
823 if axc_tok_is(src, mn, mnl, "vpsrld") { return x86_vex_shift_imm(out, 0, 1, 1, 1, 0x72, 2, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
824 if axc_tok_is(src, mn, mnl, "vpsrad") { return x86_vex_shift_imm(out, 0, 1, 1, 1, 0x72, 4, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
825 if axc_tok_is(src, mn, mnl, "vpsllq") { return x86_vex_shift_imm(out, 0, 1, 1, 1, 0x73, 6, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
826 if axc_tok_is(src, mn, mnl, "vpsrlq") { return x86_vex_shift_imm(out, 0, 1, 1, 1, 0x73, 2, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
827 // 256-bit per-128-lane dword shuffle (imm,src,dst)
828 if axc_tok_is(src, mn, mnl, "vpshufd") { return x86_vex_rmi(out, 0, 1, 1, 0, 1, 0x70, op2[OP_REG], op1[OP_REG], op0[OP_IMM]) }
829 return -1
830 }
831 if c0 == 120 { // 'x'
832 if axc_tok_is(src, mn, mnl, "xorq") { return x86_alu_rr(out, 0, X86_OP_XOR, op1[OP_REG], op0[OP_REG]) }
833 // atomic exchange (xchg-with-memory is auto-locked): xchgq %reg, (%base) = 87 /r
834 if axc_tok_is(src, mn, mnl, "xchgq") { return x86_atomic_rm(out, 0, 0x87, op0[OP_REG], op1[OP_BASE]) }
835 // atomic fetch-and-add (lock prefix from the assemble loop): xaddq %reg, (%base) = 0F C1 /r
836 if axc_tok_is(src, mn, mnl, "xaddq") { return x86_atomic_rm(out, 1, 0xc1, op0[OP_REG], op1[OP_BASE]) }
837 return -1
838 }
839 return -1 // unrecognized mnemonic (caller treats as fatal)
840}
841
842// ---- emit OR count comma-separated `.byte` values; returns count ----
843// do_emit=1 writes each (low) byte to dest[dest_off + k]; do_emit=0 only counts.
844func axc_emit_bytes(src: *u8, start: i64, le: i64, dest: *u8, dest_off: i64, do_emit: i64) -> i64 {
845 var i: i64 = start
846 var cnt: i64 = 0
847 var run: i64 = 1
848 while i < le && run == 1 {
849 while i < le {
850 let c: i64 = src[i] & 0xff
851 if c == 32 { i = i + 1 } else { if c == 9 { i = i + 1 } else { if c == 44 { i = i + 1 } else { break } } }
852 }
853 if i >= le { run = 0 } else {
854 let c0: i64 = src[i] & 0xff
855 var isnum: i64 = 0
856 if c0 == 45 { isnum = 1 }
857 if axc_is_digit(c0) == 1 { isnum = 1 }
858 if isnum == 0 { run = 0 } else {
859 var neg: i64 = 0
860 if c0 == 45 { neg = 1; i = i + 1 }
861 var v: i64 = 0
862 while i < le {
863 let d: i64 = src[i] & 0xff
864 if axc_is_digit(d) == 1 { v = v * 10 + (d - 48); i = i + 1 } else { break }
865 }
866 if neg == 1 { v = 0 - v }
867 if do_emit == 1 { dest[dest_off + cnt] = v & 0xff }
868 cnt = cnt + 1
869 }
870 }
871 }
872 return cnt
873}
874
875// ---- map a section-changing directive to a section id (-1 if none) ----
876// 0=.text, 1=.rodata, 2=ignore (e.g. .note.GNU-stack, .data, .bss).
877func axc_apply_section(src: *u8, t0: i64, tlen: i64, j: i64, le: i64) -> i64 {
878 if axc_tok_is(src, t0, tlen, ".text") == 1 { return 0 }
879 if axc_tok_is(src, t0, tlen, ".section") == 1 {
880 var p: i64 = j
881 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
882 let s2: i64 = p
883 while p < le { if axc_is_ident(src[p]) == 1 { p = p + 1 } else { break } }
884 if axc_tok_is(src, s2, p - s2, ".rodata") == 1 { return 1 }
885 if axc_tok_is(src, s2, p - s2, ".text") == 1 { return 0 }
886 return 2
887 }
888 return -1
889}
890
891// ---- one pass over the source. pass 0 = size + record (section, local
892// offset) per label; pass 1 = emit text then rodata into `out`. text
893// occupies [0,text_size); rodata follows at [text_size, ...). Returns
894// text_size (pass 0) or total image length (pass 1), or a negative error.
895func axc_pass(src: *u8, n: i64, out: *u8, text_size: i64, pass: i64,
896 lab_off: *i64, lab_len: *i64, lab_addr: *i64, lab_sec: *i64, n_lab_box: *i64,
897 lh: *i64, op0: *i64, op1: *i64, op2: *i64, scratch: *u8, posbox: *i64) -> i64 {
898 var section: i64 = 0
899 var tcur: i64 = 0
900 var rcur: i64 = 0
901 var ls: i64 = 0
902 while ls < n {
903 var le: i64 = ls
904 while le < n { if (src[le] & 0xff) == 10 { break } else { le = le + 1 } }
905 var i: i64 = ls
906 while i < le { if axc_is_space(src[i]) == 1 { i = i + 1 } else { break } }
907 if i < le {
908 let c0: i64 = src[i] & 0xff
909 if c0 != 35 { // not '#'
910 let t0: i64 = i
911 var j: i64 = i
912 while j < le { if axc_is_ident(src[j]) == 1 { j = j + 1 } else { break } }
913 let tlen: i64 = j - t0
914 var is_label: i64 = 0
915 if j < le { if (src[j] & 0xff) == 58 { is_label = 1 } } // ':'
916 if is_label == 1 {
917 if pass == 0 {
918 let nl: i64 = n_lab_box[0]
919 if nl >= ASM_MAX_LABELS {
920 // LOUD-fail (PREVENT): never overflow the label tables into adjacent mmaps
921 sys_write(2, "nxasm_x86: label table FULL (ASM_MAX_LABELS) -- raise the cap\n" as *u8, 62)
922 sys_exit(101)
923 }
924 lab_off[nl] = t0
925 lab_len[nl] = tlen
926 if section == 1 { lab_sec[nl] = 1; lab_addr[nl] = rcur } else { lab_sec[nl] = 0; lab_addr[nl] = tcur }
927 n_lab_box[0] = nl + 1
928 }
929 } else {
930 if (src[t0] & 0xff) == 46 { // '.' -> directive
931 let ns: i64 = axc_apply_section(src, t0, tlen, j, le)
932 if ns >= 0 { section = ns }
933 if axc_tok_is(src, t0, tlen, ".byte") == 1 {
934 if section == 0 {
935 tcur = tcur + axc_emit_bytes(src, j, le, out, tcur, pass)
936 }
937 if section == 1 {
938 rcur = rcur + axc_emit_bytes(src, j, le, out, text_size + rcur, pass)
939 }
940 }
941 if axc_tok_is(src, t0, tlen, ".lcomm") == 1 {
942 // .lcomm NAME, SIZE -- zero-filled MUTABLE slot (the
943 // compiler emits these for zero-init globals). gas
944 // allocates BSS; our single-image model appends to the
945 // data tail, 8-aligned. Missing this directive left the
946 // label undefined -> lea resolved -1 -> SIGSEGV
947 // (NXASM-ENC-GAP root cause, TLS gate 2026-06-10).
948 var lp: i64 = j
949 while lp < le { if axc_is_space(src[lp]) == 1 { lp = lp + 1 } else { break } }
950 let lcn: i64 = lp
951 while lp < le { if axc_is_ident(src[lp]) == 1 { lp = lp + 1 } else { break } }
952 let lcl: i64 = lp - lcn
953 while lp < le {
954 if (src[lp] & 0xff) == 44 { lp = lp + 1 }
955 else { if axc_is_space(src[lp]) == 1 { lp = lp + 1 } else { break } }
956 }
957 posbox[0] = lp
958 let lcsz: i64 = axc_parse_int(src, posbox, le)
959 let lcpad: i64 = (8 - (rcur & 7)) & 7
960 if pass == 0 {
961 let nl2: i64 = n_lab_box[0]
962 if nl2 >= ASM_MAX_LABELS {
963 sys_write(2, "nxasm_x86: label table FULL (ASM_MAX_LABELS) -- raise the cap\n" as *u8, 62)
964 sys_exit(101)
965 }
966 lab_off[nl2] = lcn
967 lab_len[nl2] = lcl
968 lab_sec[nl2] = 1
969 lab_addr[nl2] = rcur + lcpad
970 n_lab_box[0] = nl2 + 1
971 }
972 if pass == 1 {
973 var lz: i64 = 0
974 while lz < lcpad + lcsz { out[text_size + rcur + lz] = 0; lz = lz + 1 }
975 }
976 rcur = rcur + lcpad + lcsz
977 }
978 } else { // instruction
979 op0[OP_KIND] = K_NONE
980 op1[OP_KIND] = K_NONE
981 op2[OP_KIND] = K_NONE
982 // LOCK prefix: `lock <insn>` -- emit F0, then encode <insn> with a RE-EXTRACTED mnemonic
983 // + operand start (so `lock cmpxchgq %rcx, (%r11)` reads cmpxchgq + its operands, not
984 // "lock" as the mnemonic). Only the atomic RMW ops (cmpxchg/xadd) carry it.
985 var emn: i64 = t0
986 var emnl: i64 = tlen
987 var estart: i64 = j
988 var lock_pfx: i64 = 0
989 if axc_tok_is(src, t0, tlen, "lock") == 1 {
990 lock_pfx = 1
991 var lq: i64 = j
992 while lq < le { if axc_is_space(src[lq]) == 1 { lq = lq + 1 } else { break } }
993 emn = lq
994 while lq < le { if axc_is_ident(src[lq]) == 1 { lq = lq + 1 } else { break } }
995 emnl = lq - emn
996 estart = lq
997 }
998 var p: i64 = estart
999 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
1000 if p < le {
1001 posbox[0] = p
1002 axc_parse_operand(src, posbox, le, op0)
1003 p = posbox[0]
1004 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
1005 if p < le {
1006 if (src[p] & 0xff) == 44 { // ',' -> op1
1007 p = p + 1
1008 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
1009 posbox[0] = p
1010 axc_parse_operand(src, posbox, le, op1)
1011 p = posbox[0]
1012 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
1013 if p < le {
1014 if (src[p] & 0xff) == 44 { // ',' -> op2 (3-operand: pshufd, VEX/EVEX)
1015 p = p + 1
1016 while p < le { if axc_is_space(src[p]) == 1 { p = p + 1 } else { break } }
1017 posbox[0] = p
1018 axc_parse_operand(src, posbox, le, op2)
1019 }
1020 }
1021 }
1022 }
1023 }
1024 var eoff: i64 = 0
1025 if lock_pfx == 1 { scratch[0] = 0xf0 as u8; eoff = 1 }
1026 let elen0: i64 = axc_emit(((scratch as i64) + eoff) as *u8, src, emn, emnl, op0, op1, op2,
1027 lab_off, lab_len, lab_addr, n_lab_box[0], lh, tcur + eoff, pass + 1)
1028 var elen: i64 = elen0
1029 if elen0 >= 0 { elen = elen0 + eoff }
1030 if elen < 0 {
1031 // LOUD-fail (PREVENT): a bare -1 cost a full bisect
1032 // per enc-gap (NXASM-ENC-GAP) -- name the line instead
1033 sys_write(2, "nxasm_x86: cannot encode: " as *u8, 26)
1034 sys_write(2, ((src as i64) + ls) as *u8, le - ls)
1035 sys_write(2, "\n" as *u8, 1)
1036 return 0 - 100
1037 }
1038 if pass == 1 {
1039 var kk: i64 = 0
1040 while kk < elen { out[tcur + kk] = scratch[kk]; kk = kk + 1 }
1041 }
1042 tcur = tcur + elen
1043 }
1044 }
1045 }
1046 }
1047 ls = le + 1
1048 }
1049 if pass == 0 { return tcur }
1050 return text_size + rcur
1051}
1052
1053// ---- assemble full AT&T source -> machine code in `out`. ----
1054// Returns total image length (text + rodata) or a negative error; writes
1055// the `_start` entry offset to p_entry. text then rodata in one image.
1056func nxasm_x86_assemble(src: *u8, n: i64, out: *u8, out_cap: i64, p_entry: *i64) -> i64 {
1057 let lab_off: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
1058 let lab_len: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
1059 let lab_addr: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
1060 let lab_sec: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
1061 let op0: *i64 = sys_mmap(72) as *i64 // 9 slots: +OP_INDEX(7)/OP_SCALE(8) for SIB
1062 let op1: *i64 = sys_mmap(72) as *i64
1063 let op2: *i64 = sys_mmap(72) as *i64
1064 let scratch: *u8 = sys_mmap(64)
1065 let posbox: *i64 = sys_mmap(16) as *i64
1066 let n_lab_box: *i64 = sys_mmap(16) as *i64
1067 n_lab_box[0] = 0
1068
1069 // O(1) label-lookup index (see axc_lh_build); pass 0 never finds, so
1070 // an empty table is fine there -- built for real after finalize.
1071 let lh: *i64 = sys_mmap(ASM_LH_SIZE * 8) as *i64
1072
1073 // pass 0: sizes + label (section, local offset)
1074 let text_size: i64 = axc_pass(src, n, out, 0, 0, lab_off, lab_len, lab_addr, lab_sec, n_lab_box,
1075 lh, op0, op1, op2, scratch, posbox)
1076 if text_size < 0 { return text_size }
1077 let n_lab: i64 = n_lab_box[0]
1078
1079 // finalize: rodata label abs = local + text_size ; locate _start entry
1080 var entry: i64 = 0
1081 var k: i64 = 0
1082 while k < n_lab {
1083 if lab_sec[k] == 1 { lab_addr[k] = lab_addr[k] + text_size }
1084 if axc_tok_is(src, lab_off[k], lab_len[k], "_start") == 1 { entry = lab_addr[k] }
1085 k = k + 1
1086 }
1087 axc_lh_build(src, lab_off, lab_len, n_lab, lh)
1088
1089 // pass 1: emit (resolving call/jmp/jcc/lea-rip against finalized labels)
1090 let total: i64 = axc_pass(src, n, out, text_size, 1, lab_off, lab_len, lab_addr, lab_sec, n_lab_box,
1091 lh, op0, op1, op2, scratch, posbox)
1092 if total < 0 { return total }
1093 if total > out_cap { return 0 - 200 }
1094 p_entry[0] = entry
1095 return total
1096}