code wiki / (root) / nxasm_v2.nx

nxasm_v2.nx source

↩ module page · 1356 lines · 53128 B

1// nxasm_v2.nx -- the Nishi RV64 assembler, with labels + directives. 2// 3// Complete enough to assemble the .s files nxc2 emits for small 4// programs. Two-pass: 5// Pass 1: walk tokens, record labels and their addresses. 6// Pass 2: walk tokens, emit bytes to output buffer. 7// 8// Supports: 9// * Mnemonics: li, mv, ret, ecall, ld, sd, add, sub, addi, jalr 10// plus branches / jumps with label operands: j, jal, call, tail, 11// bnez, beqz. 12// * Directives: .text, .globl, .type, .size, .attribute, .option, 13// .section, .asciz, .byte. First four are ignored (metadata). 14// * Labels: `name:` at line start; referenced by jumps. 15// 16// Output is a raw byte buffer of code + rodata. An ELF wrapper 17// layer (see elf_writer.nx) adds headers to make it runnable. 18 19// Stderr logging routes through sys_write -- pull in the RV64 syscall 20// vocabulary that the assembler will run under (it produces RV64 ELFs 21// and is itself expected to execute on RV64 targets). 22import "../runtime/nx_syscalls.nx" 23 24// ===== encoder ===================================================== 25 26func enc_r(opcode: i64, funct3: i64, funct7: i64, 27 rd: i64, rs1: i64, rs2: i64) -> i64 { 28 return (funct7 << 25) | (rs2 << 20) | (rs1 << 15) 29 | (funct3 << 12) | (rd << 7) | opcode 30} 31 32func enc_i(opcode: i64, funct3: i64, 33 rd: i64, rs1: i64, imm: i64) -> i64 { 34 let imm12: i64 = imm & 0xFFF 35 return (imm12 << 20) | (rs1 << 15) | (funct3 << 12) 36 | (rd << 7) | opcode 37} 38 39func enc_s(opcode: i64, funct3: i64, 40 rs1: i64, rs2: i64, imm: i64) -> i64 { 41 let hi: i64 = (imm >> 5) & 0x7F 42 let lo: i64 = imm & 0x1F 43 return (hi << 25) | (rs2 << 20) | (rs1 << 15) 44 | (funct3 << 12) | (lo << 7) | opcode 45} 46 47func enc_u(opcode: i64, rd: i64, imm20: i64) -> i64 { 48 return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | opcode 49} 50 51// J-type with bit-scrambled 21-bit signed offset. 52func enc_j(opcode: i64, rd: i64, imm: i64) -> i64 { 53 let b20: i64 = (imm >> 20) & 1 54 let b10_1: i64 = (imm >> 1) & 0x3FF 55 let b11: i64 = (imm >> 11) & 1 56 let b19_12: i64 = (imm >> 12) & 0xFF 57 return (b20 << 31) | (b10_1 << 21) | (b11 << 20) 58 | (b19_12 << 12) | (rd << 7) | opcode 59} 60 61// B-type 13-bit signed offset. 62func enc_b(opcode: i64, funct3: i64, 63 rs1: i64, rs2: i64, imm: i64) -> i64 { 64 let b12: i64 = (imm >> 12) & 1 65 let b10_5: i64 = (imm >> 5) & 0x3F 66 let b4_1: i64 = (imm >> 1) & 0xF 67 let b11: i64 = (imm >> 11) & 1 68 return (b12 << 31) | (b10_5 << 25) | (rs2 << 20) | (rs1 << 15) 69 | (funct3 << 12) | (b4_1 << 8) | (b11 << 7) | opcode 70} 71 72// ===== tokens ======================================================= 73 74struct AsmTok { 75 kind: i64, 76 val: i64, 77 start: i64, 78 len: i64, 79} 80 81func asm_is_alpha(c: i64) -> i64 { 82 if c >= 0x41 { if c <= 0x5A { return 1 } } 83 if c >= 0x61 { if c <= 0x7A { return 1 } } 84 if c == 0x5F { return 1 } 85 if c == 0x2E { return 1 } 86 return 0 87} 88func asm_is_digit(c: i64) -> i64 { 89 if c >= 0x30 { if c <= 0x39 { return 1 } } 90 return 0 91} 92func is_hex(c: i64) -> i64 { 93 if asm_is_digit(c) { return 1 } 94 if c >= 0x41 { if c <= 0x46 { return 1 } } 95 if c >= 0x61 { if c <= 0x66 { return 1 } } 96 return 0 97} 98func hexv(c: i64) -> i64 { 99 if c >= 0x30 { if c <= 0x39 { return c - 0x30 } } 100 if c >= 0x41 { if c <= 0x46 { return c - 0x37 } } 101 if c >= 0x61 { if c <= 0x66 { return c - 0x57 } } 102 return 0 103} 104func is_space(c: i64) -> i64 { 105 if c == 0x20 { return 1 } 106 if c == 0x09 { return 1 } 107 if c == 0x0A { return 1 } 108 if c == 0x0D { return 1 } 109 return 0 110} 111 112func lex_one(src: *u8, pos: i64, len: i64, out: *AsmTok) -> i64 { 113 var done_ws: i64 = 0 114 while done_ws == 0 { 115 if pos >= len { done_ws = 1 } 116 if done_ws == 0 { 117 let c: i64 = src[pos] 118 if is_space(c) { pos = pos + 1 } 119 if is_space(c) == 0 { 120 if c == 0x23 { 121 var nl: i64 = 0 122 while nl == 0 { 123 if pos >= len { nl = 1 } 124 if nl == 0 { 125 if src[pos] == 0x0A { pos = pos + 1; nl = 1 } 126 if nl == 0 { pos = pos + 1 } 127 } 128 } 129 } 130 if c != 0x23 { done_ws = 1 } 131 } 132 } 133 } 134 if pos >= len { 135 out.kind = 0; out.start = pos; out.len = 0; out.val = 0 136 return pos 137 } 138 let c0: i64 = src[pos] 139 var sign: i64 = 1 140 if c0 == 0x2D { 141 if pos + 1 < len { 142 if asm_is_digit(src[pos + 1]) { sign = -1; pos = pos + 1 } 143 } 144 } 145 let c1: i64 = src[pos] 146 if asm_is_digit(c1) { 147 var v: i64 = 0 148 let s: i64 = pos 149 if c1 == 0x30 { 150 if pos + 1 < len { 151 if src[pos + 1] == 0x78 { 152 pos = pos + 2 153 while pos < len { 154 let d: i64 = src[pos] 155 if is_hex(d) == 0 { break } 156 v = (v << 4) | hexv(d) 157 pos = pos + 1 158 } 159 out.kind = 2; out.val = v * sign; out.start = s; out.len = pos - s 160 return pos 161 } 162 } 163 } 164 while pos < len { 165 let d: i64 = src[pos] 166 if asm_is_digit(d) == 0 { break } 167 v = v * 10 + (d - 0x30) 168 pos = pos + 1 169 } 170 out.kind = 2; out.val = v * sign; out.start = s; out.len = pos - s 171 return pos 172 } 173 if asm_is_alpha(c1) { 174 let s: i64 = pos 175 pos = pos + 1 176 while pos < len { 177 let d: i64 = src[pos] 178 if asm_is_alpha(d) == 0 { 179 if asm_is_digit(d) == 0 { break } 180 } 181 pos = pos + 1 182 } 183 out.kind = 1; out.val = pos - s; out.start = s; out.len = pos - s 184 return pos 185 } 186 if c1 == 0x22 { 187 let s: i64 = pos 188 pos = pos + 1 189 while pos < len { 190 if src[pos] == 0x22 { break } 191 pos = pos + 1 192 } 193 if pos < len { pos = pos + 1 } 194 out.kind = 4; out.val = pos - s; out.start = s; out.len = pos - s 195 return pos 196 } 197 out.kind = 3; out.val = c1; out.start = pos; out.len = 1 198 return pos + 1 199} 200 201// ===== register-name lookup ======================================== 202 203func s_eq2(src: *u8, off: i64, len: i64, a: i64, b: i64) -> i64 { 204 if len != 2 { return 0 } 205 if src[off] != a { return 0 } 206 if src[off + 1] != b { return 0 } 207 return 1 208} 209func s_eq3(src: *u8, off: i64, len: i64, a: i64, b: i64, c: i64) -> i64 { 210 if len != 3 { return 0 } 211 if src[off] != a { return 0 } 212 if src[off + 1] != b { return 0 } 213 if src[off + 2] != c { return 0 } 214 return 1 215} 216func s_eq4(src: *u8, off: i64, len: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 217 if len != 4 { return 0 } 218 if src[off] != a { return 0 } 219 if src[off + 1] != b { return 0 } 220 if src[off + 2] != c { return 0 } 221 if src[off + 3] != d { return 0 } 222 return 1 223} 224func s_eq5(src: *u8, off: i64, len: i64, a: i64, b: i64, c: i64, d: i64, e: i64) -> i64 { 225 if len != 5 { return 0 } 226 if src[off] != a { return 0 } 227 if src[off + 1] != b { return 0 } 228 if src[off + 2] != c { return 0 } 229 if src[off + 3] != d { return 0 } 230 if src[off + 4] != e { return 0 } 231 return 1 232} 233// Was: s_eq6(src, off, len, a, b, c, d, e, f) -- 9 args. The 234// self-host compiler's Instr struct only has 8 inline operand slots 235// (op0..op7); a 9-arg call falls through to bogus `mv a8, ...` 236// emission at codegen time (a8 isn't a real RV64 register). Blocked 237// Wheeler DDC self-self compile until refactored. 2026-05-16 fix: 238// take the 6 bytes as a string pointer + length so the call is 5 239// args, well under the ABI limit. Caller passes a string literal 240// like ".asciz" instead of 6 char codes. 241func s_eq6(src: *u8, off: i64, len: i64, pat: *u8, plen: i64) -> i64 { 242 if len != plen { return 0 } 243 var i: i64 = 0 244 while i < plen { 245 if src[off + i] != pat[i] { return 0 } 246 i = i + 1 247 } 248 return 1 249} 250 251func reg_lookup(src: *u8, off: i64, len: i64) -> i64 { 252 if len == 2 { 253 let a: i64 = src[off] 254 let b: i64 = src[off + 1] 255 if a == 0x61 { if b >= 0x30 { if b <= 0x37 { return 10 + (b - 0x30) } } } 256 if a == 0x74 { 257 if b >= 0x30 { if b <= 0x32 { return 5 + (b - 0x30) } } 258 if b >= 0x33 { if b <= 0x36 { return 28 + (b - 0x33) } } 259 } 260 if a == 0x73 { 261 if b == 0x30 { return 8 } 262 if b == 0x31 { return 9 } 263 if b >= 0x32 { if b <= 0x39 { return 18 + (b - 0x32) } } 264 if b == 0x70 { return 2 } 265 } 266 // Raw x-form: x0..x9 (Task #37). Kernel trap.S uses these 267 // directly instead of ABI names. Without this branch nxasm 268 // bails on `sd x1, 0(sp)` with misleading "bad mnemonic 'sd'". 269 if a == 0x78 { if b >= 0x30 { if b <= 0x39 { return b - 0x30 } } } 270 if s_eq2(src, off, len, 0x72, 0x61) { return 1 } 271 if s_eq2(src, off, len, 0x67, 0x70) { return 3 } 272 if s_eq2(src, off, len, 0x74, 0x70) { return 4 } 273 if s_eq2(src, off, len, 0x66, 0x70) { return 8 } 274 } 275 if len == 3 { 276 if s_eq3(src, off, len, 0x73, 0x31, 0x30) { return 26 } 277 if s_eq3(src, off, len, 0x73, 0x31, 0x31) { return 27 } 278 // Raw x-form: x10..x31 (Task #37). 279 let a3: i64 = src[off] 280 let b3: i64 = src[off + 1] 281 let c3: i64 = src[off + 2] 282 if a3 == 0x78 { 283 if b3 == 0x31 { // x10..x19 284 if c3 >= 0x30 { if c3 <= 0x39 { return 10 + (c3 - 0x30) } } 285 } 286 if b3 == 0x32 { // x20..x29 287 if c3 >= 0x30 { if c3 <= 0x39 { return 20 + (c3 - 0x30) } } 288 } 289 if b3 == 0x33 { // x30, x31 290 if c3 == 0x30 { return 30 } 291 if c3 == 0x31 { return 31 } 292 } 293 } 294 } 295 if len == 4 { 296 if s_eq4(src, off, len, 0x7A, 0x65, 0x72, 0x6F) { return 0 } 297 } 298 return -1 299} 300 301// ===== label table ================================================= 302// 303// Label array. The assembler walks tokens twice; pass 1 fills the 304// table with (name, addr), pass 2 consults it for offset resolution. 305 306struct Label { 307 name_start: i64, 308 name_len: i64, 309 addr: i64, 310 defined: i64, 311} 312 313struct Asm { 314 src: *u8, 315 src_len: i64, 316 pos: i64, // token cursor 317 cur_addr: i64, // address in the text section as we emit 318 pass: i64, // 1 or 2 319 out: *u8, // output byte buffer (pass 2) 320 out_pos: i64, // next byte to write 321 labels: *Label, 322 n_labels: i64, 323} 324 325func label_eq(src: *u8, a_off: i64, a_len: i64, 326 b_off: i64, b_len: i64) -> i64 { 327 if a_len != b_len { return 0 } 328 var i: i64 = 0 329 while i < a_len { 330 if src[a_off + i] != src[b_off + i] { return 0 } 331 i = i + 1 332 } 333 return 1 334} 335 336// Find a label by name; returns index or -1. 337func label_find(A: *Asm, name_off: i64, name_len: i64) -> i64 { 338 var i: i64 = 0 339 let base: i64 = A.labels as i64 340 while i < A.n_labels { 341 let lab: *Label = (base + i * 32) as *Label 342 if label_eq(A.src, lab.name_start, lab.name_len, name_off, name_len) { 343 return i 344 } 345 i = i + 1 346 } 347 return -1 348} 349 350// Define a label at the current address. 351// Bounds-checked against the pool size set in `assemble`. If we 352// overflow, print a loud message + return -1 so the caller can decide 353// (today: assemble_pass treats <0 as fatal). 354const LABELS_CAP: i64 = 8192 355 356func label_define(A: *Asm, name_off: i64, name_len: i64) -> i64 { 357 let idx: i64 = A.n_labels 358 if idx >= LABELS_CAP { 359 sys_write(2, "nxasm: label pool overflow (cap=" as *u8, 32) 360 sys_write(2, "8192)\n" as *u8, 6) 361 return -1 362 } 363 let base: i64 = A.labels as i64 364 let lab: *Label = (base + idx * 32) as *Label 365 lab.name_start = name_off 366 lab.name_len = name_len 367 lab.addr = A.cur_addr 368 lab.defined = 1 369 A.n_labels = idx + 1 370 return idx 371} 372 373// ===== byte emission helpers (pass 2 only) ========================== 374 375func emit_u32(A: *Asm, v: i64) -> i64 { 376 if A.pass == 2 { 377 let out: *u8 = A.out 378 let p: i64 = A.out_pos 379 out[p] = v & 0xFF 380 out[p + 1] = (v >> 8) & 0xFF 381 out[p + 2] = (v >> 16) & 0xFF 382 out[p + 3] = (v >> 24) & 0xFF 383 A.out_pos = p + 4 384 } 385 A.cur_addr = A.cur_addr + 4 386 return 0 387} 388 389func emit_byte(A: *Asm, v: i64) -> i64 { 390 if A.pass == 2 { 391 let out: *u8 = A.out 392 let p: i64 = A.out_pos 393 out[p] = v & 0xFF 394 A.out_pos = p + 1 395 } 396 A.cur_addr = A.cur_addr + 1 397 return 0 398} 399 400// ===== parser helpers ============================================= 401 402func next_tok(A: *Asm, t: *AsmTok) -> i64 { 403 A.pos = lex_one(A.src, A.pos, A.src_len, t) 404 return A.pos 405} 406func expect_punct(A: *Asm, t: *AsmTok, c: i64) -> i64 { 407 next_tok(A, t) 408 if t.kind != 3 { return 1 } 409 if t.val != c { return 2 } 410 return 0 411} 412func expect_reg(A: *Asm, t: *AsmTok) -> i64 { 413 next_tok(A, t) 414 if t.kind != 1 { return -1 } 415 return reg_lookup(A.src, t.start, t.len) 416} 417func expect_int(A: *Asm, t: *AsmTok, err: *i64) -> i64 { 418 next_tok(A, t) 419 if t.kind != 2 { 420 *err = 1 421 return 0 422 } 423 *err = 0 424 return t.val 425} 426 427// ===== instruction parsers (tokens after mnemonic) ================= 428 429func parse_rrr(A: *Asm, t: *AsmTok, 430 opcode: i64, funct3: i64, funct7: i64) -> i64 { 431 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 432 if expect_punct(A, t, 0x2C) != 0 { return -1 } 433 let rs1: i64 = expect_reg(A, t); if rs1 < 0 { return -1 } 434 if expect_punct(A, t, 0x2C) != 0 { return -1 } 435 let rs2: i64 = expect_reg(A, t); if rs2 < 0 { return -1 } 436 return enc_r(opcode, funct3, funct7, rd, rs1, rs2) 437} 438func parse_rri(A: *Asm, t: *AsmTok, opcode: i64, funct3: i64) -> i64 { 439 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 440 if expect_punct(A, t, 0x2C) != 0 { return -1 } 441 let rs1: i64 = expect_reg(A, t); if rs1 < 0 { return -1 } 442 if expect_punct(A, t, 0x2C) != 0 { return -1 } 443 var err: i64 = 0 444 let imm: i64 = expect_int(A, t, &err) 445 if err != 0 { return -1 } 446 return enc_i(opcode, funct3, rd, rs1, imm) 447} 448func parse_mem(A: *Asm, t: *AsmTok, is_store: i64, funct3: i64) -> i64 { 449 let first: i64 = expect_reg(A, t); if first < 0 { return -1 } 450 if expect_punct(A, t, 0x2C) != 0 { return -1 } 451 var err: i64 = 0 452 let imm: i64 = expect_int(A, t, &err); if err != 0 { return -1 } 453 if expect_punct(A, t, 0x28) != 0 { return -1 } 454 let second: i64 = expect_reg(A, t); if second < 0 { return -1 } 455 if expect_punct(A, t, 0x29) != 0 { return -1 } 456 if is_store { return enc_s(0x23, funct3, second, first, imm) } 457 return enc_i(0x03, funct3, first, second, imm) 458} 459// `li rd, imm` -- macro that expands to 1, 2, or 6 instructions 460// depending on the immediate width. The old form silently truncated 461// to 12 bits via enc_i's masking, mangling 64-bit literals like 462// 0x0001000100010001 (the SIMD smoke's packed-i16 pattern) into 463// a stored value of 1. Fixed 2026-05-16 after the silent-truncate 464// bug took 2 hours to bisect through the self-host SIMD pipeline. 465// 466// Expansion forms: 467// 12-bit signed : addi rd, zero, imm 468// 32-bit signed : lui rd, hi20 ; addi rd, rd, lo12 (sign-adjusted) 469// 64-bit any : lui rd, h32_hi20 ; addi rd, rd, h32_lo12 470// slli rd, rd, 32 471// lui t6, l32_hi20 ; addi t6, t6, l32_lo12 472// add rd, rd, t6 473// 474// The 64-bit form uses t6 (= x31) as scratch -- matches the 475// codegen-side scratch reservation used by nx_riscv.nx's 476// emit_sp_* helpers. 477func parse_li(A: *Asm, t: *AsmTok) -> i64 { 478 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 479 if expect_punct(A, t, 0x2C) != 0 { return -1 } 480 var err: i64 = 0 481 let imm: i64 = expect_int(A, t, &err); if err != 0 { return -1 } 482 // 12-bit signed fits in one addi. 483 if imm >= -2048 { 484 if imm <= 2047 { 485 return enc_i(0x13, 0, rd, 0, imm) 486 } 487 } 488 // Does the immediate fit in signed 32-bit? If so, use the 489 // 2-instruction lui+addi form. Otherwise fall through to the 490 // 8-instruction 64-bit construction. 491 var fits: i64 = 0 492 if imm >= (0 - 2147483648) { 493 if imm <= 2147483647 { 494 fits = 1 495 } 496 } 497 if fits == 1 { 498 let hi20: i64 = (imm + 0x800) >> 12 499 let lo12: i64 = imm - (hi20 << 12) 500 let lui_word: i64 = enc_u(0x37, rd, hi20) 501 emit_u32(A, lui_word) 502 if lo12 == 0 { 503 // Returning nop would still emit a u32; we want NO further 504 // instruction. Hack: return enc that the dispatcher emits, 505 // then deduct... Simpler: emit one safe addi rd, rd, 0 506 // which is `mv rd, rd` (no-op). Cheaper alternative: 507 // return the second emit ourselves and let dispatch emit 508 // a nop. Use the dispatcher's slot for the addi. 509 return enc_i(0x13, 0, rd, rd, 0) 510 } 511 return enc_i(0x13, 0, rd, rd, lo12) 512 } 513 // 64-bit case. Build via high32 << 32 | low32. 514 let hi32: i64 = (imm >> 32) & 0xFFFFFFFF 515 let lo32: i64 = imm & 0xFFFFFFFF 516 // Hi32 -- split into hi20 + lo12. Treat as signed 32-bit when 517 // computing the addi adjustment. 518 var hi32_s: i64 = hi32 519 if hi32_s >= 0x80000000 { hi32_s = hi32_s - 0x100000000 } 520 let hh20: i64 = (hi32_s + 0x800) >> 12 521 let hl12: i64 = hi32_s - (hh20 << 12) 522 // Lo32 -- same split. We need t6 to hold a value WHOSE low 32 523 // bits = lo32 and whose upper bits ZERO -- otherwise the `add` 524 // at the end would over-extend into the upper half. The 525 // canonical trick: shift up then down to zero-extend. Easier: 526 // use `srli` to clear upper bits before add. 527 var lo32_s: i64 = lo32 528 if lo32_s >= 0x80000000 { lo32_s = lo32_s - 0x100000000 } 529 let lh20: i64 = (lo32_s + 0x800) >> 12 530 let ll12: i64 = lo32_s - (lh20 << 12) 531 // Sequence: 532 // lui rd, hh20 533 // addi rd, rd, hl12 534 // slli rd, rd, 32 535 // lui t6, lh20 536 // addi t6, t6, ll12 537 // slli t6, t6, 32 ; srli t6, t6, 32 (zero-extend) 538 // add rd, rd, t6 539 emit_u32(A, enc_u(0x37, rd, hh20)) 540 emit_u32(A, enc_i(0x13, 0, rd, rd, hl12)) 541 emit_u32(A, enc_i(0x13, 1, rd, rd, 32)) // slli rd, rd, 32 542 emit_u32(A, enc_u(0x37, 31, lh20)) // lui t6, lh20 543 emit_u32(A, enc_i(0x13, 0, 31, 31, ll12)) // addi t6, t6, ll12 544 emit_u32(A, enc_i(0x13, 1, 31, 31, 32)) // slli t6, t6, 32 545 emit_u32(A, enc_i(0x13, 5, 31, 31, 32)) // srli t6, t6, 32 (zero-ext low32) 546 return enc_r(0x33, 0, 0, rd, rd, 31) // add rd, rd, t6 547} 548func parse_mv(A: *Asm, t: *AsmTok) -> i64 { 549 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 550 if expect_punct(A, t, 0x2C) != 0 { return -1 } 551 let rs: i64 = expect_reg(A, t); if rs < 0 { return -1 } 552 return enc_i(0x13, 0, rd, rs, 0) 553} 554// `srai rd, rs1, shamt` -- arithmetic right shift (sign-extending). 555// Distinct from srli (logical right shift, zero-fill) by funct7 556// bits 31:26 = 010000 (= 0x10 in the high 6 bits of the immediate 557// field, which becomes shamt's neighbor in the I-type encoding). 558// nxasm previously routed srai through the same parse_rri as srli, 559// silently turning `srai t6, t6, 48` into a logical shift -- which 560// is what made vreduce_min/max return UNSIGNED i16 values instead 561// of sign-extended i64 (2026-05-16, bisected from 562// _self_host_pack4_neg_test.nx returning 65537 instead of 1). 563func parse_srai(A: *Asm, t: *AsmTok) -> i64 { 564 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 565 if expect_punct(A, t, 0x2C) != 0 { return -1 } 566 let rs1: i64 = expect_reg(A, t); if rs1 < 0 { return -1 } 567 if expect_punct(A, t, 0x2C) != 0 { return -1 } 568 var err: i64 = 0 569 let shamt: i64 = expect_int(A, t, &err) 570 if err != 0 { return -1 } 571 // Encoding: bits 31:26 = 010000 (funct6 for srai in RV64), bits 572 // 25:20 = shamt (6 bits since XLEN=64). Combined imm field 573 // (bits 31:20) = 0x400 | (shamt & 0x3F). 574 let imm: i64 = 0x400 | (shamt & 0x3F) 575 return enc_i(0x13, 5, rd, rs1, imm) 576} 577 578// `j label` and `jal label` -- we parse just the label identifier, 579// look up its address, compute offset from cur_addr (pass 2 only), 580// and encode a JAL instruction. rd=0 for `j`, rd=1 for `jal` unless 581// a rd is given explicitly. 582func parse_j_label(A: *Asm, t: *AsmTok, rd: i64) -> i64 { 583 next_tok(A, t) 584 if t.kind != 1 { 585 sys_write(2, "nxasm:j: kind != 1\n" as *u8, 19) 586 return -1 587 } 588 if A.pass == 1 { 589 return enc_j(0x6F, rd, 0) // pass 1: offset unknown, any value 590 } 591 let idx: i64 = label_find(A, t.start, t.len) 592 if idx < 0 { 593 sys_write(2, "nxasm:j: label not found '" as *u8, 26) 594 var di: i64 = 0 595 while di < t.len { 596 sys_write(2, (((A.src as i64) + t.start + di) as *u8), 1) 597 di = di + 1 598 } 599 sys_write(2, "' n_labels=" as *u8, 11) 600 let dnbuf: *u8 = sys_mmap(16) 601 var dn: i64 = A.n_labels 602 var dk: i64 = 0 603 if dn == 0 { dnbuf[dk] = 0x30; dk = 1 } 604 while dn > 0 { 605 dnbuf[dk] = 0x30 + (dn - (dn / 10) * 10) 606 dn = dn / 10 607 dk = dk + 1 608 } 609 var dj: i64 = dk - 1 610 while dj >= 0 { 611 sys_write(2, (((dnbuf as i64) + dj) as *u8), 1) 612 dj = dj - 1 613 } 614 sys_write(2, "\n" as *u8, 1) 615 return -1 616 } 617 let base: i64 = A.labels as i64 618 let lab: *Label = (base + idx * 32) as *Label 619 let off: i64 = lab.addr - A.cur_addr 620 return enc_j(0x6F, rd, off) 621} 622 623// `bnez rs1, label` -> beq rs1, zero, label with funct3=1 (bne). 624// `beqz rs1, label` -> beq rs1, zero, label with funct3=0 (beq). 625func parse_branch_zero(A: *Asm, t: *AsmTok, funct3: i64) -> i64 { 626 let rs1: i64 = expect_reg(A, t); if rs1 < 0 { return -1 } 627 if expect_punct(A, t, 0x2C) != 0 { return -1 } 628 next_tok(A, t) 629 if t.kind != 1 { return -1 } 630 if A.pass == 1 { 631 return enc_b(0x63, funct3, rs1, 0, 0) 632 } 633 let idx: i64 = label_find(A, t.start, t.len) 634 if idx < 0 { return -1 } 635 let base: i64 = A.labels as i64 636 let lab: *Label = (base + idx * 32) as *Label 637 let off: i64 = lab.addr - A.cur_addr 638 return enc_b(0x63, funct3, rs1, 0, off) 639} 640 641func parse_ret(A: *Asm, t: *AsmTok) -> i64 { return enc_i(0x67, 0, 0, 1, 0) } 642func parse_ecall(A: *Asm, t: *AsmTok) -> i64 { return enc_i(0x73, 0, 0, 0, 0) } 643 644// B-type branch family (Task #36). Full 3-operand form: 645// beq/bne/blt/bge/bltu/bgeu rs1, rs2, label 646// funct3 selects op: 0=BEQ, 1=BNE, 4=BLT, 5=BGE, 6=BLTU, 7=BGEU. 647// Pass 1: emit a placeholder (offset=0) so label_define sees the size. 648// Pass 2: resolve label address and emit the real offset. 649func parse_branch(A: *Asm, t: *AsmTok, funct3: i64) -> i64 { 650 let rs1: i64 = expect_reg(A, t); if rs1 < 0 { return -1 } 651 if expect_punct(A, t, 0x2C) != 0 { return -1 } 652 let rs2: i64 = expect_reg(A, t); if rs2 < 0 { return -1 } 653 if expect_punct(A, t, 0x2C) != 0 { return -1 } 654 next_tok(A, t) 655 if t.kind != 1 { return -1 } 656 if A.pass == 1 { 657 return enc_b(0x63, funct3, rs1, rs2, 0) 658 } 659 let idx: i64 = label_find(A, t.start, t.len) 660 if idx < 0 { return -1 } 661 let base: i64 = A.labels as i64 662 let lab: *Label = (base + idx * 32) as *Label 663 let off: i64 = lab.addr - A.cur_addr 664 return enc_b(0x63, funct3, rs1, rs2, off) 665} 666 667// ===== CSR instructions (Task #35) ================================ 668// 669// CSR ops are I-type with opcode=0x73, funct3 selecting the op: 670// 001 CSRRW 010 CSRRS 011 CSRRC 671// 101 CSRRWI 110 CSRRSI 111 CSRRCI 672// Immediate field carries the 12-bit CSR address. 673// 674// Full form: `csrrw rd, csr, rs` -- 3 operands. 675// Pseudo: `csrr rd, csr` = csrrs rd, csr, x0 (read) 676// `csrw csr, rs` = csrrw x0, csr, rs (write) 677// `csrs csr, rs` = csrrs x0, csr, rs (set bits) 678// `csrc csr, rs` = csrrc x0, csr, rs (clear bits) 679// 680// CSR address may be a numeric literal (nxc2 emits 2816, 832 etc.) OR 681// a symbolic name (kernel .S files use mstatus, mhartid, etc.). 682 683// Read CSR address from next token: accepts either integer literal 684// or known symbolic name (mstatus / mhartid / mscratch / mepc / 685// mtvec / mcause / mtval / mie / mip / mcycle). Returns the 12-bit 686// CSR address; sets *err on unknown name or non-int/non-ident token. 687func expect_csr_or_int(A: *Asm, t: *AsmTok, err: *i64) -> i64 { 688 next_tok(A, t) 689 *err = 0 690 if t.kind == 2 { return t.val } 691 if t.kind == 1 { 692 let off: i64 = t.start 693 let n: i64 = t.len 694 if n == 3 { 695 if s_eq3(A.src, off, n, 0x6D, 0x69, 0x65) { return 0x304 } // mie 696 if s_eq3(A.src, off, n, 0x6D, 0x69, 0x70) { return 0x344 } // mip 697 } 698 if n == 4 { 699 if s_eq4(A.src, off, n, 0x6D, 0x65, 0x70, 0x63) { return 0x341 } // mepc 700 } 701 if n == 5 { 702 if s_eq5(A.src, off, n, 0x6D, 0x74, 0x76, 0x65, 0x63) { return 0x305 } // mtvec 703 if s_eq5(A.src, off, n, 0x6D, 0x74, 0x76, 0x61, 0x6C) { return 0x343 } // mtval 704 } 705 if n == 6 { 706 // mcause / mcycle (both length-6, common prefix "mc") 707 let mcause_pat: *u8 = "mcause" as *u8 708 if s_eq6(A.src, off, n, mcause_pat, 6) { return 0x342 } 709 let mcycle_pat: *u8 = "mcycle" as *u8 710 if s_eq6(A.src, off, n, mcycle_pat, 6) { return 0xB00 } 711 } 712 if n == 7 { 713 let mstatus_pat: *u8 = "mstatus" as *u8 714 if s_eq6(A.src, off, n, mstatus_pat, 7) { return 0x300 } 715 let mhartid_pat: *u8 = "mhartid" as *u8 716 if s_eq6(A.src, off, n, mhartid_pat, 7) { return 0xF14 } 717 } 718 if n == 8 { 719 let mscratch_pat: *u8 = "mscratch" as *u8 720 if s_eq6(A.src, off, n, mscratch_pat, 8) { return 0x340 } 721 } 722 } 723 *err = 1 724 return 0 725} 726 727// `csrrw rd, csr, rs` (3-operand form, used by csrrw/csrrs/csrrc). 728func parse_csr_full(A: *Asm, t: *AsmTok, funct3: i64) -> i64 { 729 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 730 if expect_punct(A, t, 0x2C) != 0 { return -1 } 731 var err: i64 = 0 732 let csr: i64 = expect_csr_or_int(A, t, &err); if err != 0 { return -1 } 733 if expect_punct(A, t, 0x2C) != 0 { return -1 } 734 let rs: i64 = expect_reg(A, t); if rs < 0 { return -1 } 735 return enc_i(0x73, funct3, rd, rs, csr & 0xFFF) 736} 737 738// `csrr rd, csr` = csrrs rd, csr, x0. 739func parse_csrr(A: *Asm, t: *AsmTok) -> i64 { 740 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 741 if expect_punct(A, t, 0x2C) != 0 { return -1 } 742 var err: i64 = 0 743 let csr: i64 = expect_csr_or_int(A, t, &err); if err != 0 { return -1 } 744 return enc_i(0x73, 2, rd, 0, csr & 0xFFF) 745} 746 747// `csrw csr, rs` = csrrw x0, csr, rs. Also covers csrs / csrc with 748// different funct3. 749func parse_csr_write(A: *Asm, t: *AsmTok, funct3: i64) -> i64 { 750 var err: i64 = 0 751 let csr: i64 = expect_csr_or_int(A, t, &err); if err != 0 { return -1 } 752 if expect_punct(A, t, 0x2C) != 0 { return -1 } 753 let rs: i64 = expect_reg(A, t); if rs < 0 { return -1 } 754 return enc_i(0x73, funct3, 0, rs, csr & 0xFFF) 755} 756 757// seqz rd, rs -- rd = (rs == 0)? Pseudo: sltiu rd, rs, 1. 758func parse_seqz(A: *Asm, t: *AsmTok) -> i64 { 759 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 760 if expect_punct(A, t, 0x2C) != 0 { return -1 } 761 let rs: i64 = expect_reg(A, t); if rs < 0 { return -1 } 762 return enc_i(0x13, 3, rd, rs, 1) // sltiu rd, rs, 1 763} 764 765// snez rd, rs -- rd = (rs != 0)? Pseudo: sltu rd, x0, rs. 766func parse_snez(A: *Asm, t: *AsmTok) -> i64 { 767 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 768 if expect_punct(A, t, 0x2C) != 0 { return -1 } 769 let rs: i64 = expect_reg(A, t); if rs < 0 { return -1 } 770 return enc_r(0x33, 3, 0, rd, 0, rs) // sltu rd, x0, rs 771} 772 773// `la rd, sym` -- two-instruction expansion: 774// auipc rd, (sym_hi20) 775// addi rd, rd, (sym_lo12) 776// emit_u32 the first word inside, return the second so the caller 777// emits it normally. pass-1: advance cur_addr by 8. 778func parse_la(A: *Asm, t: *AsmTok) -> i64 { 779 let rd: i64 = expect_reg(A, t); if rd < 0 { return -1 } 780 if expect_punct(A, t, 0x2C) != 0 { return -1 } 781 next_tok(A, t) 782 if t.kind != 1 { return -1 } 783 var tgt: i64 = 0 784 if A.pass == 2 { 785 let idx: i64 = label_find(A, t.start, t.len) 786 if idx < 0 { 787 // Task #39 diagnostic: name the missing label so the 788 // dispatcher's "bad mnemonic" message points the user at 789 // the actual problem (label resolution, not parsing). 790 sys_write(2, "nxasm:la label-not-found '" as *u8, 26) 791 var di: i64 = 0 792 while di < t.len { 793 sys_write(2, (((A.src as i64) + t.start + di) as *u8), 1) 794 di = di + 1 795 } 796 sys_write(2, "'\n" as *u8, 2) 797 return -1 798 } 799 let base: i64 = A.labels as i64 800 let lab: *Label = (base + idx * 32) as *Label 801 tgt = lab.addr - A.cur_addr 802 } 803 // Split into hi20 + lo12 with proper sign handling: lo12 is 804 // sign-extended by the addi, so pre-add 0x800 to tgt before 805 // taking the hi20 if the low 12 bits are negative-signed. 806 var hi: i64 = (tgt + 0x800) >> 12 807 let lo: i64 = tgt - (hi << 12) 808 let auipc_word: i64 = enc_u(0x17, rd, hi) 809 emit_u32(A, auipc_word) 810 // Return the addi; caller emits it. 811 return enc_i(0x13, 0, rd, rd, lo) 812} 813 814// `tail sym` -- tail call: auipc x6, hi; jalr x0, lo(x6). 815// We follow RISC-V ABI: t1 (x6) is the staging register. 816func parse_tail(A: *Asm, t: *AsmTok) -> i64 { 817 next_tok(A, t) 818 if t.kind != 1 { return -1 } 819 var tgt: i64 = 0 820 if A.pass == 2 { 821 let idx: i64 = label_find(A, t.start, t.len) 822 if idx < 0 { return -1 } 823 let base: i64 = A.labels as i64 824 let lab: *Label = (base + idx * 32) as *Label 825 tgt = lab.addr - A.cur_addr 826 } 827 var hi: i64 = (tgt + 0x800) >> 12 828 let lo: i64 = tgt - (hi << 12) 829 let auipc_word: i64 = enc_u(0x17, 6, hi) // auipc t1, hi 830 emit_u32(A, auipc_word) 831 return enc_i(0x67, 0, 0, 6, lo) // jalr x0, t1, lo 832} 833 834// ===== directive handlers ========================================== 835// 836// Only those nxc2 emits; others fall through to a no-op dispatcher. 837 838// `.byte N [, N ...]` 839func handle_byte(A: *Asm, t: *AsmTok) -> i64 { 840 var more: i64 = 1 841 while more { 842 var err: i64 = 0 843 let v: i64 = expect_int(A, t, &err) 844 if err != 0 { return -1 } 845 emit_byte(A, v) 846 // Peek next token; if it's a comma, consume and continue. 847 let save: i64 = A.pos 848 next_tok(A, t) 849 if t.kind == 3 { 850 if t.val == 0x2C { 851 // comma -- keep going 852 more = 1 853 } 854 if t.val != 0x2C { 855 A.pos = save 856 more = 0 857 } 858 } 859 if t.kind != 3 { 860 A.pos = save 861 more = 0 862 } 863 } 864 return 0 865} 866 867// `.asciz "..."` -- emit the string bytes + a trailing NUL, with 868// gas-style escape processing: \n -> 0x0A, \t -> 0x09, \r -> 0x0D, 869// \\ -> 0x5C, \" -> 0x22, \0 -> 0x00, \ooo -> octal byte. Without 870// this, a `.asciz "hi\n"` emitted by nxc.nx's globals dump would 871// land as 4 literal bytes h, i, \, n -- and sys_write(1, p, 3) would 872// print "hi\\". Closes nxasm side of T#selfhost-003. 873// `.skip N` / `.zero N` -- emit N zero bytes. Used for BSS-style 874// reservations (trap.S allocates 8 KiB trap stack; nxc2-emitted .s 875// uses .zero N for static array reservations). Without this nxasm 876// silently skipped these directives and labels following them landed 877// at the WRONG cur_addr. Closes Phase F1 structural blocker class 878// for single-segment kernel builds. 879func handle_skip(A: *Asm, t: *AsmTok) -> i64 { 880 var err: i64 = 0 881 let n: i64 = expect_int(A, t, &err) 882 if err != 0 { return -1 } 883 if n <= 0 { return 0 } 884 var i: i64 = 0 885 while i < n { 886 emit_byte(A, 0) 887 i = i + 1 888 } 889 return 0 890} 891 892func handle_asciz(A: *Asm, t: *AsmTok) -> i64 { 893 next_tok(A, t) 894 if t.kind != 4 { return -1 } 895 // Walk t.start+1 .. t.start+t.len-1 (skip surrounding quotes). 896 var i: i64 = 1 897 let end: i64 = t.len - 1 898 while i < end { 899 let c: i64 = A.src[t.start + i] 900 if c == 0x5C { 901 if i + 1 < end { 902 let nx: i64 = A.src[t.start + i + 1] 903 if nx == 0x6E { emit_byte(A, 0x0A); i = i + 2; continue } // \n 904 if nx == 0x74 { emit_byte(A, 0x09); i = i + 2; continue } // \t 905 if nx == 0x72 { emit_byte(A, 0x0D); i = i + 2; continue } // \r 906 if nx == 0x5C { emit_byte(A, 0x5C); i = i + 2; continue } // \\ 907 if nx == 0x22 { emit_byte(A, 0x22); i = i + 2; continue } // \" 908 if nx == 0x30 { 909 // \0 (single char, NOT followed by more octal digits) 910 // OR \ooo (three octal digits). Distinguish by 911 // looking at the next two characters. 912 if i + 3 < end { 913 let o2: i64 = A.src[t.start + i + 2] 914 let o3: i64 = A.src[t.start + i + 3] 915 if o2 >= 0x30 { 916 if o2 <= 0x37 { 917 if o3 >= 0x30 { 918 if o3 <= 0x37 { 919 let b: i64 = ((nx - 0x30) << 6) | 920 ((o2 - 0x30) << 3) | 921 (o3 - 0x30) 922 emit_byte(A, b) 923 i = i + 4 924 continue 925 } 926 } 927 } 928 } 929 } 930 emit_byte(A, 0x00); i = i + 2; continue 931 } 932 // \ooo for 1..3 -- treat first digit as octal escape. 933 if nx >= 0x31 { 934 if nx <= 0x37 { 935 if i + 3 < end { 936 let o2: i64 = A.src[t.start + i + 2] 937 let o3: i64 = A.src[t.start + i + 3] 938 if o2 >= 0x30 { 939 if o2 <= 0x37 { 940 if o3 >= 0x30 { 941 if o3 <= 0x37 { 942 let b: i64 = ((nx - 0x30) << 6) | 943 ((o2 - 0x30) << 3) | 944 (o3 - 0x30) 945 emit_byte(A, b) 946 i = i + 4 947 continue 948 } 949 } 950 } 951 } 952 } 953 } 954 } 955 // Unknown escape: pass through both bytes (gas warns; 956 // we keep the backslash + literal char). 957 emit_byte(A, c) 958 emit_byte(A, nx) 959 i = i + 2 960 continue 961 } 962 } 963 emit_byte(A, c) 964 i = i + 1 965 } 966 emit_byte(A, 0) 967 return 0 968} 969 970// `.word N` -- emit one 32-bit little-endian word. Used by 971// rv_emit_simd_vdot_i16 (and future SIMD/SIMT lowerings) to emit 972// pre-encoded RVV instructions without needing a full vector 973// mnemonic parser in nxasm. Bits-up sovereign path: nx_riscv.nx 974// pre-computes the 32-bit RVV encoding (substituting in the 975// run-time register IDs) and hands the assembler an opaque word. 976// 977// Supports multiple comma-separated words on one line, matching the 978// `.byte` shape, so the codegen can pack a whole RVV chain into a 979// single directive if desired. 980func handle_word(A: *Asm, t: *AsmTok) -> i64 { 981 var more: i64 = 1 982 while more { 983 var err: i64 = 0 984 let v: i64 = expect_int(A, t, &err) 985 if err != 0 { return -1 } 986 emit_u32(A, v) 987 let save: i64 = A.pos 988 next_tok(A, t) 989 if t.kind == 3 { 990 if t.val == 0x2C { more = 1 } 991 if t.val != 0x2C { A.pos = save; more = 0 } 992 } 993 if t.kind != 3 { A.pos = save; more = 0 } 994 } 995 return 0 996} 997 998// Ignored directive: eat the rest of the line (everything till \n). 999// Detects via tokens already having different lines; simpler: eat 1000// tokens until we hit a token whose start column is after a newline 1001// in source. Crudest but works: consume tokens while the next char 1002// in src is NOT a newline. 1003func skip_to_newline(A: *Asm) -> i64 { 1004 while A.pos < A.src_len { 1005 let c: i64 = A.src[A.pos] 1006 if c == 0x0A { return 0 } 1007 A.pos = A.pos + 1 1008 } 1009 return 0 1010} 1011 1012// ===== mnemonic dispatch ============================================ 1013 1014func assemble_mnemonic(A: *Asm, t: *AsmTok, off: i64, n: i64) -> i64 { 1015 // 1-char mnemonics: "j label" 1016 if n == 1 { 1017 if A.src[off] == 0x6A { return parse_j_label(A, t, 0) } 1018 } 1019 if n == 2 { 1020 if s_eq2(A.src, off, n, 0x6C, 0x69) { return parse_li(A, t) } // li 1021 if s_eq2(A.src, off, n, 0x6D, 0x76) { return parse_mv(A, t) } // mv 1022 if s_eq2(A.src, off, n, 0x6C, 0x64) { return parse_mem(A, t, 0, 3) } // ld 1023 if s_eq2(A.src, off, n, 0x73, 0x64) { return parse_mem(A, t, 1, 3) } // sd 1024 if s_eq2(A.src, off, n, 0x73, 0x62) { return parse_mem(A, t, 1, 0) } // sb (store byte) 1025 if s_eq2(A.src, off, n, 0x73, 0x68) { return parse_mem(A, t, 1, 1) } // sh (store halfword) 1026 if s_eq2(A.src, off, n, 0x73, 0x77) { return parse_mem(A, t, 1, 2) } // sw (store word) 1027 if s_eq2(A.src, off, n, 0x6C, 0x61) { return parse_la(A, t) } // la 1028 if s_eq2(A.src, off, n, 0x6F, 0x72) { return parse_rrr(A, t, 0x33, 6, 0) } // or 1029 } 1030 if n == 3 { 1031 if s_eq3(A.src, off, n, 0x72, 0x65, 0x74) { return parse_ret(A, t) } 1032 if s_eq3(A.src, off, n, 0x61, 0x64, 0x64) { return parse_rrr(A, t, 0x33, 0, 0) } // add 1033 if s_eq3(A.src, off, n, 0x73, 0x75, 0x62) { return parse_rrr(A, t, 0x33, 0, 0x20) } // sub 1034 if s_eq3(A.src, off, n, 0x6A, 0x61, 0x6C) { return parse_j_label(A, t, 1) } // jal 1035 if s_eq3(A.src, off, n, 0x61, 0x6E, 0x64) { return parse_rrr(A, t, 0x33, 7, 0) } // and 1036 if s_eq3(A.src, off, n, 0x78, 0x6F, 0x72) { return parse_rrr(A, t, 0x33, 4, 0) } // xor 1037 if s_eq3(A.src, off, n, 0x6D, 0x75, 0x6C) { return parse_rrr(A, t, 0x33, 0, 1) } // mul (M-ext) 1038 if s_eq3(A.src, off, n, 0x64, 0x69, 0x76) { return parse_rrr(A, t, 0x33, 4, 1) } // div 1039 if s_eq3(A.src, off, n, 0x72, 0x65, 0x6D) { return parse_rrr(A, t, 0x33, 6, 1) } // rem 1040 if s_eq3(A.src, off, n, 0x73, 0x6C, 0x6C) { return parse_rrr(A, t, 0x33, 1, 0) } // sll 1041 if s_eq3(A.src, off, n, 0x73, 0x72, 0x6C) { return parse_rrr(A, t, 0x33, 5, 0) } // srl 1042 if s_eq3(A.src, off, n, 0x73, 0x72, 0x61) { return parse_rrr(A, t, 0x33, 5, 0x20) } // sra 1043 if s_eq3(A.src, off, n, 0x73, 0x6C, 0x74) { return parse_rrr(A, t, 0x33, 2, 0) } // slt 1044 if s_eq3(A.src, off, n, 0x6C, 0x77, 0x75) { return parse_mem(A, t, 0, 6) } // lwu 1045 if s_eq3(A.src, off, n, 0x6E, 0x6F, 0x70) { return enc_i(0x13, 0, 0, 0, 0) } // nop 1046 // B-type branches (Task #36). funct3: 0=beq, 1=bne, 4=blt, 5=bge. 1047 if s_eq3(A.src, off, n, 0x62, 0x65, 0x71) { return parse_branch(A, t, 0) } // beq 1048 if s_eq3(A.src, off, n, 0x62, 0x6E, 0x65) { return parse_branch(A, t, 1) } // bne 1049 if s_eq3(A.src, off, n, 0x62, 0x6C, 0x74) { return parse_branch(A, t, 4) } // blt 1050 if s_eq3(A.src, off, n, 0x62, 0x67, 0x65) { return parse_branch(A, t, 5) } // bge 1051 } 1052 if n == 4 { 1053 if s_eq4(A.src, off, n, 0x61, 0x64, 0x64, 0x69) { return parse_rri(A, t, 0x13, 0) } // addi 1054 if s_eq4(A.src, off, n, 0x62, 0x6E, 0x65, 0x7A) { return parse_branch_zero(A, t, 1) } // bnez 1055 if s_eq4(A.src, off, n, 0x62, 0x65, 0x71, 0x7A) { return parse_branch_zero(A, t, 0) } // beqz 1056 if s_eq4(A.src, off, n, 0x6A, 0x61, 0x6C, 0x72) { return parse_rri(A, t, 0x67, 0) } // jalr 1057 if s_eq4(A.src, off, n, 0x63, 0x61, 0x6C, 0x6C) { return parse_j_label(A, t, 1) } // call 1058 if s_eq4(A.src, off, n, 0x74, 0x61, 0x69, 0x6C) { return parse_tail(A, t) } // tail 1059 if s_eq4(A.src, off, n, 0x73, 0x65, 0x71, 0x7A) { return parse_seqz(A, t) } // seqz 1060 if s_eq4(A.src, off, n, 0x73, 0x6E, 0x65, 0x7A) { return parse_snez(A, t) } // snez 1061 if s_eq4(A.src, off, n, 0x78, 0x6F, 0x72, 0x69) { return parse_rri(A, t, 0x13, 4) } // xori 1062 if s_eq4(A.src, off, n, 0x61, 0x6E, 0x64, 0x69) { return parse_rri(A, t, 0x13, 7) } // andi 1063 if s_eq4(A.src, off, n, 0x73, 0x6C, 0x6C, 0x69) { return parse_rri(A, t, 0x13, 1) } // slli 1064 if s_eq4(A.src, off, n, 0x73, 0x72, 0x6C, 0x69) { return parse_rri(A, t, 0x13, 5) } // srli 1065 if s_eq4(A.src, off, n, 0x73, 0x72, 0x61, 0x69) { return parse_srai(A, t) } // srai -- funct6=0x10 1066 if s_eq4(A.src, off, n, 0x6C, 0x62, 0x75, 0x20) { return parse_mem(A, t, 0, 4) } // lbu (trailing space) 1067 // CSR pseudo-ops (Task #35). csr*=0x63 0x73 0x72 prefix. 1068 if s_eq4(A.src, off, n, 0x63, 0x73, 0x72, 0x72) { return parse_csrr(A, t) } // csrr rd, csr 1069 if s_eq4(A.src, off, n, 0x63, 0x73, 0x72, 0x77) { return parse_csr_write(A, t, 1) } // csrw csr, rs 1070 if s_eq4(A.src, off, n, 0x63, 0x73, 0x72, 0x73) { return parse_csr_write(A, t, 2) } // csrs csr, rs 1071 if s_eq4(A.src, off, n, 0x63, 0x73, 0x72, 0x63) { return parse_csr_write(A, t, 3) } // csrc csr, rs 1072 // B-type 4-letter mnemonics (Task #36). bltu=6, bgeu=7. 1073 if s_eq4(A.src, off, n, 0x62, 0x6C, 0x74, 0x75) { return parse_branch(A, t, 6) } // bltu 1074 if s_eq4(A.src, off, n, 0x62, 0x67, 0x65, 0x75) { return parse_branch(A, t, 7) } // bgeu 1075 } 1076 if n == 3 { 1077 // Fall-back 3-char mnemonics that start with 'l': lb, lh, lw, sh, sw 1078 // (duplicate table so length-3 loads land here after early matches). 1079 } 1080 if n == 5 { 1081 if s_eq5(A.src, off, n, 0x65, 0x63, 0x61, 0x6C, 0x6C) { return parse_ecall(A, t) } // ecall 1082 // CSR full forms (3-operand: rd, csr, rs). 1083 if s_eq5(A.src, off, n, 0x63, 0x73, 0x72, 0x72, 0x77) { return parse_csr_full(A, t, 1) } // csrrw 1084 if s_eq5(A.src, off, n, 0x63, 0x73, 0x72, 0x72, 0x73) { return parse_csr_full(A, t, 2) } // csrrs 1085 if s_eq5(A.src, off, n, 0x63, 0x73, 0x72, 0x72, 0x63) { return parse_csr_full(A, t, 3) } // csrrc 1086 } 1087 // System instructions (no operands): wfi, mret, sret, ebreak. 1088 if n == 3 { 1089 if s_eq3(A.src, off, n, 0x77, 0x66, 0x69) { return enc_i(0x73, 0, 0, 0, 0x105) } // wfi 1090 } 1091 if n == 4 { 1092 if s_eq4(A.src, off, n, 0x6D, 0x72, 0x65, 0x74) { return enc_i(0x73, 0, 0, 0, 0x302) } // mret 1093 if s_eq4(A.src, off, n, 0x73, 0x72, 0x65, 0x74) { return enc_i(0x73, 0, 0, 0, 0x102) } // sret 1094 } 1095 if n == 6 { 1096 let ebreak_pat: *u8 = "ebreak" as *u8 1097 if s_eq6(A.src, off, n, ebreak_pat, 6) { return enc_i(0x73, 0, 0, 0, 1) } // ebreak 1098 } 1099 // Short load/store dispatch (length 2-3, final byte 'b'/'h'/'w'/'u'). 1100 if n == 2 { 1101 if A.src[off] == 0x6C { 1102 if A.src[off+1] == 0x62 { return parse_mem(A, t, 0, 0) } // lb 1103 if A.src[off+1] == 0x68 { return parse_mem(A, t, 0, 1) } // lh 1104 if A.src[off+1] == 0x77 { return parse_mem(A, t, 0, 2) } // lw 1105 } 1106 } 1107 if n == 3 { 1108 if s_eq3(A.src, off, n, 0x6C, 0x62, 0x75) { return parse_mem(A, t, 0, 4) } // lbu 1109 if s_eq3(A.src, off, n, 0x6C, 0x68, 0x75) { return parse_mem(A, t, 0, 5) } // lhu 1110 } 1111 return -1 1112} 1113 1114// ===== main assembly loop =========================================== 1115// 1116// Walk tokens line-by-line. Each line is either: 1117// * IDENT ":" -> label definition 1118// * IDENT starting with "." -> directive 1119// * IDENT otherwise -> mnemonic 1120// After handling, we skip rest-of-line (any remaining tokens) and 1121// move to next line. 1122 1123// Forward-decl: alloc is defined after assemble_pass. 1124func alloc(size: i64) -> *u8; 1125 1126func assemble_pass(A: *Asm, t: *AsmTok) -> i64 { 1127 A.pos = 0 1128 A.cur_addr = 0 1129 A.out_pos = 0 1130 var err: i64 = 0 1131 while err == 0 { 1132 next_tok(A, t) 1133 if t.kind == 0 { return 0 } 1134 if t.kind != 1 { 1135 // Unexpected leading token -- skip to newline and retry. 1136 skip_to_newline(A) 1137 continue 1138 } 1139 let off: i64 = t.start 1140 let n: i64 = t.len 1141 1142 // Label? Scan forward in src (NOT via lex_one, whose 1143 // token-struct pointer-write path has proved fragile). 1144 // Skip whitespace/newlines from A.pos; if the next non-ws 1145 // char is ':', we have a label. We then advance past the 1146 // ':' too. 1147 var is_label: i64 = 0 1148 var scan: i64 = A.pos 1149 while scan < A.src_len { 1150 let sc: i64 = A.src[scan] 1151 if sc == 0x20 { scan = scan + 1; continue } 1152 if sc == 0x09 { scan = scan + 1; continue } 1153 if sc == 0x0D { scan = scan + 1; continue } 1154 if sc == 0x3A { 1155 is_label = 1 1156 scan = scan + 1 1157 } 1158 // Note: we do NOT skip '\n' -- a ':' on a NEW line is 1159 // not part of this identifier's label. 1160 break 1161 } 1162 if is_label == 1 { 1163 if A.pass == 1 { 1164 label_define(A, off, n) 1165 } 1166 A.pos = scan 1167 continue 1168 } 1169 1170 // Directive? First char is '.' (ASCII 0x2E). 1171 // 1172 // Minimal v1 handling: swallow rest of line, regardless of 1173 // which directive. .byte / .asciz / etc are not emitted by 1174 // riscv.nx for the self-host pipeline today; when we need 1175 // them for string literals later, add per-directive handlers 1176 // here. Flag-and-fallthrough avoids the `continue`-inside- 1177 // nested-if pattern that NishiLang v1 lowers incorrectly. 1178 var is_directive: i64 = 0 1179 if A.src[off] == 0x2E { 1180 is_directive = 1 1181 // Dispatch the directives we actually emit; ignore others. 1182 // `.word N[, N...]` -- 32-bit raw word (SIMD/SIMT pre- 1183 // encoded instructions; lets nx_riscv.nx ship RVV without 1184 // requiring nxasm to know every vector mnemonic). 1185 // `.byte N[, N...]` and `.asciz "..."` -- globals dump. 1186 // Anything else (.text, .globl, .section, .cfi_*, .type, 1187 // .size, .option) -- skip to newline. 1188 if n == 5 { 1189 if s_eq5(A.src, off, n, 0x2E, 0x77, 0x6F, 0x72, 0x64) { 1190 handle_word(A, t) 1191 continue 1192 } 1193 if s_eq5(A.src, off, n, 0x2E, 0x62, 0x79, 0x74, 0x65) { 1194 handle_byte(A, t) 1195 continue 1196 } 1197 if s_eq5(A.src, off, n, 0x2E, 0x73, 0x6B, 0x69, 0x70) { 1198 handle_skip(A, t) // .skip N 1199 continue 1200 } 1201 if s_eq5(A.src, off, n, 0x2E, 0x7A, 0x65, 0x72, 0x6F) { 1202 handle_skip(A, t) // .zero N (same semantics as .skip) 1203 continue 1204 } 1205 } 1206 if n == 6 { 1207 if s_eq6(A.src, off, n, ".asciz" as *u8, 6) { 1208 handle_asciz(A, t) 1209 continue 1210 } 1211 if s_eq6(A.src, off, n, ".space" as *u8, 6) { 1212 handle_skip(A, t) // .space N (alias of .skip) 1213 continue 1214 } 1215 } 1216 skip_to_newline(A) 1217 } 1218 1219 if is_directive == 0 { 1220 // Mnemonic. Dispatch, emit word, move on. 1221 let w: i64 = assemble_mnemonic(A, t, off, n) 1222 if w < 0 { 1223 // Diagnostic on failure: dump the offending token name + 1224 // pass + cur_addr so the caller can localize the bug. 1225 sys_write(2, "nxasm: bad mnemonic '" as *u8, 21) 1226 var di: i64 = 0 1227 while di < n { 1228 sys_write(2, (((A.src as i64) + off + di) as *u8), 1) 1229 di = di + 1 1230 } 1231 sys_write(2, "' pass=" as *u8, 7) 1232 let pbuf: *u8 = sys_mmap(8) 1233 pbuf[0] = 0x30 + A.pass 1234 sys_write(2, pbuf, 1) 1235 sys_write(2, "\n" as *u8, 1) 1236 err = 1 1237 } 1238 if w >= 0 { 1239 emit_u32(A, w) 1240 } 1241 } 1242 } 1243 return err 1244} 1245 1246// ===== top-level ===================================================== 1247 1248func alloc(size: i64) -> *u8 { 1249 // Bug fix 2026-05-20: was hardcoded __syscall(222, ...) which is 1250 // RV64 SYS_MMAP only -- segfaulted on x86_64 (syscall 222 is 1251 // invalid there). Now uses SYS_MMAP from nx_syscalls.nx which is 1252 // @ifdef-switched per arch (x86_64=9, RV64=222). Closes Task #33 1253 // x86_64 nxld silent-no-op. 1254 let raw: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0) 1255 return raw as *u8 1256} 1257 1258// One-shot stderr report: where pass-1 stopped + how many labels 1259// it found. Helps localize early-bail bugs in the tokenize loop 1260// (e.g. a NUL byte in src making next_tok return EOF too early). 1261func nxasm_diag_pass1(A: *Asm, src_len: i64) -> i64 { 1262 sys_write(2, "nxasm: pass1 stopped at pos=" as *u8, 28) 1263 let pbuf: *u8 = sys_mmap(32) 1264 var pn: i64 = A.pos 1265 var pk: i64 = 0 1266 if pn == 0 { pbuf[pk] = 0x30; pk = 1 } 1267 while pn > 0 { 1268 pbuf[pk] = 0x30 + (pn - (pn / 10) * 10) 1269 pn = pn / 10 1270 pk = pk + 1 1271 } 1272 var pj: i64 = pk - 1 1273 while pj >= 0 { 1274 sys_write(2, (((pbuf as i64) + pj) as *u8), 1) 1275 pj = pj - 1 1276 } 1277 sys_write(2, "/" as *u8, 1) 1278 let sbuf: *u8 = sys_mmap(32) 1279 var sn: i64 = src_len 1280 var sk: i64 = 0 1281 if sn == 0 { sbuf[sk] = 0x30; sk = 1 } 1282 while sn > 0 { 1283 sbuf[sk] = 0x30 + (sn - (sn / 10) * 10) 1284 sn = sn / 10 1285 sk = sk + 1 1286 } 1287 var sj: i64 = sk - 1 1288 while sj >= 0 { 1289 sys_write(2, (((sbuf as i64) + sj) as *u8), 1) 1290 sj = sj - 1 1291 } 1292 sys_write(2, " n_labels=" as *u8, 10) 1293 let lbuf: *u8 = sys_mmap(32) 1294 var ln: i64 = A.n_labels 1295 var lk: i64 = 0 1296 if ln == 0 { lbuf[lk] = 0x30; lk = 1 } 1297 while ln > 0 { 1298 lbuf[lk] = 0x30 + (ln - (ln / 10) * 10) 1299 ln = ln / 10 1300 lk = lk + 1 1301 } 1302 var lj: i64 = lk - 1 1303 while lj >= 0 { 1304 sys_write(2, (((lbuf as i64) + lj) as *u8), 1) 1305 lj = lj - 1 1306 } 1307 sys_write(2, "\n" as *u8, 1) 1308 return 0 1309} 1310 1311// Assemble `src_len` bytes starting at `src` into `out_buf`, returning 1312// the number of code bytes emitted (out_buf length), or -1 on error. 1313func assemble(src: *u8, src_len: i64, out_buf: *u8, out_cap: i64) -> i64 { 1314 // 32 bytes per Label * LABELS_CAP slots. Real-world nxc.s today 1315 // has ~5K labels; 8K gives 60% headroom. Bounds-checked in 1316 // label_define so an overflow now prints + returns -1 instead of 1317 // writing past the pool. 1318 let labels_raw: *u8 = alloc(32 * LABELS_CAP) 1319 let labels: *Label = labels_raw as *Label 1320 1321 let actx_raw: *u8 = alloc(80) 1322 let A: *Asm = actx_raw as *Asm 1323 A.src = src 1324 A.src_len = src_len 1325 A.pos = 0 1326 A.cur_addr = 0 1327 A.pass = 1 1328 A.out = out_buf 1329 A.out_pos = 0 1330 A.labels = labels 1331 A.n_labels = 0 1332 1333 let t_raw: *u8 = alloc(64) 1334 let t: *AsmTok = t_raw as *AsmTok 1335 1336 // Pass 1: discover labels + compute sizes. 1337 let e1: i64 = assemble_pass(A, t) 1338 // Diagnostic: report how far pass-1 walked vs total. Lets us see 1339 // whether the loop bailed (pos < src_len) or a label-table cap 1340 // was hit (n_labels at LABELS_CAP). 1341 nxasm_diag_pass1(A, src_len) 1342 if e1 != 0 { return -2 } 1343 1344 // Pass 2: emit bytes. 1345 A.pass = 2 1346 A.pos = 0 1347 A.cur_addr = 0 1348 A.out_pos = 0 1349 let e2: i64 = assemble_pass(A, t) 1350 if e2 != 0 { return -3 } 1351 1352 return A.out_pos 1353} 1354 1355 1356// Library only; self-test lives in nxasm_v2_test.nx.