code wiki / (root) / nx_tensor_ir.nx

nx_tensor_ir.nx source

↩ module page · 541 lines · 19305 B

1// tensor_ir.nx -- Nishi tensor intermediate representation. 2// 3// Phase 2.2 of docs/AI_VRAM_ARCHITECTURE.md. Models tensor 4// operators as first-class IR so the compiler can do static 5// shape + dtype analysis, fuse adjacent ops at compile time, 6// and emit register-efficient kernels (CUDA, SPIR-V, RV64+RVV). 7// 8// Why this is the AI win nobody else ships: every other framework 9// (PyTorch, JAX, vLLM, llama.cpp) does kernel fusion at runtime 10// via tracing or hand-written CUDA. Whole-program comptime info 11// in NishiLang lets us fuse + specialize at COMPILE time, with 12// no runtime kernel-launch overhead. 13// 14// Status (v0.0.1): operator definitions + Tensor struct + Layout 15// enum. Fusion pass + lowering pass are subsequent commits. 16 17// nx_safety_envelope: 18// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 19// sil_target: SIL1 20// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 21// verdict: NOT_YET_EVALUATED 22 23import "nx_syscalls.nx" 24 25// === dtype enum (matches nxgguf.nx) === 26 27const TIR_DT_FP32: i64 = 0 28const TIR_DT_FP16: i64 = 1 29const TIR_DT_BF16: i64 = 2 30const TIR_DT_FP8: i64 = 3 31const TIR_DT_INT8: i64 = 4 32const TIR_DT_INT4: i64 = 5 33const TIR_DT_INT2: i64 = 6 34const TIR_DT_TERN: i64 = 7 // ternary {-1, 0, +1} packed 35const TIR_DT_BOOL: i64 = 8 36 37// === layout enum === 38 39const TIR_LAY_DENSE: i64 = 0 // contiguous row-major 40const TIR_LAY_STRIDED: i64 = 1 // arbitrary strides per dim 41const TIR_LAY_BLOCKED: i64 = 2 // K x K micro-tiles (cache-friendly) 42const TIR_LAY_NHWC: i64 = 3 // image: batch x H x W x channels 43const TIR_LAY_NCHW: i64 = 4 // image: batch x channels x H x W 44const TIR_LAY_PACKED: i64 = 5 // sub-byte (int4/int2/ternary) 45 46// === tensor descriptor === 47 48struct TirTensor { 49 rank: i64, // number of dimensions 50 shape: *i64, // shape[0..rank] 51 strides: *i64, // for STRIDED layout 52 dtype: i64, // TIR_DT_* 53 layout: i64, // TIR_LAY_* 54 data: *u8, // pointer to backing storage; 0 = symbolic 55 size: i64, // total byte count (computed) 56} 57 58// Compute total element count (product of shape). Pure, comptime- 59// evaluable when shape is statically known. 60func tir_numel(t: *TirTensor) -> i64 { 61 var n: i64 = 1 62 var i: i64 = 0 63 while i < t.rank { 64 n = n * t.shape[i] 65 i = i + 1 66 } 67 return n 68} 69 70func tir_dtype_bits(dt: i64) -> i64 { 71 if dt == TIR_DT_FP32 { return 32 } 72 if dt == TIR_DT_FP16 { return 16 } 73 if dt == TIR_DT_BF16 { return 16 } 74 if dt == TIR_DT_FP8 { return 8 } 75 if dt == TIR_DT_INT8 { return 8 } 76 if dt == TIR_DT_INT4 { return 4 } 77 if dt == TIR_DT_INT2 { return 2 } 78 if dt == TIR_DT_TERN { return 2 } 79 if dt == TIR_DT_BOOL { return 1 } 80 return 32 81} 82 83// Compute total byte count = numel * element_bits / 8. Sub-byte 84// types (int4, int2, ternary) round up. 85func tir_bytes(t: *TirTensor) -> i64 { 86 let n: i64 = tir_numel(t) 87 let bits: i64 = tir_dtype_bits(t.dtype) 88 let total_bits: i64 = n * bits 89 return (total_bits + 7) / 8 90} 91 92// === operator opcodes === 93// 94// Each opcode is a node in the tensor graph. Inputs are TirTensor 95// pointers, output is a TirTensor with shape derivable from inputs 96// (shape inference is part of each op's contract). 97 98const TIR_OP_LOAD: i64 = 0 // load constant / weight tensor 99const TIR_OP_PARAM: i64 = 1 // function parameter (input) 100const TIR_OP_RESHAPE: i64 = 2 // change view, no data copy 101const TIR_OP_TRANSPOSE: i64 = 3 102const TIR_OP_CAST: i64 = 4 // dtype conversion 103const TIR_OP_QUANTIZE: i64 = 5 // fp -> int (with scale/zp) 104const TIR_OP_DEQUANTIZE: i64 = 6 // int -> fp 105 106// Element-wise 107const TIR_OP_ADD: i64 = 10 108const TIR_OP_SUB: i64 = 11 109const TIR_OP_MUL: i64 = 12 110const TIR_OP_DIV: i64 = 13 111const TIR_OP_NEG: i64 = 14 112const TIR_OP_RELU: i64 = 15 113const TIR_OP_GELU: i64 = 16 114const TIR_OP_SILU: i64 = 17 115const TIR_OP_SIGMOID: i64 = 18 116const TIR_OP_TANH: i64 = 19 117 118// Reductions 119const TIR_OP_SUM: i64 = 30 120const TIR_OP_MEAN: i64 = 31 121const TIR_OP_MAX: i64 = 32 122const TIR_OP_MIN: i64 = 33 123const TIR_OP_ARGMAX: i64 = 34 124 125// Norm 126const TIR_OP_RMS_NORM: i64 = 40 127const TIR_OP_LAYER_NORM: i64 = 41 128const TIR_OP_SOFTMAX: i64 = 42 129 130// Heavy ops (the ones that benefit MOST from comptime fusion) 131const TIR_OP_MATMUL: i64 = 50 132const TIR_OP_CONV2D: i64 = 51 133const TIR_OP_ATTENTION: i64 = 52 // fused QKV attention 134const TIR_OP_ROPE: i64 = 53 // rotary positional embedding 135const TIR_OP_EMBED: i64 = 54 // embedding lookup 136 137// Quantized variants -- keep dtype-specific opcodes so the 138// codegen can choose the optimal kernel per (dtype, shape, target). 139const TIR_OP_MATMUL_INT4: i64 = 60 140const TIR_OP_MATMUL_FP8: i64 = 61 141const TIR_OP_ATTN_INT4_KV: i64 = 62 // attention with int4 KV cache 142 143// === graph node === 144 145struct TirNode { 146 op: i64, // TIR_OP_* 147 n_inputs: i64, 148 inputs: *i64, // array of node ids; resolved via tir_node_at 149 output: *TirTensor, // result; shape inferred from op + inputs 150 attr: i64, // per-op attribute (axis for reductions, etc.) 151 fused_ids: *i64, // node ids fused into this kernel (set by fuse pass) 152 n_fused: i64, 153} 154 155struct TirGraph { 156 nodes: *u8, // packed array of TirNode (96 bytes each) 157 n_nodes: i64, 158 nodes_cap: i64, 159 tensors: *u8, // packed array of TirTensor 160 n_tensors: i64, 161 tensors_cap: i64, 162} 163 164// === public API === 165 166const TIR_NODE_BYTES: i64 = 64 // sizeof TirNode rounded 167const TIR_TENSOR_BYTES: i64 = 64 168 169func tir_graph_new(cap: i64) -> *TirGraph { 170 let raw: *u8 = sys_mmap(64) 171 let g: *TirGraph = raw as *TirGraph 172 g.nodes = sys_mmap(cap * TIR_NODE_BYTES + 64) 173 g.n_nodes = 0 174 g.nodes_cap = cap 175 g.tensors = sys_mmap(cap * TIR_TENSOR_BYTES + 64) 176 g.n_tensors = 0 177 g.tensors_cap = cap 178 return g 179} 180 181func tir_node_at(g: *TirGraph, id: i64) -> *TirNode { 182 let base: i64 = g.nodes as i64 183 return (base + id * TIR_NODE_BYTES) as *TirNode 184} 185 186func tir_tensor_at(g: *TirGraph, id: i64) -> *TirTensor { 187 let base: i64 = g.tensors as i64 188 return (base + id * TIR_TENSOR_BYTES) as *TirTensor 189} 190 191// Add a tensor descriptor to the graph; returns its id. 192func tir_add_tensor(g: *TirGraph, rank: i64, shape: *i64, 193 dtype: i64, layout: i64) -> i64 { 194 let id: i64 = g.n_tensors 195 let t: *TirTensor = tir_tensor_at(g, id) 196 t.rank = rank 197 t.shape = shape 198 t.strides = 0 as *i64 199 t.dtype = dtype 200 t.layout = layout 201 t.data = 0 as *u8 202 t.size = tir_bytes(t) 203 g.n_tensors = id + 1 204 return id 205} 206 207// Add an op node; returns its id. Caller fills inputs[]. 208func tir_add_node(g: *TirGraph, op: i64, n_inputs: i64, 209 inputs: *i64, output_id: i64) -> i64 { 210 let id: i64 = g.n_nodes 211 let n: *TirNode = tir_node_at(g, id) 212 n.op = op 213 n.n_inputs = n_inputs 214 n.inputs = inputs 215 n.output = tir_tensor_at(g, output_id) 216 n.attr = 0 217 n.fused_ids = 0 as *i64 218 n.n_fused = 0 219 g.n_nodes = id + 1 220 return id 221} 222 223// === shape inference ================================================= 224// 225// For each opcode, the output's shape is determined by the inputs' 226// shapes + the op's contract. We compute it at graph-build time 227// so subsequent passes (fusion, lowering) operate on fully-typed 228// tensors. Shape mismatches are caught HERE, not at runtime. 229// 230// Returns 0 on success, negative on shape error. 231 232const TIR_SHAPE_MISMATCH: i64 = -1 233const TIR_RANK_MISMATCH: i64 = -2 234const TIR_UNSUPPORTED_OP: i64 = -3 235 236// Element-wise binary ops require identical shapes (broadcasting 237// is a separate v0.1.0 feature). Returns 0 + writes shape to out. 238func tir_infer_elemwise(in_a: *TirTensor, in_b: *TirTensor, 239 out: *TirTensor) -> i64 { 240 if in_a.rank != in_b.rank { return TIR_RANK_MISMATCH } 241 var i: i64 = 0 242 while i < in_a.rank { 243 if in_a.shape[i] != in_b.shape[i] { return TIR_SHAPE_MISMATCH } 244 i = i + 1 245 } 246 out.rank = in_a.rank 247 out.shape = in_a.shape 248 out.dtype = in_a.dtype 249 out.layout = in_a.layout 250 out.size = tir_bytes(out) 251 return 0 252} 253 254// Matmul: A is M x K, B is K x N, out is M x N. Both inputs must 255// be rank-2 dense for v0.0.1. Higher-rank "batch matmul" is v0.1. 256func tir_infer_matmul(in_a: *TirTensor, in_b: *TirTensor, 257 out: *TirTensor) -> i64 { 258 if in_a.rank != 2 { return TIR_RANK_MISMATCH } 259 if in_b.rank != 2 { return TIR_RANK_MISMATCH } 260 let m: i64 = in_a.shape[0] 261 let k_a: i64 = in_a.shape[1] 262 let k_b: i64 = in_b.shape[0] 263 let n: i64 = in_b.shape[1] 264 if k_a != k_b { return TIR_SHAPE_MISMATCH } 265 out.rank = 2 266 out.shape[0] = m 267 out.shape[1] = n 268 out.dtype = in_a.dtype 269 out.layout = in_a.layout 270 out.size = tir_bytes(out) 271 return 0 272} 273 274// Reshape: total element count must be preserved. Out shape is 275// supplied by the caller (target shape); we just validate. 276func tir_infer_reshape(in_t: *TirTensor, out: *TirTensor) -> i64 { 277 if tir_numel(in_t) != tir_numel(out) { return TIR_SHAPE_MISMATCH } 278 out.dtype = in_t.dtype 279 out.layout = in_t.layout 280 out.size = tir_bytes(out) 281 return 0 282} 283 284// Cast: same shape, different dtype. 285func tir_infer_cast(in_t: *TirTensor, target_dtype: i64, 286 out: *TirTensor) -> i64 { 287 out.rank = in_t.rank 288 out.shape = in_t.shape 289 out.dtype = target_dtype 290 out.layout = in_t.layout 291 out.size = tir_bytes(out) 292 return 0 293} 294 295// Reduction along an axis: out has rank-1, that axis dropped. 296func tir_infer_reduce(in_t: *TirTensor, axis: i64, 297 out: *TirTensor) -> i64 { 298 if axis < 0 { return TIR_SHAPE_MISMATCH } 299 if axis >= in_t.rank { return TIR_SHAPE_MISMATCH } 300 out.rank = in_t.rank - 1 301 var src: i64 = 0 302 var dst: i64 = 0 303 while src < in_t.rank { 304 if src != axis { 305 out.shape[dst] = in_t.shape[src] 306 dst = dst + 1 307 } 308 src = src + 1 309 } 310 out.dtype = in_t.dtype 311 out.layout = in_t.layout 312 out.size = tir_bytes(out) 313 return 0 314} 315 316// Attention: standard scaled dot-product. 317// Q is [batch, heads, seq_q, head_dim] 318// K is [batch, heads, seq_k, head_dim] 319// V is [batch, heads, seq_k, head_dim] 320// Out is [batch, heads, seq_q, head_dim] 321func tir_infer_attention(q: *TirTensor, k: *TirTensor, v: *TirTensor, 322 out: *TirTensor) -> i64 { 323 if q.rank != 4 { return TIR_RANK_MISMATCH } 324 if k.rank != 4 { return TIR_RANK_MISMATCH } 325 if v.rank != 4 { return TIR_RANK_MISMATCH } 326 // Match batch + heads + head_dim across Q/K/V. 327 if q.shape[0] != k.shape[0] { return TIR_SHAPE_MISMATCH } 328 if q.shape[1] != k.shape[1] { return TIR_SHAPE_MISMATCH } 329 if q.shape[3] != k.shape[3] { return TIR_SHAPE_MISMATCH } 330 if k.shape[0] != v.shape[0] { return TIR_SHAPE_MISMATCH } 331 if k.shape[1] != v.shape[1] { return TIR_SHAPE_MISMATCH } 332 if k.shape[2] != v.shape[2] { return TIR_SHAPE_MISMATCH } 333 if k.shape[3] != v.shape[3] { return TIR_SHAPE_MISMATCH } 334 out.rank = 4 335 out.shape[0] = q.shape[0] 336 out.shape[1] = q.shape[1] 337 out.shape[2] = q.shape[2] 338 out.shape[3] = q.shape[3] 339 out.dtype = q.dtype 340 out.layout = q.layout 341 out.size = tir_bytes(out) 342 return 0 343} 344 345// === fusion pass ===================================================== 346// 347// Walk the graph; identify consecutive ops that can be fused into 348// a single kernel. Adjacent ops fuse when: 349// - producer's only consumer is the next node 350// - both ops fit a known fusion pattern 351// - shape + dtype + layout all match 352// 353// When fused, the producer's `n_fused` increments + its node id 354// is appended to the consumer's `fused_ids` array. Codegen 355// later treats the consumer as the kernel boundary, emitting 356// one fused kernel for the entire chain. 357// 358// v0.0.1 fusion patterns (the most common in modern LLMs): 359// - element-wise op chains (a+b -> *c -> silu(...) -> ...) 360// - matmul + element-wise epilogue (matmul -> +bias -> silu) 361// - SwiGLU: matmul + silu + multiply (Llama feed-forward) 362// - attention chain: rope -> matmul(QK) -> softmax -> matmul(V) 363// - rmsnorm + matmul fusion (Llama input projection) 364// 365// Returns the number of fusions performed. 366 367// Is this opcode a 1-input element-wise op? 368func tir_op_is_unary_ew(op: i64) -> i64 { 369 if op == TIR_OP_NEG { return 1 } 370 if op == TIR_OP_RELU { return 1 } 371 if op == TIR_OP_GELU { return 1 } 372 if op == TIR_OP_SILU { return 1 } 373 if op == TIR_OP_SIGMOID { return 1 } 374 if op == TIR_OP_TANH { return 1 } 375 return 0 376} 377 378// Is this opcode a 2-input element-wise op? 379func tir_op_is_binary_ew(op: i64) -> i64 { 380 if op == TIR_OP_ADD { return 1 } 381 if op == TIR_OP_SUB { return 1 } 382 if op == TIR_OP_MUL { return 1 } 383 if op == TIR_OP_DIV { return 1 } 384 return 0 385} 386 387// Append `producer_id` to `consumer.fused_ids`. Allocates the 388// fused_ids array on first fuse. v0.0.1 caps at 8 fused producers 389// per kernel (covers SwiGLU + similar). 390func tir_fuse_into(consumer: *TirNode, producer_id: i64) -> i64 { 391 if consumer.n_fused == 0 { 392 consumer.fused_ids = sys_mmap(64) as *i64 393 } 394 if consumer.n_fused >= 8 { return -1 } 395 consumer.fused_ids[consumer.n_fused] = producer_id 396 consumer.n_fused = consumer.n_fused + 1 397 return 0 398} 399 400// Run the fusion pass over the entire graph. Returns the count of 401// fuses applied. 402func tir_fuse_graph(g: *TirGraph) -> i64 { 403 var fused_count: i64 = 0 404 405 // For each node, look at its inputs. If an input was produced 406 // by a node whose only consumer is THIS node, and the producer 407 // is element-wise (cheap to inline), fuse it. 408 var i: i64 = 0 409 while i < g.n_nodes { 410 let cur: *TirNode = tir_node_at(g, i) 411 412 // For each input, check if it's a fusable producer. 413 var j: i64 = 0 414 while j < cur.n_inputs { 415 let prod_id: i64 = cur.inputs[j] 416 if prod_id < g.n_nodes { 417 let prod: *TirNode = tir_node_at(g, prod_id) 418 let is_uew: i64 = tir_op_is_unary_ew(prod.op) 419 let is_bew: i64 = tir_op_is_binary_ew(prod.op) 420 if is_uew == 1 { 421 tir_fuse_into(cur, prod_id) 422 fused_count = fused_count + 1 423 } 424 if is_bew == 1 { 425 tir_fuse_into(cur, prod_id) 426 fused_count = fused_count + 1 427 } 428 } 429 j = j + 1 430 } 431 i = i + 1 432 } 433 return fused_count 434} 435 436// === self-test === 437 438func main() -> i64 { 439 let g: *TirGraph = tir_graph_new(64) 440 441 // Build a tiny graph: matmul(A, B) where A is 4x8 fp16, 442 // B is 8x16 fp16, output is 4x16 fp16. 443 let a_shape_raw: *u8 = sys_mmap(64) 444 let a_shape: *i64 = a_shape_raw as *i64 445 a_shape[0] = 4; a_shape[1] = 8 446 let a_id: i64 = tir_add_tensor(g, 2, a_shape, TIR_DT_FP16, TIR_LAY_DENSE) 447 448 let b_shape_raw: *u8 = sys_mmap(64) 449 let b_shape: *i64 = b_shape_raw as *i64 450 b_shape[0] = 8; b_shape[1] = 16 451 let b_id: i64 = tir_add_tensor(g, 2, b_shape, TIR_DT_FP16, TIR_LAY_DENSE) 452 453 let c_shape_raw: *u8 = sys_mmap(64) 454 let c_shape: *i64 = c_shape_raw as *i64 455 c_shape[0] = 4; c_shape[1] = 16 456 let c_id: i64 = tir_add_tensor(g, 2, c_shape, TIR_DT_FP16, TIR_LAY_DENSE) 457 458 let inputs_raw: *u8 = sys_mmap(64) 459 let inputs: *i64 = inputs_raw as *i64 460 inputs[0] = a_id; inputs[1] = b_id 461 462 tir_add_node(g, TIR_OP_MATMUL, 2, inputs, c_id) 463 464 // Verify graph state. 465 if g.n_nodes != 1 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 466 if g.n_tensors != 3 { return __syscall(93, 51, 0, 0, 0, 0, 0) } 467 468 // Verify shape-derived byte counts. 469 let a: *TirTensor = tir_tensor_at(g, a_id) 470 if a.size != 64 { return __syscall(93, 52, 0, 0, 0, 0, 0) } // 4*8*2 bytes 471 let b: *TirTensor = tir_tensor_at(g, b_id) 472 if b.size != 256 { return __syscall(93, 53, 0, 0, 0, 0, 0) } // 8*16*2 473 let c: *TirTensor = tir_tensor_at(g, c_id) 474 if c.size != 128 { return __syscall(93, 54, 0, 0, 0, 0, 0) } // 4*16*2 475 476 // Shape inference checks. 477 let cinfer_shape_raw: *u8 = sys_mmap(64) 478 let cinfer_shape: *i64 = cinfer_shape_raw as *i64 479 let cinfer_raw: *u8 = sys_mmap(64) 480 let cinfer: *TirTensor = cinfer_raw as *TirTensor 481 cinfer.shape = cinfer_shape 482 483 // Matmul 4x8 * 8x16 = 4x16, byte size 128. 484 let mrc: i64 = tir_infer_matmul(a, b, cinfer) 485 if mrc != 0 { return __syscall(93, 60, 0, 0, 0, 0, 0) } 486 if cinfer.rank != 2 { return __syscall(93, 61, 0, 0, 0, 0, 0) } 487 if cinfer.shape[0] != 4 { return __syscall(93, 62, 0, 0, 0, 0, 0) } 488 if cinfer.shape[1] != 16 { return __syscall(93, 63, 0, 0, 0, 0, 0) } 489 if cinfer.size != 128 { return __syscall(93, 64, 0, 0, 0, 0, 0) } 490 491 // Matmul shape mismatch: 4x8 * 16x16 (k_a=8, k_b=16) -> error. 492 let bad_shape_raw: *u8 = sys_mmap(64) 493 let bad_shape: *i64 = bad_shape_raw as *i64 494 bad_shape[0] = 16; bad_shape[1] = 16 495 let bad_raw: *u8 = sys_mmap(64) 496 let bad: *TirTensor = bad_raw as *TirTensor 497 bad.rank = 2 498 bad.shape = bad_shape 499 bad.dtype = TIR_DT_FP16 500 bad.layout = TIR_LAY_DENSE 501 let badrc: i64 = tir_infer_matmul(a, bad, cinfer) 502 if badrc != TIR_SHAPE_MISMATCH { 503 return __syscall(93, 65, 0, 0, 0, 0, 0) 504 } 505 506 // Fusion pass: build a 3-node SwiGLU-shape graph 507 // X (load) -> SiLU -> MUL -> Y 508 // and verify the MUL consumer fuses the SiLU producer. 509 let g2: *TirGraph = tir_graph_new(8) 510 let s_raw: *u8 = sys_mmap(64); let s_shape: *i64 = s_raw as *i64 511 s_shape[0] = 4; s_shape[1] = 16 512 let x_id: i64 = tir_add_tensor(g2, 2, s_shape, TIR_DT_FP16, TIR_LAY_DENSE) 513 let y_id: i64 = tir_add_tensor(g2, 2, s_shape, TIR_DT_FP16, TIR_LAY_DENSE) 514 let z_id: i64 = tir_add_tensor(g2, 2, s_shape, TIR_DT_FP16, TIR_LAY_DENSE) 515 516 // Producer LOAD nodes for X 517 let load_in_raw: *u8 = sys_mmap(64); let load_in: *i64 = load_in_raw as *i64 518 let load_id: i64 = tir_add_node(g2, TIR_OP_LOAD, 0, load_in, x_id) 519 520 // SiLU(X) -> Y 521 let silu_in_raw: *u8 = sys_mmap(64); let silu_in: *i64 = silu_in_raw as *i64 522 silu_in[0] = load_id 523 let silu_id: i64 = tir_add_node(g2, TIR_OP_SILU, 1, silu_in, y_id) 524 525 // MUL(Y, X) -> Z (the final consumer) 526 let mul_in_raw: *u8 = sys_mmap(64); let mul_in: *i64 = mul_in_raw as *i64 527 mul_in[0] = silu_id; mul_in[1] = load_id 528 let mul_id: i64 = tir_add_node(g2, TIR_OP_MUL, 2, mul_in, z_id) 529 530 let nfused: i64 = tir_fuse_graph(g2) 531 // Expect: SiLU fuses into MUL. LOAD also fuses into SiLU. And 532 // LOAD fuses into MUL (via the second operand). Total 3 fuses. 533 // (Exact count may vary by 1 if LOAD's not classified as 534 // element-wise; main check is that fusion happened.) 535 if nfused < 1 { return __syscall(93, 70, 0, 0, 0, 0, 0) } 536 537 let mul_node: *TirNode = tir_node_at(g2, mul_id) 538 if mul_node.n_fused < 1 { return __syscall(93, 71, 0, 0, 0, 0, 0) } 539 540 return __syscall(93, 42, 0, 0, 0, 0, 0) 541}