code wiki / (root) / nx_ir.nx

nx_ir.nx source

↩ module page · 1582 lines · 62557 B

1// ir.nx -- nxc2's SSA IR, ported to NishiLang. 2// 3// Mirrors ir.c: Type, Value, Instr, BasicBlock, Function, Module. 4// Uses fixed-capacity arena pools instead of per-object malloc -- 5// simple, fast, and no free path needed for a run-once compiler. 6// 7// Layout choices: 8// * Each entity is a struct allocated in a dedicated pool (big 9// up-front arena, indexed by a small int id). 10// * Instructions are linked into their block's doubly-linked list. 11// * Values carry their type as a pointer to a shared Type table. 12// * No TypeTable interning yet -- each call to ir_type_i64 returns 13// the same shared Type pointer. 14// 15// Scope this turn: enough to emit a small function body (consts, 16// binops, returns, branches). Covers the opcodes the fib and 17// const benches need. Extending to the full opset is mechanical. 18// 19// Opcode/TypeKind/ValueKind integer assignments live in types.nx 20// comments and are reproduced in callers that actually emit them. 21 22// Shared IR layouts (Type, Value, Instr, BasicBlock, Function, Module). 23import "nx_types.nx" 24 25// sys_mmap + friends live here; ir.nx's own copy was removed during 26// the module-import refactor to avoid duplicate-symbol clashes at 27// link time. 28// 29// nx_safety_envelope: (schema: nishi-library/seeds/safety-critical-standards.toml) 30// intended_use: "SSA IR builder + Module/Function/BasicBlock/ 31// Instr/Value/Type pools. Foundation for the 32// self-host compile pipeline (parse -> ir -> 33// opt -> regalloc -> riscv)." 34// sil_target: SIL2 (IR shape correctness affects every 35// downstream pass) 36// asil_target: QM 37// dal_target: DAL B 38// iec_62304_class: NONE 39// evidence: [no_floating_point_in_pool_management, 40// fixed_capacity_arena_no_realloc, 41// typed_Value_kind_VAL_CONST_PARAM_INSTR_GLOBAL, 42// doubly_linked_Instr_chain_per_block, 43// sealed_opcode_constants_complete] 44// hazard_register: [bug-tape-F16-self-compile-via-this-file, 45// bug-tape-pool-overflow-cap-256-BB-class, 46// bug-tape-arena-exhaustion-silent-failure] 47// residual_risk: "Pool capacities are static (BB pool cap=256 48// documented as scale-dependent failure mode). 49// Future grower-or-die discipline needed for 50// large modules. No realloc currently." 51// verdict: NOT_YET_EVALUATED 52 53import "nx_syscalls.nx" 54 55// nx_assert_*, nx_puts_err, etc. The ASAN-style runtime self-check 56// helpers used in alloc_value / alloc_instr / ir_emit_* bounds guards. 57import "nx_assert.nx" 58 59// ===== primitive type singletons ================================== 60 61// We stash a few Types in a small pool sized at module-init time. 62// Each call to ir_type_* returns the same pointer. 63 64func alloc_type(kind: i64, size: i64, align: i64) -> *Type { 65 // 96 bytes covers the primitive fields (32) plus struct metadata 66 // (name_bytes, name_len, fields, n_fields = 32 more) plus template 67 // + TY_PARAM extras (n_type_params, type_params, param_name, 68 // param_name_len = 32 more). Struct/template/param kinds 69 // populate their respective extras via ir_type_struct_* etc. 70 let raw: *u8 = sys_mmap(104) // 13 fields x 8B (was 96/12; +sext tags signed-subword-load types) 71 let t: *Type = raw as *Type 72 t.kind = kind 73 t.size = size 74 t.align = align 75 t.pointee = 0 as *Type 76 t.name_bytes = 0 as *u8 77 t.name_len = 0 78 t.fields = 0 as *StructField 79 t.n_fields = 0 80 t.n_type_params = 0 81 t.type_params = 0 as *i64 82 t.param_name = 0 as *u8 83 t.param_name_len = 0 84 t.sext = 0 85 return t 86} 87// alloc a SIGNED subword type (sext=1) -> its subword loads sign-extend on every backend. Use for the 88// i8/i16/i32 annotations only; u8/u16/u32 use plain alloc_type (sext=0 -> zero-extend). 8-byte types 89// don't care (qword load, no extension). Primitive leaf Types are shared BY REFERENCE through 90// clone_type_substituting, so the bit travels with the leaf -- no clone propagation needed. 91func alloc_type_s(kind: i64, size: i64, align: i64) -> *Type { 92 let t: *Type = alloc_type(kind, size, align) 93 t.sext = 1 94 return t 95} 96 97// Allocate a fresh TY_STRUCT with the given declared name. Size 98// starts at 0; callers call ir_type_struct_add_field for each member, 99// which updates size + assigns the member's offset. 100func ir_type_struct_new(name_bytes: *u8, name_len: i64) -> *Type { 101 let t: *Type = alloc_type(TY_STRUCT, 0, 8) 102 t.name_bytes = name_bytes 103 t.name_len = name_len 104 // Backing array big enough for typical compiler records (~16 105 // fields); wider structs will need a realloc path later. 106 let raw: *u8 = sys_mmap(16 * 32 + 16) 107 t.fields = raw as *StructField 108 return t 109} 110 111// Append one field to a struct type. Assigns the offset (running 112// sum of previous sizes) and grows the struct's total size. Field 113// records are 32 bytes: name_bytes(8) + name_len(8) + ty(8) + offset(8). 114func ir_type_struct_add_field(t: *Type, name_bytes: *u8, name_len: i64, 115 fty: *Type) -> i64 { 116 let base: i64 = t.fields as i64 117 let f: *StructField = (base + t.n_fields * 32) as *StructField 118 f.name_bytes = name_bytes 119 f.name_len = name_len 120 f.ty = fty 121 f.offset = t.size 122 t.size = t.size + fty.size 123 if fty.align > t.align { t.align = fty.align } 124 t.n_fields = t.n_fields + 1 125 return 0 126} 127 128// Find a field by name on a struct Type. Returns null if missing. 129func ir_type_struct_find_field(t: *Type, name_bytes: *u8, name_len: i64) -> *StructField { 130 let base: i64 = t.fields as i64 131 var i: i64 = 0 132 while i < t.n_fields { 133 let f: *StructField = (base + i * 32) as *StructField 134 if f.name_len == name_len { 135 var j: i64 = 0 136 var ok: i64 = 1 137 while j < name_len { 138 if f.name_bytes[j] != name_bytes[j] { ok = 0 } 139 j = j + 1 140 } 141 if ok == 1 { return f } 142 } 143 i = i + 1 144 } 145 return 0 as *StructField 146} 147 148func ir_type_void() -> *Type { return alloc_type(0, 0, 1) } 149func ir_type_bool() -> *Type { return alloc_type(1, 1, 1) } 150func ir_type_i32() -> *Type { return alloc_type(4, 4, 4) } 151func ir_type_i64() -> *Type { return alloc_type(5, 8, 8) } 152// Floating-point types. kind values mirror TY_F32/TY_F64 in types.nx 153// (9 and 10). Size/align follow the RV64F + RV64D ABI: f32 is 154// 4 bytes 4-aligned, f64 is 8 bytes 8-aligned. 155func ir_type_f32() -> *Type { return alloc_type(9, 4, 4) } 156func ir_type_f64() -> *Type { return alloc_type(10, 8, 8) } 157 158// ===== function / block / value construction ====================== 159 160// Allocate an empty Function with a backing arena big enough for 161// the self-host compiling itself (nxc.nx -> Wheeler DDC byte-equal 162// proof). Pool sizes raised 2026-05-16 from "small benchmarks 163// (~4k values, 256 blocks, 4k instrs)" because compiling nxc.nx 164// itself hit blocks_cap=256 in the assert_lt guard: 165// 166// nx_assert_lt: idx=256 cap=256 tag=ir_block_new: blocks pool 167// 168// nxc.nx is ~6KLOC NishiLang with ~419 functions; some single 169// functions (parser dispatch, opt passes) have well over 256 170// basic blocks. Bumped to 4096 blocks for headroom. 171// 172// Backing arena: 128 (header) + 32768 * 48 (values) + 173// 4096 * 96 (blocks) + 32768 * 192 (instrs) 174// = ~8.4 MB per function, all from sys_mmap which is 175// page-aligned + lazy-faulted so unused pages cost 176// ~zero RSS. Instr stride bumped 128 -> 192 on 177// 2026-05-20 to add op8..op15 for >8-arg calls 178// (11-arg AEAD primitives in the HTTPS chain). 179 180func ir_function_new(m: *Module, name: *u8, name_len: i64, 181 ret_ty: *Type) -> *Function { 182 let raw: *u8 = sys_mmap(128 + 32768*48 + 4096*96 + 32768*256) 183 let f: *Function = raw as *Function 184 let base: i64 = raw as i64 185 // Until the Module gets a proper name table, stash the *u8 name 186 // pointer directly in f.name_start (both are i64-sized). The 187 // driver casts back to *u8 when it needs to emit the label. 188 f.name_start = name as i64 189 f.name_len = name_len 190 f.ret_ty = ret_ty 191 f.n_params = 0 192 f.param_ptr_mask = 0 // 0 = signature not yet known; call sites skip type checking 193 f.values = (base + 128) as *Value 194 f.n_values = 0 195 f.values_cap = 32768 196 f.blocks = (base + 128 + 32768*48) as *BasicBlock 197 f.n_blocks = 0 198 f.blocks_cap = 4096 199 f.instrs = (base + 128 + 32768*48 + 4096*96) as *Instr 200 f.n_instrs = 0 201 f.instrs_cap = 32768 202 f.entry = 0 as *BasicBlock 203 204 // Append to module's functions pool so m.n_functions reflects 205 // reality and the driver can walk m.functions to emit each one. 206 // Stride is 176 bytes per Function pool slot. 207 // 208 // Skip the copy if m.functions is null (test harnesses + the 209 // top-level parse_module bootstrap that haven't allocated a pool 210 // yet); just return the heap-allocated f. Otherwise NULL+offset 211 // = SIGSEGV when ir_test or parse_test creates Functions without 212 // a pre-allocated pool. 213 if m != (0 as *Module) { 214 let fn_base: i64 = m.functions as i64 215 if fn_base == 0 { return f } 216 let dst: i64 = fn_base + m.n_functions * 176 217 // Copy the Function struct we allocated into the pool slot so 218 // pool iteration walks contiguous records. We ALSO preserve 219 // the fresh heap allocation for values/blocks/instrs pointers 220 // which live inside `raw` (the 128-byte header shares space 221 // with the Function struct itself, so the copy is the first 222 // 128 bytes of raw). 223 var k: i64 = 0 224 let src_u8: *u8 = raw 225 let dst_u8: *u8 = dst as *u8 226 while k < 176 { 227 dst_u8[k] = src_u8[k] 228 k = k + 1 229 } 230 m.n_functions = m.n_functions + 1 231 return (dst as *Function) 232 } 233 return f 234} 235 236// Resolve a Value by id in f's values pool. Pure pointer arithmetic; 237// used by every pass that inspects existing SSA values. 238func val_at(f: *Function, id: i64) -> *Value { 239 let base: i64 = f.values as i64 240 return (base + id * 48) as *Value 241} 242 243// Resolve a BasicBlock by id in f's blocks pool. 244func block_at(f: *Function, id: i64) -> *BasicBlock { 245 let base: i64 = f.blocks as i64 246 return (base + id * 96) as *BasicBlock 247} 248 249// Allocate one Value in `f`'s pool; return its id. 250func alloc_value(f: *Function, kind: i64, ty: *Type) -> i64 { 251 nx_assert_ptr(f as *u8, "alloc_value: f" as *u8) 252 let id: i64 = f.n_values 253 let cap: i64 = f.values_cap 254 nx_assert(cap > 0, "alloc_value: f.values_cap > 0 (f initialised)" as *u8) 255 nx_assert_lt(id, cap, "alloc_value: values pool" as *u8) 256 let base: i64 = f.values as i64 257 nx_assert(base != 0, "alloc_value: f.values != NULL" as *u8) 258 let v: *Value = (base + id * 48) as *Value 259 v.id = id 260 v.kind = kind 261 v.ty = ty 262 v.const_int = 0 263 v.param_index = 0 264 v.instr = 0 as *Instr 265 f.n_values = id + 1 266 return id 267} 268 269// Create a BasicBlock and append to f's list. 270func ir_block_new(f: *Function) -> *BasicBlock { 271 let id: i64 = f.n_blocks 272 let base: i64 = f.blocks as i64 273 nx_assert_ptr(f as *u8, "ir_block_new: f" as *u8) 274 nx_assert(f.blocks_cap > 0, "ir_block_new: f.blocks_cap > 0" as *u8) 275 nx_assert_lt(id, f.blocks_cap, "ir_block_new: blocks pool" as *u8) 276 nx_assert(base != 0, "ir_block_new: f.blocks != NULL" as *u8) 277 let b: *BasicBlock = (base + id * 96) as *BasicBlock 278 b.id = id 279 b.head = 0 as *Instr 280 b.tail = 0 as *Instr 281 b.parent = f 282 b.n_preds = 0 283 b.n_succs = 0 284 f.n_blocks = id + 1 285 if f.entry == (0 as *BasicBlock) { 286 f.entry = b 287 } 288 return b 289} 290 291// Integer literal. 292func ir_const_i64(f: *Function, n: i64) -> i64 { 293 nx_assert_ptr(f as *u8, "ir_const_i64: f" as *u8) 294 nx_assert(f.values_cap > 0, "ir_const_i64: f init" as *u8) 295 let id: i64 = alloc_value(f, 0, ir_type_i64()) 296 let base: i64 = f.values as i64 297 let v: *Value = (base + id * 48) as *Value 298 v.const_int = n 299 return id 300} 301 302// Parameter Value. 303func ir_param(f: *Function, idx: i64, ty: *Type) -> i64 { 304 nx_assert_ptr(f as *u8, "ir_param: f" as *u8) 305 nx_assert(f.values_cap > 0, "ir_param: f init" as *u8) 306 let id: i64 = alloc_value(f, 1, ty) 307 let base: i64 = f.values as i64 308 let v: *Value = (base + id * 48) as *Value 309 v.param_index = idx 310 return id 311} 312 313// Append `inst` to `bb`'s instruction list. Updates prev/next/tail/head. 314func append_instr(bb: *BasicBlock, inst: *Instr) -> i64 { 315 inst.parent = bb 316 inst.prev = bb.tail 317 inst.next = 0 as *Instr 318 if bb.tail != (0 as *Instr) { 319 bb.tail.next = inst 320 } 321 if bb.head == (0 as *Instr) { 322 bb.head = inst 323 } 324 bb.tail = inst 325 return 0 326} 327 328// Allocate a fresh Instr from the function's pool. 329func alloc_instr(f: *Function, op: i64, ty: *Type) -> *Instr { 330 nx_assert_ptr(f as *u8, "alloc_instr: f" as *u8) 331 nx_assert(f.instrs_cap > 0, "alloc_instr: f.instrs_cap > 0" as *u8) 332 nx_assert_lt(f.n_instrs, f.instrs_cap, "alloc_instr: instrs pool" as *u8) 333 let id: i64 = f.n_instrs 334 let base: i64 = f.instrs as i64 335 nx_assert(base != 0, "alloc_instr: f.instrs != NULL" as *u8) 336 // Stride 256 = sizeof(Instr) after op16..op23 added 2026-06-18 (was 192 after op8..op15). 337 let i: *Instr = (base + id * 256) as *Instr 338 i.op = op 339 i.result = 0 340 i.ty = ty 341 i.n_operands = 0 342 i.op0 = 0; i.op1 = 0; i.op2 = 0; i.op3 = 0 343 i.op4 = 0; i.op5 = 0; i.op6 = 0; i.op7 = 0 344 i.op8 = 0; i.op9 = 0; i.op10 = 0; i.op11 = 0 345 i.op12 = 0; i.op13 = 0; i.op14 = 0; i.op15 = 0 346 i.op16 = 0; i.op17 = 0; i.op18 = 0; i.op19 = 0 347 i.op20 = 0; i.op21 = 0; i.op22 = 0; i.op23 = 0 348 i.callee = 0 as *Function 349 i.parent = 0 as *BasicBlock 350 i.prev = 0 as *Instr 351 i.next = 0 as *Instr 352 f.n_instrs = id + 1 353 return i 354} 355 356// Emit a binary op. Result Value's id is stored in inst.result 357// and returned so the caller can use it as an operand. 358func ir_emit_binop(bb: *BasicBlock, op: i64, 359 a: i64, b: i64, ret_ty: *Type) -> i64 { 360 nx_assert_ptr(bb as *u8, "ir_emit_binop: bb" as *u8) 361 let f: *Function = bb.parent 362 nx_assert_ptr(f as *u8, "ir_emit_binop: bb.parent" as *u8) 363 nx_assert(f.values_cap > 0, "ir_emit_binop: bb.parent init" as *u8) 364 let i: *Instr = alloc_instr(f, op, ret_ty) 365 let rid: i64 = alloc_value(f, 2, ret_ty) 366 let base: i64 = f.values as i64 367 let v: *Value = (base + rid * 48) as *Value 368 v.instr = i 369 i.result = rid 370 i.n_operands = 2 371 i.op0 = a 372 i.op1 = b 373 append_instr(bb, i) 374 return rid 375} 376 377// Single-operand instruction (bswap / clz / ctz / popcnt / casts). 378// op0 = a; void-free, returns a fresh value id of ret_ty. 379func ir_emit_unop(bb: *BasicBlock, op: i64, a: i64, ret_ty: *Type) -> i64 { 380 nx_assert_ptr(bb as *u8, "ir_emit_unop: bb" as *u8) 381 let f: *Function = bb.parent 382 nx_assert_ptr(f as *u8, "ir_emit_unop: bb.parent" as *u8) 383 nx_assert(f.values_cap > 0, "ir_emit_unop: bb.parent init" as *u8) 384 let i: *Instr = alloc_instr(f, op, ret_ty) 385 let rid: i64 = alloc_value(f, 2, ret_ty) 386 let base: i64 = f.values as i64 387 let v: *Value = (base + rid * 48) as *Value 388 v.instr = i 389 i.result = rid 390 i.n_operands = 1 391 i.op0 = a 392 append_instr(bb, i) 393 return rid 394} 395 396// ---- Atomic intrinsics (C bootstrap parity) ---------------------- 397// Memory-order operand is last; backend emits conservative-strong. 398 399func ir_emit_atomic_load_i64(bb: *BasicBlock, addr: i64, mo: i64) -> i64 { 400 let f: *Function = bb.parent 401 let i: *Instr = alloc_instr(f, OP_ATOMIC_LOAD_I64, ir_type_i64()) 402 let rid: i64 = alloc_value(f, 2, ir_type_i64()) 403 let v: *Value = ((f.values as i64) + rid * 48) as *Value 404 v.instr = i 405 i.result = rid 406 i.n_operands = 2 407 i.op0 = addr 408 i.op1 = mo 409 append_instr(bb, i) 410 return rid 411} 412 413func ir_emit_atomic_store_i64(bb: *BasicBlock, addr: i64, val: i64, mo: i64) -> i64 { 414 let f: *Function = bb.parent 415 let i: *Instr = alloc_instr(f, OP_ATOMIC_STORE_I64, ir_type_void()) 416 i.n_operands = 3 417 i.op0 = addr 418 i.op1 = val 419 i.op2 = mo 420 append_instr(bb, i) 421 return 0 422} 423 424// G3: add the 128-bit (hi:lo) into the 3-word accumulator at acc_ptr with carry. 425// Void + 3-operand (acc_ptr escaping pointer + lo + hi), so it is opt-opaque and 426// never homed; the backend lowers it to one contiguous addq;adcq;adcq block. 427func ir_emit_adc_acc(bb: *BasicBlock, acc_ptr: i64, lo: i64, hi: i64) -> i64 { 428 let f: *Function = bb.parent 429 let i: *Instr = alloc_instr(f, OP_ADC_ACC, ir_type_void()) 430 i.n_operands = 3 431 i.op0 = acc_ptr 432 i.op1 = lo 433 i.op2 = hi 434 append_instr(bb, i) 435 return 0 436} 437 438func ir_emit_q8rowdot(bb: *BasicBlock, qrow: i64, arow: i64, nblk: i64) -> i64 { 439 let f: *Function = bb.parent 440 let i: *Instr = alloc_instr(f, OP_Q8ROWDOT, ir_type_i64()) 441 let rid: i64 = alloc_value(f, 2, ir_type_i64()) 442 let v: *Value = ((f.values as i64) + rid * 48) as *Value 443 v.instr = i 444 i.result = rid 445 i.n_operands = 3 446 i.op0 = qrow 447 i.op1 = arow 448 i.op2 = nblk 449 append_instr(bb, i) 450 return rid 451} 452 453func ir_emit_i8fma32(bb: *BasicBlock, a: i64, b: i64, d: i64, acc: i64) -> i64 { 454 let f: *Function = bb.parent 455 let i: *Instr = alloc_instr(f, OP_I8FMA32, ir_type_i64()) 456 let rid: i64 = alloc_value(f, 2, ir_type_i64()) 457 let v: *Value = ((f.values as i64) + rid * 48) as *Value 458 v.instr = i 459 i.result = rid 460 i.n_operands = 4 461 i.op0 = a 462 i.op1 = b 463 i.op2 = d 464 i.op3 = acc 465 append_instr(bb, i) 466 return rid 467} 468 469func ir_emit_atomic_cas_i64(bb: *BasicBlock, addr: i64, exp: i64, newv: i64, mo: i64) -> i64 { 470 let f: *Function = bb.parent 471 let i: *Instr = alloc_instr(f, OP_ATOMIC_CAS_I64, ir_type_i64()) 472 let rid: i64 = alloc_value(f, 2, ir_type_i64()) 473 let v: *Value = ((f.values as i64) + rid * 48) as *Value 474 v.instr = i 475 i.result = rid 476 i.n_operands = 4 477 i.op0 = addr 478 i.op1 = exp 479 i.op2 = newv 480 i.op3 = mo 481 append_instr(bb, i) 482 return rid 483} 484 485func ir_emit_atomic_faa_i64(bb: *BasicBlock, addr: i64, delta: i64, mo: i64) -> i64 { 486 let f: *Function = bb.parent 487 let i: *Instr = alloc_instr(f, OP_ATOMIC_FAA_I64, ir_type_i64()) 488 let rid: i64 = alloc_value(f, 2, ir_type_i64()) 489 let v: *Value = ((f.values as i64) + rid * 48) as *Value 490 v.instr = i 491 i.result = rid 492 i.n_operands = 3 493 i.op0 = addr 494 i.op1 = delta 495 i.op2 = mo 496 append_instr(bb, i) 497 return rid 498} 499 500func ir_emit_atomic_fence(bb: *BasicBlock, mo: i64) -> i64 { 501 let f: *Function = bb.parent 502 let i: *Instr = alloc_instr(f, OP_ATOMIC_FENCE, ir_type_void()) 503 i.n_operands = 1 504 i.op0 = mo 505 append_instr(bb, i) 506 return 0 507} 508 509// __thread_clone(stack_top, entry_fn, ctx) -> child_tid. 3 operands. 510func ir_emit_thread_clone(bb: *BasicBlock, stack_top: i64, entry_fn: i64, ctx: i64) -> i64 { 511 let f: *Function = bb.parent 512 let i: *Instr = alloc_instr(f, OP_THREAD_CLONE, ir_type_i64()) 513 let rid: i64 = alloc_value(f, 2, ir_type_i64()) 514 let v: *Value = ((f.values as i64) + rid * 48) as *Value 515 v.instr = i 516 i.result = rid 517 i.n_operands = 3 518 i.op0 = stack_top 519 i.op1 = entry_fn 520 i.op2 = ctx 521 append_instr(bb, i) 522 return rid 523} 524 525// A block that already ends in an unconditional terminator is SEALED: emitting 526// another terminator into it is a no-op. Without this, `break`/`continue`/`return` 527// inside an if-arm left TWO terminators in the arm block (the statement's br plus 528// parse_stmt_if's merge br): bb.tail then pointed at the DEAD second br, every 529// CFG pass saw the wrong edge, and the G12/G15 emit-time fallthrough made the 530// dead br REACHABLE -- the dt_parse_attrs infinite-spin miscompile (2026-07-27, 531// minimal repro nx_breakprobe P4; oracle nx_cc_known_good disagrees-and-is-right). 532// Defined ABOVE every caller (ir_emit_return + the br emitters below) per the 533// define-before-use rule. 534func ir_bb_sealed(bb: *BasicBlock) -> i64 { 535 let t: *Instr = bb.tail 536 if t == (0 as *Instr) { return 0 } 537 if t.op == OP_BR { return 1 } 538 if t.op == OP_BR_COND { return 1 } 539 if t.op == OP_RETURN { return 1 } 540 if t.op == OP_TAIL_CALL { return 1 } 541 return 0 542} 543 544// Emit return. Void-typed instruction. 545func ir_emit_return(bb: *BasicBlock, v: i64) -> i64 { 546 if ir_bb_sealed(bb) == 1 { return 0 } 547 let f: *Function = bb.parent 548 let i: *Instr = alloc_instr(f, 30, ir_type_void()) 549 i.n_operands = 1 550 i.op0 = v 551 append_instr(bb, i) 552 return 0 553} 554 555// Kernel intrinsics -- wait-for-interrupt, CSR read/write, fence, 556// mret. These don't have return values (except CSR_READ); we still 557// allocate a Value for them so the IR stays uniform and opt passes 558// that walk values-by-id don't crash. 559 560// wfi: no operands, void result. Lowered to `wfi` instruction. 561func ir_emit_wfi(bb: *BasicBlock) -> i64 { 562 let f: *Function = bb.parent 563 let i: *Instr = alloc_instr(f, OP_WFI, ir_type_void()) 564 i.n_operands = 0 565 append_instr(bb, i) 566 return 0 567} 568 569// csrr(csr_num) -> i64. csr_num must be a constant at codegen time. 570// Lowered to `csrr <dst>, <csr>`. 571func ir_emit_csr_read(bb: *BasicBlock, csr_num: i64) -> i64 { 572 let f: *Function = bb.parent 573 let i: *Instr = alloc_instr(f, OP_CSR_READ, ir_type_i64()) 574 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 575 let v: *Value = val_at(f, rid) 576 v.instr = i 577 i.result = rid 578 i.n_operands = 1 579 i.op0 = csr_num 580 append_instr(bb, i) 581 return rid 582} 583 584// csrw(csr_num, val). val is a Value id; csr_num is a constant. 585// Lowered to `csrw <csr>, <src>`. 586func ir_emit_csr_write(bb: *BasicBlock, csr_num: i64, val: i64) -> i64 { 587 let f: *Function = bb.parent 588 let i: *Instr = alloc_instr(f, OP_CSR_WRITE, ir_type_void()) 589 i.n_operands = 2 590 i.op0 = csr_num 591 i.op1 = val 592 append_instr(bb, i) 593 return 0 594} 595 596// fence -- full memory barrier. Lowered to `fence rw, rw`. 597func ir_emit_fence(bb: *BasicBlock) -> i64 { 598 let f: *Function = bb.parent 599 let i: *Instr = alloc_instr(f, OP_FENCE, ir_type_void()) 600 i.n_operands = 0 601 append_instr(bb, i) 602 return 0 603} 604 605// mret -- machine-mode return. Lowered to `mret`. Used at the end 606// of trap handlers to return from M-mode trap to the originating 607// privilege + pc captured in mepc/mstatus. 608func ir_emit_mret(bb: *BasicBlock) -> i64 { 609 let f: *Function = bb.parent 610 let i: *Instr = alloc_instr(f, OP_MRET, ir_type_void()) 611 i.n_operands = 0 612 append_instr(bb, i) 613 return 0 614} 615 616// Emit a raw syscall (ECALL). op0 = syscall number, op1..op6 = args. 617// Linux RV64 ABI: a7 = syscall number, a0..a5 = args, result in a0. 618// Up to 6 args supported (the kernel ABI limit); extras would need 619// a side-operands array, not warranted today. 620func ir_emit_syscall(bb: *BasicBlock, args: *i64, n_args: i64) -> i64 { 621 let f: *Function = bb.parent 622 let i: *Instr = alloc_instr(f, OP_SYSCALL, ir_type_i64()) 623 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 624 let v: *Value = val_at(f, rid) 625 v.instr = i 626 i.result = rid 627 i.n_operands = n_args 628 if n_args > 0 { i.op0 = args[0] } 629 if n_args > 1 { i.op1 = args[1] } 630 if n_args > 2 { i.op2 = args[2] } 631 if n_args > 3 { i.op3 = args[3] } 632 if n_args > 4 { i.op4 = args[4] } 633 if n_args > 5 { i.op5 = args[5] } 634 if n_args > 6 { i.op6 = args[6] } 635 append_instr(bb, i) 636 return rid 637} 638 639// Emit a hardware-f32 BINARY op (OP_FADD/FSUB/FMUL/FDIV). a, b, and the result are 640// i64-CARRIED IEEE-754 binary32 bit-patterns -- NishiLang has no f32 type, so a float 641// rides in the low 32 bits of an i64; the x86 backend moves it GPR<->xmm and computes 642// with SSE scalar-single. Result Value is typed i64 (the carrier); lowering owns the 643// float semantics. The OP_F* opcodes were reserved in nx_types.nx awaiting this. 644func ir_emit_f32_binop(bb: *BasicBlock, op: i64, a: i64, b: i64) -> i64 { 645 let f: *Function = bb.parent 646 let i: *Instr = alloc_instr(f, op, ir_type_i64()) 647 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 648 let v: *Value = val_at(f, rid) 649 v.instr = i 650 i.result = rid 651 i.n_operands = 2 652 i.op0 = a 653 i.op1 = b 654 append_instr(bb, i) 655 return rid 656} 657// Emit a hardware-f32 UNARY cast: OP_FCAST_I_TO_F (i64 int -> f32 bits, cvtsi2ss) or 658// OP_FCAST_F_TO_I (f32 bits -> i64 int, cvttss2si truncate). One operand, i64 carrier. 659func ir_emit_f32_unop(bb: *BasicBlock, op: i64, a: i64) -> i64 { 660 let f: *Function = bb.parent 661 let i: *Instr = alloc_instr(f, op, ir_type_i64()) 662 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 663 let v: *Value = val_at(f, rid) 664 v.instr = i 665 i.result = rid 666 i.n_operands = 1 667 i.op0 = a 668 append_instr(bb, i) 669 return rid 670} 671// f64-RESULT unop (OP_FCAST_I_TO_F, OP_FSQRT): the result carrier is TY_F64 so 672// the backend's x86ctx_emit_float routes it to double-precision codegen and 673// downstream f64 arithmetic sees a float operand. (F_TO_I keeps ir_emit_f32_unop: 674// its result is a true i64, and the backend reads op0's f64 type for precision.) 675func ir_emit_f64_unop(bb: *BasicBlock, op: i64, a: i64) -> i64 { 676 let f: *Function = bb.parent 677 let fty: *Type = alloc_type(TY_F64, 8, 8) 678 let i: *Instr = alloc_instr(f, op, fty) 679 let rid: i64 = alloc_value(f, VK_INSTR, fty) 680 let v: *Value = val_at(f, rid) 681 v.instr = i 682 i.result = rid 683 i.n_operands = 1 684 i.op0 = a 685 append_instr(bb, i) 686 return rid 687} 688// FMA vector-accumulate: *acc += a*b (8-wide fused). 3 operands; result carrier i64 (unused). 689func ir_emit_f32x8_fma(bb: *BasicBlock, acc: i64, a: i64, b: i64) -> i64 { 690 let f: *Function = bb.parent 691 let i: *Instr = alloc_instr(f, OP_F32X8_FMA, ir_type_i64()) 692 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 693 let v: *Value = val_at(f, rid) 694 v.instr = i 695 i.result = rid 696 i.n_operands = 3 697 i.op0 = acc 698 i.op1 = a 699 i.op2 = b 700 append_instr(bb, i) 701 return rid 702} 703// NO-FLOAT integer madd-accumulate: *acc(i32x8) += vpmaddwd(a(i16x16), b(i16x16)). 3 operands. 704func ir_emit_i16x16_madd(bb: *BasicBlock, acc: i64, a: i64, b: i64) -> i64 { 705 let f: *Function = bb.parent 706 let i: *Instr = alloc_instr(f, OP_I16X16_MADD, ir_type_i64()) 707 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 708 let v: *Value = val_at(f, rid) 709 v.instr = i 710 i.result = rid 711 i.n_operands = 3 712 i.op0 = acc 713 i.op1 = a 714 i.op2 = b 715 append_instr(bb, i) 716 return rid 717} 718// Hardware SHA-NI: one full SHA-256 block compression IN PLACE. op0=state ptr (*u32[8]), 719// op1=block ptr (*u8[64] big-endian msg), op2=K ptr (*u32[64]). 3 operands; i64 result (0). 720func ir_emit_q5unpack32(bb: *BasicBlock, qhqs: i64, out: i64, consts: i64) -> i64 { 721 let f: *Function = bb.parent 722 let i: *Instr = alloc_instr(f, OP_Q5UNPACK32, ir_type_i64()) 723 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 724 let v: *Value = val_at(f, rid) 725 v.instr = i 726 i.result = rid 727 i.n_operands = 3 728 i.op0 = qhqs 729 i.op1 = out 730 i.op2 = consts 731 append_instr(bb, i) 732 return rid 733} 734 735func ir_emit_sha256_ni_block(bb: *BasicBlock, state: i64, block: i64, k: i64) -> i64 { 736 let f: *Function = bb.parent 737 let i: *Instr = alloc_instr(f, OP_SHA256_NI_BLOCK, ir_type_i64()) 738 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 739 let v: *Value = val_at(f, rid) 740 v.instr = i 741 i.result = rid 742 i.n_operands = 3 743 i.op0 = state 744 i.op1 = block 745 i.op2 = k 746 append_instr(bb, i) 747 return rid 748} 749 750// Fused 4x64-limb wide multiply: *dst(u64[8]) = *a(u64[4]) * *b(u64[4]) (512-bit product). 751// op0=dst ptr, op1=a ptr, op2=b ptr. 3 operands; i64 result (0). Lowers to the ADX/BMI2 752// mulx+adcx+adox dual-carry kernel; the pure 8x32 u256_mul_wide stays the byte-exact oracle. 753func ir_emit_mul256_wide(bb: *BasicBlock, dst: i64, a: i64, b: i64) -> i64 { 754 let f: *Function = bb.parent 755 let i: *Instr = alloc_instr(f, OP_MUL256_WIDE, ir_type_i64()) 756 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 757 let v: *Value = val_at(f, rid) 758 v.instr = i 759 i.result = rid 760 i.n_operands = 3 761 i.op0 = dst 762 i.op1 = a 763 i.op2 = b 764 append_instr(bb, i) 765 return rid 766} 767 768// Emit a widening SIMD dot product i16x16 -> i64. Consumes two 769// i16-lane-loaded vectors (whose pointers callers materialise into 770// stack slots ahead of time -- the v0.0.1 SIMD shape is 771// stack-slot-based, no v-reg allocator yet). Returns the i64 772// scalar sum, just like the C-side OP_SIMD_VDOT_I16_X16. 773func ir_emit_simd_vdot_i16_x16(bb: *BasicBlock, a: i64, b: i64) -> i64 { 774 let f: *Function = bb.parent 775 let i: *Instr = alloc_instr(f, OP_SIMD_VDOT_I16_X16, ir_type_i64()) 776 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 777 let v: *Value = val_at(f, rid) 778 v.instr = i 779 i.result = rid 780 i.n_operands = 2 781 i.op0 = a 782 i.op1 = b 783 append_instr(bb, i) 784 return rid 785} 786// i16x16 horizontal min/max: read 16 i16 lanes from *i64 src, return 787// sign-extended i64 scalar min/max-of-lanes. 788func ir_emit_simd_vreduce_min_i16_x16(bb: *BasicBlock, p: i64) -> i64 { 789 let f: *Function = bb.parent 790 let i: *Instr = alloc_instr(f, OP_SIMD_VREDUCE_MIN_I16_X16, ir_type_i64()) 791 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 792 let v: *Value = val_at(f, rid) 793 v.instr = i 794 i.result = rid 795 i.n_operands = 1 796 i.op0 = p 797 append_instr(bb, i) 798 return rid 799} 800func ir_emit_simd_vreduce_max_i16_x16(bb: *BasicBlock, p: i64) -> i64 { 801 let f: *Function = bb.parent 802 let i: *Instr = alloc_instr(f, OP_SIMD_VREDUCE_MAX_I16_X16, ir_type_i64()) 803 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 804 let v: *Value = val_at(f, rid) 805 v.instr = i 806 i.result = rid 807 i.n_operands = 1 808 i.op0 = p 809 append_instr(bb, i) 810 return rid 811} 812// i16x16 signed saturating add: a + b per-lane, clipped to INT16 813// bounds, stored to *out as packed i16x16. Returns 0. 814func ir_emit_simd_vsadd_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 815 let f: *Function = bb.parent 816 let i: *Instr = alloc_instr(f, OP_SIMD_VSADD_I16_X16, ir_type_i64()) 817 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 818 let v: *Value = val_at(f, rid) 819 v.instr = i 820 i.result = rid 821 i.n_operands = 3 822 i.op0 = a 823 i.op1 = b 824 i.op2 = out 825 append_instr(bb, i) 826 return rid 827} 828// Per-lane signed saturating sub. Same shape as vsadd. 829func ir_emit_simd_vssub_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 830 let f: *Function = bb.parent 831 let i: *Instr = alloc_instr(f, OP_SIMD_VSSUB_I16_X16, ir_type_i64()) 832 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 833 let v: *Value = val_at(f, rid) 834 v.instr = i 835 i.result = rid 836 i.n_operands = 3 837 i.op0 = a; i.op1 = b; i.op2 = out 838 append_instr(bb, i) 839 return rid 840} 841func ir_emit_simd_vsaddu_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 842 let f: *Function = bb.parent 843 let i: *Instr = alloc_instr(f, OP_SIMD_VSADDU_I16_X16, ir_type_i64()) 844 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 845 let v: *Value = val_at(f, rid) 846 v.instr = i 847 i.result = rid 848 i.n_operands = 3 849 i.op0 = a; i.op1 = b; i.op2 = out 850 append_instr(bb, i) 851 return rid 852} 853func ir_emit_simd_vssubu_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 854 let f: *Function = bb.parent 855 let i: *Instr = alloc_instr(f, OP_SIMD_VSSUBU_I16_X16, ir_type_i64()) 856 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 857 let v: *Value = val_at(f, rid) 858 v.instr = i 859 i.result = rid 860 i.n_operands = 3 861 i.op0 = a; i.op1 = b; i.op2 = out 862 append_instr(bb, i) 863 return rid 864} 865// Per-lane min/max/add/sub/mul -- same 3-arg shape. 866func ir_emit_simd_vmin_lane_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 867 let f: *Function = bb.parent 868 let i: *Instr = alloc_instr(f, OP_SIMD_VMIN_LANE_I16_X16, ir_type_i64()) 869 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 870 let v: *Value = val_at(f, rid) 871 v.instr = i; i.result = rid; i.n_operands = 3 872 i.op0 = a; i.op1 = b; i.op2 = out 873 append_instr(bb, i) 874 return rid 875} 876func ir_emit_simd_vmax_lane_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 877 let f: *Function = bb.parent 878 let i: *Instr = alloc_instr(f, OP_SIMD_VMAX_LANE_I16_X16, ir_type_i64()) 879 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 880 let v: *Value = val_at(f, rid) 881 v.instr = i; i.result = rid; i.n_operands = 3 882 i.op0 = a; i.op1 = b; i.op2 = out 883 append_instr(bb, i) 884 return rid 885} 886func ir_emit_simd_vadd_lane_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 887 let f: *Function = bb.parent 888 let i: *Instr = alloc_instr(f, OP_SIMD_VADD_LANE_I16_X16, ir_type_i64()) 889 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 890 let v: *Value = val_at(f, rid) 891 v.instr = i; i.result = rid; i.n_operands = 3 892 i.op0 = a; i.op1 = b; i.op2 = out 893 append_instr(bb, i) 894 return rid 895} 896func ir_emit_simd_vsub_lane_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 897 let f: *Function = bb.parent 898 let i: *Instr = alloc_instr(f, OP_SIMD_VSUB_LANE_I16_X16, ir_type_i64()) 899 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 900 let v: *Value = val_at(f, rid) 901 v.instr = i; i.result = rid; i.n_operands = 3 902 i.op0 = a; i.op1 = b; i.op2 = out 903 append_instr(bb, i) 904 return rid 905} 906func ir_emit_simd_vmul_lane_i16_x16(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 907 let f: *Function = bb.parent 908 let i: *Instr = alloc_instr(f, OP_SIMD_VMUL_LANE_I16_X16, ir_type_i64()) 909 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 910 let v: *Value = val_at(f, rid) 911 v.instr = i; i.result = rid; i.n_operands = 3 912 i.op0 = a; i.op1 = b; i.op2 = out 913 append_instr(bb, i) 914 return rid 915} 916// Per-lane immediate-count shifts. op0 = *i64 source, op1 = count 917// (scalar i64, lowered into t4 at codegen), op2 = *i64 out. 918func ir_emit_simd_vsll_i16_x16(bb: *BasicBlock, a: i64, count: i64, out: i64) -> i64 { 919 let f: *Function = bb.parent 920 let i: *Instr = alloc_instr(f, OP_SIMD_VSLL_I16_X16, ir_type_i64()) 921 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 922 let v: *Value = val_at(f, rid) 923 v.instr = i; i.result = rid; i.n_operands = 3 924 i.op0 = a; i.op1 = count; i.op2 = out 925 append_instr(bb, i) 926 return rid 927} 928func ir_emit_simd_vsrl_i16_x16(bb: *BasicBlock, a: i64, count: i64, out: i64) -> i64 { 929 let f: *Function = bb.parent 930 let i: *Instr = alloc_instr(f, OP_SIMD_VSRL_I16_X16, ir_type_i64()) 931 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 932 let v: *Value = val_at(f, rid) 933 v.instr = i; i.result = rid; i.n_operands = 3 934 i.op0 = a; i.op1 = count; i.op2 = out 935 append_instr(bb, i) 936 return rid 937} 938func ir_emit_simd_vsra_i16_x16(bb: *BasicBlock, a: i64, count: i64, out: i64) -> i64 { 939 let f: *Function = bb.parent 940 let i: *Instr = alloc_instr(f, OP_SIMD_VSRA_I16_X16, ir_type_i64()) 941 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 942 let v: *Value = val_at(f, rid) 943 v.instr = i; i.result = rid; i.n_operands = 3 944 i.op0 = a; i.op1 = count; i.op2 = out 945 append_instr(bb, i) 946 return rid 947} 948// Horizontal sum of 16 i16 lanes -> i64 (widening, sign-extended). 949func ir_emit_simd_vreduce_sum_i16_x16(bb: *BasicBlock, p: i64) -> i64 { 950 let f: *Function = bb.parent 951 let i: *Instr = alloc_instr(f, OP_SIMD_VREDUCE_SUM_I16_X16, ir_type_i64()) 952 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 953 let v: *Value = val_at(f, rid) 954 v.instr = i; i.result = rid; i.n_operands = 1 955 i.op0 = p 956 append_instr(bb, i) 957 return rid 958} 959// Broadcast scalar into 16 i16 lanes -> *i64 out. 960func ir_emit_simd_vbroadcast_i16_x16(bb: *BasicBlock, scalar: i64, out: i64) -> i64 { 961 let f: *Function = bb.parent 962 let i: *Instr = alloc_instr(f, OP_SIMD_VBROADCAST_I16_X16, ir_type_i64()) 963 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 964 let v: *Value = val_at(f, rid) 965 v.instr = i; i.result = rid; i.n_operands = 2 966 i.op0 = scalar; i.op1 = out 967 append_instr(bb, i) 968 return rid 969} 970// i8x32 ops -- same shape as i16x16 lane binops (3-arg) + reduce 971// (1-arg) + broadcast (2-arg). Builder body identical modulo 972// the OP_SIMD_V*_I8_X32 opcode tag. 973func ir_emit_simd_vadd_i8_x32(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 974 let f: *Function = bb.parent 975 let i: *Instr = alloc_instr(f, OP_SIMD_VADD_I8_X32, ir_type_i64()) 976 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 977 let v: *Value = val_at(f, rid) 978 v.instr = i; i.result = rid; i.n_operands = 3 979 i.op0 = a; i.op1 = b; i.op2 = out 980 append_instr(bb, i) 981 return rid 982} 983func ir_emit_simd_vsub_i8_x32(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 984 let f: *Function = bb.parent 985 let i: *Instr = alloc_instr(f, OP_SIMD_VSUB_I8_X32, ir_type_i64()) 986 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 987 let v: *Value = val_at(f, rid) 988 v.instr = i; i.result = rid; i.n_operands = 3 989 i.op0 = a; i.op1 = b; i.op2 = out 990 append_instr(bb, i) 991 return rid 992} 993func ir_emit_simd_vsadd_i8_x32(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 994 let f: *Function = bb.parent 995 let i: *Instr = alloc_instr(f, OP_SIMD_VSADD_I8_X32, ir_type_i64()) 996 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 997 let v: *Value = val_at(f, rid) 998 v.instr = i; i.result = rid; i.n_operands = 3 999 i.op0 = a; i.op1 = b; i.op2 = out 1000 append_instr(bb, i) 1001 return rid 1002} 1003func ir_emit_simd_vssub_i8_x32(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1004 let f: *Function = bb.parent 1005 let i: *Instr = alloc_instr(f, OP_SIMD_VSSUB_I8_X32, ir_type_i64()) 1006 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1007 let v: *Value = val_at(f, rid) 1008 v.instr = i; i.result = rid; i.n_operands = 3 1009 i.op0 = a; i.op1 = b; i.op2 = out 1010 append_instr(bb, i) 1011 return rid 1012} 1013func ir_emit_simd_vreduce_sum_i8_x32(bb: *BasicBlock, p: i64) -> i64 { 1014 let f: *Function = bb.parent 1015 let i: *Instr = alloc_instr(f, OP_SIMD_VREDUCE_SUM_I8_X32, ir_type_i64()) 1016 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1017 let v: *Value = val_at(f, rid) 1018 v.instr = i; i.result = rid; i.n_operands = 1 1019 i.op0 = p 1020 append_instr(bb, i) 1021 return rid 1022} 1023func ir_emit_simd_vbroadcast_i8_x32(bb: *BasicBlock, scalar: i64, out: i64) -> i64 { 1024 let f: *Function = bb.parent 1025 let i: *Instr = alloc_instr(f, OP_SIMD_VBROADCAST_I8_X32, ir_type_i64()) 1026 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1027 let v: *Value = val_at(f, rid) 1028 v.instr = i; i.result = rid; i.n_operands = 2 1029 i.op0 = scalar; i.op1 = out 1030 append_instr(bb, i) 1031 return rid 1032} 1033// i32x8 set -- same 3-arg / 1-arg / 2-arg shapes as i8 and i16 families. 1034func ir_emit_simd_vadd_i32_x8(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1035 let f: *Function = bb.parent 1036 let i: *Instr = alloc_instr(f, OP_SIMD_VADD_I32_X8, ir_type_i64()) 1037 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1038 let v: *Value = val_at(f, rid) 1039 v.instr = i; i.result = rid; i.n_operands = 3 1040 i.op0 = a; i.op1 = b; i.op2 = out 1041 append_instr(bb, i) 1042 return rid 1043} 1044func ir_emit_simd_vsub_i32_x8(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1045 let f: *Function = bb.parent 1046 let i: *Instr = alloc_instr(f, OP_SIMD_VSUB_I32_X8, ir_type_i64()) 1047 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1048 let v: *Value = val_at(f, rid) 1049 v.instr = i; i.result = rid; i.n_operands = 3 1050 i.op0 = a; i.op1 = b; i.op2 = out 1051 append_instr(bb, i) 1052 return rid 1053} 1054func ir_emit_simd_vmul_i32_x8(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1055 let f: *Function = bb.parent 1056 let i: *Instr = alloc_instr(f, OP_SIMD_VMUL_I32_X8, ir_type_i64()) 1057 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1058 let v: *Value = val_at(f, rid) 1059 v.instr = i; i.result = rid; i.n_operands = 3 1060 i.op0 = a; i.op1 = b; i.op2 = out 1061 append_instr(bb, i) 1062 return rid 1063} 1064func ir_emit_simd_vsadd_i32_x8(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1065 let f: *Function = bb.parent 1066 let i: *Instr = alloc_instr(f, OP_SIMD_VSADD_I32_X8, ir_type_i64()) 1067 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1068 let v: *Value = val_at(f, rid) 1069 v.instr = i; i.result = rid; i.n_operands = 3 1070 i.op0 = a; i.op1 = b; i.op2 = out 1071 append_instr(bb, i) 1072 return rid 1073} 1074func ir_emit_simd_vssub_i32_x8(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1075 let f: *Function = bb.parent 1076 let i: *Instr = alloc_instr(f, OP_SIMD_VSSUB_I32_X8, ir_type_i64()) 1077 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1078 let v: *Value = val_at(f, rid) 1079 v.instr = i; i.result = rid; i.n_operands = 3 1080 i.op0 = a; i.op1 = b; i.op2 = out 1081 append_instr(bb, i) 1082 return rid 1083} 1084func ir_emit_simd_vreduce_sum_i32_x8(bb: *BasicBlock, p: i64) -> i64 { 1085 let f: *Function = bb.parent 1086 let i: *Instr = alloc_instr(f, OP_SIMD_VREDUCE_SUM_I32_X8, ir_type_i64()) 1087 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1088 let v: *Value = val_at(f, rid) 1089 v.instr = i; i.result = rid; i.n_operands = 1 1090 i.op0 = p 1091 append_instr(bb, i) 1092 return rid 1093} 1094func ir_emit_simd_vbroadcast_i32_x8(bb: *BasicBlock, scalar: i64, out: i64) -> i64 { 1095 let f: *Function = bb.parent 1096 let i: *Instr = alloc_instr(f, OP_SIMD_VBROADCAST_I32_X8, ir_type_i64()) 1097 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1098 let v: *Value = val_at(f, rid) 1099 v.instr = i; i.result = rid; i.n_operands = 2 1100 i.op0 = scalar; i.op1 = out 1101 append_instr(bb, i) 1102 return rid 1103} 1104// i64x4 set. 1105func ir_emit_simd_vadd_i64_x4(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1106 let f: *Function = bb.parent 1107 let i: *Instr = alloc_instr(f, OP_SIMD_VADD_I64_X4, ir_type_i64()) 1108 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1109 let v: *Value = val_at(f, rid) 1110 v.instr = i; i.result = rid; i.n_operands = 3 1111 i.op0 = a; i.op1 = b; i.op2 = out 1112 append_instr(bb, i); return rid 1113} 1114func ir_emit_simd_vsub_i64_x4(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1115 let f: *Function = bb.parent 1116 let i: *Instr = alloc_instr(f, OP_SIMD_VSUB_I64_X4, ir_type_i64()) 1117 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1118 let v: *Value = val_at(f, rid) 1119 v.instr = i; i.result = rid; i.n_operands = 3 1120 i.op0 = a; i.op1 = b; i.op2 = out 1121 append_instr(bb, i); return rid 1122} 1123func ir_emit_simd_vmul_i64_x4(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1124 let f: *Function = bb.parent 1125 let i: *Instr = alloc_instr(f, OP_SIMD_VMUL_I64_X4, ir_type_i64()) 1126 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1127 let v: *Value = val_at(f, rid) 1128 v.instr = i; i.result = rid; i.n_operands = 3 1129 i.op0 = a; i.op1 = b; i.op2 = out 1130 append_instr(bb, i); return rid 1131} 1132func ir_emit_simd_vsadd_i64_x4(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1133 let f: *Function = bb.parent 1134 let i: *Instr = alloc_instr(f, OP_SIMD_VSADD_I64_X4, ir_type_i64()) 1135 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1136 let v: *Value = val_at(f, rid) 1137 v.instr = i; i.result = rid; i.n_operands = 3 1138 i.op0 = a; i.op1 = b; i.op2 = out 1139 append_instr(bb, i); return rid 1140} 1141func ir_emit_simd_vssub_i64_x4(bb: *BasicBlock, a: i64, b: i64, out: i64) -> i64 { 1142 let f: *Function = bb.parent 1143 let i: *Instr = alloc_instr(f, OP_SIMD_VSSUB_I64_X4, ir_type_i64()) 1144 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1145 let v: *Value = val_at(f, rid) 1146 v.instr = i; i.result = rid; i.n_operands = 3 1147 i.op0 = a; i.op1 = b; i.op2 = out 1148 append_instr(bb, i); return rid 1149} 1150func ir_emit_simd_vreduce_sum_i64_x4(bb: *BasicBlock, p: i64) -> i64 { 1151 let f: *Function = bb.parent 1152 let i: *Instr = alloc_instr(f, OP_SIMD_VREDUCE_SUM_I64_X4, ir_type_i64()) 1153 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1154 let v: *Value = val_at(f, rid) 1155 v.instr = i; i.result = rid; i.n_operands = 1 1156 i.op0 = p 1157 append_instr(bb, i); return rid 1158} 1159func ir_emit_simd_vbroadcast_i64_x4(bb: *BasicBlock, scalar: i64, out: i64) -> i64 { 1160 let f: *Function = bb.parent 1161 let i: *Instr = alloc_instr(f, OP_SIMD_VBROADCAST_I64_X4, ir_type_i64()) 1162 let rid: i64 = alloc_value(f, VK_INSTR, ir_type_i64()) 1163 let v: *Value = val_at(f, rid) 1164 v.instr = i; i.result = rid; i.n_operands = 2 1165 i.op0 = scalar; i.op1 = out 1166 append_instr(bb, i); return rid 1167} 1168 1169// Splice instruction `movee` (currently in its block's list) to 1170// appear immediately before `anchor` in the same block. No-op if 1171// `movee == anchor` or `movee` is already just before `anchor`. 1172// Used by the inliner to relocate cloned instrs from the block's 1173// tail (where ir_emit_* append them) to just before the call site. 1174func ir_move_instr_before(anchor: *Instr, movee: *Instr) -> i64 { 1175 if movee == anchor { return 0 } 1176 let bb: *BasicBlock = anchor.parent 1177 // Unlink movee from current position. 1178 let mp: *Instr = movee.prev 1179 let mn: *Instr = movee.next 1180 if mp != (0 as *Instr) { mp.next = mn } 1181 if mn != (0 as *Instr) { mn.prev = mp } 1182 if bb.head == movee { bb.head = mn } 1183 if bb.tail == movee { bb.tail = mp } 1184 // Insert before anchor. 1185 let ap: *Instr = anchor.prev 1186 movee.prev = ap 1187 movee.next = anchor 1188 anchor.prev = movee 1189 if ap != (0 as *Instr) { 1190 ap.next = movee 1191 } else { 1192 bb.head = movee 1193 } 1194 return 0 1195} 1196 1197// Emit a getelementptr: result = base + offset. Used for struct 1198// field address computation (after parse_primary sees `.field`). 1199// Result type is the field's pointee-ish type the caller provides 1200// -- the IR's "this Value holds an address" convention. 1201func ir_emit_gep(bb: *BasicBlock, base: i64, offset: i64, 1202 field_ty: *Type) -> i64 { 1203 nx_assert_ptr(bb as *u8, "ir_emit_gep: bb" as *u8) 1204 let f: *Function = bb.parent 1205 nx_assert(f.values_cap > 0, "ir_emit_gep: bb.parent init" as *u8) 1206 let i: *Instr = alloc_instr(f, OP_GEP, field_ty) 1207 let rid: i64 = alloc_value(f, VK_INSTR, field_ty) 1208 let v: *Value = val_at(f, rid) 1209 v.instr = i 1210 i.result = rid 1211 i.n_operands = 2 1212 i.op0 = base 1213 i.op1 = offset 1214 append_instr(bb, i) 1215 return rid 1216} 1217 1218// Emit a call to `callee` with up to 4 positional args (inline op 1219// slots). Return the result Value's id. Callers pass args as an 1220// array of ids plus the count; we copy into op0..op3. More than 1221// 4 args is a follow-up (needs an operands-array side store). 1222func ir_emit_call(bb: *BasicBlock, callee: *Function, 1223 args: *i64, n_args: i64) -> i64 { 1224 nx_assert_ptr(bb as *u8, "ir_emit_call: bb" as *u8) 1225 let f: *Function = bb.parent 1226 nx_assert(f.values_cap > 0, "ir_emit_call: bb.parent init" as *u8) 1227 let ret_ty: *Type = callee.ret_ty 1228 let i: *Instr = alloc_instr(f, OP_CALL, ret_ty) 1229 let rid: i64 = alloc_value(f, VK_INSTR, ret_ty) 1230 let v: *Value = val_at(f, rid) 1231 v.instr = i 1232 i.result = rid 1233 i.callee = callee 1234 i.n_operands = n_args 1235 if n_args > 0 { i.op0 = args[0] } 1236 if n_args > 1 { i.op1 = args[1] } 1237 if n_args > 2 { i.op2 = args[2] } 1238 if n_args > 3 { i.op3 = args[3] } 1239 if n_args > 4 { i.op4 = args[4] } 1240 if n_args > 5 { i.op5 = args[5] } 1241 if n_args > 6 { i.op6 = args[6] } 1242 if n_args > 7 { i.op7 = args[7] } 1243 if n_args > 8 { i.op8 = args[8] } 1244 if n_args > 9 { i.op9 = args[9] } 1245 if n_args > 10 { i.op10 = args[10] } 1246 if n_args > 11 { i.op11 = args[11] } 1247 if n_args > 12 { i.op12 = args[12] } 1248 if n_args > 13 { i.op13 = args[13] } 1249 if n_args > 14 { i.op14 = args[14] } 1250 if n_args > 15 { i.op15 = args[15] } 1251 if n_args > 16 { i.op16 = args[16] } 1252 if n_args > 17 { i.op17 = args[17] } 1253 if n_args > 18 { i.op18 = args[18] } 1254 if n_args > 19 { i.op19 = args[19] } 1255 if n_args > 20 { i.op20 = args[20] } 1256 if n_args > 21 { i.op21 = args[21] } 1257 if n_args > 22 { i.op22 = args[22] } 1258 if n_args > 23 { i.op23 = args[23] } 1259 append_instr(bb, i) 1260 return rid 1261} 1262 1263// fp(args) IR: indirect call through a func-pointer VALUE `callee_val` (stored in op0). Args -> op1.. (MVP caps 1264// at 6, matching the x86 register-arg codegen). n_operands = n_args+1. callee = null (no static callee, so 1265// inlining/tail-call/call-graph passes skip it). Result typed by the fn-ptr's declared return type. 1266func ir_emit_call_indirect(bb: *BasicBlock, callee_val: i64, ret_ty: *Type, args: *i64, n_args: i64) -> i64 { 1267 let f: *Function = bb.parent 1268 let i: *Instr = alloc_instr(f, OP_CALL_INDIRECT, ret_ty) 1269 let rid: i64 = alloc_value(f, VK_INSTR, ret_ty) 1270 let v: *Value = val_at(f, rid) 1271 v.instr = i 1272 i.result = rid 1273 i.callee = 0 as *Function 1274 i.n_operands = n_args + 1 1275 i.op0 = callee_val 1276 if n_args > 0 { i.op1 = args[0] } 1277 if n_args > 1 { i.op2 = args[1] } 1278 if n_args > 2 { i.op3 = args[2] } 1279 if n_args > 3 { i.op4 = args[3] } 1280 if n_args > 4 { i.op5 = args[4] } 1281 if n_args > 5 { i.op6 = args[5] } 1282 // Args 7..23 (op7..op23). Until 2026-07-25 this emitter stopped at op6 while 1283 // STILL setting n_operands = n_args+1, so a backend walking n_operands read 1284 // operand slots that were never written -- garbage arguments, silently. Only 1285 // the parser's 6-arg cap kept it off the road (seq715 family). The IR now 1286 // carries every argument the SysV ABI can actually pass. 1287 if n_args > 6 { i.op7 = args[6] } 1288 if n_args > 7 { i.op8 = args[7] } 1289 if n_args > 8 { i.op9 = args[8] } 1290 if n_args > 9 { i.op10 = args[9] } 1291 if n_args > 10 { i.op11 = args[10] } 1292 if n_args > 11 { i.op12 = args[11] } 1293 if n_args > 12 { i.op13 = args[12] } 1294 if n_args > 13 { i.op14 = args[13] } 1295 if n_args > 14 { i.op15 = args[14] } 1296 if n_args > 15 { i.op16 = args[15] } 1297 if n_args > 16 { i.op17 = args[16] } 1298 if n_args > 17 { i.op18 = args[17] } 1299 if n_args > 18 { i.op19 = args[18] } 1300 if n_args > 19 { i.op20 = args[19] } 1301 if n_args > 20 { i.op21 = args[20] } 1302 if n_args > 21 { i.op22 = args[21] } 1303 if n_args > 22 { i.op23 = args[22] } 1304 append_instr(bb, i) 1305 return rid 1306} 1307 1308// Allocate a fresh Module with pre-sized pools. Callers can keep 1309// using this shape directly; the fields stay public so passes can 1310// walk functions/globals with pointer arithmetic. 1311func ir_module_new(name_bytes: *u8) -> *Module { 1312 // Globals pool grown from 128 -> 4096 slots (task #21 stage-2): 1313 // self-compile of nxc.nx generates >128 string-literal globals. 1314 // Functions pool grown from 256 -> 4096 (closes T#selfhost-004, 1315 // 2026-04-25): nxc.nx has 419 functions; the 256-cap pool 1316 // overflowed at function #257 and corrupted the adjacent 1317 // globals region, causing nxc.elf to SIGSEGV during opt or 1318 // codegen (varied by where the corruption landed). 1319 // nx_pass_bisect.sh confirmed the bug was OUTSIDE opt_run. 1320 let raw: *u8 = sys_mmap(128 + 4096 * 176 + 4096 * 80) 1321 let m: *Module = raw as *Module 1322 m.name = name_bytes 1323 let base: i64 = raw as i64 1324 m.functions = (base + 128) as *Function 1325 m.n_functions = 0 1326 m.fn_cap = 4096 1327 m.globals = (base + 128 + 4096 * 176) as *Global 1328 m.n_globals = 0 1329 m.globals_cap = 4096 1330 return m 1331} 1332 1333// Append a zero-initialised BSS global. Returns its id; also writes 1334// the Global record into the module's globals pool. Each Global is 1335// 72 bytes (id+name_bytes+name_len+bytes+len+is_string+zero_init+ 1336// writable = 9 * 8). 1337func ir_add_global_bss(m: *Module, name_bytes: *u8, name_len: i64, 1338 size: i64) -> i64 { 1339 let id: i64 = m.n_globals 1340 nx_assert_lt(id, m.globals_cap, 1341 "ir_add_global_bss: globals pool exhausted" as *u8) 1342 let base: i64 = m.globals as i64 1343 // Stride 80 -- matches ir_module_new's allocator (4096 * 80). 1344 // Was 72 by accident; the 8-byte slack-per-slot gap meant 1345 // readers using stride 80 (the dump in nxc.nx) drifted past 1346 // the live entries by global #9. Closes T#selfhost-006. 1347 let g: *Global = (base + id * 80) as *Global 1348 g.id = id 1349 g.name_bytes = name_bytes 1350 g.name_len = name_len 1351 g.bytes = 0 as *u8 1352 g.len = size 1353 g.is_string = 0 1354 g.zero_init = 1 1355 g.writable = 1 1356 m.n_globals = id + 1 1357 return id 1358} 1359 1360// Append an INITIALISED (non-zero) data global holding an integer value. 1361// `init_val` is written little-endian into a fresh `size`-byte buffer that 1362// backs g.bytes, so the emitter's `zero_init==0` path lists it as `.byte` 1363// storage (module statics live in the loaded image; writable in the bare- 1364// metal / RISC-V RAM target). Unlike ir_add_global_string this is is_string=0 1365// + writable=1 -- a mutable module datum, the storage a `static NAME = INIT` 1366// (or a zero-initialised `static NAME`, init_val=0) resolves to. Returns id. 1367func ir_add_global_data(m: *Module, name_bytes: *u8, name_len: i64, 1368 init_val: i64, size: i64) -> i64 { 1369 let id: i64 = m.n_globals 1370 nx_assert_lt(id, m.globals_cap, 1371 "ir_add_global_data: globals pool exhausted" as *u8) 1372 var nbytes: i64 = size 1373 if nbytes <= 0 { nbytes = 8 } 1374 let buf: *u8 = sys_mmap(nbytes) 1375 var b: i64 = 0 1376 var v: i64 = init_val 1377 while b < nbytes { 1378 buf[b] = (v & 0xFF) as u8 1379 v = v >> 8 1380 b = b + 1 1381 } 1382 let base: i64 = m.globals as i64 1383 let g: *Global = (base + id * 80) as *Global 1384 g.id = id 1385 g.name_bytes = name_bytes 1386 g.name_len = name_len 1387 g.bytes = buf 1388 g.len = nbytes 1389 g.is_string = 0 1390 g.zero_init = 0 1391 g.writable = 1 1392 m.n_globals = id + 1 1393 return id 1394} 1395 1396// Append a string literal global. Returns its id. `bytes` is 1397// null-terminated; len does NOT include the terminator. Emits as 1398// `.asciz` in .rodata. 1399func ir_add_global_string(m: *Module, bytes: *u8, len: i64) -> i64 { 1400 let id: i64 = m.n_globals 1401 nx_assert_lt(id, m.globals_cap, 1402 "ir_add_global_string: globals pool exhausted" as *u8) 1403 let base: i64 = m.globals as i64 1404 let g: *Global = (base + id * 80) as *Global 1405 g.id = id 1406 g.name_bytes = 0 as *u8 1407 g.name_len = 0 1408 g.bytes = bytes 1409 g.len = len 1410 g.is_string = 1 1411 g.zero_init = 0 1412 g.writable = 0 1413 m.n_globals = id + 1 1414 return id 1415} 1416 1417// Build a VK_GLOBAL Value referring to global id `gid`. Backend 1418// lowers this to `la <reg>, .Lg<gid>` (auipc + addi) so the address 1419// at runtime is the loaded virtual address of the global, not the 1420// literal id. 1421// 1422// Previously this tagged as VK_CONST_INT and stuffed gid into 1423// const_int -- backend then emitted `li <reg>, <gid>` and runtime 1424// dereferenced the integer id as if it were an address. Worked at 1425// the C-anchor level (because main.c happens to do whole-program 1426// rewriting) but blew up immediately on self-host: write(2, 0x1d3, 1427// 18) = EFAULT on every stderr message. Closes T#selfhost-003. 1428func ir_global_value(f: *Function, gid: i64, ty: *Type) -> i64 { 1429 let rid: i64 = alloc_value(f, VK_GLOBAL, ty) 1430 let v: *Value = val_at(f, rid) 1431 v.const_int = gid 1432 return rid 1433} 1434 1435// Address of a named function `fn` as a value. Rematerialised at each use site as 1436// `leaq <fn.name>(%rip), %reg` (VK_FUNC_ADDR). const_int holds the *Function pointer. 1437// Mirrors ir_global_value; used by `&fn`, bare-fn-name-as-value, and __thread_clone's entry. 1438func ir_func_addr_value(f: *Function, fn: *Function, ty: *Type) -> i64 { 1439 let rid: i64 = alloc_value(f, VK_FUNC_ADDR, ty) 1440 let v: *Value = val_at(f, rid) 1441 v.const_int = fn as i64 1442 return rid 1443} 1444 1445// Find a function by name in a module's function table. Linear 1446// scan; fine until modules get large. Returns null if not found. 1447func find_function(m: *Module, name: *u8, name_len: i64) -> *Function { 1448 var i: i64 = 0 1449 while i < m.n_functions { 1450 let base: i64 = m.functions as i64 1451 let f: *Function = (base + i * 176) as *Function 1452 if f.name_len == name_len { 1453 // f.name_start holds the *u8 name pointer directly 1454 // (see ir_function_new comment). Compare bytes. 1455 let fname: *u8 = f.name_start as *u8 1456 var j: i64 = 0 1457 var eq: i64 = 1 1458 while j < name_len { 1459 if fname[j] != name[j] { eq = 0; j = name_len } 1460 if eq == 1 { j = j + 1 } 1461 } 1462 if eq == 1 { return f } 1463 } 1464 i = i + 1 1465 } 1466 return 0 as *Function 1467} 1468 1469// Emit unconditional branch. 1470func ir_emit_br(bb: *BasicBlock, target: *BasicBlock) -> i64 { 1471 if ir_bb_sealed(bb) == 1 { return 0 } 1472 let f: *Function = bb.parent 1473 let i: *Instr = alloc_instr(f, OP_BR, ir_type_void()) 1474 i.n_operands = 1 1475 i.op0 = target.id 1476 append_instr(bb, i) 1477 // Wire CFG edges. Inline up to 2 succs per block. 1478 if bb.n_succs == 0 { 1479 bb.succ0 = target 1480 } 1481 if bb.n_succs == 1 { 1482 bb.succ1 = target 1483 } 1484 bb.n_succs = bb.n_succs + 1 1485 if target.n_preds == 0 { target.pred0 = bb } 1486 if target.n_preds == 1 { target.pred1 = bb } 1487 if target.n_preds == 2 { target.pred2 = bb } 1488 target.n_preds = target.n_preds + 1 1489 return 0 1490} 1491 1492// Emit conditional branch: br_cond cond, on_true, on_false. 1493// Operands: [cond_value_id, on_true_bb_id, on_false_bb_id]. 1494// Wires both targets as successors and both back-edges as preds. 1495func ir_emit_br_cond(bb: *BasicBlock, cond: i64, 1496 on_true: *BasicBlock, on_false: *BasicBlock) -> i64 { 1497 if ir_bb_sealed(bb) == 1 { return 0 } 1498 let f: *Function = bb.parent 1499 let i: *Instr = alloc_instr(f, OP_BR_COND, ir_type_void()) 1500 i.n_operands = 3 1501 i.op0 = cond 1502 i.op1 = on_true.id 1503 i.op2 = on_false.id 1504 append_instr(bb, i) 1505 1506 // CFG wiring -- br_cond is always a 2-succ instruction. 1507 // Wire both succs in a single shot. The previous "step n_succs 1508 // twice with overlapping conditions" pattern overwrote succ0 with 1509 // on_false and never set succ1, leaving on_true unreachable from 1510 // opt_sweep's BFS so most blocks looked dead. 1511 bb.succ0 = on_true 1512 bb.succ1 = on_false 1513 bb.n_succs = 2 1514 1515 if on_true.n_preds == 0 { on_true.pred0 = bb } 1516 if on_true.n_preds == 1 { on_true.pred1 = bb } 1517 if on_true.n_preds == 2 { on_true.pred2 = bb } 1518 on_true.n_preds = on_true.n_preds + 1 1519 1520 if on_false.n_preds == 0 { on_false.pred0 = bb } 1521 if on_false.n_preds == 1 { on_false.pred1 = bb } 1522 if on_false.n_preds == 2 { on_false.pred2 = bb } 1523 on_false.n_preds = on_false.n_preds + 1 1524 return 0 1525} 1526 1527// Emit alloca for a single value of `elem_type`. The result Value's 1528// id is returned; it's typed as a pointer to elem_type in the IR's 1529// "the Value holds an address" convention. 1530func ir_emit_alloca(bb: *BasicBlock, elem_ty: *Type) -> i64 { 1531 nx_assert_ptr(bb as *u8, "ir_emit_alloca: bb" as *u8) 1532 let f: *Function = bb.parent 1533 nx_assert(f.values_cap > 0, "ir_emit_alloca: bb.parent init" as *u8) 1534 let i: *Instr = alloc_instr(f, OP_ALLOCA, elem_ty) 1535 let rid: i64 = alloc_value(f, VK_INSTR, elem_ty) 1536 let base: i64 = f.values as i64 1537 let v: *Value = (base + rid * 48) as *Value 1538 v.instr = i 1539 i.result = rid 1540 i.n_operands = 0 1541 append_instr(bb, i) 1542 return rid 1543} 1544 1545// Emit load from `addr`, producing a Value of `load_ty`. Operands: 1546// [addr_value_id]. 1547func ir_emit_load(bb: *BasicBlock, addr: i64, load_ty: *Type) -> i64 { 1548 nx_assert_ptr(bb as *u8, "ir_emit_load: bb" as *u8) 1549 let f: *Function = bb.parent 1550 nx_assert_ptr(f as *u8, "ir_emit_load: bb.parent" as *u8) 1551 let i: *Instr = alloc_instr(f, OP_LOAD, load_ty) 1552 let rid: i64 = alloc_value(f, VK_INSTR, load_ty) 1553 let base: i64 = f.values as i64 1554 let v: *Value = (base + rid * 48) as *Value 1555 v.instr = i 1556 i.result = rid 1557 i.n_operands = 1 1558 i.op0 = addr 1559 append_instr(bb, i) 1560 return rid 1561} 1562 1563// Emit store of `val` into `addr`. No result Value (control-flow 1564// instruction shape), but i.ty carries the ELEMENT type so codegen 1565// can pick the right store width (sb/sh/sw/sd). Previously the 1566// type was discarded and i.ty was TY_VOID, so OP_STORE always 1567// emitted `sd` regardless of the actual element width -- a `*u8` 1568// write picked up the next 7 bytes (close T#types-001-codegen). 1569func ir_emit_store(bb: *BasicBlock, addr: i64, val: i64, ty: *Type) -> i64 { 1570 let f: *Function = bb.parent 1571 var st_ty: *Type = ty 1572 if st_ty == (0 as *Type) { st_ty = ir_type_i64() } 1573 let i: *Instr = alloc_instr(f, OP_STORE, st_ty) 1574 i.n_operands = 2 1575 i.op0 = addr 1576 i.op1 = val 1577 append_instr(bb, i) 1578 return 0 1579} 1580 1581// Library only; self-test lives in ir_test.nx. Compile this file as 1582// part of a multi-file build via the `import "nx_ir.nx"` directive.