code wiki / (root) / nxasm_x86.nx

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}