From 3a06e21e480a084cf94204dbb0cd4eecbb11d4ec Mon Sep 17 00:00:00 2001 From: ZaneHam Date: Wed, 12 Aug 2026 14:16:29 +1200 Subject: [PATCH 1/5] bir: record pool overflow rather than answering with index 0 --- CHANGELOG.md | 16 +++++++++ src/ir/bir.c | 77 +++++++++++++++++++++++++++++++++++++++----- src/ir/bir.h | 22 +++++++++++++ src/ir/bir_insert.c | 5 ++- src/ir/bir_lower.c | 41 +++++++++++++++++------ src/ir/bir_mem2reg.c | 17 ++++++++-- src/triton/lower.c | 34 +++++++++++++------ tests/ttriton.c | 18 +++++++++++ 8 files changed, 199 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe00ef..8b2537c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,24 @@ Booth — Changelog ## Unreleased +### Frontend + +- the Triton lowering records pool overflow through `bir_pfull`, which the C99 + one already did and it never has. It answered a full block pool with index 0, + a live block, so `bir_pchk` could not see a Triton arena exhaustion at all + (Zane Hambly, 2026-08-11) + +- Triton blocks are named. String offset 0 is a live string, so a nameless + block printed as whatever went into the table first, and all four blocks of + a loop kernel were labelled with the kernel's own name + (Zane Hambly, 2026-08-11) + ### Architecture +- BIR arena writers record a `pool_full` bit rather than returning index 0, + which is a live entry and not a sentinel. A full pool emitted wrong + immediates under exit 0; `bir_pchk` now refuses (Zane Hambly, 2026-08-11) + - #160: DCE and mem2reg move instructions without moving `inst_lines[]` with them, so every line number past the first deleted instruction pointed at the wrong source. Four sites fixed diff --git a/src/ir/bir.c b/src/ir/bir.c index 1d92abd..5b91c0f 100644 --- a/src/ir/bir.c +++ b/src/ir/bir.c @@ -1,5 +1,6 @@ #include "bir.h" #include +#include /* ---- Name Tables ---- */ @@ -187,6 +188,45 @@ const char *bir_order_name(int ord) return "???"; } +/* ---- Pool overflow ---- */ + +/* Fixed order, so the report reads the same whichever pool filled first. */ +static const struct { uint32_t bit; const char *name; uint32_t cap; } +pool_tab[] = { + { BIR_P_TYPES, "type", BIR_MAX_TYPES }, + { BIR_P_TFIELDS, "type field", BIR_MAX_TYPE_FIELDS }, + { BIR_P_STRINGS, "string table", BIR_MAX_STRINGS }, + { BIR_P_CONSTS, "constant", BIR_MAX_CONSTS }, + { BIR_P_INSTS, "instruction", BIR_MAX_INSTS }, + { BIR_P_BLOCKS, "block", BIR_MAX_BLOCKS }, + { BIR_P_FUNCS, "function", BIR_MAX_FUNCS }, + { BIR_P_GLOBALS, "global", BIR_MAX_GLOBALS }, + { BIR_P_EXTRAOPS, "extra operand", BIR_MAX_EXTRA_OPS }, + { BIR_P_PHIS, "mem2reg phi", 0u }, +}; + +void bir_pfull(bir_module_t *M, uint32_t bit) +{ + if (M != NULL) M->pool_full |= bit; +} + +int bir_pchk(const bir_module_t *M, const char *phase) +{ + if (M == NULL || M->pool_full == 0u) return BC_OK; + + for (uint32_t i = 0; i < sizeof(pool_tab) / sizeof(pool_tab[0]); i++) { + if (!(M->pool_full & pool_tab[i].bit)) continue; + if (pool_tab[i].cap != 0u) + fprintf(stderr, "E120: BIR %s pool exhausted during %s " + "(capacity %u). Raise the matching BIR_MAX_* and " + "rebuild.\n", pool_tab[i].name, phase, pool_tab[i].cap); + else + fprintf(stderr, "E120: BIR %s pool exhausted during %s.\n", + pool_tab[i].name, phase); + } + return BC_ERR_OVERFLOW; +} + /* ---- Module Init ---- */ void bir_module_init(bir_module_t *M) @@ -219,8 +259,10 @@ static uint32_t intern_type(bir_module_t *M, const bir_type_t *t) if (type_eq_simple(&M->types[i], t)) return i; } - if (M->num_types >= BIR_MAX_TYPES) + if (M->num_types >= BIR_MAX_TYPES) { + bir_pfull(M, BIR_P_TYPES); return 0; + } uint32_t idx = M->num_types++; M->types[idx] = *t; return idx; @@ -244,10 +286,14 @@ static uint32_t intern_compound(bir_module_t *M, uint8_t kind, } if (match) return i; } - if (M->num_type_fields + (uint32_t)nfields > BIR_MAX_TYPE_FIELDS) + if (M->num_type_fields + (uint32_t)nfields > BIR_MAX_TYPE_FIELDS) { + bir_pfull(M, BIR_P_TFIELDS); return 0; - if (M->num_types >= BIR_MAX_TYPES) + } + if (M->num_types >= BIR_MAX_TYPES) { + bir_pfull(M, BIR_P_TYPES); return 0; + } uint32_t start = M->num_type_fields; for (int i = 0; i < nfields; i++) @@ -343,8 +389,11 @@ uint32_t bir_type_func(bir_module_t *M, uint32_t ret, uint32_t bir_add_string(bir_module_t *M, const char *s, uint32_t len) { - if (M->string_len + len + 1 > BIR_MAX_STRINGS) + /* Offset 0 is a live string, not a sentinel. */ + if (M->string_len + len + 1 > BIR_MAX_STRINGS) { + bir_pfull(M, BIR_P_STRINGS); return 0; + } uint32_t offset = M->string_len; memcpy(&M->strings[offset], s, len); M->strings[offset + len] = '\0'; @@ -354,6 +403,9 @@ uint32_t bir_add_string(bir_module_t *M, const char *s, uint32_t len) /* ---- Constants ---- */ +/* Nothing is pinned at const 0 the way void is at type 0, so a refusal + here is indistinguishable from a real index. Hence the bit. */ + uint32_t bir_const_int(bir_module_t *M, uint32_t type, int64_t val) { uint32_t guard = M->num_consts; @@ -363,8 +415,10 @@ uint32_t bir_const_int(bir_module_t *M, uint32_t type, int64_t val) && M->consts[i].d.ival == val) return i; } - if (M->num_consts >= BIR_MAX_CONSTS) + if (M->num_consts >= BIR_MAX_CONSTS) { + bir_pfull(M, BIR_P_CONSTS); return 0; + } uint32_t idx = M->num_consts++; M->consts[idx].kind = BIR_CONST_INT; memset(M->consts[idx].pad, 0, sizeof(M->consts[idx].pad)); @@ -384,7 +438,10 @@ uint32_t bir_const_int(bir_module_t *M, uint32_t type, int64_t val) uint32_t bir_const_bytes(bir_module_t *M, uint32_t type, uint32_t off, uint32_t len) { - if (M->num_consts >= BIR_MAX_CONSTS) return 0; + if (M->num_consts >= BIR_MAX_CONSTS) { + bir_pfull(M, BIR_P_CONSTS); + return 0; + } uint32_t idx = M->num_consts++; M->consts[idx].kind = BIR_CONST_BYTES; memset(M->consts[idx].pad, 0, sizeof(M->consts[idx].pad)); @@ -414,8 +471,10 @@ uint32_t bir_const_float(bir_module_t *M, uint32_t type, double val) && M->consts[i].d.fval == val) return i; } - if (M->num_consts >= BIR_MAX_CONSTS) + if (M->num_consts >= BIR_MAX_CONSTS) { + bir_pfull(M, BIR_P_CONSTS); return 0; + } uint32_t idx = M->num_consts++; M->consts[idx].kind = BIR_CONST_FLOAT; memset(M->consts[idx].pad, 0, sizeof(M->consts[idx].pad)); @@ -431,8 +490,10 @@ uint32_t bir_const_null(bir_module_t *M, uint32_t type) if (M->consts[i].kind == BIR_CONST_NULL && M->consts[i].type == type) return i; } - if (M->num_consts >= BIR_MAX_CONSTS) + if (M->num_consts >= BIR_MAX_CONSTS) { + bir_pfull(M, BIR_P_CONSTS); return 0; + } uint32_t idx = M->num_consts++; M->consts[idx].kind = BIR_CONST_NULL; memset(M->consts[idx].pad, 0, sizeof(M->consts[idx].pad)); diff --git a/src/ir/bir.h b/src/ir/bir.h index c1386e4..c442fd8 100644 --- a/src/ir/bir.h +++ b/src/ir/bir.h @@ -267,6 +267,21 @@ typedef struct { uint8_t is_const; } bir_global_t; /* 16 bytes */ +/* ---- Pool overflow ---- */ + +/* One bit per arena, set when a writer refuses for want of room. Sticky and + OR-ed, so the mask doesn't depend on which pool filled first. */ +#define BIR_P_TYPES 0x001u +#define BIR_P_TFIELDS 0x002u +#define BIR_P_STRINGS 0x004u +#define BIR_P_CONSTS 0x008u +#define BIR_P_INSTS 0x010u +#define BIR_P_BLOCKS 0x020u +#define BIR_P_FUNCS 0x040u +#define BIR_P_GLOBALS 0x080u +#define BIR_P_EXTRAOPS 0x100u +#define BIR_P_PHIS 0x200u + /* ---- Module ---- */ /* The whole program in one struct. No malloc. Deterministic layout. */ @@ -296,12 +311,19 @@ typedef struct { char strings[BIR_MAX_STRINGS]; uint32_t string_len; + + uint32_t pool_full; /* BIR_P_* bits; zeroed by bir_module_init */ } bir_module_t; /* ---- API ---- */ void bir_module_init(bir_module_t *M); +/* bir_pfull records a refusal; bir_pchk reports them and answers + BC_ERR_OVERFLOW if the module is unsafe to emit. */ +void bir_pfull(bir_module_t *M, uint32_t bit); +int bir_pchk(const bir_module_t *M, const char *phase); + /* Type interning — returns index of existing or newly created type */ uint32_t bir_type_void(bir_module_t *M); uint32_t bir_type_int(bir_module_t *M, int width_bits); diff --git a/src/ir/bir_insert.c b/src/ir/bir_insert.c index d0e79da..f13183a 100644 --- a/src/ir/bir_insert.c +++ b/src/ir/bir_insert.c @@ -107,7 +107,10 @@ uint32_t bir_insert(bir_module_t *M, uint32_t block_idx, uint32_t pos, if (!M || !src || n == 0) return BIR_VAL_NONE; if (block_idx >= M->num_blocks) return BIR_VAL_NONE; - if (M->num_insts + n > BIR_MAX_INSTS) return BIR_VAL_NONE; + if (M->num_insts + n > BIR_MAX_INSTS) { + bir_pfull(M, BIR_P_INSTS); + return BIR_VAL_NONE; + } TB = &M->blocks[block_idx]; if (pos > TB->num_insts) return BIR_VAL_NONE; diff --git a/src/ir/bir_lower.c b/src/ir/bir_lower.c index cabb723..5665cbb 100644 --- a/src/ir/bir_lower.c +++ b/src/ir/bir_lower.c @@ -297,7 +297,10 @@ static void op_name_from_tok(int tok, char *out, int outsz) static uint32_t emit(lower_t *L, uint16_t op, uint32_t type, uint8_t nops, uint8_t subop) { - if (L->M->num_insts >= BIR_MAX_INSTS) return 0; + if (L->M->num_insts >= BIR_MAX_INSTS) { + bir_pfull(L->M, BIR_P_INSTS); + return 0; + } uint32_t idx = L->M->num_insts++; bir_inst_t *I = &L->M->insts[idx]; memset(I, 0, sizeof(*I)); @@ -314,6 +317,9 @@ static uint32_t emit(lower_t *L, uint16_t op, uint32_t type, static void set_op(lower_t *L, uint32_t inst, int slot, uint32_t val) { + /* emit answers 0 on a full pool, and inst 0 is real. Don't write it. */ + if (inst >= L->M->num_insts) return; + if (slot < 0 || slot >= BIR_OPERANDS_INLINE) return; L->M->insts[inst].operands[slot] = val; } @@ -321,7 +327,10 @@ static void set_op(lower_t *L, uint32_t inst, int slot, uint32_t val) static uint32_t new_block(lower_t *L, const char *name) { - if (L->M->num_blocks >= BIR_MAX_BLOCKS) return 0; + if (L->M->num_blocks >= BIR_MAX_BLOCKS) { + bir_pfull(L->M, BIR_P_BLOCKS); + return 0; + } uint32_t idx = L->M->num_blocks++; bir_block_t *B = &L->M->blocks[idx]; B->name = bir_add_string(L->M, name, (uint32_t)strlen(name)); @@ -2003,11 +2012,14 @@ static uint32_t lower_expr(lower_t *L, uint32_t node) /* Overflow mode: pack into extra_operands */ { uint32_t extra_start = L->M->num_extra_ops; - if (L->M->num_extra_ops < BIR_MAX_EXTRA_OPS) - L->M->extra_operands[L->M->num_extra_ops++] = fi; + /* All of it or none: packing what fits drops arguments. */ + if (L->M->num_extra_ops + 1u + (uint32_t)nargs > BIR_MAX_EXTRA_OPS) { + bir_pfull(L->M, BIR_P_EXTRAOPS); + return BIR_VAL_NONE; + } + L->M->extra_operands[L->M->num_extra_ops++] = fi; for (int i = 0; i < nargs; i++) - if (L->M->num_extra_ops < BIR_MAX_EXTRA_OPS) - L->M->extra_operands[L->M->num_extra_ops++] = args[i]; + L->M->extra_operands[L->M->num_extra_ops++] = args[i]; uint32_t total = L->M->num_extra_ops - extra_start; uint32_t inst = emit(L, BIR_CALL, ret_t, BIR_OPERANDS_OVERFLOW, 0); set_op(L, inst, 0, extra_start); @@ -2758,6 +2770,11 @@ static void lower_stmt(lower_t *L, uint32_t node) /* Emit BIR_SWITCH in overflow mode */ { uint32_t extra_start = L->M->num_extra_ops; + /* Worst case is cond, default, then a pair per case. Flag up + front; the pack below still truncates, but nothing reads it. */ + if (L->M->num_extra_ops + 2u + 2u * (uint32_t)ncases + > BIR_MAX_EXTRA_OPS) + bir_pfull(L->M, BIR_P_EXTRAOPS); /* Pack: cond_val, default_block, (case_const, target_block)... */ if (L->M->num_extra_ops < BIR_MAX_EXTRA_OPS) L->M->extra_operands[L->M->num_extra_ops++] = cond_v; @@ -2953,8 +2970,11 @@ static void lower_func_body(lower_t *L, uint32_t func_def, /* Create function type */ uint32_t fn_type = bir_type_func(L->M, ret_t, param_types, nparams); - /* Create function */ - if (L->M->num_funcs >= BIR_MAX_FUNCS) return; + /* Create function. Bailing leaves cur_func on the previous one. */ + if (L->M->num_funcs >= BIR_MAX_FUNCS) { + bir_pfull(L->M, BIR_P_FUNCS); + return; + } uint32_t fi = L->M->num_funcs++; L->cur_func = fi; @@ -3188,7 +3208,10 @@ static void collect_global_var(lower_t *L, uint32_t node) { uint16_t cuda = ND(L, node)->cuda_flags; if (!(cuda & (CUDA_SHARED | CUDA_CONSTANT | CUDA_DEVICE))) return; - if (L->M->num_globals >= BIR_MAX_GLOBALS) return; + if (L->M->num_globals >= BIR_MAX_GLOBALS) { + bir_pfull(L->M, BIR_P_GLOBALS); + return; + } uint32_t type_n = child_at(L, node, 0); uint32_t name_n = child_at(L, node, 1); diff --git a/src/ir/bir_mem2reg.c b/src/ir/bir_mem2reg.c index eb56976..0539f43 100644 --- a/src/ir/bir_mem2reg.c +++ b/src/ir/bir_mem2reg.c @@ -145,8 +145,10 @@ static uint32_t make_undef(bir_module_t *M, uint32_t type) if (M->consts[i].kind == BIR_CONST_UNDEF && M->consts[i].type == type) return BIR_MAKE_CONST(i); } - if (M->num_consts >= BIR_MAX_CONSTS) + if (M->num_consts >= BIR_MAX_CONSTS) { + bir_pfull(M, BIR_P_CONSTS); return BIR_VAL_NONE; + } uint32_t ci = M->num_consts++; M->consts[ci].kind = BIR_CONST_UNDEF; memset(M->consts[ci].pad, 0, sizeof(M->consts[ci].pad)); @@ -468,8 +470,16 @@ static void step5_insert_phis(m2r_t *S) if (has_phi[d]) continue; has_phi[d] = 1; - if (M->num_insts >= BIR_MAX_INSTS) continue; - if (S->num_phis >= M2R_MAX_PHIS) continue; + /* A skipped phi is a wrong value at the join, not a lost + optimisation. */ + if (M->num_insts >= BIR_MAX_INSTS) { + bir_pfull(M, BIR_P_INSTS); + continue; + } + if (S->num_phis >= M2R_MAX_PHIS) { + bir_pfull(M, BIR_P_PHIS); + continue; + } uint32_t phi_idx = M->num_insts++; bir_inst_t *phi = &M->insts[phi_idx]; @@ -498,6 +508,7 @@ static void step5_insert_phis(m2r_t *S) M->extra_operands[M->num_extra_ops++] = BIR_VAL_NONE; } } else { + bir_pfull(M, BIR_P_EXTRAOPS); M->num_insts--; continue; } diff --git a/src/triton/lower.c b/src/triton/lower.c index 561af2c..b6f8090 100644 --- a/src/triton/lower.c +++ b/src/triton/lower.c @@ -78,7 +78,10 @@ static uint32_t l_nkids(const tn_node_t *n) static uint32_t l_emit(tn_lower_t *L, int op, uint32_t type, int subop) { bir_module_t *M = L->bir; - if (M->num_insts >= BIR_MAX_INSTS) return BIR_VAL_NONE; + if (M->num_insts >= BIR_MAX_INSTS) { + bir_pfull(M, BIR_P_INSTS); + return BIR_VAL_NONE; + } uint32_t idx = M->num_insts++; bir_inst_t *I = &M->insts[idx]; I->op = (uint16_t)op; @@ -108,14 +111,22 @@ static void l_op(tn_lower_t *L, uint32_t inst_val, uint32_t operand) } } -/* Create a new BIR block as a child of the current function. */ +/* Create a new BIR block as a child of the current function. The name is not + * decoration: string offset 0 is a live string rather than a sentinel, so a + * block left nameless is printed with whatever went into the table first, + * which is the function's own name. */ -static uint32_t l_new_block(tn_lower_t *L) +static uint32_t l_new_block(tn_lower_t *L, const char *name) { bir_module_t *M = L->bir; - if (M->num_blocks >= BIR_MAX_BLOCKS) return 0; + /* Block 0 is a real block, so answering a refusal with it hands the caller + * somebody else's block. Record it and let bir_pchk stop the compile. */ + if (M->num_blocks >= BIR_MAX_BLOCKS) { + bir_pfull(M, BIR_P_BLOCKS); + return 0; + } uint32_t idx = M->num_blocks++; - M->blocks[idx].name = 0; + M->blocks[idx].name = bir_add_string(M, name, (uint32_t)strlen(name)); M->blocks[idx].first_inst = M->num_insts; M->blocks[idx].num_insts = 0; /* Attach to the current function. */ @@ -1168,14 +1179,14 @@ static void l_for(tn_lower_t *L, uint32_t node_idx) uint32_t pre = L->cur_block; uint32_t br0 = l_emit(L, BIR_BR, L->t_void, 0); /* preheader -> head */ - uint32_t head = l_new_block(L); l_op(L, br0, head); + uint32_t head = l_new_block(L, "for.head"); l_op(L, br0, head); L->cur_block = head; uint32_t kphi = l_emit(L, BIR_PHI, L->t_i32, 0); l_op(L, kphi, pre); l_op(L, kphi, start); /* [preheader: start] */ uint32_t cond = l_emit(L, BIR_ICMP, L->t_i32, BIR_ICMP_SLT); l_op(L,cond,kphi); l_op(L,cond,stop); uint32_t brc = l_emit(L, BIR_BR_COND, L->t_void, 0); l_op(L,brc,cond); /* [0]=cond */ - uint32_t bodyb = l_new_block(L); l_op(L, brc, bodyb); /* [1]=true */ + uint32_t bodyb = l_new_block(L, "for.body"); l_op(L, brc, bodyb); /* [1]=true */ L->cur_block = bodyb; L->node_val[node_idx] = kphi; /* bind the loop variable k */ l_block(L, body); @@ -1183,7 +1194,7 @@ static void l_for(tn_lower_t *L, uint32_t node_idx) l_op(L, kphi, bodyb); l_op(L, kphi, kn); /* phi back-edge pair [body: k+step] */ uint32_t brh = l_emit(L, BIR_BR, L->t_void, 0); l_op(L,brh,head); /* back-edge */ - uint32_t exitb = l_new_block(L); l_op(L, brc, exitb); /* [2]=false */ + uint32_t exitb = l_new_block(L, "for.exit"); l_op(L, brc, exitb); /* [2]=false */ L->cur_block = exitb; } @@ -1338,7 +1349,10 @@ static void l_funcdef(tn_lower_t *L, uint32_t node_idx, int is_kernel) const tn_parse_t *P = L->parser; const tn_node_t *n = &P->nodes[node_idx]; bir_module_t *M = L->bir; - if (M->num_funcs >= BIR_MAX_FUNCS) return; + if (M->num_funcs >= BIR_MAX_FUNCS) { + bir_pfull(M, BIR_P_FUNCS); + return; + } uint32_t fi = M->num_funcs++; bir_func_t *F = &M->funcs[fi]; @@ -1427,7 +1441,7 @@ static void l_funcdef(tn_lower_t *L, uint32_t node_idx, int is_kernel) F->cuda_flags = is_kernel ? CUDA_GLOBAL : CUDA_DEVICE; L->cur_func = fi; - L->cur_block = l_new_block(L); + L->cur_block = l_new_block(L, "entry"); L->cur_param_base = M->num_insts; /* Emit one BIR_PARAM instruction per parameter, in order. The diff --git a/tests/ttriton.c b/tests/ttriton.c index fa14afa..dd6bf69 100644 --- a/tests/ttriton.c +++ b/tests/ttriton.c @@ -480,3 +480,21 @@ static void tri27(void) PASS(); } TH_REG("tri", 27, "AMD ai slop still compiles", tri27) + +/* String offset 0 is a live string rather than a sentinel, so a block left + * nameless is printed with whatever went into the table first. That was the + * function's own name, and this kernel's four blocks all carried it. */ + +static void tri28(void) +{ + int rc = tt_run("--triton --ir tests/tri_matmul_k.py"); + CHEQ(rc, 0); + CHECK(strstr(obuf, "\nentry:") != NULL); + CHECK(strstr(obuf, "\nfor.head:") != NULL); + CHECK(strstr(obuf, "\nfor.body:") != NULL); + /* for.exit falls past the 4 KB capture; the three above already show the + * names are real and that none of them is the function's. */ + CHECK(strstr(obuf, "\nmatmul_k:") == NULL); + PASS(); +} +TH_REG("tri", 28, "loop blocks are named", tri28) From d9d9a6f21ca185a04c46b5c69a1fac7339838f36 Mon Sep 17 00:00:00 2001 From: ZaneHam Date: Wed, 12 Aug 2026 14:17:29 +1200 Subject: [PATCH 2/5] mlir: read MLIR text and lower func.func and arith to BIR --- CHANGELOG.md | 28 + Makefile | 50 +- src/main.c | 66 + src/mlir/lower.c | 565 +++ src/mlir/mlir_fe.c | 128 + src/mlir/mlir_fe.h | 29 + src/mlir/mlir_lower.h | 19 + src/mlir/vendor/LICENSE.corec | 21 + src/mlir/vendor/LICENSE.mlir | 25 + src/mlir/vendor/base/arena.c | 202 + src/mlir/vendor/base/arena.h | 143 + src/mlir/vendor/base/assert.c | 23 + src/mlir/vendor/base/assert.h | 20 + src/mlir/vendor/base/buddy.c | 530 ++ src/mlir/vendor/base/buddy.h | 23 + src/mlir/vendor/base/exit.c | 11 + src/mlir/vendor/base/exit.h | 16 + src/mlir/vendor/base/format.c | 261 + src/mlir/vendor/base/format.h | 81 + src/mlir/vendor/base/hashtable.h | 95 + src/mlir/vendor/base/io.c | 159 + src/mlir/vendor/base/io.h | 55 + src/mlir/vendor/base/math.c | 77 + src/mlir/vendor/base/math.h | 51 + src/mlir/vendor/base/mem.c | 176 + src/mlir/vendor/base/mem.h | 29 + src/mlir/vendor/base/numconv.c | 382 ++ src/mlir/vendor/base/numconv.h | 48 + src/mlir/vendor/base/scratch.c | 35 + src/mlir/vendor/base/scratch.h | 61 + src/mlir/vendor/base/stdarg.h | 49 + src/mlir/vendor/base/strbuf.c | 72 + src/mlir/vendor/base/strbuf.h | 63 + src/mlir/vendor/base/string.c | 94 + src/mlir/vendor/base/string.h | 41 + src/mlir/vendor/base/types.h | 132 + src/mlir/vendor/base/vector.h | 78 + src/mlir/vendor/mlir_api.h | 1104 +++++ src/mlir/vendor/mlir_api_impl.c | 2688 +++++++++++ src/mlir/vendor/mlir_classic_printer.c | 2718 +++++++++++ src/mlir/vendor/mlir_classic_printer.h | 22 + src/mlir/vendor/mlir_codegen_buf.h | 88 + src/mlir/vendor/mlir_lift_cf_to_scf.c | 3166 ++++++++++++ src/mlir/vendor/mlir_lift_cf_to_scf.h | 60 + src/mlir/vendor/mlir_op_names.c | 284 ++ src/mlir/vendor/mlir_op_names.h | 14 + src/mlir/vendor/mlir_parser.c | 1794 +++++++ src/mlir/vendor/mlir_parser.h | 185 + src/mlir/vendor/op_parsers.c | 4260 +++++++++++++++++ src/mlir/vendor/op_parsers.h | 72 + src/mlir/vendor/platform/platform.h | 274 ++ src/mlir/vendor/platform/platform_linux.c | 544 +++ src/mlir/vendor/platform/platform_macos.c | 412 ++ src/mlir/vendor/platform/platform_windows.c | 741 +++ src/mlir/vendor/platform/syscall6.h | 86 + src/mlir/vendor/tokenizer.c | 901 ++++ src/mlir/vendor/tokenizer.h | 67 + src/mlir/vendor/tokenizer.re | 157 + tests/mlir/a.mlir | 6 + tests/mlir/add1.ttir | 26 + tests/mlir/add_kernel.ttir | 35 + tests/mlir/b.mlir | 33 + tests/mlir/binops.mlir | 18 + tests/mlir/c.mlir | 30 + tests/mlir/chunked_cross_entropy_forward.ttir | 65 + tests/mlir/consts.mlir | 8 + tests/mlir/conv2d.ttir | 67 + tests/mlir/d.mlir | 48 + tests/mlir/effect.mlir | 139 + tests/mlir/floats.mlir | 9 + tests/mlir/matmul1.ttir | 166 + tests/mlir/mix.mlir | 12 + tests/mlir/reject.mlir | 7 + tests/mlir/simple.mlir | 23 + tests/mlir/sumrow.ttir | 70 + tests/mlir/t1.mlir | 18 + tests/mlir/t2.mlir | 25 + tests/mlir/t3.mlir | 28 + tests/mlir/triton_mm.ttir | 142 + tests/tmain.c | 1 + tests/tmlir.c | 369 ++ 81 files changed, 24915 insertions(+), 5 deletions(-) create mode 100644 src/mlir/lower.c create mode 100644 src/mlir/mlir_fe.c create mode 100644 src/mlir/mlir_fe.h create mode 100644 src/mlir/mlir_lower.h create mode 100644 src/mlir/vendor/LICENSE.corec create mode 100644 src/mlir/vendor/LICENSE.mlir create mode 100644 src/mlir/vendor/base/arena.c create mode 100644 src/mlir/vendor/base/arena.h create mode 100644 src/mlir/vendor/base/assert.c create mode 100644 src/mlir/vendor/base/assert.h create mode 100644 src/mlir/vendor/base/buddy.c create mode 100644 src/mlir/vendor/base/buddy.h create mode 100644 src/mlir/vendor/base/exit.c create mode 100644 src/mlir/vendor/base/exit.h create mode 100644 src/mlir/vendor/base/format.c create mode 100644 src/mlir/vendor/base/format.h create mode 100644 src/mlir/vendor/base/hashtable.h create mode 100644 src/mlir/vendor/base/io.c create mode 100644 src/mlir/vendor/base/io.h create mode 100644 src/mlir/vendor/base/math.c create mode 100644 src/mlir/vendor/base/math.h create mode 100644 src/mlir/vendor/base/mem.c create mode 100644 src/mlir/vendor/base/mem.h create mode 100644 src/mlir/vendor/base/numconv.c create mode 100644 src/mlir/vendor/base/numconv.h create mode 100644 src/mlir/vendor/base/scratch.c create mode 100644 src/mlir/vendor/base/scratch.h create mode 100644 src/mlir/vendor/base/stdarg.h create mode 100644 src/mlir/vendor/base/strbuf.c create mode 100644 src/mlir/vendor/base/strbuf.h create mode 100644 src/mlir/vendor/base/string.c create mode 100644 src/mlir/vendor/base/string.h create mode 100644 src/mlir/vendor/base/types.h create mode 100644 src/mlir/vendor/base/vector.h create mode 100644 src/mlir/vendor/mlir_api.h create mode 100644 src/mlir/vendor/mlir_api_impl.c create mode 100644 src/mlir/vendor/mlir_classic_printer.c create mode 100644 src/mlir/vendor/mlir_classic_printer.h create mode 100644 src/mlir/vendor/mlir_codegen_buf.h create mode 100644 src/mlir/vendor/mlir_lift_cf_to_scf.c create mode 100644 src/mlir/vendor/mlir_lift_cf_to_scf.h create mode 100644 src/mlir/vendor/mlir_op_names.c create mode 100644 src/mlir/vendor/mlir_op_names.h create mode 100644 src/mlir/vendor/mlir_parser.c create mode 100644 src/mlir/vendor/mlir_parser.h create mode 100644 src/mlir/vendor/op_parsers.c create mode 100644 src/mlir/vendor/op_parsers.h create mode 100644 src/mlir/vendor/platform/platform.h create mode 100644 src/mlir/vendor/platform/platform_linux.c create mode 100644 src/mlir/vendor/platform/platform_macos.c create mode 100644 src/mlir/vendor/platform/platform_windows.c create mode 100644 src/mlir/vendor/platform/syscall6.h create mode 100644 src/mlir/vendor/tokenizer.c create mode 100644 src/mlir/vendor/tokenizer.h create mode 100644 src/mlir/vendor/tokenizer.re create mode 100644 tests/mlir/a.mlir create mode 100644 tests/mlir/add1.ttir create mode 100644 tests/mlir/add_kernel.ttir create mode 100644 tests/mlir/b.mlir create mode 100644 tests/mlir/binops.mlir create mode 100644 tests/mlir/c.mlir create mode 100644 tests/mlir/chunked_cross_entropy_forward.ttir create mode 100644 tests/mlir/consts.mlir create mode 100644 tests/mlir/conv2d.ttir create mode 100644 tests/mlir/d.mlir create mode 100644 tests/mlir/effect.mlir create mode 100644 tests/mlir/floats.mlir create mode 100644 tests/mlir/matmul1.ttir create mode 100644 tests/mlir/mix.mlir create mode 100644 tests/mlir/reject.mlir create mode 100644 tests/mlir/simple.mlir create mode 100644 tests/mlir/sumrow.ttir create mode 100644 tests/mlir/t1.mlir create mode 100644 tests/mlir/t2.mlir create mode 100644 tests/mlir/t3.mlir create mode 100644 tests/mlir/triton_mm.ttir create mode 100644 tests/tmlir.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2537c..c530974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,34 @@ Booth — Changelog ### Frontend +- `kath --mlir` reads MLIR text, no LLVM in the path. Čertík's pure-C + reader vendored under `src/mlir/vendor` (mlir 826b69c9, corec a160199d), + reached only through `src/mlir/mlir_fe.c` (Zane Hambly, 2026-08-11) + +- `src/mlir/lower.c` walks the parsed module into BIR: `func.func`, `return`, + `arith.constant` and every arith binop, compare and conversion the reader + classifies. From there it is the pipeline CUDA and Triton already use, and + MLIR reaches all four backends. `--mlir --pp` reprints instead + (Zane Hambly, 2026-08-11) + +- an op outside the subset stops the lowering and names itself. Skipping it + would leave a function that compiles and computes something else + (Zane Hambly, 2026-08-11) + +- five fixes to the vendored reader, all worth upstreaming, and four of them + are `func.func` being unfinished where `tt.func` is not: `parser_init` + renamed off Booth's own, `parser_error`'s `exit(1)` replaced by a + `mlir_parse_fail()` the linker supplies, `func.func` binding its arguments + before parsing the body rather than after, `func.func` accepting the + `attributes` clause where MLIR actually writes it, and `arith.xori`, + `shli` and `shrsi` added to `op_string_to_type`, which the printer could + already write but the parser could not read back + (Zane Hambly, 2026-08-11) + +- `ml_parse` resets the reader's process-wide type interning, which upstream + assumes one context per process. Without it a closed context left the next + parse in freed memory (Zane Hambly, 2026-08-11) + - the Triton lowering records pool overflow through `bir_pfull`, which the C99 one already did and it never has. It answered a full block pool with index 0, a live block, so `bir_pchk` could not see a Triton arena exhaustion at all diff --git a/Makefile b/Makefile index 988adff..06f5ccd 100644 --- a/Makefile +++ b/Makefile @@ -72,8 +72,40 @@ SOURCES = src/main.c src/kauri_impl.c \ src/nvidia/isel.c src/nvidia/emit.c src/nvidia/nv_be.c \ src/metal/emit.c src/metal/metal_be.c \ src/intel/emit.c src/intel/intel_be.c \ - src/triton/lex.c src/triton/parse.c src/triton/sema.c src/triton/lower.c -OBJECTS = $(SOURCES:%.c=$(OBJDIR)/%.o) + src/triton/lex.c src/triton/parse.c src/triton/sema.c src/triton/lower.c \ + src/mlir/mlir_fe.c src/mlir/lower.c + +# Certik's pure-C MLIR reader, vendored under src/mlir/vendor. It carries his +# corec base library and a syscall shim per host, so only one of the three +# platform files is ever built. +# +# c2x rather than c99 because corec's format.h dispatches on _Generic and +# needs __VA_OPT__, and the ~100 format() call sites through it are not worth +# rewriting. -Wno-switch-enum because these switch over a 140-value op enum +# with a default label and upstream keeps adding ops. Nothing else in the +# warning set is relaxed. PLATFORM_SKIP_ENTRY leaves main to Booth, +# COREC_STDLIB_PROVIDES_MEM stops corec defining memcpy and memset when a real +# libc is already doing it. +VDIR = src/mlir/vendor +VPLAT = platform_windows.c +ifeq ($(UNAME_S),Linux) + VPLAT = platform_linux.c +endif +ifeq ($(UNAME_S),Darwin) + VPLAT = platform_macos.c +endif +VSOURCES = $(VDIR)/tokenizer.c $(VDIR)/mlir_parser.c $(VDIR)/op_parsers.c \ + $(VDIR)/mlir_api_impl.c $(VDIR)/mlir_op_names.c \ + $(VDIR)/mlir_classic_printer.c $(VDIR)/mlir_lift_cf_to_scf.c \ + $(VDIR)/base/io.c $(VDIR)/base/buddy.c $(VDIR)/base/arena.c \ + $(VDIR)/base/scratch.c $(VDIR)/base/format.c $(VDIR)/base/math.c \ + $(VDIR)/base/string.c $(VDIR)/base/strbuf.c $(VDIR)/base/mem.c \ + $(VDIR)/base/numconv.c $(VDIR)/base/assert.c $(VDIR)/base/exit.c \ + $(VDIR)/platform/$(VPLAT) +VCFLAGS = $(subst -std=c99,-std=c2x,$(CFLAGS)) -Wno-switch-enum \ + -DPLATFORM_SKIP_ENTRY -DCOREC_STDLIB_PROVIDES_MEM -I$(VDIR) + +OBJECTS = $(SOURCES:%.c=$(OBJDIR)/%.o) $(VSOURCES:%.c=$(OBJDIR)/%.o) TARGET = kath all: $(TARGET) $(ALT_RT) @@ -85,10 +117,16 @@ $(OBJDIR)/%.o: %.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ +# Everything under src/mlir builds on VCFLAGS, vendored or not. mlir_fe.c is +# ours but it speaks corec types, so it needs the same flags. +$(OBJDIR)/src/mlir/%.o: src/mlir/%.c + @mkdir -p $(dir $@) + $(CC) $(VCFLAGS) -c $< -o $@ + # ---- Test Suite ---- TCFLAGS = -std=c99 -MMD -MP -D_POSIX_C_SOURCE=200809L -Wall -Wextra -O0 -g \ -Isrc -Isrc/fe -Isrc/ir -Isrc/tdf -Isrc/backend -Isrc/amdgpu -Isrc/tensix -Isrc/nvidia -Isrc/metal -Isrc/intel -Isrc/triton -Isrc/cpu -Isrc/runtime \ - -Iruntime $(COVFLAGS) + -Isrc/mlir -Iruntime $(COVFLAGS) TSRC = tests/tmain.c tests/tsmoke.c tests/tcomp.c tests/tenc.c \ tests/ttabs.c tests/ttypes.c tests/terrs.c tests/tphase.c \ tests/tdce.c \ @@ -112,7 +150,8 @@ TSRC = tests/tmain.c tests/tsmoke.c tests/tcomp.c tests/tenc.c \ tests/tsysprint.c \ tests/tbackend.c \ tests/tordr.c \ - tests/trpi.c + tests/trpi.c \ + tests/tmlir.c TOBJS = $(TSRC:%.c=$(OBJDIR)/%.o) COBJS = $(OBJDIR)/src/kauri_impl.o $(OBJDIR)/src/ir/bir.o $(OBJDIR)/src/ir/bir_print.o $(OBJDIR)/src/ir/bir_lower.o $(OBJDIR)/src/ir/bir_mem2reg.o $(OBJDIR)/src/ir/bir_cfold.o $(OBJDIR)/src/ir/bir_dce.o $(OBJDIR)/src/ir/bir_struct.o $(OBJDIR)/src/ir/bir_insert.o $(OBJDIR)/src/ir/bir_sroa.o $(OBJDIR)/src/ir/bir_inline.o \ @@ -130,7 +169,8 @@ COBJS = $(OBJDIR)/src/kauri_impl.o $(OBJDIR)/src/ir/bir.o $(OBJDIR)/src/ir/bir $(OBJDIR)/src/cpu/cpu_emit.o $(OBJDIR)/src/cpu/cpu_elf.o \ $(OBJDIR)/src/cpu/rv64_emit.o $(OBJDIR)/src/cpu/rv64_elf.o \ $(OBJDIR)/src/tensix/isel.o $(OBJDIR)/src/tensix/coarsen.o $(OBJDIR)/src/tensix/datamov.o \ - $(OBJDIR)/src/metal/emit.o $(OBJDIR)/src/intel/emit.o + $(OBJDIR)/src/metal/emit.o $(OBJDIR)/src/intel/emit.o \ + $(OBJDIR)/src/mlir/mlir_fe.o $(OBJDIR)/src/mlir/lower.o $(VSOURCES:%.c=$(OBJDIR)/%.o) test: $(TARGET) trunner ./trunner --all diff --git a/src/main.c b/src/main.c index 79ddfd2..5ff9280 100644 --- a/src/main.c +++ b/src/main.c @@ -17,6 +17,8 @@ #include "metal.h" #include "intel.h" #include "triton.h" +#include "mlir/mlir_fe.h" +#include "mlir/mlir_lower.h" #include "tdf.h" #include "rv_buf.h" #include "rv_elf.h" @@ -70,6 +72,11 @@ static int run_bir_backends(bir_module_t *bir, const backend_cfg_t *cfg) { int rc = BC_OK; + /* Arena overflow during lowering. Checked before the passes get a + * chance to fold the wrong values into something that looks right. */ + rc = bir_pchk(bir, "lowering"); + if (rc != BC_OK) return rc; + /* String literal globals (BIR_CONST_BYTES initializer) require * backend support that is still being wired in. Phase 1 of the * string-literal work landed the BIR shape and the frontend @@ -110,6 +117,10 @@ static int run_bir_backends(bir_module_t *bir, const backend_cfg_t *cfg) if (!cfg->no_cfold) bir_cfold(bir); if (!cfg->no_dce) bir_dce(bir); + /* Inlining and mem2reg both grow the arenas, so ask again. */ + rc = bir_pchk(bir, "optimisation"); + if (rc != BC_OK) return rc; + /* Wrap the BIR in a TDF module and lower it. For AMD and NVIDIA * this is a degenerate passthrough, the lowering hands the same * BIR pointer straight back, and the cost is one memset plus @@ -248,6 +259,7 @@ static void usage(const char *prog) " --nvidia-ptx Compile to NVIDIA PTX (sm_89)\n" " --hip HIP frontend mode (predefines __HIPCC__ and platform macros;\n" " auto-on for .hip files; combine with --amdgpu-bin or --nvidia-ptx)\n" + " --mlir Read MLIR text. Core dialects only, anything else is refused\n" " --triton Triton frontend mode (parses Python source). Pair with a target\n" " backend (--cpu, --amdgpu-bin, --nvidia-ptx). tl.dot matmul runs.\n" " --cpu x86-64 host backend; emits a normal object you can link and run\n" @@ -284,6 +296,7 @@ int main(int argc, char *argv[]) int mode_tdf_fission = 0; int mode_hip = 0; /* HIP frontend: see HIP NOTES below */ int mode_triton = 0; /* Triton frontend: see TRITON NOTES below */ + int mode_mlir = 0; /* MLIR frontend: see MLIR NOTES below */ int no_mem2reg = 0; int no_cfold = 0; int no_dce = 0; @@ -330,6 +343,8 @@ int main(int argc, char *argv[]) mode_hip = 1; else if (strcmp(argv[i], "--triton") == 0) mode_triton = 1; + else if (strcmp(argv[i], "--mlir") == 0) + mode_mlir = 1; else if (strcmp(argv[i], "--tt-chip") == 0 && i + 1 < argc) { if (td_pchip(argv[++i], &tt_chip) != BC_OK) { fprintf(stderr, "unknown Tenstorrent chip: %s " @@ -434,6 +449,57 @@ int main(int argc, char *argv[]) if (read_file(file, source_buf, BC_MAX_SOURCE, &src_len) != BC_OK) return 1; + /* ---- MLIR NOTES --------------------------------------------------- + * MLIR arrives already structured, so there is no lexer, parser or + * sema on this path. src/mlir/vendor/ reads the text, src/mlir/lower.c + * walks it into BIR, and from there it is the same pipeline CUDA and + * Triton use. --pp reprints what was read instead, which is the + * quickest way to tell a misreading from a bad file. */ + if (mode_mlir) { + ml_ctx_t *mc = ml_open(64u * 1024u * 1024u); + int mrc; + + if (!mc) { + fprintf(stderr, "mlir: out of memory\n"); + return 1; + } + mrc = ml_parse(mc, source_buf, src_len); + if (mrc != 0) { + ml_close(mc); + return 1; + } + if (mode_pp) { + mrc = ml_echo(mc, stdout); + ml_close(mc); + return mrc == 0 ? 0 : 1; + } + + bir_module = (bir_module_t *)malloc(sizeof(bir_module_t)); + if (!bir_module) { + fprintf(stderr, "error: failed to allocate BIR module\n"); + ml_close(mc); + return 1; + } + mrc = ml_lowr(mc, (struct bir_module *)bir_module); + ml_close(mc); + + if (mrc == 0) { + backend_cfg_t cfg = {0}; + cfg.no_mem2reg = no_mem2reg; + cfg.no_cfold = no_cfold; + cfg.no_dce = no_dce; + cfg.no_sched = no_sched; + cfg.no_sroa = no_sroa; + cfg.mode_ir = mode_ir; + cfg.mode_tdf = mode_tdf; + cfg.mode_tdf_fission = mode_tdf_fission; + cfg.output_file = output_file; + mrc = (run_bir_backends(bir_module, &cfg) == BC_OK) ? 0 : 1; + } + free(bir_module); + return mrc == 0 ? 0 : 1; + } + /* ---- TRITON NOTES ------------------------------------------------- * The Triton frontend is a parallel input path that does not share * the C99 preprocessor or lexer. When --triton is on, we route the diff --git a/src/mlir/lower.c b/src/mlir/lower.c new file mode 100644 index 0000000..40470f5 --- /dev/null +++ b/src/mlir/lower.c @@ -0,0 +1,565 @@ +/* MLIR to BIR. Anyone can define a dialect, so MLIR has no natural boundary + * and this one is ours. An op off the list is named and refused, never + * skipped, because a skipped op compiles and computes the wrong thing. */ + +#include + +#include + +#include "mlir_api.h" +#include "mlir_op_names.h" +#include "mlir_fe.h" +#include "mlir_lower.h" + +#include "barracuda.h" +#include "bir.h" + +/* One module at a time, and the map is 128 KB, so it does not go on a stack. */ +#define LW_MAX_VALS 4096 +#define LW_MAX_ERRS 16 +#define LW_MAX_PARAMS 64 + +typedef struct { + MLIR_ValueHandle key; + string nm; /* register name, see lw_look */ + uint32_t val; +} lw_ent_t; + +typedef struct { + bir_module_t *M; + MLIR_Context *ctx; + uint32_t cur_func; + uint32_t cur_block; + lw_ent_t map[LW_MAX_VALS]; + uint32_t nmap; + uint32_t nerr; +} lw_t; + +static lw_t lw; + +/* ---- Refusal ---- */ + +/* A file from an unsupported dialect would print a line per op, so past + * LW_MAX_ERRS we stop talking and keep counting. */ +static void +lw_no(MLIR_OpHandle op, const char *why) +{ + string n; + + if (lw.nerr < LW_MAX_ERRS) { + n = op_type_to_string(MLIR_GetOpType(op)); + if (n.size == 0) + n = MLIR_GetOpName(op); /* unregistered ops keep theirs */ + fprintf(stderr, "E120: mlir: %.*s: %s\n", (int)n.size, n.str, why); + } else if (lw.nerr == LW_MAX_ERRS) { + fprintf(stderr, "E120: mlir: further refusals not printed\n"); + } + lw.nerr++; +} + +/* ---- Value map ---- */ + +static void +lw_bind(MLIR_ValueHandle v, uint32_t bv) +{ + /* A dropped binding surfaces later as an operand with no definition, which + * lw_use refuses against the wrong op, so count it here too. */ + if (!v) + return; + if (lw.nmap >= LW_MAX_VALS) { + lw.nerr++; + return; + } + lw.map[lw.nmap].key = v; + lw.map[lw.nmap].nm = MLIR_GetValueRegisterName(v); + lw.map[lw.nmap].val = bv; + lw.nmap++; +} + +/* Handle first, name second. arith.cmpi and the conversions build a second + * value object for the same %n, so identity alone loses them. Later wins. */ +static uint32_t +lw_look(MLIR_ValueHandle v) +{ + string nm; + uint32_t i; + + for (i = lw.nmap; i > 0; i--) + if (lw.map[i - 1].key == v) + return lw.map[i - 1].val; + + nm = MLIR_GetValueRegisterName(v); + if (nm.size == 0) + return BIR_VAL_NONE; + for (i = lw.nmap; i > 0; i--) + if (lw.map[i - 1].nm.size > 0 && str_eq(lw.map[i - 1].nm, nm)) + return lw.map[i - 1].val; + return BIR_VAL_NONE; +} + +/* ---- Types ---- */ + +/* No width accessor in the reader, so the textual form is what we have. + * Anything unnamed here has no BIR equivalent and is not worth guessing at. */ +static uint32_t +lw_type(MLIR_TypeHandle t) +{ + string s; + char buf[32]; + + if (!t) + return bir_type_void(lw.M); + + s = MLIR_GetTypeString(lw.ctx, t); + if (s.size == 0 || s.size >= sizeof buf) + return 0; + memcpy(buf, s.str, (size_t)s.size); + buf[s.size] = '\0'; + + if (strcmp(buf, "i1") == 0) return bir_type_int(lw.M, 1); + if (strcmp(buf, "i8") == 0) return bir_type_int(lw.M, 8); + if (strcmp(buf, "i16") == 0) return bir_type_int(lw.M, 16); + if (strcmp(buf, "i32") == 0) return bir_type_int(lw.M, 32); + if (strcmp(buf, "i64") == 0) return bir_type_int(lw.M, 64); + if (strcmp(buf, "f16") == 0) return bir_type_float(lw.M, 16); + if (strcmp(buf, "f32") == 0) return bir_type_float(lw.M, 32); + if (strcmp(buf, "f64") == 0) return bir_type_float(lw.M, 64); + if (strcmp(buf, "bf16") == 0) return bir_type_bfloat(lw.M); + /* index is whatever the target's pointer arithmetic wants, 64 for now. */ + if (strcmp(buf, "index") == 0) return bir_type_int(lw.M, 64); + + return 0; /* type 0 is the sentinel, so this reads as "no idea" */ +} + +/* ---- Emission ---- */ + +static uint32_t +lw_emit(int op, uint32_t type, int subop) +{ + bir_module_t *M = lw.M; + uint32_t idx; + bir_inst_t *I; + int k; + + if (M->num_insts >= BIR_MAX_INSTS) { + bir_pfull(M, BIR_P_INSTS); + return BIR_VAL_NONE; + } + idx = M->num_insts++; + I = &M->insts[idx]; + I->op = (uint16_t)op; + I->num_operands = 0; + I->subop = (uint8_t)subop; + I->type = type; + for (k = 0; k < BIR_OPERANDS_INLINE; k++) + I->operands[k] = BIR_VAL_NONE; + M->blocks[lw.cur_block].num_insts++; + return BIR_MAKE_VAL(idx); +} + +static void +lw_arg(uint32_t inst, uint32_t operand) +{ + bir_inst_t *I; + uint32_t idx; + + if (inst == BIR_VAL_NONE || BIR_VAL_IS_CONST(inst)) + return; + idx = BIR_VAL_INDEX(inst); + if (idx >= lw.M->num_insts) + return; + I = &lw.M->insts[idx]; + if (I->num_operands < BIR_OPERANDS_INLINE) + I->operands[I->num_operands++] = operand; +} + +/* Named because string offset 0 is a live string, so a nameless block is + * labelled with whatever went into the table first. */ +static uint32_t +lw_block(const char *name) +{ + bir_module_t *M = lw.M; + bir_func_t *F; + uint32_t idx; + + /* Block 0 is real, so a refusal answered with it hands back someone else's + * block. Record it and let bir_pchk stop the compile. */ + if (M->num_blocks >= BIR_MAX_BLOCKS) { + bir_pfull(M, BIR_P_BLOCKS); + return 0; + } + idx = M->num_blocks++; + M->blocks[idx].name = bir_add_string(M, name, (uint32_t)strlen(name)); + M->blocks[idx].first_inst = M->num_insts; + M->blocks[idx].num_insts = 0; + F = &M->funcs[lw.cur_func]; + if (F->num_blocks == 0) + F->first_block = idx; + F->num_blocks++; + return idx; +} + +/* ---- Operands ---- */ + +/* Unbound means we refused the op that should have defined it. */ +static uint32_t +lw_use(MLIR_OpHandle op, size_t i) +{ + MLIR_ValueHandle v = MLIR_GetOpOperand(op, i); + uint32_t bv = lw_look(v); + + if (bv == BIR_VAL_NONE) + lw_no(op, "operand has no lowered definition"); + return bv; +} + +/* ---- arith ---- */ + +static int +lw_pred(string p) +{ + char buf[8]; + + if (p.size == 0 || p.size >= sizeof buf) + return -1; + memcpy(buf, p.str, (size_t)p.size); + buf[p.size] = '\0'; + + if (strcmp(buf, "eq") == 0) return BIR_ICMP_EQ; + if (strcmp(buf, "ne") == 0) return BIR_ICMP_NE; + if (strcmp(buf, "slt") == 0) return BIR_ICMP_SLT; + if (strcmp(buf, "sle") == 0) return BIR_ICMP_SLE; + if (strcmp(buf, "sgt") == 0) return BIR_ICMP_SGT; + if (strcmp(buf, "sge") == 0) return BIR_ICMP_SGE; + if (strcmp(buf, "ult") == 0) return BIR_ICMP_ULT; + if (strcmp(buf, "ule") == 0) return BIR_ICMP_ULE; + if (strcmp(buf, "ugt") == 0) return BIR_ICMP_UGT; + if (strcmp(buf, "uge") == 0) return BIR_ICMP_UGE; + return -1; +} + +static int +lw_fpred(string p) +{ + char buf[8]; + + if (p.size == 0 || p.size >= sizeof buf) + return -1; + memcpy(buf, p.str, (size_t)p.size); + buf[p.size] = '\0'; + + if (strcmp(buf, "oeq") == 0) return BIR_FCMP_OEQ; + if (strcmp(buf, "one") == 0) return BIR_FCMP_ONE; + if (strcmp(buf, "olt") == 0) return BIR_FCMP_OLT; + if (strcmp(buf, "ole") == 0) return BIR_FCMP_OLE; + if (strcmp(buf, "ogt") == 0) return BIR_FCMP_OGT; + if (strcmp(buf, "oge") == 0) return BIR_FCMP_OGE; + if (strcmp(buf, "ueq") == 0) return BIR_FCMP_UEQ; + if (strcmp(buf, "une") == 0) return BIR_FCMP_UNE; + if (strcmp(buf, "ult") == 0) return BIR_FCMP_ULT; + if (strcmp(buf, "ule") == 0) return BIR_FCMP_ULE; + if (strcmp(buf, "ugt") == 0) return BIR_FCMP_UGT; + if (strcmp(buf, "uge") == 0) return BIR_FCMP_UGE; + if (strcmp(buf, "ord") == 0) return BIR_FCMP_ORD; + if (strcmp(buf, "uno") == 0) return BIR_FCMP_UNO; + return -1; +} + +/* Two operands in, one result out, which covers every arith binop we take. */ +static uint32_t +lw_bin(MLIR_OpHandle op, int birop, uint32_t ty) +{ + uint32_t a = lw_use(op, 0); + uint32_t b = lw_use(op, 1); + uint32_t r; + + if (a == BIR_VAL_NONE || b == BIR_VAL_NONE) + return BIR_VAL_NONE; + r = lw_emit(birop, ty, 0); + lw_arg(r, a); + lw_arg(r, b); + return r; +} + +static uint32_t +lw_const(MLIR_OpHandle op, uint32_t ty) +{ + MLIR_AttributeHandle a = MLIR_GetOpAttributeByName(op, "value"); + MLIR_AttrKind k; + + if (!a) { + lw_no(op, "constant with no value attribute"); + return BIR_VAL_NONE; + } + k = MLIR_GetAttributeKind(a); + + /* The result type decides, not how the literal was written. The reader + * builds `1 : f32` as an integer attribute, and trusting that put the bits + * 0x1 where 1.0f belonged, quietly. */ + if (ty < lw.M->num_types && lw.M->types[ty].kind == BIR_TYPE_FLOAT) { + if (k == MLIR_ATTR_KIND_FLOAT) + return BIR_MAKE_CONST(bir_const_float(lw.M, ty, MLIR_GetAttributeFloat(a))); + if (k == MLIR_ATTR_KIND_INTEGER) + return BIR_MAKE_CONST(bir_const_float(lw.M, ty, + (double)MLIR_GetAttributeInteger(a))); + lw_no(op, "float constant has no numeric value"); + return BIR_VAL_NONE; + } + + if (k == MLIR_ATTR_KIND_INTEGER) + return BIR_MAKE_CONST(bir_const_int(lw.M, ty, MLIR_GetAttributeInteger(a))); + if (k == MLIR_ATTR_KIND_BOOL) + return BIR_MAKE_CONST(bir_const_int(lw.M, ty, MLIR_GetAttributeBool(a) ? 1 : 0)); + + /* A float literal on an integer type is not something to round silently. */ + lw_no(op, "constant value does not match its type"); + return BIR_VAL_NONE; +} + +/* One operand, so not lw_bin, which would refuse on the missing second. */ +static uint32_t +lw_un(MLIR_OpHandle op, int birop, uint32_t ty) +{ + uint32_t a = lw_use(op, 0); + uint32_t r; + + if (a == BIR_VAL_NONE) + return BIR_VAL_NONE; + r = lw_emit(birop, ty, 0); + lw_arg(r, a); + return r; +} + +/* ---- Operations ---- */ + +static void +lw_op(MLIR_OpHandle op) +{ + MLIR_OpType kind = MLIR_GetOpType(op); + uint32_t ty = 0; + uint32_t r = BIR_VAL_NONE; + int pred; + + if (MLIR_GetOpNumResultTypes(op) > 0) { + ty = lw_type(MLIR_GetOpResult_type(op, 0)); + if (ty == 0) { + lw_no(op, "result type has no BIR equivalent"); + return; + } + } + + switch (kind) { + case OP_TYPE_ARITH_CONSTANT: r = lw_const(op, ty); break; + + case OP_TYPE_ARITH_ADDI: r = lw_bin(op, BIR_ADD, ty); break; + case OP_TYPE_ARITH_SUBI: r = lw_bin(op, BIR_SUB, ty); break; + case OP_TYPE_ARITH_MULI: r = lw_bin(op, BIR_MUL, ty); break; + case OP_TYPE_ARITH_DIVSI: r = lw_bin(op, BIR_SDIV, ty); break; + case OP_TYPE_ARITH_DIVUI: r = lw_bin(op, BIR_UDIV, ty); break; + case OP_TYPE_ARITH_REMSI: r = lw_bin(op, BIR_SREM, ty); break; + case OP_TYPE_ARITH_REMUI: r = lw_bin(op, BIR_UREM, ty); break; + case OP_TYPE_ARITH_ADDF: r = lw_bin(op, BIR_FADD, ty); break; + case OP_TYPE_ARITH_SUBF: r = lw_bin(op, BIR_FSUB, ty); break; + case OP_TYPE_ARITH_MULF: r = lw_bin(op, BIR_FMUL, ty); break; + case OP_TYPE_ARITH_DIVF: r = lw_bin(op, BIR_FDIV, ty); break; + case OP_TYPE_ARITH_ANDI: r = lw_bin(op, BIR_AND, ty); break; + case OP_TYPE_ARITH_ORI: r = lw_bin(op, BIR_OR, ty); break; + case OP_TYPE_ARITH_XORI: r = lw_bin(op, BIR_XOR, ty); break; + case OP_TYPE_ARITH_SHLI: r = lw_bin(op, BIR_SHL, ty); break; + case OP_TYPE_ARITH_SHRUI: r = lw_bin(op, BIR_LSHR, ty); break; + case OP_TYPE_ARITH_SHRSI: r = lw_bin(op, BIR_ASHR, ty); break; + + case OP_TYPE_ARITH_CMPI: + pred = lw_pred(MLIR_GetAttributeString(MLIR_GetOpAttributeByName(op, "predicate"))); + if (pred < 0) { lw_no(op, "unknown integer compare predicate"); return; } + r = lw_bin(op, BIR_ICMP, ty); + if (r != BIR_VAL_NONE) + lw.M->insts[BIR_VAL_INDEX(r)].subop = (uint8_t)pred; + break; + + case OP_TYPE_ARITH_CMPF: + pred = lw_fpred(MLIR_GetAttributeString(MLIR_GetOpAttributeByName(op, "predicate"))); + if (pred < 0) { lw_no(op, "unknown float compare predicate"); return; } + r = lw_bin(op, BIR_FCMP, ty); + if (r != BIR_VAL_NONE) + lw.M->insts[BIR_VAL_INDEX(r)].subop = (uint8_t)pred; + break; + + case OP_TYPE_ARITH_EXTSI: r = lw_un(op, BIR_SEXT, ty); break; + case OP_TYPE_ARITH_EXTUI: r = lw_un(op, BIR_ZEXT, ty); break; + case OP_TYPE_ARITH_TRUNCI: r = lw_un(op, BIR_TRUNC, ty); break; + case OP_TYPE_ARITH_SITOFP: r = lw_un(op, BIR_SITOFP, ty); break; + case OP_TYPE_ARITH_FPTOSI: r = lw_un(op, BIR_FPTOSI, ty); break; + + case OP_TYPE_RETURN: + case OP_TYPE_FUNC_RETURN: { + uint32_t v; + + r = lw_emit(BIR_RET, bir_type_void(lw.M), 0); + if (MLIR_GetOpNumOperands(op) > 0) { + v = lw_use(op, 0); + if (v != BIR_VAL_NONE) + lw_arg(r, v); + } + return; /* a return defines nothing */ + } + + default: + lw_no(op, "op is outside the accepted subset"); + return; + } + + if (r == BIR_VAL_NONE) + return; + if (MLIR_GetOpNumResults(op) > 0) + lw_bind(MLIR_GetOpResult(op, 0), r); +} + +/* ---- Functions ---- */ + +static void +lw_func(MLIR_OpHandle op) +{ + bir_module_t *M = lw.M; + bir_func_t *F; + MLIR_AttributeHandle nm; + MLIR_RegionHandle body; + MLIR_BlockHandle entry; + uint32_t ptypes[LW_MAX_PARAMS]; + uint32_t ret = 0; + size_t np, i, nb, j; + string s; + + if (M->num_funcs >= BIR_MAX_FUNCS) { + bir_pfull(M, BIR_P_FUNCS); + lw_no(op, "too many functions"); + return; + } + if (MLIR_GetOpNumRegions(op) == 0) { + /* A declaration with no body. Nothing to emit and nothing wrong. */ + return; + } + + lw.cur_func = M->num_funcs++; + F = &M->funcs[lw.cur_func]; + memset(F, 0, sizeof *F); + + nm = MLIR_GetOpAttributeByName(op, "sym_name"); + if (nm) { + s = MLIR_GetAttributeString(nm); + F->name = bir_add_string(M, s.str, (uint32_t)s.size); + } + + /* Never a kernel. MLIR marks those with a gpu.kernel attribute and the + * reader skips the attributes clause without recording it, so there is + * nothing to read and guessing would promote every helper. */ + F->cuda_flags = CUDA_DEVICE; + + body = MLIR_GetOpRegion(op, 0); + if (!body || MLIR_GetRegionNumBlocks(body) == 0) { + lw_no(op, "function body has no blocks"); + return; + } + entry = MLIR_GetRegionBlock(body, 0); + + /* The reader binds the header's arguments onto the entry block. */ + np = MLIR_GetBlockNumArgs(entry); + if (np > LW_MAX_PARAMS) { + lw_no(op, "too many parameters"); + return; + } + + lw.cur_block = lw_block("entry"); + + for (i = 0; i < np; i++) { + MLIR_ValueHandle a = MLIR_GetBlockArg(entry, i); + uint32_t t = lw_type(MLIR_GetValueType(a)); + uint32_t p; + + if (t == 0) { + lw_no(op, "parameter type has no BIR equivalent"); + return; + } + ptypes[i] = t; + p = lw_emit(BIR_PARAM, t, (int)i); + lw_bind(a, p); + } + F->num_params = (uint16_t)np; + + if (MLIR_GetOpNumResultTypes(op) > 0) + ret = lw_type(MLIR_GetOpResult_type(op, 0)); + if (ret == 0) + ret = bir_type_void(M); + F->type = bir_type_func(M, ret, ptypes, (int)np); + + /* Branches are not lowered yet, so more than one block is refused rather + * than run straight through. */ + nb = MLIR_GetRegionNumBlocks(body); + if (nb > 1) { + lw_no(op, "multi-block function bodies need scf or cf lowering"); + return; + } + + for (j = 0; j < MLIR_GetBlockNumOps(entry); j++) + lw_op(MLIR_GetBlockOp(entry, j)); + + /* Every block ends in a terminator, whatever the source did. */ + { + bir_block_t *B = &M->blocks[lw.cur_block]; + if (B->num_insts == 0 || + M->insts[B->first_inst + B->num_insts - 1].op != BIR_RET) + (void)lw_emit(BIR_RET, bir_type_void(M), 0); + } + + for (j = 0; j < F->num_blocks; j++) + F->total_insts += M->blocks[F->first_block + j].num_insts; +} + +/* ---- Entry ---- */ + +int +ml_lowr(const ml_ctx_t *C, struct bir_module *Mp) +{ + bir_module_t *M = (bir_module_t *)Mp; + MLIR_OpHandle root; + MLIR_RegionHandle r; + MLIR_BlockHandle b; + size_t i; + + root = (MLIR_OpHandle)(uintptr_t)ml_root(C); + if (!root || !M) + return -1; + + memset(&lw, 0, sizeof lw); + lw.M = M; + lw.ctx = (MLIR_Context *)ml_ctx(C); + + bir_module_init(M); + + if (MLIR_GetOpType(root) != OP_TYPE_MODULE) { + lw_no(root, "top level is not a module"); + return -1; + } + if (MLIR_GetOpNumRegions(root) == 0) + return 0; /* an empty module is a module */ + + r = MLIR_GetOpRegion(root, 0); + if (!r || MLIR_GetRegionNumBlocks(r) == 0) + return 0; + b = MLIR_GetRegionBlock(r, 0); + + for (i = 0; i < MLIR_GetBlockNumOps(b); i++) { + MLIR_OpHandle op = MLIR_GetBlockOp(b, i); + + if (MLIR_GetOpType(op) == OP_TYPE_FUNC_FUNC) + lw_func(op); + else + lw_no(op, "only func.func is accepted at module level"); + } + + if (lw.nerr > 0) { + fprintf(stderr, "E120: mlir: %u op(s) refused, nothing emitted\n", lw.nerr); + return -1; + } + return bir_pchk(M, "mlir lowering") == BC_OK ? 0 : -1; +} diff --git a/src/mlir/mlir_fe.c b/src/mlir/mlir_fe.c new file mode 100644 index 0000000..6679b16 --- /dev/null +++ b/src/mlir/mlir_fe.c @@ -0,0 +1,128 @@ +/* MLIR frontend boundary. See mlir_fe.h. The reader normally owns _start, so + * platform_*.c is built with PLATFORM_SKIP_ENTRY and we bring corec's heap up + * ourselves the first time anyone asks for a context. */ + +#include +#include + +#include +#include +#include + +#include "mlir_api.h" +#include "mlir_parser.h" +#include "mlir_fe.h" + +struct ml_ctx { + Arena *arena; + MLIR_Context ctx; + MLIR_OpHandle root; + int dead; /* a failed parse leaves the reader mid-descent */ +}; + +/* One mmap, process-wide, so this happens once however many contexts open. */ +static int ml_up = 0; + +/* Where mlir_parse_fail lands, set only across one parse. */ +static jmp_buf ml_bail; +static int ml_catching = 0; + +void +mlir_parse_fail(void) +{ + if (ml_catching) + longjmp(ml_bail, 1); + + /* Nothing to unwind to, so the standalone behaviour is the honest one. */ + exit(1); +} + +ml_ctx_t * +ml_open(size_t arena_bytes) +{ + ml_ctx_t *C; + + if (!ml_up) { + platform_init(0, NULL, NULL); + ml_up = 1; + } + + C = (ml_ctx_t *)calloc(1, sizeof *C); + if (!C) + return NULL; + + C->arena = arena_create(arena_bytes); + if (!C->arena) { + free(C); + return NULL; + } + MLIR_SetArenaAllocator(&C->ctx, C->arena); + return C; +} + +void +ml_close(ml_ctx_t *C) +{ + if (!C) + return; + + /* The interning caches hold handles into this arena. Upstream assumes one + * context per process, we do not, and a stale one is a walk through freed + * memory on the next parse rather than anything that announces itself. */ + MLIR_ResetInternRegistry(); + arena_destroy(C->arena); + free(C); +} + +int +ml_parse(ml_ctx_t *C, const char *src, size_t len) +{ + if (!C || !src || C->dead) + return -1; + + /* Clearing the interning tables is what makes a module self-contained. + * Every type it names is interned afresh into this context's arena, so + * nothing outlives its own ml_close. The cost is cross-module dedup. */ + MLIR_ResetInternRegistry(); + + /* Unwinding abandons whatever the reader had half built, which is safe + * only because it all came from our arena. The context is done after + * that though, since the reader's statics are past saving. */ + ml_catching = 1; + if (setjmp(ml_bail)) { + ml_catching = 0; + C->dead = 1; + C->root = 0; + return -1; + } + C->root = MLIR_ParseTextClassic(&C->ctx, + str_from_cstr_len_view_const(src, len)); + ml_catching = 0; + return C->root ? 0 : -1; +} + +void * +ml_root(const ml_ctx_t *C) +{ + return (C && C->root) ? (void *)C->root : NULL; +} + +void * +ml_ctx(const ml_ctx_t *C) +{ + return C ? (void *)&C->ctx : NULL; +} + +int +ml_echo(const ml_ctx_t *C, FILE *out) +{ + string s; + + if (!C || !C->root) + return -1; + + /* Const off only because the printer allocates into the arena. */ + s = MLIR_PrintOperationClassic((MLIR_Context *)&C->ctx, C->root); + fprintf(out, "%.*s", (int)s.size, s.str); + return 0; +} diff --git a/src/mlir/mlir_fe.h b/src/mlir/mlir_fe.h new file mode 100644 index 0000000..d76ee78 --- /dev/null +++ b/src/mlir/mlir_fe.h @@ -0,0 +1,29 @@ +/* MLIR frontend boundary. Booth is c99 and the vendored reader is not, so + * nothing of corec's or MLIR's may appear here. Opaque pointers only. */ + +#ifndef MLIR_FE_H +#define MLIR_FE_H + +#include +#include + +typedef struct ml_ctx ml_ctx_t; + +/* Parsing never allocates outside the arena, so a module that does not fit is + * a refusal rather than a crawl into swap. A parsed module owns everything it + * names, which costs ml_parse a reset and rules out calling it from threads. */ +ml_ctx_t *ml_open(size_t arena_bytes); +void ml_close(ml_ctx_t *C); + +/* Returns 0 on success. The reader reports its own syntax errors on stderr. */ +int ml_parse(ml_ctx_t *C, const char *src, size_t len); + +/* Reprints what was read, which is how you tell a misreading from a bad file. */ +int ml_echo(const ml_ctx_t *C, FILE *out); + +/* MLIR handles, for lower.c only. Opaque so nothing else is tempted, and null + * before a successful ml_parse. */ +void *ml_root(const ml_ctx_t *C); +void *ml_ctx(const ml_ctx_t *C); + +#endif diff --git a/src/mlir/mlir_lower.h b/src/mlir/mlir_lower.h new file mode 100644 index 0000000..d17d125 --- /dev/null +++ b/src/mlir/mlir_lower.h @@ -0,0 +1,19 @@ +/* MLIR to BIR. See lower.c. + * + * Split from mlir_fe.h so a caller that only wants to read MLIR does not have + * to pull in the IR headers. + */ + +#ifndef MLIR_LOWER_H +#define MLIR_LOWER_H + +#include "mlir_fe.h" + +struct bir_module; + +/* Fills M from the module C last parsed. Returns 0 on success. Any op outside + * the accepted subset is named on stderr and the whole lowering fails; a + * partial module is worse than none. */ +int ml_lowr(const ml_ctx_t *C, struct bir_module *M); + +#endif diff --git a/src/mlir/vendor/LICENSE.corec b/src/mlir/vendor/LICENSE.corec new file mode 100644 index 0000000..f1ebc43 --- /dev/null +++ b/src/mlir/vendor/LICENSE.corec @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ondřej Čertík + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/mlir/vendor/LICENSE.mlir b/src/mlir/vendor/LICENSE.mlir new file mode 100644 index 0000000..1786aa9 --- /dev/null +++ b/src/mlir/vendor/LICENSE.mlir @@ -0,0 +1,25 @@ +MIT License + +Copyright (c) 2025 Ondřej Čertík + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- +The files in the test_mlir/ directory were copied from public github +repositories and are licensed under their respective upstream licenses. diff --git a/src/mlir/vendor/base/arena.c b/src/mlir/vendor/base/arena.c new file mode 100644 index 0000000..fc4e000 --- /dev/null +++ b/src/mlir/vendor/base/arena.c @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include + +// All allocations will be aligned to this boundary (must be a power of two). +#define ARENA_ALIGNMENT 16 +// New chunks will be at least this large. +#define MIN_CHUNK_SIZE 4096 + +// Represents a single chunk of memory obtained from the buddy allocator. +struct arena_chunk { + struct arena_chunk *next; + // Total size of the block returned by buddy_alloc for this chunk. + size_t size; + // The data area for this chunk begins immediately after this struct. +}; + +// The main arena structure. Its definition is hidden from the public API. +struct arena_s { + struct arena_chunk *first_chunk; + struct arena_chunk *current_chunk; + char *current_ptr; + size_t remaining_in_chunk; + size_t default_chunk_size; +}; + +// Aligns a value up to the nearest multiple of ARENA_ALIGNMENT. +static inline uintptr_t align_up(uintptr_t val) { + return (val + ARENA_ALIGNMENT - 1) & ~(uintptr_t)(ARENA_ALIGNMENT - 1); +} + +Arena *arena_create(size_t initial_size) { + // Allocate the arena controller struct itself. + Arena *arena = buddy_alloc(sizeof(Arena), NULL); + if (!arena) { + FATAL_ERROR("buddy_alloc failed for Arena"); + } + + if (initial_size < MIN_CHUNK_SIZE) { + initial_size = MIN_CHUNK_SIZE; + } + arena->default_chunk_size = initial_size; + arena->first_chunk = NULL; + + // Allocate the first chunk. + // Request enough space for the chunk header, the caller's requested size, + // and any padding that might be needed to align the data pointer. + size_t requested_size = sizeof(struct arena_chunk) + initial_size + ARENA_ALIGNMENT; + size_t actual_size; + struct arena_chunk *first = buddy_alloc(requested_size, &actual_size); + if (!first) { + //buddy_free(arena); + FATAL_ERROR("buddy_alloc failed for size"); + } + first->next = NULL; + first->size = actual_size; + + // Initialize arena state to point to the start of the first chunk. + arena->first_chunk = first; + arena->current_chunk = first; + + uintptr_t data_start = align_up((uintptr_t)(first + 1)); + uintptr_t chunk_end = (uintptr_t)first + actual_size; + + arena->current_ptr = (char *)data_start; + arena->remaining_in_chunk = (data_start < chunk_end) ? (chunk_end - data_start) : 0; + + return arena; +} + +void *arena_alloc(Arena *arena, size_t size) { + assert(arena); + assert(size > 0); + + size_t aligned_size = (size + ARENA_ALIGNMENT - 1) & ~(size_t)(ARENA_ALIGNMENT - 1); + +try_alloc: + // If the current chunk has enough space, perform a simple bump allocation. + if (aligned_size <= arena->remaining_in_chunk) { + void *ptr = arena->current_ptr; + arena->current_ptr += aligned_size; + arena->remaining_in_chunk -= aligned_size; + return ptr; + } + + // Not enough space. If a next chunk already exists (from previous use), move to it. + if (arena->current_chunk && arena->current_chunk->next) { + arena->current_chunk = arena->current_chunk->next; + + struct arena_chunk* chunk = arena->current_chunk; + uintptr_t data_start = align_up((uintptr_t)(chunk + 1)); + uintptr_t chunk_end = (uintptr_t)chunk + chunk->size; + + arena->current_ptr = (char *)data_start; + arena->remaining_in_chunk = (data_start < chunk_end) ? (chunk_end - data_start) : 0; + + goto try_alloc; // Retry allocation in the next chunk. + } + + // No more reusable chunks are available, so allocate a new one. + size_t new_chunk_data_size = arena->default_chunk_size; + if (aligned_size > new_chunk_data_size) { + new_chunk_data_size = aligned_size; // Ensure the new chunk is large enough. + } + + // Request: header + data + alignment padding + // The usable data area starts at align_up(chunk + 1), so we may lose up to ARENA_ALIGNMENT bytes + size_t requested_size = sizeof(struct arena_chunk) + new_chunk_data_size + ARENA_ALIGNMENT; + + // Allocate and get the actual size buddy provides (rounded up to power-of-2) + size_t actual_size; + struct arena_chunk *new_chunk = buddy_alloc(requested_size, &actual_size); + if (!new_chunk) { + FATAL_ERROR("buddy_alloc failed"); + } + + new_chunk->next = NULL; + // Store the actual size we got from buddy, not what we requested + new_chunk->size = actual_size; + + // Link the new chunk to the end of the list. + if (arena->current_chunk) { + arena->current_chunk->next = new_chunk; + } else { + arena->first_chunk = new_chunk; + } + arena->current_chunk = new_chunk; + + // Set the allocation pointer to the start of the new chunk. + // data_start is after the arena_chunk header, aligned up + uintptr_t data_start = align_up((uintptr_t)(new_chunk + 1)); + // chunk_end is based on the actual size buddy gave us + uintptr_t chunk_end = (uintptr_t)new_chunk + actual_size; + arena->current_ptr = (char *)data_start; + arena->remaining_in_chunk = (data_start < chunk_end) ? (chunk_end - data_start) : 0; + + // Retry the allocation now that we have a new, sufficiently large chunk. + goto try_alloc; +} + +void arena_destroy(Arena *arena) { + assert(arena); + struct arena_chunk *current = arena->first_chunk; + while (current) { + struct arena_chunk *next = current->next; + buddy_free(current); + current = next; + } + buddy_free(arena); +} + +arena_pos_t arena_get_pos(Arena *arena) { + assert(arena); + return (arena_pos_t){ + .chunk = arena->current_chunk, + .ptr = arena->current_ptr + }; +} + +void arena_reset(Arena *arena, arena_pos_t pos) { + assert(arena); + assert(pos.chunk); + assert(pos.ptr); + + // Restore the state from the saved position + arena->current_chunk = pos.chunk; + arena->current_ptr = pos.ptr; + + // Recalculate the remaining size in the restored chunk + uintptr_t chunk_end = (uintptr_t)pos.chunk + pos.chunk->size; + uintptr_t current_pos = (uintptr_t)pos.ptr; + + arena->remaining_in_chunk = (current_pos < chunk_end) ? (chunk_end - current_pos) : 0; +} + +size_t arena_chunk_count(Arena *arena) { + assert(arena); + + size_t count = 0; + struct arena_chunk *chunk = arena->first_chunk; + while (chunk) { + count++; + chunk = chunk->next; + } + return count; +} + +size_t arena_current_chunk_index(Arena *arena) { + assert(arena); + assert(arena->current_chunk); + + size_t index = 0; + struct arena_chunk *chunk = arena->first_chunk; + while (chunk && chunk != arena->current_chunk) { + index++; + chunk = chunk->next; + } + return index; +} diff --git a/src/mlir/vendor/base/arena.h b/src/mlir/vendor/base/arena.h new file mode 100644 index 0000000..70e4726 --- /dev/null +++ b/src/mlir/vendor/base/arena.h @@ -0,0 +1,143 @@ +#pragma once + +#include + +// An opaque data type for the arena allocator. + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct arena_s Arena; + +// Forward-declare the internal chunk struct. This is needed for the arena_pos_t +// but keeps the full definition private to the .c file. +struct arena_chunk; + +/** + * @brief A handle representing a specific position within the arena. + * Use this to save the arena's state and later reset back to it. + */ +typedef struct { + struct arena_chunk *chunk; + char *ptr; +} arena_pos_t; + + +/** + * @brief Creates a new arena allocator. + * + * This function initializes an arena and allocates an initial memory chunk + * from the buddy allocator. + * @param initial_size The suggested size for the first chunk. The actual size + * may be larger to meet alignment and minimum size requirements. + * @return A pointer to the newly created arena, or NULL on failure. + */ +Arena *arena_create(size_t initial_size); + +/** + * @brief Allocates a block of memory from the arena. + * + * Memory is allocated using a bump pointer for high efficiency. If the current + * chunk is full, the arena will advance to the next chunk (if available after a + * reset) or allocate a new one from the buddy system. + * + * @param arena A pointer to the arena. + * @param size The number of bytes to allocate. + * @return A pointer to the allocated memory, aligned to 16 bytes. + * + * The returned pointer is always valid. If the allocation fails it aborts. + */ +void *arena_alloc(Arena *arena, size_t size); + +/** + * @brief Captures the current allocation position in the arena. + * + * @param arena A pointer to the arena. + * @return An arena_pos_t handle that can be used with arena_reset_to(). + */ +arena_pos_t arena_get_pos(Arena *arena); + +/** + * @brief Resets the arena's allocation pointer to a previously saved position. + * + * This invalidates all allocations made since the position was saved, + * making that memory available for new allocations. + * + * Chunks allocated after the saved position are NOT freed. They remain + * linked via the chunk list and will be reused by subsequent allocations: + * once the restored chunk fills up again, arena_alloc walks the existing + * `next` pointers and bump-allocates from those already-allocated chunks + * before requesting new memory from the buddy allocator. The chunks are + * only released when arena_destroy is called. + * + * @param arena A pointer to the arena. + * @param pos The saved position to restore. + */ +void arena_reset(Arena *arena, arena_pos_t pos); + +/** + * @brief Deallocates all memory used by the arena. + * + * This function iterates through all chunks owned by the arena, frees them + * using `buddy_free`, and finally frees the arena structure itself. + * The arena pointer is invalid after this call. + * + * @param arena A pointer to the arena. + */ +void arena_destroy(Arena *arena); + +/** + * @brief Returns the total number of chunks in the arena. + * + * This function walks the chunk linked list to count all chunks. + * Useful for testing and debugging to verify arena expansion. + * + * @param arena A pointer to the arena. + * @return The total number of chunks, or 0 if arena is NULL. + */ +size_t arena_chunk_count(Arena *arena); + +/** + * @brief Returns the index of the current chunk (0-based). + * + * This function walks from the first chunk to the current chunk, + * counting the number of steps. The first chunk has index 0, + * the second chunk has index 1, and so on. + * Useful for testing to verify which chunk allocations are in. + * + * @param arena A pointer to the arena. + * @return The index of the current chunk (0-based), or 0 if arena is NULL. + */ +size_t arena_current_chunk_index(Arena *arena); + +/** + * @brief Convenience macro to allocate a single element of `type` from the arena. + * + * Behaves like C++ `new type`: returns a pointer to one freshly allocated + * `type`, cast to `type *`. The memory is uninitialized. + * + * @param arena A pointer to the arena. + * @param type The type of the element to allocate. + * @return A pointer to the allocated element, cast to `type *`. + */ +#define arena_new(arena, type) \ + ((type *)arena_alloc((arena), sizeof(type))) + +/** + * @brief Convenience macro to allocate an array of `count` elements of `type` + * from the arena. + * + * Behaves like C++ `new type[count]`: returns a pointer to the first element + * of a freshly allocated array, cast to `type *`. The memory is uninitialized. + * + * @param arena A pointer to the arena. + * @param type The type of elements to allocate. + * @param count The number of elements to allocate. + * @return A pointer to the allocated array, cast to `type *`. + */ +#define arena_new_array(arena, type, count) \ + ((type *)arena_alloc((arena), sizeof(type) * (size_t)(count))) +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/assert.c b/src/mlir/vendor/base/assert.c new file mode 100644 index 0000000..3be5d93 --- /dev/null +++ b/src/mlir/vendor/base/assert.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +/* After proper strings and formatting, this should just be: + +void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function) { + printf("Assertion failed: (%s) at '%s:%u' in function '%s'\n", assertion, file, line, function); + exit(1); +} + +However, we do not want to depend on arenas working, we only want to use the +lower-level API, no other base dependencies, so that asser() can be used +anywhere in base. +*/ + +void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function) { + // Simple assertion failure handler using only base/ dependencies + writeln_loc(PLATFORM_STDERR_FD, assertion, file, line, function); + platform_exit(1); +} diff --git a/src/mlir/vendor/base/assert.h b/src/mlir/vendor/base/assert.h new file mode 100644 index 0000000..49e20d8 --- /dev/null +++ b/src/mlir/vendor/base/assert.h @@ -0,0 +1,20 @@ +#pragma once + +// Base assertion facility - self-contained, no dependencies on stdio/stdlib +// Used by base/ tests and re-exported by stdlib/assert.h + +#ifdef __cplusplus +extern "C" { +#endif + +void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function); + +#undef assert +#ifdef NDEBUG +#define assert(condition) ((void)0) +#else +#define assert(condition) ((condition) ? (void)0 : __assert_fail("Assertion failed '" #condition "'", __FILE__, __LINE__, __func__)) +#endif +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/buddy.c b/src/mlir/vendor/base/buddy.c new file mode 100644 index 0000000..0ff4456 --- /dev/null +++ b/src/mlir/vendor/base/buddy.c @@ -0,0 +1,530 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIN_PAGE_SIZE 4096UL +#define MAX_ORDER 20 // 2^20 * 4KB = 4GB + +/* + * Header for each memory block (free or allocated). + * This header is stored "inline" at the beginning of each block of memory. + */ +struct buddy_block { + // The order of the block. Positive if free, negative if allocated. + int order; + struct buddy_block *prev; + struct buddy_block *next; +}; + +struct list_head { + struct buddy_block *first; +}; + +// free_lists[i] contains a doubly-linked list of free blocks of order i. +static struct list_head free_lists[MAX_ORDER + 1]; +static void *heap_base; + +static void list_add(struct list_head *lh, struct buddy_block *p) { + p->next = lh->first; + p->prev = NULL; + if (lh->first) { + lh->first->prev = p; + } + lh->first = p; +} + +static void list_remove(struct list_head *lh, struct buddy_block *p) { + if (p->prev) { + p->prev->next = p->next; + } else { + lh->first = p->next; + } + if (p->next) { + p->next->prev = p->prev; + } +} + +static void add_memory(void *mem, size_t bytes) { + uintptr_t start = (uintptr_t)mem; + uintptr_t end = start + bytes; + + // Align start address up to a MIN_PAGE_SIZE boundary + uintptr_t mis = start % MIN_PAGE_SIZE; + if (mis) { + start += MIN_PAGE_SIZE - mis; + } + + // Carve the memory region into the largest possible power-of-two blocks + while (start + MIN_PAGE_SIZE <= end) { + int order = 0; + size_t block_size = MIN_PAGE_SIZE; + while ((block_size << 1) <= (end - start) && (start % (block_size << 1) == 0) && order < MAX_ORDER) { + block_size <<= 1; + order++; + } + + struct buddy_block *p = (struct buddy_block *)start; + p->order = order; + list_add(&free_lists[order], p); + + start += block_size; + } +} + +void buddy_init(void) { + heap_base = platform_heap_base(); + for (int o = 0; o <= MAX_ORDER; o++) { + free_lists[o].first = NULL; + } + size_t initial_size = platform_heap_size(); + if (initial_size > 0) { + add_memory(heap_base, initial_size); + } +} + +// Helper to append a string to a buffer (simple strcat that knows buffer size) +static void str_append_safe(char *dest, const char *src, size_t dest_size) { + size_t dest_len = base_strlen(dest); + size_t src_len = base_strlen(src); + size_t space_left = dest_size - dest_len - 1; // -1 for null terminator + + if (src_len > space_left) { + src_len = space_left; + } + + base_memcpy(dest + dest_len, src, src_len); + dest[dest_len + src_len] = '\0'; +} + +void buddy_print_stats() { + int fd = PLATFORM_STDOUT_FD; + writeln(fd, ""); + writeln(fd, "=== Buddy Allocator Statistics ==="); + writeln(fd, ""); + + // Calculate total free and allocated bytes per order + size_t free_counts[MAX_ORDER + 1]; + size_t allocated_counts[MAX_ORDER + 1]; + size_t total_free_bytes = 0; + size_t total_allocated_bytes = 0; + + for (int o = 0; o <= MAX_ORDER; o++) { + free_counts[o] = 0; + allocated_counts[o] = 0; + } + + // Count free blocks + for (int o = 0; o <= MAX_ORDER; o++) { + struct buddy_block *block = free_lists[o].first; + while (block) { + size_t block_size = MIN_PAGE_SIZE << o; + total_free_bytes += block_size; + free_counts[o]++; + block = block->next; + } + } + + // Count allocated blocks by scanning all committed memory + uintptr_t heap_start = (uintptr_t)heap_base; + uintptr_t heap_end = heap_start + platform_heap_size(); + + // Scan through memory looking for allocated blocks + // This is a heuristic scan - we check each MIN_PAGE_SIZE aligned address + for (uintptr_t addr = heap_start; addr < heap_end; addr += MIN_PAGE_SIZE) { + struct buddy_block *block = (struct buddy_block *)addr; + // Check if this looks like an allocated block (negative order) + if (block->order < 0) { + int order = -block->order - 1; + if (order >= 0 && order <= MAX_ORDER) { + size_t block_size = MIN_PAGE_SIZE << order; + // Verify the block is within bounds and properly aligned + if ((addr % block_size) == 0 && addr + block_size <= heap_end) { + total_allocated_bytes += block_size; + allocated_counts[order]++; + // Skip past this allocated block + addr += block_size - MIN_PAGE_SIZE; // -MIN_PAGE_SIZE because loop adds it + } + } + } + } + + size_t committed_bytes = platform_heap_size(); + + // Helper to print size_t value with label + #define PRINT_SIZE(label, value) do { \ + char buf[32]; \ + size_t len = uint64_to_str((value), buf); \ + buf[len] = '\0'; \ + write_all(fd, (ciovec_t[]){(ciovec_t){(label), base_strlen(label)}}, 1); \ + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); \ + write_all(fd, (ciovec_t[]){(ciovec_t){buf, len}}, 1); \ + write_all(fd, (ciovec_t[]){(ciovec_t){"\n", 1}}, 1); \ + } while(0) + + // Helper to print floating point MiB with 2 decimal places + #define PRINT_MIB(label, bytes) do { \ + double mib = (double)(bytes) / (1024.0 * 1024.0); \ + char buf[32]; \ + size_t len = double_to_str(mib, buf, 2); \ + buf[len] = '\0'; \ + write_all(fd, (ciovec_t[]){(ciovec_t){(label), base_strlen(label)}}, 1); \ + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); \ + write_all(fd, (ciovec_t[]){(ciovec_t){buf, len}}, 1); \ + write_all(fd, (ciovec_t[]){(ciovec_t){"\n", 1}}, 1); \ + } while(0) + + // Print memory overview + writeln(fd, "Memory Overview:"); + PRINT_SIZE(" Committed (bytes): ", committed_bytes); + PRINT_MIB(" Committed (MiB): ", committed_bytes); + PRINT_SIZE(" Free (bytes): ", total_free_bytes); + PRINT_MIB(" Free (MiB): ", total_free_bytes); + PRINT_SIZE(" Allocated (bytes): ", total_allocated_bytes); + PRINT_MIB(" Allocated (MiB): ", total_allocated_bytes); + if (committed_bytes > 0) { + double utilization = ((double)total_allocated_bytes * 100.0) / (double)committed_bytes; + char util_buf[32]; + size_t util_len = double_to_str(utilization, util_buf, 2); + util_buf[util_len] = '\0'; + write_all(fd, (ciovec_t[]){(ciovec_t){" Utilization (%): ", 21}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){util_buf, util_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){"\n", 1}}, 1); + } + writeln(fd, ""); + + // Print per-order breakdown (all orders) + writeln(fd, "Per-Order Breakdown (all orders 0-20):"); + writeln(fd, " 'Free' = blocks in free list, 'Allocated' = blocks given to user"); + writeln(fd, "Order BlockSize Free Allocated FreeMiB AllocMiB"); + writeln(fd, "----- -------------- ----- --------- -------- --------"); + + int total_orders_with_free = 0; + int total_orders_with_allocated = 0; + for (int o = 0; o <= MAX_ORDER; o++) { + if (free_counts[o] > 0) total_orders_with_free++; + if (allocated_counts[o] > 0) total_orders_with_allocated++; + } + + for (int o = 0; o <= MAX_ORDER; o++) { + size_t block_size = MIN_PAGE_SIZE << o; + size_t free_bytes = free_counts[o] * block_size; + size_t alloc_bytes = allocated_counts[o] * block_size; + + // Print order (width 5, right-aligned) + char order_str[32]; + size_t order_len = int_to_str(o, order_str); + order_str[order_len] = '\0'; + for (size_t i = order_len; i < 5; i++) { + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + } + write_all(fd, (ciovec_t[]){(ciovec_t){order_str, order_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 2}}, 1); + + // Print block size (width 14, right-aligned) + char size_str[32]; + size_t size_len = uint64_to_str(block_size, size_str); + size_str[size_len] = '\0'; + for (size_t i = size_len; i < 14; i++) { + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + } + write_all(fd, (ciovec_t[]){(ciovec_t){size_str, size_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 2}}, 1); + + // Print free count (width 5, right-aligned) + char free_count_str[32]; + size_t free_count_len = int_to_str((int)free_counts[o], free_count_str); + free_count_str[free_count_len] = '\0'; + for (size_t i = free_count_len; i < 5; i++) { + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + } + write_all(fd, (ciovec_t[]){(ciovec_t){free_count_str, free_count_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 2}}, 1); + + // Print allocated count (width 9, right-aligned) + char alloc_count_str[32]; + size_t alloc_count_len = int_to_str((int)allocated_counts[o], alloc_count_str); + alloc_count_str[alloc_count_len] = '\0'; + for (size_t i = alloc_count_len; i < 9; i++) { + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + } + write_all(fd, (ciovec_t[]){(ciovec_t){alloc_count_str, alloc_count_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 2}}, 1); + + // Print free MiB (width 9, right-aligned, 2 decimal places) + char free_mib_str[32]; + double free_mib = (double)free_bytes / (1024.0 * 1024.0); + size_t free_mib_len = double_to_str(free_mib, free_mib_str, 2); + free_mib_str[free_mib_len] = '\0'; + for (size_t i = free_mib_len; i < 9; i++) { + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + } + write_all(fd, (ciovec_t[]){(ciovec_t){free_mib_str, free_mib_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 2}}, 1); + + // Print alloc MiB (width 9, right-aligned, 2 decimal places) + char alloc_mib_str[32]; + double alloc_mib = (double)alloc_bytes / (1024.0 * 1024.0); + size_t alloc_mib_len = double_to_str(alloc_mib, alloc_mib_str, 2); + alloc_mib_str[alloc_mib_len] = '\0'; + for (size_t i = alloc_mib_len; i < 9; i++) { + write_all(fd, (ciovec_t[]){(ciovec_t){" ", 1}}, 1); + } + write_all(fd, (ciovec_t[]){(ciovec_t){alloc_mib_str, alloc_mib_len}}, 1); + + write_all(fd, (ciovec_t[]){(ciovec_t){"\n", 1}}, 1); + } + + // Print summary + writeln(fd, ""); + char summary1[128], summary2[128]; + char free_orders_str[16], alloc_orders_str[16]; + size_t free_orders_len = int_to_str(total_orders_with_free, free_orders_str); + free_orders_str[free_orders_len] = '\0'; + size_t alloc_orders_len = int_to_str(total_orders_with_allocated, alloc_orders_str); + alloc_orders_str[alloc_orders_len] = '\0'; + + base_strcpy(summary1, "Summary: "); + str_append_safe(summary1, free_orders_str, sizeof(summary1)); + str_append_safe(summary1, " orders have free blocks (buddy has memory at these sizes)", sizeof(summary1)); + writeln(fd, summary1); + + base_strcpy(summary2, " "); + str_append_safe(summary2, alloc_orders_str, sizeof(summary2)); + str_append_safe(summary2, " orders have allocated blocks (user is using these sizes)", sizeof(summary2)); + writeln(fd, summary2); + writeln(fd, ""); + + // Print alignment diagnostics for large orders + writeln(fd, "Alignment Diagnostics:"); + writeln(fd, " (Buddy allocator requires blocks to be aligned to their size)"); + uintptr_t current_top = (uintptr_t)heap_base + platform_heap_size(); + + char top_str[32]; + size_t top_len = uint64_to_str(current_top, top_str); + top_str[top_len] = '\0'; + write_all(fd, (ciovec_t[]){(ciovec_t){" Current heap top: ", 23}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){top_str, top_len}}, 1); + write_all(fd, (ciovec_t[]){(ciovec_t){"\n", 1}}, 1); + + writeln(fd, ""); + writeln(fd, " Alignment status for large orders:"); + for (int o = 9; o <= 14; o++) { + size_t alignment = MIN_PAGE_SIZE << o; + uintptr_t misalignment = current_top % alignment; + size_t size_mib = alignment >> 20; + + char line[128]; + char order_str[32], size_str[32], status[64]; + size_t order_str_len = int_to_str(o, order_str); + order_str[order_str_len] = '\0'; + size_t size_str_len = int_to_str((int)size_mib, size_str); + size_str[size_str_len] = '\0'; + + if (misalignment == 0) { + base_strcpy(status, "ALIGNED"); + } else { + char mis_str[32]; + size_t mis_str_len = uint64_to_str(misalignment, mis_str); + mis_str[mis_str_len] = '\0'; + base_strcpy(status, "MISALIGNED by "); + str_append_safe(status, mis_str, sizeof(status)); + str_append_safe(status, " bytes", sizeof(status)); + } + + base_strcpy(line, " Order "); + str_append_safe(line, order_str, sizeof(line)); + str_append_safe(line, " (", sizeof(line)); + str_append_safe(line, size_str, sizeof(line)); + str_append_safe(line, " MiB): ", sizeof(line)); + str_append_safe(line, status, sizeof(line)); + + writeln(fd, line); + } + writeln(fd, ""); + + // Print warnings + writeln(fd, "DIAGNOSIS:"); + int has_warnings = 0; + for (int o = 9; o <= MAX_ORDER; o++) { + size_t alignment = MIN_PAGE_SIZE << o; + uintptr_t misalignment = current_top % alignment; + if (misalignment != 0 && free_counts[o] == 0) { + if (!has_warnings) { + writeln(fd, " *** ALIGNMENT BUG DETECTED ***"); + writeln(fd, " Cannot allocate large blocks because heap top is misaligned."); + writeln(fd, " Even though memory is available, add_memory() cannot create"); + writeln(fd, " properly aligned blocks at these orders:"); + writeln(fd, ""); + has_warnings = 1; + } + char msg[128]; + char order_str[32], size_str[32], padding_str[32]; + size_t order_str_len = int_to_str(o, order_str); + order_str[order_str_len] = '\0'; + size_t size_str_len = int_to_str((int)(alignment >> 20), size_str); + size_str[size_str_len] = '\0'; + + // Calculate padding needed to align + size_t padding_needed = (alignment - misalignment) % alignment; + size_t padding_len = uint64_to_str(padding_needed, padding_str); + padding_str[padding_len] = '\0'; + + base_strcpy(msg, " Order "); + str_append_safe(msg, order_str, sizeof(msg)); + str_append_safe(msg, " (", sizeof(msg)); + str_append_safe(msg, size_str, sizeof(msg)); + str_append_safe(msg, " MiB): needs ", sizeof(msg)); + str_append_safe(msg, padding_str, sizeof(msg)); + str_append_safe(msg, " bytes padding", sizeof(msg)); + writeln(fd, msg); + } + } + + if (!has_warnings) { + writeln(fd, " No alignment issues detected."); + } + writeln(fd, ""); + writeln(fd, "=== End Statistics ==="); + writeln(fd, ""); + + #undef PRINT_SIZE + #undef PRINT_MIB +} + +static void *buddy_alloc_order(int order) { + assert(order >= 0 && order <= MAX_ORDER); + + // Find the smallest available block that is large enough + int current_order; + for (current_order = order; current_order <= MAX_ORDER; current_order++) { + if (free_lists[current_order].first) { + break; // Found a suitable block + } + } + + // If no block is available, grow the heap + if (current_order > MAX_ORDER) { + size_t required_size = MIN_PAGE_SIZE << order; + size_t alignment = MIN_PAGE_SIZE << order; + + // Calculate current heap top and alignment padding needed + uintptr_t current_top = (uintptr_t)heap_base + platform_heap_size(); + uintptr_t aligned_top = (current_top + alignment - 1) / alignment * alignment; + size_t padding = aligned_top - current_top; + + // Total bytes to grow: padding + required_size + size_t total_grow = padding + required_size; + + // Round up to PLATFORM_WASM_PAGE_SIZE boundary + size_t grow_by = ((total_grow + PLATFORM_WASM_PAGE_SIZE - 1) / PLATFORM_WASM_PAGE_SIZE) * PLATFORM_WASM_PAGE_SIZE; + + void *new_mem = platform_heap_grow(grow_by); + if (!new_mem) { + buddy_print_stats(); + writeln_int(PLATFORM_STDERR_FD, "order =", order); + writeln_int(PLATFORM_STDERR_FD, "required_size =", (int)required_size); + writeln_int(PLATFORM_STDERR_FD, "grow_by =", (int)grow_by); + writeln_int(PLATFORM_STDERR_FD, "padding =", (int)padding); + writeln_int(PLATFORM_STDERR_FD, "current_top % alignment =", (int)(current_top % alignment)); + FATAL_ERROR("platform_heap_grow(grow_by) failed"); + } + add_memory(new_mem, grow_by); + return buddy_alloc_order(order); // Retry allocation + } + + // We have a block of order 'current_order'. Remove it from its free list. + struct buddy_block *p = free_lists[current_order].first; + list_remove(&free_lists[current_order], p); + + // Split the block until it's the desired size + while (current_order > order) { + current_order--; + size_t half_size = MIN_PAGE_SIZE << current_order; + struct buddy_block *buddy = (struct buddy_block *)((uintptr_t)p + half_size); + buddy->order = current_order; + list_add(&free_lists[current_order], buddy); + } + + // Mark the block as allocated by making its order negative + p->order = -(order + 1); + + // Return the pointer to the memory *after* our inline header + return (void *)(p + 1); +} + +void *buddy_alloc(size_t size, size_t *actual_size) { + assert(size > 0); + + // Add space for our header to the requested size + size_t size_with_header = size + sizeof(struct buddy_block); + + // Calculate the order required for the allocation + int order = 0; + size_t block_size = MIN_PAGE_SIZE; + while (block_size < size_with_header) { + block_size <<= 1; + order++; + if (order > MAX_ORDER) { + return NULL; // Request is too large + } + } + + // If caller wants to know the actual size, calculate it + if (actual_size) { + *actual_size = block_size - sizeof(struct buddy_block); + } + + return buddy_alloc_order(order); +} + +void buddy_free(void *ptr) { + assert(ptr); + + // Get the block header from the user-provided pointer + struct buddy_block *p = ((struct buddy_block *)ptr) - 1; + + // Retrieve the original order and mark the block as free + int order = -p->order - 1; + if (order < 0 || order > MAX_ORDER) { + return; // Invalid pointer or heap corruption + } + + uintptr_t heap_end = (uintptr_t)heap_base + platform_heap_size(); + + // Coalesce with buddy if possible + while (order < MAX_ORDER) { + size_t block_size = MIN_PAGE_SIZE << order; + uintptr_t p_addr = (uintptr_t)p; + uintptr_t buddy_addr = p_addr ^ block_size; + + // Ensure the buddy is within the heap bounds before accessing it + if (buddy_addr < (uintptr_t)heap_base || buddy_addr >= heap_end) { + break; + } + + struct buddy_block *buddy = (struct buddy_block *)buddy_addr; + + if (buddy->order != order) { + break; // Buddy is not free or not the same size + } + + // Buddy is free and of the same order, so merge them. + list_remove(&free_lists[order], buddy); + + // The merged block starts at the lower of the two addresses + if (buddy_addr < p_addr) { + p = buddy; + } + + order++; + } + + p->order = order; + list_add(&free_lists[order], p); +} diff --git a/src/mlir/vendor/base/buddy.h b/src/mlir/vendor/base/buddy.h new file mode 100644 index 0000000..0691505 --- /dev/null +++ b/src/mlir/vendor/base/buddy.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void buddy_init(void); + +// Allocate memory from the buddy allocator. +// Returns NULL on allocation failure. +// If actual_size is not NULL, stores the actual usable size allocated (which may be +// larger than requested due to power-of-2 rounding). +void *buddy_alloc(size_t size, size_t *actual_size); + +void buddy_free(void *ptr); + +// Print detailed statistics about the buddy allocator state +void buddy_print_stats(); +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/exit.c b/src/mlir/vendor/base/exit.c new file mode 100644 index 0000000..55479fe --- /dev/null +++ b/src/mlir/vendor/base/exit.c @@ -0,0 +1,11 @@ +#include +#include + +void base_exit(int status) { + platform_exit(status); +} + +void base_abort(void) { + PRINT_ERR("Aborting..."); + base_exit(1); +} diff --git a/src/mlir/vendor/base/exit.h b/src/mlir/vendor/base/exit.h new file mode 100644 index 0000000..2559281 --- /dev/null +++ b/src/mlir/vendor/base/exit.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FATAL_ERROR(x) do { PRINT_ERR(x); base_abort(); } while (0) + +// Process exit for base/ +void base_exit(int status); +void base_abort(void); +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/format.c b/src/mlir/vendor/base/format.c new file mode 100644 index 0000000..2fa69ec --- /dev/null +++ b/src/mlir/vendor/base/format.c @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Inline implementation of isdigit +static inline int fmt_isdigit(int c) { + return c >= '0' && c <= '9'; +} + + +typedef struct { + char alignment; // '<', '>', '^', or '\0' + int width; // -1 if not specified + int precision; // -1 if not specified +} FormatSpec; + +// Parse format specifier +static FormatSpec parse_format_spec(string spec) { + FormatSpec fs = {.alignment = '\0', .width = -1, .precision = -1}; + const char *p = spec.str; + const char *end = spec.str + spec.size; + if (p < end) { + if (*p == '<' || *p == '>' || *p == '^') { + fs.alignment = *p++; + } + } + if (p < end && fmt_isdigit(*p)) { + fs.width = 0; + while (p < end && fmt_isdigit(*p)) { + fs.width = fs.width * 10 + (*p++ - '0'); + } + } + if (p < end && *p == '.') { + p++; + if (p < end && fmt_isdigit(*p)) { + fs.precision = 0; + while (p < end && fmt_isdigit(*p)) { + fs.precision = fs.precision * 10 + (*p++ - '0'); + } + } + } + return fs; +} + +// Core formatting function with variadic arguments +string format_explicit_varg(Arena *arena, string fmt, size_t arg_count, + va_list ap) { + Scratch scratch = scratch_begin_avoid_conflict(arena); + strbuf result = strbuf_make_cap(scratch.arena, fmt.size + 16); + const char *p = fmt.str; + const char *end = fmt.str + fmt.size; + size_t arg_index = 0; + while (p < end) { + const char *open_brace = base_memchr(p, '{', (size_t)(end - p)); + if (open_brace == NULL) { + string remaining = {.str = (char*)p, .size = (uint64_t)(end - p)}; + strbuf_append(scratch.arena, &result, remaining); + break; + } + if (open_brace > p) { + string part = {.str = (char*)p, .size = (uint64_t)(open_brace - p)}; + strbuf_append(scratch.arena, &result, part); + } + p = open_brace + 1; + if (p >= end) { + strbuf_append_char(scratch.arena, &result, '{'); + break; + } + if (*p == '{') { + strbuf_append_char(scratch.arena, &result, '{'); + p++; + continue; + } + const char *close_brace = base_memchr(p, '}', (size_t)(end - p)); + if (close_brace == NULL) { + string error = str_lit("Error: missing closing brace"); + strbuf_append(scratch.arena, &result, error); + break; + } + const char *colon = base_memchr(p, ':', (size_t)(close_brace - p)); + FormatSpec spec; + if (colon) { + string spec_str = {.str = (char*)colon + 1, .size = (uint64_t)(close_brace - (colon + 1))}; + spec = parse_format_spec(spec_str); + } else { + if (p != close_brace) { + string error = str_lit("Error: invalid format specifier"); + strbuf_append(scratch.arena, &result, error); + p = close_brace + 1; + continue; + } + spec = (FormatSpec){.alignment = '\0', .width = -1, .precision = -1}; + } + if (arg_index >= arg_count) { + FATAL_ERROR("Missing argument"); + } + ArgType type = (ArgType)va_arg(ap, int); + string s; + switch (type) { + case ARG_INT8: { + int8_t value = (int8_t)va_arg(ap, int); + s = int_to_string(scratch.arena, value); + break; + } + case ARG_UINT8: { + uint8_t value = (uint8_t)va_arg(ap, int); + s = uint_to_string(scratch.arena, value); + break; + } + case ARG_INT16: { + int16_t value = (int16_t)va_arg(ap, int); + s = int_to_string(scratch.arena, value); + break; + } + case ARG_UINT16: { + uint16_t value = (uint16_t)va_arg(ap, int); + s = uint_to_string(scratch.arena, value); + break; + } + case ARG_INT32: { + int32_t value = va_arg(ap, int32_t); + s = int_to_string(scratch.arena, value); + break; + } + case ARG_UINT32: { + uint32_t value = va_arg(ap, uint32_t); + s = uint_to_string(scratch.arena, value); + break; + } + case ARG_INT64: { + int64_t value = va_arg(ap, int64_t); + s = int_to_string(scratch.arena, value); + break; + } + case ARG_UINT64: { + uint64_t value = va_arg(ap, uint64_t); + s = uint_to_string(scratch.arena, value); + break; + } + case ARG_DOUBLE: { + double value = va_arg(ap, double); + s = double_to_string(scratch.arena, value, spec.precision); + break; + } + case ARG_STRING: { + char* value = va_arg(ap, char*); + s = str_from_cstr_view(value); + if (spec.precision >= 0 && (uint64_t)spec.precision < s.size) { + s.size = (uint64_t)spec.precision; + } + break; + } + case ARG_STRING2: { +#if defined(_WIN64) + // On Windows x64, structs > 8 bytes are passed by reference in varargs + string value = *va_arg(ap, string*); +#else + string value = va_arg(ap, string); +#endif + s = value; + if (spec.precision >= 0 && (uint64_t)spec.precision < s.size) { + s.size = (uint64_t)spec.precision; + } + break; + } + case ARG_POINTER: { + void* value = va_arg(ap, void*); + s = uint_to_string(scratch.arena, (uint64_t)value); + break; + } + case ARG_VECTOR_INT64: { +#if defined(_WIN64) + // On Windows x64, structs > 8 bytes are passed by reference in varargs + vector_i64 value = *va_arg(ap, vector_i64*); +#else + vector_i64 value = va_arg(ap, vector_i64); +#endif + strbuf vec_buf = strbuf_make_cap(scratch.arena, 32); + strbuf_append_char(scratch.arena, &vec_buf, '{'); + for (size_t i=0; i= 0 && (uint64_t)spec.precision < s.size) { + s.size = (uint64_t)spec.precision; + } + break; + } + default: + s = str_lit("Unknown type"); + } + arg_index++; + // Apply width and alignment + if (spec.alignment == '\0') { + // Right-align numeric types, left-align everything else + if (type == ARG_INT8 || type == ARG_UINT8 || + type == ARG_INT16 || type == ARG_UINT16 || + type == ARG_INT32 || type == ARG_UINT32 || + type == ARG_INT64 || type == ARG_UINT64 || + type == ARG_DOUBLE) { + spec.alignment = '>'; + } else { + spec.alignment = '<'; + } + } + if (spec.width > 0 && s.size < (uint64_t)spec.width) { + size_t pad_size = (size_t)spec.width - s.size; + char pad_char = ' '; + if (spec.alignment == '<') { + strbuf_append(scratch.arena, &result, s); + for (size_t k = 0; k < pad_size; k++) { + strbuf_append_char(scratch.arena, &result, pad_char); + } + } else if (spec.alignment == '^') { + size_t left_pad = pad_size / 2; + size_t right_pad = pad_size - left_pad; + for (size_t k = 0; k < left_pad; k++) { + strbuf_append_char(scratch.arena, &result, pad_char); + } + strbuf_append(scratch.arena, &result, s); + for (size_t k = 0; k < right_pad; k++) { + strbuf_append_char(scratch.arena, &result, pad_char); + } + } else { // '>' or default + for (size_t k = 0; k < pad_size; k++) { + strbuf_append_char(scratch.arena, &result, pad_char); + } + strbuf_append(scratch.arena, &result, s); + } + } else { + strbuf_append(scratch.arena, &result, s); + } + p = close_brace + 1; + } + if (arg_index != arg_count) { + FATAL_ERROR("Arguments do not match the format string"); + } + + // Copy final result to the supplied arena + string final_result = str_copy(arena, strbuf_to_string(result)); + scratch_end(scratch); + return final_result; +} + +string format_explicit(Arena *arena, string fmt, size_t arg_count, ...) { + va_list ap; + va_start(ap, arg_count); + string result = format_explicit_varg(arena, fmt, arg_count, ap); + va_end(ap); + return result; +} diff --git a/src/mlir/vendor/base/format.h b/src/mlir/vendor/base/format.h new file mode 100644 index 0000000..c8794d0 --- /dev/null +++ b/src/mlir/vendor/base/format.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ARG_INT8, + ARG_UINT8, + ARG_INT16, + ARG_UINT16, + ARG_INT32, + ARG_UINT32, + ARG_INT64, + ARG_UINT64, + ARG_DOUBLE, + ARG_STRING, + ARG_STRING2, + ARG_POINTER, + ARG_VECTOR_INT64 +} ArgType; + +string format_explicit_varg(Arena *arena, string fmt, size_t arg_count, + va_list ap); +string format_explicit(Arena *arena, string fmt, size_t arg_count, ...); + +#define GET_ARG_COUNT(_0, _1, _2, _3, _4, _5, _6, _7, _8, N, ...) N +#define COUNT_ARGS(...) GET_ARG_COUNT(0 __VA_OPT__(,) __VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +// Map all types to explicit ArgType enum values +// Using fixed-width types for consistency across platforms +#define A(x) _Generic((x), \ + char*: ARG_STRING, \ + string: ARG_STRING2, \ + double: ARG_DOUBLE, \ + int8_t: ARG_INT8, \ + uint8_t: ARG_UINT8, \ + int16_t: ARG_INT16, \ + uint16_t: ARG_UINT16, \ + int32_t: ARG_INT32, \ + uint32_t: ARG_UINT32, \ + int64_t: ARG_INT64, \ + uint64_t: ARG_UINT64, \ + void*: ARG_POINTER, \ + Arena*: ARG_POINTER, \ + vector_i64: ARG_VECTOR_INT64 \ + ), (x) + +#define APPLY_A0() +#define APPLY_A1(a) A(a) +#define APPLY_A2(a, b) A(a), A(b) +#define APPLY_A3(a, b, c) A(a), A(b), A(c) +#define APPLY_A4(a, b, c, d) A(a), A(b), A(c), A(d) +#define APPLY_A5(a, b, c, d, e) A(a), A(b), A(c), A(d), A(e) +#define APPLY_A6(a, b, c, d, e, f) A(a), A(b), A(c), A(d), A(e), A(f) +#define APPLY_A7(a, b, c, d, e, f, g) A(a), A(b), A(c), A(d), A(e), A(f), A(g) +#define APPLY_A8(a, b, c, d, e, f, g, h) A(a), A(b), A(c), A(d), A(e), A(f), A(g), A(h) + +#define APPLY_A_FOR_COUNT_0 APPLY_A0 +#define APPLY_A_FOR_COUNT_1 APPLY_A1 +#define APPLY_A_FOR_COUNT_2 APPLY_A2 +#define APPLY_A_FOR_COUNT_3 APPLY_A3 +#define APPLY_A_FOR_COUNT_4 APPLY_A4 +#define APPLY_A_FOR_COUNT_5 APPLY_A5 +#define APPLY_A_FOR_COUNT_6 APPLY_A6 +#define APPLY_A_FOR_COUNT_7 APPLY_A7 +#define APPLY_A_FOR_COUNT_8 APPLY_A8 + +#define CONCAT_AFTER_EXPAND(prefix, count) prefix ## count +#define APPLY_WITH_COUNT(count, ...) CONCAT_AFTER_EXPAND(APPLY_A_FOR_COUNT_, count)(__VA_ARGS__) + +#define format(arena, fmt, ...) \ + format_explicit(arena, fmt, COUNT_ARGS(__VA_ARGS__) __VA_OPT__(,) APPLY_WITH_COUNT(COUNT_ARGS(__VA_ARGS__) __VA_OPT__(,) __VA_ARGS__)) +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/hashtable.h b/src/mlir/vendor/base/hashtable.h new file mode 100644 index 0000000..9dc7c02 --- /dev/null +++ b/src/mlir/vendor/base/hashtable.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +// Helper Macros (internal use) + +#ifdef __cplusplus +extern "C" { +#endif + +#define _GV_CONCAT_IMPL(a, b) a##b +#define _GV_CONCAT(a, b) _GV_CONCAT_IMPL(a, b) + +#define _GV_CONCAT3_IMPL(a, b, c) a##b##c +#define _GV_CONCAT3(a, b, c) _GV_CONCAT3_IMPL(a, b, c) + +// Hashtable Definition Macro with Inlined Hash and Equality +#define DEFINE_HASHTABLE_FOR_TYPES(KEY_TYPE, VALUE_TYPE, NAME) \ + typedef struct _GV_CONCAT3(NAME, _, Entry) { \ + KEY_TYPE key; \ + VALUE_TYPE value; \ + int occupied; \ + } _GV_CONCAT3(NAME, _, Entry); \ + \ + typedef struct NAME { \ + _GV_CONCAT3(NAME, _, Entry) *buckets; \ + size_t num_buckets; \ + size_t size; \ + } NAME; \ + \ + static inline void _GV_CONCAT3(NAME, _, init)(Arena *arena, NAME *ht, size_t initial_buckets) { \ + ht->num_buckets = initial_buckets; \ + ht->size = 0; \ + ht->buckets = arena_new_array(arena, _GV_CONCAT3(NAME, _, Entry), initial_buckets); \ + for (size_t i = 0; i < initial_buckets; i++) { \ + ht->buckets[i].occupied = 0; \ + } \ + } \ + \ + static inline void _GV_CONCAT3(NAME, _, insert)(Arena *arena, NAME *ht, KEY_TYPE key, VALUE_TYPE value) { \ + if ((double)ht->size >= 0.75 * (double)ht->num_buckets) { \ + size_t new_num_buckets = ht->num_buckets * 2; \ + _GV_CONCAT3(NAME, _, Entry) *new_buckets = arena_new_array(arena, _GV_CONCAT3(NAME, _, Entry), new_num_buckets); \ + for (size_t i = 0; i < new_num_buckets; i++) { \ + new_buckets[i].occupied = 0; \ + } \ + for (size_t i = 0; i < ht->num_buckets; i++) { \ + if (ht->buckets[i].occupied) { \ + KEY_TYPE existing_key = ht->buckets[i].key; \ + VALUE_TYPE existing_value = ht->buckets[i].value; \ + size_t hash_value = _GV_CONCAT3(NAME, _, HASH)(existing_key); \ + size_t index = hash_value % new_num_buckets; \ + while (new_buckets[index].occupied) { \ + index = (index + 1) % new_num_buckets; \ + } \ + new_buckets[index].key = existing_key; \ + new_buckets[index].value = existing_value; \ + new_buckets[index].occupied = 1; \ + } \ + } \ + ht->buckets = new_buckets; \ + ht->num_buckets = new_num_buckets; \ + } \ + size_t hash_value = _GV_CONCAT3(NAME, _, HASH)(key); \ + size_t index = hash_value % ht->num_buckets; \ + while (ht->buckets[index].occupied) { \ + if (_GV_CONCAT3(NAME, _, EQUAL)(ht->buckets[index].key, key)) { \ + ht->buckets[index].value = value; \ + return; \ + } \ + index = (index + 1) % ht->num_buckets; \ + } \ + ht->buckets[index].key = key; \ + ht->buckets[index].value = value; \ + ht->buckets[index].occupied = 1; \ + ht->size++; \ + } \ + \ + static inline VALUE_TYPE* _GV_CONCAT3(NAME, _, get)(NAME *ht, KEY_TYPE key) { \ + size_t hash_value = _GV_CONCAT3(NAME, _, HASH)(key); \ + size_t index = hash_value % ht->num_buckets; \ + size_t start_index = index; \ + while (ht->buckets[index].occupied) { \ + if (_GV_CONCAT3(NAME, _, EQUAL)(ht->buckets[index].key, key)) { \ + return &ht->buckets[index].value; \ + } \ + index = (index + 1) % ht->num_buckets; \ + if (index == start_index) break; \ + } \ + return NULL; \ + } +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/io.c b/src/mlir/vendor/base/io.c new file mode 100644 index 0000000..1527219 --- /dev/null +++ b/src/mlir/vendor/base/io.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include + +uint32_t write_all(int fd, ciovec_t* iovs, size_t iovs_len) { + size_t i; + size_t nwritten; + uint32_t ret; + + for (i = 0; i < iovs_len; ) { + ret = platform_fd_write(fd, &iovs[i], iovs_len - i, &nwritten); + if (ret != 0) { + return ret; // Return error code + } + + // Advance through the iovecs based on how much was written + while (nwritten > 0 && i < iovs_len) { + if (nwritten >= iovs[i].buf_len) { + nwritten -= iovs[i].buf_len; + i++; + } else { + iovs[i].buf = (const uint8_t*)iovs[i].buf + nwritten; + iovs[i].buf_len -= nwritten; + nwritten = 0; + } + } + } + return 0; // Success +} + +void writeln(int fd, const char* text) { + const char *msg1 = text; + const char *msg2 = "\n"; + + ciovec_t iovs[2]; + iovs[0].buf = msg1; + iovs[0].buf_len = base_strlen(msg1); + iovs[1].buf = msg2; + iovs[1].buf_len = base_strlen(msg2); + + write_all(fd, iovs, 2); +} + +void writeln_int(int fd, const char* text, int n) { + (void)fd; /* upstream writes to stderr regardless. Left alone deliberately. */ + const char *msg1 = text; + const char *msg2 = " "; + char p[32]; size_t p_len = int_to_str(n, p); p[p_len] = '\0'; + const char *msg3 = "\n"; + + ciovec_t iovs[4]; + iovs[0].buf = msg1; + iovs[0].buf_len = base_strlen(msg1); + iovs[1].buf = msg2; + iovs[1].buf_len = base_strlen(msg2); + iovs[2].buf = p; + iovs[2].buf_len = base_strlen(p); + iovs[3].buf = msg3; + iovs[3].buf_len = base_strlen(msg3); + + write_all(PLATFORM_STDERR_FD, iovs, 4); +} + +void writeln_loc(int fd, const char *text, const char *file, unsigned int line, const char *function) { + char line_str[32]; size_t p_len = int_to_str((int)line, line_str); + line_str[p_len] = '\0'; + + const char *msg[] = {file, ":", line_str, " in ", + function, "(): ", text, "\n"}; + + ciovec_t iovs[array_size(msg)]; + for (size_t i=0; istr = bytes; + text->size = filesize+1; + scratch_end(scratch); + return true; +} + + +string read_file_ok(Arena *arena, const string filename) { + string text; + if (read_file(arena, filename, &text)) { + return text; + } else { + FATAL_ERROR("File cannot be opened."); + return text; + } +} + +void println_explicit(string fmt, size_t arg_count, ...) { + Scratch scratch = scratch_begin(); + va_list varg; + va_start(varg, arg_count); + + string text = format_explicit_varg(scratch.arena, fmt, arg_count, varg); + va_end(varg); + text = str_concat(scratch.arena, text, str_lit("\n")); + ciovec_t iov = {.buf = text.str, .buf_len = text.size}; + write_all(PLATFORM_STDOUT_FD, &iov, 1); + + scratch_end(scratch); +} diff --git a/src/mlir/vendor/base/io.h b/src/mlir/vendor/base/io.h new file mode 100644 index 0000000..eeafabe --- /dev/null +++ b/src/mlir/vendor/base/io.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +// The functions below this comment do not allocate memory (no arenas), so they +// are safe to use anywhere, including in arena / buddy allocator code, or in +// asserts. + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Writes all data from the iovecs to the specified file descriptor. + * + * This function repeatedly calls fd_write until all data is written or an error occurs. + * It updates the iovecs to skip already-written data. + * + * @param fd The file descriptor to write to. + * @param iovs Array of ciovec_t structures containing the data to write. + * @param iovs_len Number of iovecs in the array. + * @return 0 on success, or an error code if fd_write fails. + */ +uint32_t write_all(int fd, ciovec_t* iovs, size_t iovs_len); + +// Prints a single line, appends `\n` +void writeln(int fd, const char* text); + +// Prints: text + ' ' + int + '\n' +void writeln_int(int fd, const char* text, int n); + +// Prints text with location information, appends '\n' +void writeln_loc(int fd, const char *text, const char *file, unsigned int line, const char *function); + +#define PRINT_ERR(x) writeln_loc(PLATFORM_STDERR_FD, (x), __FILE__, __LINE__, __func__) + +// The functions below allocate via arenas/scratch and depend on more of base/. + +// Returns the file contents as a null-terminated string in `text`. +// Returns `true` on success, otherwise `false`. +bool read_file(Arena *arena, const string filename, string *text); +string read_file_ok(Arena *arena, const string filename); + +void println_explicit(string fmt, size_t arg_count, ...); + +#define println(fmt, ...) \ + println_explicit(fmt, COUNT_ARGS(__VA_ARGS__) __VA_OPT__(,) APPLY_WITH_COUNT(COUNT_ARGS(__VA_ARGS__) __VA_OPT__(,) __VA_ARGS__)) + +#define PRINT_LOG(x) println(str_lit("{}:{} in {}(): {}"), str_lit(__FILE__), __LINE__, str_lit(__func__), str_lit(x)) +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/math.c b/src/mlir/vendor/base/math.c new file mode 100644 index 0000000..29f9473 --- /dev/null +++ b/src/mlir/vendor/base/math.c @@ -0,0 +1,77 @@ +#include +#include + +static const float S6 = -0.01445205f; +static const float S5 = 0.09838380f; +static const float S4 = -0.01243614f; +static const float S3 = -0.64157278f; +static const float S2 = -0.00077364f; +static const float S1 = 1.5708527f; +static const float S0 = -0.000000992f; + +static const double DPI_D = 6.28318530717958647692; +static const double PI_D = 3.14159265358979323846; +static const float PI2_F = 1.57079632679f; +static const float PI_F = 3.14159265f; + +static inline float poly_sincos(float z) { + float p = S6 * z + S5; + p = p * z + S4; + p = p * z + S3; + p = p * z + S2; + p = p * z + S1; + p = p * z + S0; + return p; +} + +static inline void reduce_to_quarter(float x, float* y, float* sin_sign, float* cos_sign) { + double xd = (double)x; + double q = xd / DPI_D; + double k = base_round(q); + double rd = xd - k * DPI_D; + if (rd > PI_D) { + rd -= DPI_D; + } else if (rd < -PI_D) { + rd += DPI_D; + } + float r = (float)rd; + bool flip_sin = (r < 0.0f); + if (flip_sin) { + r = -r; + } + *sin_sign = flip_sin ? -1.0f : 1.0f; + *cos_sign = 1.0f; + if (r > PI2_F) { + *y = PI_F - r; + *cos_sign = -1.0f; + } else { + *y = r; + } +} + +float fast_sinf(float x) { + if (x == 0.0f) return 0.0f; + float y, sin_s, cos_s; + reduce_to_quarter(x, &y, &sin_s, &cos_s); + float z = y / PI2_F; + float sy = poly_sincos(z); + return sin_s * sy; +} + +float fast_cosf(float x) { + if (x == 0.0f) return 1.0f; + float y, sin_s, cos_s; + reduce_to_quarter(x, &y, &sin_s, &cos_s); + float z = y / PI2_F; + float cy = poly_sincos(1.0f - z); + return cos_s * cy; +} + +float fast_tanf(float x) { + float s = fast_sinf(x); + float c = fast_cosf(x); + if (c == 0.0f) { + return (s < 0.0f ? -INFINITY : INFINITY); + } + return s / c; +} diff --git a/src/mlir/vendor/base/math.h b/src/mlir/vendor/base/math.h new file mode 100644 index 0000000..3de843c --- /dev/null +++ b/src/mlir/vendor/base/math.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +// We build with -nostdinc / /X, so is not available. Define the +// usual C99 floating-point macros ourselves. The expression +// `(float)(1e308 * 1e308)` overflows to +inf at compile time on every +// conforming compiler (Clang, GCC, MSVC), so no compiler-specific spelling +// is needed. + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef INFINITY +#define INFINITY ((float)(1e308 * 1e308)) +#endif + +#ifndef NAN +#define NAN ((float)(INFINITY * 0.0f)) +#endif + +#ifndef HUGE_VAL +#define HUGE_VAL ((double)INFINITY) +#endif + +static inline double base_fabs(double x) { + return x < 0 ? -x : x; +} + +static inline float base_fabsf(float x) { + return x < 0 ? -x : x; +} + +// Simple round implementation. Note: Overflows for values outside [INT64_MIN, INT64_MAX]. +static inline double base_round(double x) { + return (x >= 0.0) ? (double)(int64_t)(x + 0.5) : (double)(int64_t)(x - 0.5); +} + +// Fast single-precision trigonometric functions +float fast_sinf(float x); +float fast_cosf(float x); +float fast_tanf(float x); + +// Fast square-root functions (declared in platform.h, repeated here for +// discoverability alongside the other math primitives). +double fast_sqrt(double x); +float fast_sqrtf(float x); +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/mem.c b/src/mlir/vendor/base/mem.c new file mode 100644 index 0000000..569236e --- /dev/null +++ b/src/mlir/vendor/base/mem.c @@ -0,0 +1,176 @@ +#include + +size_t base_strlen(const char* str) { + const char* s; + for (s = str; *s; ++s); + return (size_t)(s - str); +} + +char* base_strcpy(char* dest, const char* src) { + char* d = dest; + while ((*d++ = *src++) != '\0'); + return dest; +} + +int base_strcmp(const char* s1, const char* s2) { + while (*s1 && (*s1 == *s2)) { + s1++; + s2++; + } + return *(const unsigned char*)s1 - *(const unsigned char*)s2; +} + +void* base_memcpy(void* dest, const void* src, size_t n) { + unsigned char* d = (unsigned char*)dest; + const unsigned char* s = (const unsigned char*)src; + for (size_t i = 0; i < n; i++) { + d[i] = s[i]; + } + return dest; +} + +void* base_memmove(void* dest, const void* src, size_t n) { + unsigned char* d = (unsigned char*)dest; + const unsigned char* s = (const unsigned char*)src; + + if (d == s || n == 0) { + return dest; + } + + // Copy forward when regions do not overlap or destination is before source + if (d < s || d >= s + n) { + for (size_t i = 0; i < n; i++) { + d[i] = s[i]; + } + return dest; + } + + // Copy backward to handle overlapping regions safely + for (size_t i = n; i != 0; i--) { + d[i - 1] = s[i - 1]; + } + return dest; +} + +int base_memcmp(const void* s1, const void* s2, size_t n) { + const unsigned char* p1 = (const unsigned char*)s1; + const unsigned char* p2 = (const unsigned char*)s2; + for (size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] - p2[i]; + } + } + return 0; +} + +void* base_memset(void* s, int c, size_t n) { + unsigned char* p = (unsigned char*)s; + for (size_t i = 0; i < n; i++) { + p[i] = (unsigned char)c; + } + return s; +} + +void* base_memchr(const void* s, int c, size_t n) { + const unsigned char* p = (const unsigned char*)s; + for (size_t i = 0; i < n; i++) { + if (p[i] == (unsigned char)c) { + return (void*)(p + i); + } + } + return (void*)0; +} + +char* base_strchr(const char* s, int c) { + while (*s) { + if (*s == (char)c) { + return (char*)s; + } + s++; + } + // Check for null terminator match + if ((char)c == '\0') { + return (char*)s; + } + return (void*)0; +} + +char* base_strrchr(const char* s, int c) { + const char* last = (void*)0; + while (*s) { + if (*s == (char)c) { + last = s; + } + s++; + } + // Check for null terminator match + if ((char)c == '\0') { + return (char*)s; + } + return (char*)last; +} + +char* base_strncpy(char* dest, const char* src, size_t n) { + size_t i; + for (i = 0; i < n && src[i] != '\0'; i++) { + dest[i] = src[i]; + } + // Pad with null bytes if src is shorter than n + for (; i < n; i++) { + dest[i] = '\0'; + } + return dest; +} + +size_t base_strcspn(const char* s, const char* reject) { + const char* p; + const char* r; + size_t count = 0; + + for (p = s; *p != '\0'; p++) { + for (r = reject; *r != '\0'; r++) { + if (*p == *r) { + return count; + } + } + count++; + } + return count; +} + +int base_strncmp(const char* s1, const char* s2, size_t n) { + if (n == 0) { + return 0; + } + for (size_t i = 0; i < n; i++) { + if (s1[i] != s2[i]) { + return (unsigned char)s1[i] - (unsigned char)s2[i]; + } + if (s1[i] == '\0') { + return 0; + } + } + return 0; +} + +char* base_strstr(const char* haystack, const char* needle) { + if (*needle == '\0') { + return (char*)haystack; + } + + for (; *haystack != '\0'; haystack++) { + const char* h = haystack; + const char* n = needle; + + while (*h != '\0' && *n != '\0' && *h == *n) { + h++; + n++; + } + + if (*n == '\0') { + return (char*)haystack; + } + } + + return (void*)0; +} diff --git a/src/mlir/vendor/base/mem.h b/src/mlir/vendor/base/mem.h new file mode 100644 index 0000000..a94ca22 --- /dev/null +++ b/src/mlir/vendor/base/mem.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Memory and string manipulation functions for base/ +// Self-contained implementations with no external dependencies +// Prefixed with base_ to avoid conflicts with system headers + +#ifdef __cplusplus +extern "C" { +#endif + +size_t base_strlen(const char *str); +char *base_strcpy(char *dest, const char *src); +int base_strcmp(const char *s1, const char *s2); +void *base_memcpy(void *dest, const void *src, size_t n); +void *base_memmove(void *dest, const void *src, size_t n); +int base_memcmp(const void *s1, const void *s2, size_t n); +void *base_memset(void *s, int c, size_t n); +void *base_memchr(const void *s, int c, size_t n); +char *base_strchr(const char *s, int c); +char *base_strrchr(const char *s, int c); +char *base_strncpy(char *dest, const char *src, size_t n); +size_t base_strcspn(const char *s, const char *reject); +int base_strncmp(const char *s1, const char *s2, size_t n); +char *base_strstr(const char *haystack, const char *needle); +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/numconv.c b/src/mlir/vendor/base/numconv.c new file mode 100644 index 0000000..932ce7b --- /dev/null +++ b/src/mlir/vendor/base/numconv.c @@ -0,0 +1,382 @@ +#include +#include + +size_t uint64_to_str(uint64_t val, char* buf) { + if (val == 0) { + buf[0] = '0'; + return 1; + } + + size_t len = 0; + // Convert digits in reverse order + while (val > 0) { + buf[len++] = (char)('0' + (val % 10)); + val /= 10; + } + // Reverse + for (size_t i = 0; i < len / 2; i++) { + char t = buf[i]; + buf[i] = buf[len - 1 - i]; + buf[len - 1 - i] = t; + } + return len; +} + +size_t int64_to_str(int64_t val, char* buf) { + if (val < 0) { + buf[0] = '-'; + size_t len = uint64_to_str((uint64_t)(-val), buf + 1); + return len + 1; + } else { + return uint64_to_str((uint64_t)val, buf); + } +} + +size_t int_to_str(int val, char* buf) { + return int64_to_str((int64_t)val, buf); +} + +size_t double_to_str(double val, char* buf, int precision) { + // Simple implementation: handle sign, integer part, decimal point, fractional part + size_t pos = 0; + + if (val < 0) { + buf[pos++] = '-'; + val = -val; + } + + // Integer part + int64_t int_part = (int64_t)val; + pos += int64_to_str(int_part, buf + pos); + + // Default precision is 6 if not specified + if (precision < 0) precision = 6; + + if (precision > 0) { + buf[pos++] = '.'; + + // Fractional part + double frac_part = val - (double)int_part; + for (int i = 0; i < precision; i++) { + frac_part *= 10; + int digit = (int)frac_part; + buf[pos++] = (char)('0' + digit); + frac_part -= digit; + } + } + + return pos; +} + +static size_t double_to_str_g(double val, char* buf, int precision) { + if (precision < 0) precision = 6; + size_t len = double_to_str(val, buf, precision); + while (len > 0 && buf[len - 1] == '0') len--; + if (len > 0 && buf[len - 1] == '.') len--; + if (len == 0) buf[len++] = '0'; + return len; +} + +// Format `val` as `[-]d.ddde[+-]NN` (C's "%e"), with `precision` fractional +// digits in the mantissa. Always at least two exponent digits. +size_t double_to_str_e(double val, char* buf, int precision) { + size_t pos = 0; + + int neg = 0; + if (val < 0) { neg = 1; val = -val; } + if (precision < 0) precision = 6; + // Clamp precision so the integer mantissa fits in int64_t. + if (precision > 17) precision = 17; + + // Normalize to [1, 10) and compute the decimal exponent. + int exp = 0; + if (val != 0.0) { + while (val >= 10.0) { val /= 10.0; exp++; } + while (val < 1.0) { val *= 10.0; exp--; } + } + + // Compute the integer mantissa with `precision+1` digits, rounded. + double scale = 1.0; + for (int i = 0; i < precision; i++) scale *= 10.0; + int64_t mantissa = (int64_t)(val * scale + 0.5); + int64_t cutoff = 10; + for (int i = 0; i < precision; i++) cutoff *= 10; + if (mantissa >= cutoff) { mantissa /= 10; exp++; } + + if (neg) buf[pos++] = '-'; + + // First digit (integer part). + int64_t scale_i = (int64_t)scale; + int first = (int)(mantissa / scale_i); + int64_t frac = mantissa % scale_i; + if (first > 9) first = 9; + buf[pos++] = (char)('0' + first); + + if (precision > 0) { + buf[pos++] = '.'; + char digbuf[32]; + int n = 0; + int64_t r = frac; + while (r > 0) { digbuf[n++] = (char)('0' + (r % 10)); r /= 10; } + while (n < precision) digbuf[n++] = '0'; + while (n > 0) buf[pos++] = digbuf[--n]; + } + + buf[pos++] = 'e'; + if (exp < 0) { buf[pos++] = '-'; exp = -exp; } + else { buf[pos++] = '+'; } + + char digits[8]; + int n = 0; + if (exp == 0) digits[n++] = '0'; + while (exp > 0) { digits[n++] = (char)('0' + (exp % 10)); exp /= 10; } + while (n < 2) digits[n++] = '0'; + while (n > 0) buf[pos++] = digits[--n]; + + return pos; +} + +size_t uint64_to_hex_str(uint64_t val, char* buf, int uppercase) { + if (val == 0) { + buf[0] = '0'; + return 1; + } + + const char* digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; + size_t len = 0; + + // Convert digits in reverse order + while (val > 0) { + buf[len++] = digits[val & 0xF]; + val >>= 4; + } + + // Reverse + for (size_t i = 0; i < len / 2; i++) { + char t = buf[i]; + buf[i] = buf[len - 1 - i]; + buf[len - 1 - i] = t; + } + return len; +} + +// vsnprintf/snprintf implementations (only for nostdlib builds) +int base_vsnprintf(char *str, size_t size, const char *format, va_list args) { + if (size == 0) return 0; + + size_t pos = 0; + const char* p = format; + char temp_buf[32]; + + while (*p && pos < size - 1) { + if (*p == '%' && *(p + 1)) { + p++; + + // Check for length modifiers: l, ll, z + int is_long = 0; + int is_long_long = 0; + int is_size_t = 0; + + if (*p == 'l') { + p++; + is_long = 1; + if (*p == 'l') { + p++; + is_long_long = 1; + is_long = 0; + } + } else if (*p == 'z') { + p++; + is_size_t = 1; + } + + // Check for precision specifier (e.g., %.2f, %.*s) + int precision = -1; + if (*p == '.') { + p++; + if (*p == '*') { + // %.*X form: precision read from a preceding int arg. + precision = va_arg(args, int); + if (precision < 0) precision = 0; + p++; + } else { + precision = 0; + while (*p >= '0' && *p <= '9') { + precision = precision * 10 + (*p - '0'); + p++; + } + } + } + + switch (*p) { + case 'd': + case 'i': { + if (is_long_long) { + int64_t val = va_arg(args, int64_t); + size_t len = int64_to_str(val, temp_buf); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } else if (is_long) { + long val = va_arg(args, long); + size_t len = int64_to_str((int64_t)val, temp_buf); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } else { + int val = va_arg(args, int); + size_t len = int_to_str(val, temp_buf); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } + break; + } + case 'u': { + if (is_long_long) { + uint64_t val = va_arg(args, uint64_t); + size_t len = uint64_to_str(val, temp_buf); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } else if (is_long || is_size_t) { + unsigned long val = va_arg(args, unsigned long); + size_t len = uint64_to_str((uint64_t)val, temp_buf); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } else { + unsigned int val = va_arg(args, unsigned int); + size_t len = uint64_to_str((uint64_t)val, temp_buf); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } + break; + } + case 'x': + case 'X': { + if (is_long_long) { + uint64_t val = va_arg(args, uint64_t); + size_t len = uint64_to_hex_str(val, temp_buf, *p == 'X'); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } else if (is_long || is_size_t) { + unsigned long val = va_arg(args, unsigned long); + size_t len = uint64_to_hex_str((uint64_t)val, temp_buf, *p == 'X'); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } else { + unsigned int val = va_arg(args, unsigned int); + size_t len = uint64_to_hex_str(val, temp_buf, *p == 'X'); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + } + break; + } + case 'p': { + void* ptr = va_arg(args, void*); + if (pos < size - 2) { + str[pos++] = '0'; + str[pos++] = 'x'; + } + size_t len = uint64_to_hex_str((uint64_t)(uintptr_t)ptr, temp_buf, 0); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + break; + } + case 'f': { + double val = va_arg(args, double); + if (precision < 0) precision = 6; + size_t len = double_to_str(val, temp_buf, precision); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + break; + } + case 'g': { + double val = va_arg(args, double); + size_t len = double_to_str_g(val, temp_buf, precision); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + break; + } + case 'e': { + double val = va_arg(args, double); + if (precision < 0) precision = 6; + size_t len = double_to_str_e(val, temp_buf, precision); + size_t copy_len = (pos + len < size - 1) ? len : (size - 1 - pos); + for (size_t i = 0; i < copy_len; i++) { + str[pos++] = temp_buf[i]; + } + break; + } + case 's': { + const char* s = va_arg(args, char*); + if (s == NULL) s = "(null)"; + if (precision >= 0) { + // %.*s / %.Ns: emit at most `precision` chars (do not + // require NUL termination — needed for slice-style + // strings like the corec `string` { ptr, size } view). + int n = 0; + while (n < precision && pos < size - 1) { + str[pos++] = s[n++]; + } + } else { + while (*s && pos < size - 1) { + str[pos++] = *s++; + } + } + break; + } + case 'c': { + char c = (char)va_arg(args, int); + if (pos < size - 1) { + str[pos++] = c; + } + break; + } + case '%': { + str[pos++] = '%'; + break; + } + default: + // Unknown format specifier, just skip it + break; + } + p++; + } else { + str[pos++] = *p++; + } + } + + str[pos] = '\0'; + return (int)pos; +} + +// Simple snprintf implementation for base/ +// Supports: %d, %u, %f, %.Nf, %s +int base_snprintf(char *str, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = base_vsnprintf(str, size, format, args); + va_end(args); + return result; +} diff --git a/src/mlir/vendor/base/numconv.h b/src/mlir/vendor/base/numconv.h new file mode 100644 index 0000000..27071da --- /dev/null +++ b/src/mlir/vendor/base/numconv.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +// Number to string conversion functions for base/ +// Self-contained implementations with no external dependencies + +// Convert unsigned 64-bit integer to string +// Returns length of string written (not including null terminator) + +#ifdef __cplusplus +extern "C" { +#endif + +size_t uint64_to_str(uint64_t val, char* buf); + +// Convert signed 64-bit integer to string +// Returns length of string written (not including null terminator) +size_t int64_to_str(int64_t val, char* buf); + +// Convert int to string (calls int64_to_str) +// Returns length of string written (not including null terminator) +size_t int_to_str(int val, char* buf); + +// Convert double to string with specified precision +// Returns length of string written (not including null terminator) +// precision: number of decimal places (-1 for default of 6) +size_t double_to_str(double val, char* buf, int precision); +size_t double_to_str_e(double val, char* buf, int precision); + +// Convert unsigned 64-bit integer to hexadecimal string +// Returns length of string written (not including null terminator) +// uppercase: 0 for lowercase (a-f), non-zero for uppercase (A-F) +size_t uint64_to_hex_str(uint64_t val, char* buf, int uppercase); + +// Simple vsnprintf/snprintf implementations for base/ (only for nostdlib builds) +// Supports: %d, %i, %u, %ld, %li, %lu, %lld, %lli, %llu, %zu, %x, %X, %lx, %lX, %llx, %llX, %p, %c, %s, %f, %g, %.Nf, %% +// Returns number of characters written (not including null terminator) +int base_vsnprintf(char *str, size_t size, const char *format, va_list args); + +// Simple snprintf implementation for base/ +// Supports: %d, %i, %u, %ld, %li, %lu, %lld, %lli, %llu, %zu, %x, %X, %lx, %lX, %llx, %llX, %p, %c, %s, %f, %g, %.Nf, %% +// Returns number of characters written (not including null terminator) +int base_snprintf(char *str, size_t size, const char *format, ...); +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/scratch.c b/src/mlir/vendor/base/scratch.c new file mode 100644 index 0000000..717ecee --- /dev/null +++ b/src/mlir/vendor/base/scratch.c @@ -0,0 +1,35 @@ +#include +#include +#include + +Scratch scratch_begin_from_arena(Arena *arena) { + return (Scratch){.arena=arena, .saved_pos=arena_get_pos(arena)}; +} + +Scratch scratch_begin() { + return scratch_begin_avoid_conflict(NULL); +} + +Arena* scratch_arenas[2] = {NULL, NULL}; + +static void init_scratch_arenas(void) { + scratch_arenas[0] = arena_create(1024); + scratch_arenas[1] = arena_create(1024); +} + +Scratch scratch_begin_avoid_conflict(Arena *conflict) { + if (scratch_arenas[0] == NULL) { + init_scratch_arenas(); + } + for (int i = 0; i < 2; i++) { + if (scratch_arenas[i] != conflict) { + return scratch_begin_from_arena(scratch_arenas[i]); + } + } + FATAL_ERROR("Cannot find conflict-free arena."); + return (Scratch){NULL,{0}}; +} + +void scratch_end(Scratch scratch) { + arena_reset(scratch.arena, scratch.saved_pos); +} diff --git a/src/mlir/vendor/base/scratch.h b/src/mlir/vendor/base/scratch.h new file mode 100644 index 0000000..a2f9344 --- /dev/null +++ b/src/mlir/vendor/base/scratch.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + Arena *arena; + arena_pos_t saved_pos; +} Scratch; + +// Internally there are 2 scratch arenas. You can use a scratch in the +// region marked by scratch_begin*() and scratch_end(). + +// Use if there are no other arenas that you allocate from in the scratch region. +// Always returns the first scratch arena. +Scratch scratch_begin(); + +// Use if there is another arena that you allocate from in the scratch region. +// Pass the other arena as an argument `conflict`. +// Returns a scratch arena that does not conflict with `conflict`. +/* +The example below shows the case when you need to call +scratch_begin_avoid_conflict(). The inner_fn() is returning result to the caller +using the `outer_arena`, which was allocated using scratch_begin() by the caller +in `outer_fn`. The inner arena must be allocated using +scratch_begin_avoid_conflict() otherwise both the outer_temp and outer_temp2 +will point to the same memory and we get a conflict (the result of outer_temp +will be lost). + + char* inner_fn(Arena *outer_arena) { + Scratch inner = scratch_begin_avoid_conflict(outer_arena); + char *result = arena_alloc(outer_arena, 50); + ... + scratch_end(inner); + return result; + } + + void outer_fn() { + Scratch outer = scratch_begin(); + char *outer_temp = inner_fn(outer.arena); + char *outer_temp2 = arena_alloc(outer.arena, 50); + ... + scratch_end(outer); + } +*/ +Scratch scratch_begin_avoid_conflict(Arena *conflict); + +// Use if there is another arena available in the region but you do not push +// into it in the scratch region. The scratch will be created in `arena`. +Scratch scratch_begin_from_arena(Arena *arena); + +// Marks the end of the scratch region. Resets the arena that was used to +// create the scratch to the position before the scratch. +void scratch_end(Scratch scratch); +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/stdarg.h b/src/mlir/vendor/base/stdarg.h new file mode 100644 index 0000000..5c40c91 --- /dev/null +++ b/src/mlir/vendor/base/stdarg.h @@ -0,0 +1,49 @@ +#pragma once + +// Variadic arguments support +// This provides cross-platform variadic argument macros using compiler +// builtins. The typedefs use the same underlying types as the system +// on each platform (clang/gcc both implement 's +// va_list as `__builtin_va_list`; on MSVC va_list is `char*`), so they can +// safely coexist with the system header in a hosted TU. + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) +// MSVC varargs implementation +// x86: 4-byte alignment, x64/ARM64: 8-byte alignment +typedef char* va_list; + +#if defined(_M_X64) || defined(_M_ARM64) +#define _VA_ALIGN 8 +#else +#define _VA_ALIGN 4 +#endif + +#define _VA_ROUNDED_SIZE(t) ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1)) + +#ifndef va_start +#define va_start(ap, v) ((void)((ap) = (va_list)((char*)(&(v)) + _VA_ROUNDED_SIZE(v)))) +#define va_arg(ap, t) (*(t*)((ap += _VA_ROUNDED_SIZE(t)) - _VA_ROUNDED_SIZE(t))) +#define va_end(ap) ((void)((ap) = (va_list)0)) +#define va_copy(dest, src) ((dest) = (src)) +#endif + +#else +// Use compiler builtins for Clang/GCC +typedef __builtin_va_list va_list; + +#ifndef va_start +#define va_start(ap, last) __builtin_va_start((ap), (last)) +#define va_arg(ap, type) __builtin_va_arg((ap), type) +#define va_end(ap) __builtin_va_end((ap)) +#define va_copy(dest, src) __builtin_va_copy((dest), (src)) +#endif + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/strbuf.c b/src/mlir/vendor/base/strbuf.c new file mode 100644 index 0000000..6d645c0 --- /dev/null +++ b/src/mlir/vendor/base/strbuf.c @@ -0,0 +1,72 @@ +#include +#include +#include + +// Initial capacity granted on the first append when the buffer was created +// with capacity zero. Large enough that short strings never grow, small +// enough that we don't waste arena space on buffers that stay tiny. +#define STRBUF_MIN_CAP 64 + +strbuf strbuf_make(void) { + return (strbuf){NULL, 0, 0}; +} + +strbuf strbuf_make_cap(Arena *arena, uint64_t cap) { + strbuf b = (strbuf){NULL, 0, 0}; + if (cap > 0) { + b.str = arena_new_array(arena, char, cap); + b.cap = cap; + } + return b; +} + +// Grow the buffer so that at least `min_cap` total bytes are available. +// Doubling strategy: `new_cap = max(cap * 2, min_cap, STRBUF_MIN_CAP)`. +static void strbuf_grow(Arena *arena, strbuf *b, uint64_t min_cap) { + if (min_cap <= b->cap) return; + uint64_t new_cap = b->cap ? b->cap : STRBUF_MIN_CAP; + while (new_cap < min_cap) { + new_cap *= 2; + } + char *new_buf = arena_new_array(arena, char, new_cap); + if (b->size > 0) { + base_memcpy(new_buf, b->str, b->size); + } + b->str = new_buf; + b->cap = new_cap; +} + +void strbuf_reserve(Arena *arena, strbuf *b, uint64_t min_cap) { + assert(arena); + assert(b); + strbuf_grow(arena, b, min_cap); +} + +void strbuf_append_bytes(Arena *arena, strbuf *b, const void *p, uint64_t n) { + assert(arena); + assert(b); + if (n == 0) return; + strbuf_grow(arena, b, b->size + n); + base_memcpy(b->str + b->size, p, n); + b->size += n; +} + +void strbuf_append(Arena *arena, strbuf *b, string s) { + strbuf_append_bytes(arena, b, s.str, s.size); +} + +void strbuf_append_char(Arena *arena, strbuf *b, char c) { + assert(arena); + assert(b); + strbuf_grow(arena, b, b->size + 1); + b->str[b->size++] = c; +} + +void strbuf_append_cstr(Arena *arena, strbuf *b, const char *s) { + assert(s); + strbuf_append_bytes(arena, b, s, base_strlen(s)); +} + +string strbuf_to_string(strbuf b) { + return (string){b.str, b.size}; +} diff --git a/src/mlir/vendor/base/strbuf.h b/src/mlir/vendor/base/strbuf.h new file mode 100644 index 0000000..69960d8 --- /dev/null +++ b/src/mlir/vendor/base/strbuf.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Growable byte buffer with explicit capacity. +// +// `strbuf` exists for the "build up a string by appending to it" workload, +// where the read-only `string` (pointer + length view) type is the wrong +// abstraction — there is no capacity to track and every `str_concat` would +// have to copy the accumulator. +// +// The arena that owns the backing memory is NOT stored inside `strbuf`; +// callers pass it to every mutating call. This lets a caller: +// - build a temporary buffer in a scratch arena, copy the final view +// into a long-lived arena, then `scratch_end`; or +// - build directly in a long-lived arena when the result must outlive +// the call. +// +// On growth, `strbuf` allocates a fresh buffer (capacity doubled) from the +// arena and memcpys the existing contents. The old buffer is left as +// arena slack — bounded at ≤2× the final size geometrically. Use +// `strbuf_make_cap()` with a reasonable initial capacity to avoid early +// reallocations in hot loops. + +typedef struct { + char *str; // mutable buffer (NOT NUL-terminated; same convention as `string`) + uint64_t size; // current length + uint64_t cap; // allocated capacity +} strbuf; + +// Initialise to an empty, unallocated buffer (size=0, cap=0). +// Equivalent to a zero-initialised struct literal `(strbuf){0}`. +strbuf strbuf_make(void); + +// Initialise with at least `cap` bytes of capacity pre-reserved. Useful +// for hot loops and large outputs to avoid repeated doublings. If +// `cap == 0` no allocation is performed; the first append will allocate. +strbuf strbuf_make_cap(Arena *arena, uint64_t cap); + +// Append bytes. Grows by doubling when needed. +void strbuf_append(Arena *arena, strbuf *b, string s); +void strbuf_append_char(Arena *arena, strbuf *b, char c); +void strbuf_append_cstr(Arena *arena, strbuf *b, const char *s); +void strbuf_append_bytes(Arena *arena, strbuf *b, const void *p, uint64_t n); + +// Ensure the buffer has at least `min_cap` bytes of total capacity. No-op +// if already sufficient. +void strbuf_reserve(Arena *arena, strbuf *b, uint64_t min_cap); + +// View the current contents as an immutable `string` (no copy). +// Subsequent mutating calls may invalidate the returned view if growth +// moves the underlying buffer. +string strbuf_to_string(strbuf b); + +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/string.c b/src/mlir/vendor/base/string.c new file mode 100644 index 0000000..a4a87d5 --- /dev/null +++ b/src/mlir/vendor/base/string.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +string str_from_cstr_view(char *cstr) { + return (string){cstr, base_strlen(cstr)}; +} + +string str_from_cstr_len_view(char *cstr, uint64_t size) { + return (string){cstr, size}; +} + +string str_from_cstr_len_view_const(const char *cstr, uint64_t size) { + return (string){(char*)cstr, size}; +} + +char *str_to_cstr_copy(Arena *arena, string str) { + char *cstr = arena_new_array(arena, char, str.size+1); + base_memcpy(cstr, str.str, str.size); + cstr[str.size] = '\0'; + return cstr; +} + +bool str_eq(string a, string b) { + if (a.size == b.size) { + return (base_memcmp(a.str, b.str, a.size) == 0); + } else { + return false; + } +} + +string str_substr(string str, uint64_t min, uint64_t size) { + return (string){str.str+min, size}; +} + +string int_to_string(Arena *arena, int64_t value) { + char buf[32]; + size_t len = int64_to_str(value, buf); + char *str = arena_new_array(arena, char, len); + base_memcpy(str, buf, len); + return (string){str, len}; +} + +string uint_to_string(Arena *arena, uint64_t value) { + char buf[32]; + size_t len = uint64_to_str(value, buf); + char *str = arena_new_array(arena, char, len); + base_memcpy(str, buf, len); + return (string){str, len}; +} + +string double_to_string(Arena *arena, double value, int precision) { + char buf[32]; + size_t len = double_to_str(value, buf, precision); + char *str = arena_new_array(arena, char, len); + base_memcpy(str, buf, len); + return (string){str, len}; +} + +string char_to_string(Arena *arena, char c) { + char *buf = arena_new_array(arena, char, 1); + *buf = c; + return (string){buf, 1}; +} + +string str_concat(Arena *arena, string a, string b) { + if (b.size == 0) return a; + if (a.size == 0) return str_copy(arena, b); + size_t total = a.size + b.size; + char *str = arena_new_array(arena, char, total); + base_memcpy(str, a.str, a.size); + base_memcpy(str + a.size, b.str, b.size); + return (string){str, total}; +} + +string str_copy(Arena *arena, string a) { + char *str = NULL; + if (a.size > 0) { + str = arena_new_array(arena, char, a.size); + base_memcpy(str, a.str, a.size); + } + return (string){str, a.size}; +} + +uint32_t str_hash(string str) { + // FNV-1a hash + uint32_t hash = 2166136261u; + for (size_t i = 0; i < str.size; i++) { + hash ^= (uint32_t)(unsigned char)str.str[i]; + hash *= 16777619u; + } + return hash; +} diff --git a/src/mlir/vendor/base/string.h b/src/mlir/vendor/base/string.h new file mode 100644 index 0000000..5b1d5ee --- /dev/null +++ b/src/mlir/vendor/base/string.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Non-null-terminated string, represented by a pointer and a length. + +// The string is typically owned by the Arena, so `string` can be seen as a +// "view" of the string. + +typedef struct { + char *str; + uint64_t size; +} string; + +#define str_lit(S) str_from_cstr_len_view_const(S, sizeof(S)-1) + +string str_from_cstr_view(char *cstr); +string str_from_cstr_len_view(char *cstr, uint64_t size); +string str_from_cstr_len_view_const(const char *cstr, uint64_t size); +char *str_to_cstr_copy(Arena *arena, string str); +bool str_eq(string a, string b); +string str_substr(string str, uint64_t min, uint64_t max); + +string int_to_string(Arena *arena, int64_t value); +string uint_to_string(Arena *arena, uint64_t value); +string double_to_string(Arena *arena, double value, int precision); +string char_to_string(Arena *arena, char c); +string str_concat(Arena *arena, string a, string b); +string str_copy(Arena *arena, string a); +uint32_t str_hash(string str); + + + +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/types.h b/src/mlir/vendor/base/types.h new file mode 100644 index 0000000..d146276 --- /dev/null +++ b/src/mlir/vendor/base/types.h @@ -0,0 +1,132 @@ +#pragma once + +// Basic integer types and a few macros. +// +// corec defines its own size_t/uint8_t/int64_t/etc. typedefs and integer +// limit macros. These typedefs use the same underlying primitive types as +// every libc we target (size_t = unsigned long on 64-bit Unix and +// uint64_t/Windows; int64_t = long long; uint8_t = unsigned char; ...). +// C11 and C++ both permit redundant identical typedef redeclarations, so +// these definitions can coexist with system // +// in the same TU. +// +// Macros (NULL, SIZE_MAX, INT*_MAX, FLT_MAX, true/false) are guarded with +// #ifndef so they don't clash with the system definitions if those happen +// to be included first (e.g. via libc++ headers in a hosted C++ TU). +// +// The result: corec headers never include any system or libc header, while +// remaining safe to include in hosted C/C++ TUs (e.g. when bridging the +// mlir C API to upstream LLVM/MLIR). + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +// On Linux x86_64, long is 64 bits, so use unsigned long for uint64_t +// On other platforms (especially Windows), long is 32 bits, so use unsigned long long +#if defined(__linux__) && defined(__x86_64__) +typedef unsigned long uint64_t; +#else +typedef unsigned long long uint64_t; +#endif + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; + +#if defined(__linux__) && defined(__x86_64__) +typedef long int64_t; +#else +typedef signed long long int64_t; +#endif + +// Pointer-sized integer type and size types + +#if defined(_WIN32) && defined(_WIN64) + // For 64 bit Windows the long is 4 bytes, but pointer is 8 bytes + typedef uint64_t uintptr_t; + typedef int64_t ptrdiff_t; +#else + // For 32 bit platforms and wasm64 the long and a pointer is 4 bytes, for + // 64 bit macOS/Linux the long and pointer is 8 bytes + typedef unsigned long uintptr_t; + typedef long ptrdiff_t; +#endif + +#if defined(_WIN32) && defined(_WIN64) + // 64 bit Windows has 8 byte size_t (but 4 byte long) + typedef uint64_t size_t; + typedef int64_t ssize_t; +#else + // All other platforms have long and size_t the same number of bytes (4 or + // 8) + typedef unsigned long size_t; + typedef signed long ssize_t; +#endif + +#ifndef NULL +# ifdef __cplusplus +# define NULL nullptr +# else +# define NULL ((void*)0) +# endif +#endif + +#ifndef __cplusplus +// In C, bool is provided by (a macro for _Bool). Mirror that. +# ifndef bool +# define bool _Bool +# endif +# ifndef true +# define true 1 +# endif +# ifndef false +# define false 0 +# endif +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +#ifndef INT8_C +#define INT8_C(value) value +#define UINT8_C(value) value##u +#define INT16_C(value) value +#define UINT16_C(value) value##u +#define INT32_C(value) value +#define UINT32_C(value) value##u +#define INT64_C(value) value##ll +#define UINT64_C(value) value##ull +#define INTMAX_C(value) INT64_C(value) +#define UINTMAX_C(value) UINT64_C(value) +#endif + +#ifndef UINT16_MAX +#define UINT16_MAX ((uint16_t)0xFFFFu) +#endif +#ifndef INT32_MAX +#define INT32_MAX ((int32_t)0x7FFFFFFF) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX ((uint32_t)0xFFFFFFFFu) +#endif +#ifndef INT64_MAX +#define INT64_MAX ((int64_t)0x7FFFFFFFFFFFFFFFll) +#endif +#ifndef UINT64_MAX +#define UINT64_MAX ((uint64_t)0xFFFFFFFFFFFFFFFFull) +#endif +#ifndef FLT_MAX +#define FLT_MAX 3.402823466e+38F +#endif + +#define array_size(a) (sizeof(a) / sizeof((a)[0])) + +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/base/vector.h b/src/mlir/vendor/base/vector.h new file mode 100644 index 0000000..332d1dd --- /dev/null +++ b/src/mlir/vendor/base/vector.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include + +// --- Helper Macros (internal use) --- + +#ifdef __cplusplus +extern "C" { +#endif + +#define _GV_CONCAT_IMPL(a, b) a##b +#define _GV_CONCAT(a, b) _GV_CONCAT_IMPL(a, b) + +#define _GV_CONCAT3_IMPL(a, b, c) a##b##c +#define _GV_CONCAT3(a, b, c) _GV_CONCAT3_IMPL(a, b, c) + +// --- Conditional Compilation Helper for WITH_BASE_ASSERT --- +#if defined(WITH_BASE_ASSERT) + #define IF_GENERIC_VECTOR_WITH_BASE_ASSERT(code) code + // This constant is used for assertion checks. + // Declared static const for internal linkage, avoiding multiple definition errors. + static const int GV_INTERNAL_RESERVE_CALLED_MAGIC = 0xDEADBEEF; +#else + #define IF_GENERIC_VECTOR_WITH_BASE_ASSERT(code) +#endif + +// --- Main Macro to Define a Vector Type and its Functions --- +// TYPE: The data type to be stored (e.g., int, MyStructA). +// NAME: A prefix for the generated struct and function names (e.g., IntVec, MyStructAVec). +// - Struct type will be: NAME +// - Functions will be: NAME_init, NAME_reserve, NAME_push_back. +#define DEFINE_VECTOR_FOR_TYPE(TYPE, NAME) \ + \ + /* Vector Struct Definition */ \ + typedef struct NAME { \ + TYPE *data; \ + size_t size; \ + size_t max; \ + /* This field is conditionally compiled based on WITH_BASE_ASSERT */ \ + IF_GENERIC_VECTOR_WITH_BASE_ASSERT(int reserve_called_flag;) \ + } NAME; \ + \ + /* Reserves memory for at least 'new_max_capacity' elements. */ \ + /* Resets size to 0. Old data is not preserved. */ \ + static inline void _GV_CONCAT3(NAME, _, reserve)(Arena *arena, NAME *vec, size_t new_max_capacity) { \ + vec->size = 0; \ + if (new_max_capacity <= 0) new_max_capacity = 1; /* Minimum capacity of 1 */ \ + vec->data = arena_new_array(arena, TYPE, new_max_capacity); \ + vec->max = new_max_capacity; \ + IF_GENERIC_VECTOR_WITH_BASE_ASSERT(vec->reserve_called_flag = GV_INTERNAL_RESERVE_CALLED_MAGIC;) \ + } \ + \ + /* Adds an element to the end of the vector, resizing if necessary. */ \ + static inline void _GV_CONCAT3(NAME, _, push_back)(Arena *arena, NAME *vec, TYPE value) { \ + IF_GENERIC_VECTOR_WITH_BASE_ASSERT( \ + assert(vec->reserve_called_flag == GV_INTERNAL_RESERVE_CALLED_MAGIC && \ + "Vector reserve() not called before push_back()."); \ + ) \ + if (vec->size == vec->max) { \ + size_t new_max_capacity = 2 * vec->max; \ + TYPE* new_data = arena_new_array(arena, TYPE, new_max_capacity); \ + base_memcpy(new_data, vec->data, sizeof(TYPE) * vec->size); \ + vec->data = new_data; \ + vec->max = new_max_capacity; \ + } \ + vec->data[vec->size] = value; \ + vec->size++; \ + } + + + +DEFINE_VECTOR_FOR_TYPE(int64_t, vector_i64) +#ifdef __cplusplus +} +#endif diff --git a/src/mlir/vendor/mlir_api.h b/src/mlir/vendor/mlir_api.h new file mode 100644 index 0000000..9b13578 --- /dev/null +++ b/src/mlir/vendor/mlir_api.h @@ -0,0 +1,1104 @@ +// Public MLIR C API declarations without exposing internal data structures. +// Implementations can back these APIs with different MLIR representations. + +#pragma once + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ----------------------------------------------------------------------------- +// Handle types - opaque integer handles replacing raw pointers +// ----------------------------------------------------------------------------- + +typedef uintptr_t MLIR_OpHandle; +typedef uintptr_t MLIR_RegionHandle; +typedef uintptr_t MLIR_BlockHandle; +typedef uintptr_t MLIR_ValueHandle; +typedef uintptr_t MLIR_TypeHandle; +typedef uintptr_t MLIR_AttributeHandle; +typedef uintptr_t MLIR_LocationHandle; + +#define MLIR_INVALID_HANDLE 0 + +typedef struct MLIR_Context { + Arena *arena; + // When true, MLIR_CreateOp* skips building the per-operand def-use + // chains (UseNode allocations + the parallel operand_uses / + // successor_operand_uses arrays). The def-use machinery is only needed + // by the cf->scf and lower-to-LLVM passes; the wasm -> wasmstack -> + // wasmssa -> llvm -> aarch64 -> macho pipeline never queries uses, so + // disabling it there removes a large per-op memory overhead. Defaults + // to false (tracking on) so all existing callers are unaffected. + bool no_def_use_tracking; + // When non-NULL, the process-wide type/struct intern registry allocates + // its IR_Type objects AND its handle arrays into this arena instead of + // `arena`. The streaming llvm->aarch64->macho backend lowers each function + // into a throwaway temp arena (swapping `arena`) but must keep interned + // types — which are cached in the global registry across functions — in a + // persistent arena so cached handles never dangle after a temp arena is + // released. Defaults to NULL (= use `arena`, no behaviour change). + Arena *type_arena; +} MLIR_Context; + +typedef struct MLIR_LocationMap { + void *impl; +} MLIR_LocationMap; + +size_t MLIR_GetLocationMapSize(const MLIR_LocationMap *location_map); +size_t MLIR_CollectLocationMap(const MLIR_LocationMap *location_map, string *out_keys, MLIR_LocationHandle *out_locs, size_t max); + +// Vector helpers used by the parser implementation +DEFINE_VECTOR_FOR_TYPE(MLIR_OpHandle, VecOp) +DEFINE_VECTOR_FOR_TYPE(MLIR_ValueHandle, VecValue) +DEFINE_VECTOR_FOR_TYPE(MLIR_BlockHandle, VecBlock) +DEFINE_VECTOR_FOR_TYPE(MLIR_AttributeHandle, VecAttribute) + +// ----------------------------------------------------------------------------- +// Operation kinds +// ----------------------------------------------------------------------------- + +typedef enum { + // Core ops + OP_TYPE_UNREGISTERED = 0, + OP_TYPE_MODULE, + + // Arithmetic dialect + OP_TYPE_ARITH_ADDI, + OP_TYPE_ARITH_SUBI, + OP_TYPE_ARITH_MULI, + OP_TYPE_ARITH_DIVI, + OP_TYPE_ARITH_ADDF, + OP_TYPE_ARITH_SUBF, + OP_TYPE_ARITH_MULF, + OP_TYPE_ARITH_DIVF, + OP_TYPE_ARITH_CONSTANT, + OP_TYPE_ARITH_CMPI, + OP_TYPE_ARITH_CMPF, + OP_TYPE_ARITH_SELECT, + OP_TYPE_ARITH_BITCAST, + OP_TYPE_ARITH_SITOFP, + OP_TYPE_ARITH_FPTOSI, + OP_TYPE_ARITH_INDEX_CAST, + OP_TYPE_ARITH_EXTSI, + OP_TYPE_ARITH_TRUNCI, + OP_TYPE_ARITH_EXTF, + OP_TYPE_ARITH_TRUNCF, + OP_TYPE_ARITH_EXTUI, + OP_TYPE_ARITH_MAXF, + OP_TYPE_ARITH_DIVSI, + OP_TYPE_ARITH_REMSI, + OP_TYPE_ARITH_DIVUI, + OP_TYPE_ARITH_REMUI, + OP_TYPE_ARITH_SHRUI, + OP_TYPE_ARITH_ORI, + OP_TYPE_ARITH_MINSI, + OP_TYPE_ARITH_ANDI, + + // Math dialect + OP_TYPE_MATH_EXP, + OP_TYPE_MATH_LOG, + + // Memory dialect + OP_TYPE_MEMREF_LOAD, + OP_TYPE_MEMREF_STORE, + OP_TYPE_MEMREF_ALLOC, + OP_TYPE_MEMREF_DEALLOC, + + // Control flow + OP_TYPE_CF_BR, + OP_TYPE_CF_COND_BR, + OP_TYPE_CF_SWITCH, + + // Function dialect + OP_TYPE_FUNC_FUNC, + OP_TYPE_FUNC_RETURN, + OP_TYPE_FUNC_CALL, + OP_TYPE_FUNC_CALL_INDIRECT, + OP_TYPE_FUNC_CONSTANT, + OP_TYPE_UNREALIZED_CONVERSION_CAST, + + // SCF dialect + OP_TYPE_SCF_FOR, + OP_TYPE_SCF_WHILE, + OP_TYPE_SCF_IF, + OP_TYPE_SCF_YIELD, + OP_TYPE_SCF_CONDITION, + OP_TYPE_SCF_INDEX_SWITCH, + + // Triton dialect + OP_TYPE_TT_GET_PROGRAM_ID, + OP_TYPE_TT_LOAD, + OP_TYPE_TT_STORE, + OP_TYPE_TT_MAKE_RANGE, + OP_TYPE_TT_SPLAT, + OP_TYPE_TT_ADDPTR, + OP_TYPE_TT_RETURN, + OP_TYPE_TT_FUNC, + OP_TYPE_TT_CALL, + OP_TYPE_TT_REDUCE, + OP_TYPE_TT_BROADCAST, + OP_TYPE_TT_EXPAND_DIMS, + OP_TYPE_TT_DOT, + OP_TYPE_TT_PURE_EXTERN_ELEMENTWISE, + + // GPU dialect + OP_TYPE_GPU_LAUNCH, + + // Affine dialect + OP_TYPE_AFFINE_FOR, + OP_TYPE_AFFINE_LOAD, + + // Vector dialect + OP_TYPE_VECTOR_PRINT, + + // Standard dialect + OP_TYPE_STD_CONSTANT, + OP_TYPE_STD_RETURN, + + // Tensor dialect + OP_TYPE_TENSOR_EXTRACT, + OP_TYPE_TENSOR_SPLAT, + OP_TYPE_TENSOR_COLLAPSE_SHAPE, + + // Linalg dialect + OP_TYPE_LINALG_FILL, + OP_TYPE_LINALG_COPY, + + // Index dialect + OP_TYPE_INDEX_CONSTANT, + + // LLVM dialect + OP_TYPE_LLVM_MLIR_UNDEF, + OP_TYPE_LLVM_ALLOCA, + OP_TYPE_LLVM_LOAD, + OP_TYPE_LLVM_STORE, + OP_TYPE_LLVM_GEP, + OP_TYPE_LLVM_MLIR_ZERO, + OP_TYPE_LLVM_MLIR_CONSTANT, + OP_TYPE_LLVM_ICMP, + OP_TYPE_LLVM_MLIR_ADDRESSOF, + OP_TYPE_LLVM_MLIR_GLOBAL, + OP_TYPE_LLVM_RETURN, + OP_TYPE_LLVM_PTRTOINT, + OP_TYPE_LLVM_FUNC, + OP_TYPE_LLVM_CALL, + OP_TYPE_LLVM_SEXT, + OP_TYPE_LLVM_ADD, + OP_TYPE_LLVM_SUB, + OP_TYPE_LLVM_MUL, + OP_TYPE_LLVM_SDIV, + OP_TYPE_LLVM_UDIV, + OP_TYPE_LLVM_SREM, + OP_TYPE_LLVM_UREM, + OP_TYPE_LLVM_AND, + OP_TYPE_LLVM_OR, + OP_TYPE_LLVM_XOR, + OP_TYPE_LLVM_SHL, + OP_TYPE_LLVM_LSHR, + OP_TYPE_LLVM_ASHR, + OP_TYPE_LLVM_TRUNC, + OP_TYPE_LLVM_ZEXT, + OP_TYPE_ARITH_XORI, + OP_TYPE_ARITH_SHLI, + OP_TYPE_ARITH_SHRSI, + + // Return operations + OP_TYPE_RETURN, + OP_TYPE_TT_REDUCE_RETURN, + + // ------------------------------------------------------------------------- + // wasmssa dialect — high-level SSA-form WebAssembly ops. Produced by + // the LLVM-dialect -> wasmssa lowering pass and consumed by the + // wasmssa -> wasmstack stackification pass. All values are SSA; + // there are no explicit local.get / local.set / local.tee here. + // + // Op operand-order convention (non-commutative ops): + // sub: %r = wasmssa.sub %lhs, %rhs ; emits "lhs rhs i32.sub" + // load: %r = wasmssa.load %addr ; offset/align as attrs + // store: wasmssa.store %addr, %val + // ------------------------------------------------------------------------- + OP_TYPE_WASMSSA_FUNC, + OP_TYPE_WASMSSA_IMPORT_FUNC, + OP_TYPE_WASMSSA_IMPORT_GLOBAL, + OP_TYPE_WASMSSA_CONST, + OP_TYPE_WASMSSA_ADD, + OP_TYPE_WASMSSA_SUB, + OP_TYPE_WASMSSA_BINOP, + OP_TYPE_WASMSSA_UNOP, + OP_TYPE_WASMSSA_LOAD, + OP_TYPE_WASMSSA_STORE, + OP_TYPE_WASMSSA_GLOBAL_GET, + OP_TYPE_WASMSSA_GLOBAL_SET, + OP_TYPE_WASMSSA_EXTEND_I32_S, + OP_TYPE_WASMSSA_RETURN, + OP_TYPE_WASMSSA_CALL, + // Region-bearing structured control flow. Cross-CF value passing is + // modelled via MLIR block arguments on the region's entry block (for + // loops) and via the variadic operands of block_return / br (for all + // three op kinds). Results of the region-bearing op carry the values + // that flow out via fall-through or `br` to its label. + // + // %r:R* = wasmssa.block () : (R*) { ... } + // %r:R* = wasmssa.loop (init:T*) : (R*) { ^entry(a:T*): ... } + // %r:R* = wasmssa.if (%cond:i32) : (R*) { then } else { else } + // + // Region terminators: block_return | br | br_if (non-terminator) | + // unreachable | return. + OP_TYPE_WASMSSA_BLOCK, + OP_TYPE_WASMSSA_LOOP, + OP_TYPE_WASMSSA_IF, + // Region terminator carrying the values that flow out of the enclosing + // wasmssa.block / wasmssa.loop / wasmssa.if via fall-through. + OP_TYPE_WASMSSA_BLOCK_RETURN, + // Terminator emitting wasm `unreachable` (0x00). Used after wasmssa.loop + // when the loop never falls through (every back-edge `br 0` or outward + // `br N` exits the loop's own end). + OP_TYPE_WASMSSA_UNREACHABLE, + // Branch carrying variadic values matching the target label's signature + // (depth-relative). Terminator. + OP_TYPE_WASMSSA_BR, + // Conditional branch carrying only the condition; no value operands. + // For value-carrying conditional branches we use `wasmssa.if %cond { br N (vals) }`. + OP_TYPE_WASMSSA_BR_IF, + OP_TYPE_WASMSSA_SELECT, + OP_TYPE_WASMSSA_EQZ, + OP_TYPE_WASMSSA_ADDRESSOF, + // Function-pointer support: FUNC_ADDR pushes the table-index of a + // named function (lowered via R_WASM_TABLE_INDEX_SLEB); CALL_INDIRECT + // pops args + table-index and dispatches via wasm `call_indirect`. + OP_TYPE_WASMSSA_FUNC_ADDR, + OP_TYPE_WASMSSA_CALL_INDIRECT, + // Wasm function-locals access. Each function has a fixed set of + // typed locals (params + declared locals). LOCAL_GET reads slot + // `local_idx`, LOCAL_SET writes it. Locals are NEVER address-taken + // in wasm semantics, so they live in a native (non-linmem) stack + // frame that the llvm->aarch64 backend allocates. The lifter used + // to spill them into linmem cells (8 bytes each), which inflated + // the wasm stack by ~20x and forced unrealistic stack-size grows. + // attrs: valtype (i32 byte: i32|i64|f32|f64), local_idx (i32). + OP_TYPE_WASMSSA_LOCAL_GET, + OP_TYPE_WASMSSA_LOCAL_SET, + // WebAssembly memory instructions: `memory.size` (0x3F 0x00) and + // `memory.grow` (0x40 0x00). MEMORY_SIZE has no operands and an i32 + // result (size in pages). MEMORY_GROW takes one i32 operand (pages + // to grow by) and returns the previous size in pages (or -1 on + // failure) as i32. Both carry a trailing single-byte immediate + // identifying the memory index (always 0 for the default memory). + OP_TYPE_WASMSSA_MEMORY_SIZE, + OP_TYPE_WASMSSA_MEMORY_GROW, + + // ------------------------------------------------------------------------- + // wasmstack dialect — low-level stack-machine WebAssembly ops. 1:1 + // with the wasm bytecode opcodes. Produced by the wasmssa -> wasmstack + // stackification pass and consumed by the binary emitter. + // ------------------------------------------------------------------------- + OP_TYPE_WASMSTACK_FUNC, + OP_TYPE_WASMSTACK_IMPORT_FUNC, + OP_TYPE_WASMSTACK_IMPORT_GLOBAL, + OP_TYPE_WASMSTACK_LOCAL_GET, + OP_TYPE_WASMSTACK_LOCAL_SET, + OP_TYPE_WASMSTACK_LOCAL_TEE, + OP_TYPE_WASMSTACK_CONST, + OP_TYPE_WASMSTACK_ADD, + OP_TYPE_WASMSTACK_SUB, + OP_TYPE_WASMSTACK_BINOP, + OP_TYPE_WASMSTACK_UNOP, + OP_TYPE_WASMSTACK_LOAD, + OP_TYPE_WASMSTACK_STORE, + OP_TYPE_WASMSTACK_GLOBAL_GET, + OP_TYPE_WASMSTACK_GLOBAL_SET, + OP_TYPE_WASMSTACK_EXTEND_I32_S, + OP_TYPE_WASMSTACK_RETURN, + OP_TYPE_WASMSTACK_CALL, + // Structured-CF + select. + OP_TYPE_WASMSTACK_BLOCK, + OP_TYPE_WASMSTACK_LOOP, + OP_TYPE_WASMSTACK_IF, + OP_TYPE_WASMSTACK_ELSE, + OP_TYPE_WASMSTACK_END, + OP_TYPE_WASMSTACK_BR, + OP_TYPE_WASMSTACK_BR_IF, + // Terminator emitting wasm `unreachable` (0x00). + OP_TYPE_WASMSTACK_UNREACHABLE, + OP_TYPE_WASMSTACK_SELECT, + OP_TYPE_WASMSTACK_EQZ, + OP_TYPE_WASMSTACK_ADDRESSOF, + OP_TYPE_WASMSTACK_FUNC_ADDR, + OP_TYPE_WASMSTACK_CALL_INDIRECT, + // 1:1 wasmstack mirrors of OP_TYPE_WASMSSA_MEMORY_{SIZE,GROW}. + OP_TYPE_WASMSTACK_MEMORY_SIZE, + OP_TYPE_WASMSTACK_MEMORY_GROW, + // `drop` (wasm 0x1a): pop one stack value, no result. Only used by + // the wasm -> wasmstack lifter; the C-frontend wasmssa pipeline + // never emits this op. + OP_TYPE_WASMSTACK_DROP, + // `br_table`: emitted by clang for switch dispatch in linked + // runtime objects. Carries a comma-separated list of target depths + // plus a default depth. + OP_TYPE_WASMSTACK_BR_TABLE, + // Module-level data segment lifted from a wasm DATA section. The + // wasmstack -> wasmssa pass converts this into a wasmssa.import_global + // sized appropriately. + OP_TYPE_WASMSTACK_DATA_SEGMENT, + // Module-level global declaration with an initial value. Lifted + // from the wasm GLOBAL section. The backend allocates a static + // i64 slot per global; only the initial value matters. + OP_TYPE_WASMSTACK_GLOBAL_DECL, + + // Module-level table entry lifted from a wasm ELEM section. Carries + // (slot, target) — i.e. table_slot_index N now contains a reference + // to function `target`. The wasmstack -> wasmssa pass collects these + // and emits a synthetic wasmssa.func "_tinyc_fnptr_init" containing + // one wasmssa.func_addr per entry, so the lifter pre-pass sees them. + OP_TYPE_WASMSTACK_FUNC_ADDR_DECL, + + // ------------------------------------------------------------------------- + // aarch64 dialect — 1:1 with the AArch64 instruction encoding. The + // `aarch64 → Mach-O` backend is a "dumb" byte emitter; all isel / + // register-allocation knowledge lives in the `llvm → aarch64` lowering. + // First-light slice: just enough to run `int main() { return 42; }` + // and have its return value become the process exit code via a + // direct `svc #0x80` Mach syscall (no `proc_exit` shim required). + // ------------------------------------------------------------------------- + OP_TYPE_AARCH64_FUNC, + OP_TYPE_AARCH64_MOVZ, // movz Wd|Xd, #imm16, LSL #(hw*16) + OP_TYPE_AARCH64_MOVK, // movk Wd|Xd, #imm16, LSL #(hw*16) + OP_TYPE_AARCH64_MOV_X, // mov Xd, Xn (register move; X-form) + OP_TYPE_AARCH64_BL, // bl (branch-and-link, PC-relative) + OP_TYPE_AARCH64_BLR, // blr Xn (indirect branch-and-link via register) + OP_TYPE_AARCH64_SVC, // svc #imm16 + OP_TYPE_AARCH64_RET, // ret (== ret x30) + + // Arithmetic + memory + stack-frame ops added in the arith slice. + OP_TYPE_AARCH64_ADD_IMM, // add Wd|Xd, Wn|Xn, #imm12 (LSL 0) + OP_TYPE_AARCH64_SUB_IMM, // sub Wd|Xd, Wn|Xn, #imm12 (LSL 0) + OP_TYPE_AARCH64_ADD_REG, // add Wd|Xd, Wn|Xn, Wm|Xm + OP_TYPE_AARCH64_SUB_REG, // sub Wd|Xd, Wn|Xn, Wm|Xm + OP_TYPE_AARCH64_MUL, // mul Wd|Xd, Wn|Xn, Wm|Xm (== madd ..., xzr) + OP_TYPE_AARCH64_SDIV, // sdiv Wd|Xd, Wn|Xn, Wm|Xm + OP_TYPE_AARCH64_UDIV, // udiv Wd|Xd, Wn|Xn, Wm|Xm + OP_TYPE_AARCH64_MSUB, // msub Wd, Wn, Wm, Wa (used for srem/urem) + OP_TYPE_AARCH64_AND_REG, // and Wd|Xd, Wn|Xn, Wm|Xm + OP_TYPE_AARCH64_AND_IMM, // and Wd|Xd, Wn|Xn, #(1<) + OP_TYPE_AARCH64_ADD_DATA_LO, // add Xd, Xn, #lo12() + // Function prologue/epilogue (modeled as an op pair so the dumb + // backend can emit the exact instruction sequence; details of + // sp/fp/lr handling baked into the encoder). + OP_TYPE_AARCH64_PROLOGUE, + OP_TYPE_AARCH64_EPILOGUE, + // Comparison + condition-set. Used to materialise i32 booleans for + // `llvm.icmp` and eqz results. + OP_TYPE_AARCH64_CMP_REG, // cmp Wn, Wm (== subs Wzr, Wn, Wm) + OP_TYPE_AARCH64_CMP_IMM, // cmp Wn, #imm12 (== subs Wzr, Wn, #imm12) + OP_TYPE_AARCH64_CSET, // cset Wd, COND (== csinc Wd, Wzr, Wzr, invert(COND)) + // Conditional select. `csel Wd, Wn, Wm, COND`. Used for llvm.select. + OP_TYPE_AARCH64_CSEL, + // Control-flow ops. `target` attribute is the symbolic label name. + // The macho backend tracks all `aarch64.label` positions inside a + // function and patches the branch immediates after layout. + OP_TYPE_AARCH64_B, // b