kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit a2b893c3822002a81cfc24d286dbe3971ea163e3
parent 28f393cc0706394c3300923b6e1bb3bf7192d131
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 17:20:03 -0700

Fix runtime bootstrap parser blockers

Diffstat:
Mdoc/RT_CFREERT_CHECKLIST.md | 22+++++++++++-----------
Mlang/c/parse/parse.c | 1+
Mlang/c/parse/parse_expr.c | 42++++++++++++++++++++++++++++++++++++++++++
Mlang/c/parse/parse_init.c | 30++++++++++++++++++++++++++++++
Mlang/c/parse/parse_priv.h | 1+
Msrc/arch/aa64/alloc.c | 20+++++++++++++++++++-
Msrc/arch/aa64/internal.h | 5+++++
Msrc/arch/rv64/alloc.c | 10+++++++++-
Msrc/arch/x64/alloc.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Atest/parse/cases/6_7_9_01_static_ptr_null_void_cast.c | 4++++
Atest/parse/cases/6_7_9_01_static_ptr_null_void_cast.expected | 1+
Atest/parse/cases/6_7_9_02_static_ptr_null_read.c | 4++++
Atest/parse/cases/6_7_9_02_static_ptr_null_read.expected | 1+
Atest/parse/cases/6_7_9_02_static_ptr_null_read.skip | 1+
Atest/parse/cases/builtin_27_isnan.c | 5+++++
Atest/parse/cases/builtin_27_isnan.expected | 1+
16 files changed, 184 insertions(+), 13 deletions(-)

diff --git a/doc/RT_CFREERT_CHECKLIST.md b/doc/RT_CFREERT_CHECKLIST.md @@ -27,10 +27,10 @@ env CC="$PWD/build/cfree cc" \ ## Current Status -As of this probe, cfree builds or assembles: +As of the latest probe, cfree builds or assembles: - [x] `rt/lib/int/int.c` -- [ ] `rt/lib/fp/fp.c` +- [x] `rt/lib/fp/fp.c` - [x] `rt/lib/mem/mem.c` - [x] `rt/lib/atomic/atomic_freestanding.c` - [x] `rt/lib/cfree/ifunc_init.c` @@ -39,8 +39,8 @@ As of this probe, cfree builds or assembles: - [x] `rt/lib/coro/coro.c` - [x] `rt/lib/coro/aarch64_macho.s` -That is 8 / 9 compile or assemble steps green for this variant. The archive -does not build yet because `rt/lib/fp/fp.c` still fails. +That is 9 / 9 compile or assemble steps green for this variant, and +`cfree ar` produces `rt/build/aarch64-apple-darwin/libcfree_rt.a`. ## Completed @@ -80,16 +80,16 @@ does not build yet because `rt/lib/fp/fp.c` still fails. argument prescan now preserves raw-token hidesets, so self-referential suffix-renaming macros such as `rep_t -> _FP_NAME(rep_t)` do not recurse until stack exhaustion. +- [x] Added parser support for `__builtin_isnan`, lowered as a single-evaluation + floating self-compare. +- [x] Routed C floating comparisons through FP comparison lowering for aarch64, + x86-64, and riscv64 instead of integer compare paths. +- [x] Accepted null pointer constants such as `((void*)0)` in static pointer + initializers. This clears the `_Thread_local coro_t* __cfree_current = NULL` + initializer in `rt/lib/coro/coro.c`. ## Remaining Blockers -- [ ] Finish `rt/lib/fp/fp.c`. - Current symptom after the preprocessor crash fix: - `rt/lib/include/common/fp_lib.h:338:7: fatal: undeclared identifier - '__builtin_sadd_overflow'`. The crash reduced to a self-referential macro - argument-prescan case and is covered by - `test/pp/cases/65_rescan_arg_hideset_prescan.c`. - - [ ] Lift remaining coroutine file-scope assembly. Current file-scope asm remains in: `rt/lib/coro/x86_64.c`, `rt/lib/coro/x86_64_win.c`, diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -1213,6 +1213,7 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg) { p.sym_b_memmove = pool_intern_cstr(p.pool, "__builtin_memmove"); p.sym_b_memcmp = pool_intern_cstr(p.pool, "__builtin_memcmp"); p.sym_b_memset = pool_intern_cstr(p.pool, "__builtin_memset"); + p.sym_b_isnan = pool_intern_cstr(p.pool, "__builtin_isnan"); p.sym_func = pool_intern_cstr(p.pool, "__func__"); p.sym_func_gcc = pool_intern_cstr(p.pool, "__FUNCTION__"); p.sym_pretty_func_gcc = pool_intern_cstr(p.pool, "__PRETTY_FUNCTION__"); diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -7,6 +7,7 @@ static const Type* ty_int(Parser* p) { return type_prim(p->pool, TY_INT); } static const Type* ty_size_t(Parser* p) { return c_abi_size_type(p->abi, p->pool); } +static int type_is_fp(const Type* t); static CKw ident_kw(const Parser* p, Sym name) { return ident_kw_inline(p, name); @@ -1323,6 +1324,26 @@ static int parse_builtin_overflow_call(Parser* p, Sym name, SrcLoc loc) { return 1; } +static int parse_builtin_isnan_call(Parser* p, Sym name, SrcLoc loc) { + const Type* arg_ty; + if (name != p->sym_b_isnan) return 0; + + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after __builtin_isnan"); + parse_assign_expr(p); + to_rvalue(p); + arg_ty = cg_top_type(p->cg); + if (!type_is_fp(arg_ty)) { + perr(p, "__builtin_isnan argument must have floating type"); + } + expect_punct(p, ')', "')' after __builtin_isnan"); + + cg_set_loc(p->cg, loc); + cg_dup(p->cg); + cg_cmp(p->cg, CMP_NE); + return 1; +} + static int try_parse_builtin_call(Parser* p) { Sym name = p->cur.v.ident; SrcLoc loc = p->cur.loc; @@ -1333,6 +1354,7 @@ static int try_parse_builtin_call(Parser* p) { } if (parse_builtin_overflow_call(p, name, loc)) return 1; + if (parse_builtin_isnan_call(p, name, loc)) return 1; if (name != p->sym_b_alloca && name != p->sym_b_ctz && name != p->sym_b_ctzl && name != p->sym_b_ctzll && name != p->sym_b_clz && @@ -2297,6 +2319,13 @@ static void emit_fp_binop(Parser* p, BinOp bop, const Type* common) { cg_binop(p->cg, fop); } +static void coerce_fp_cmp_operands(Parser* p, const Type* common) { + if (cg_top_type(p->cg) != common) cg_convert(p->cg, common); + cg_swap(p->cg); + if (cg_top_type(p->cg) != common) cg_convert(p->cg, common); + cg_swap(p->cg); +} + static void parse_mul(Parser* p) { parse_unary(p); for (;;) { @@ -2463,6 +2492,7 @@ static void parse_rel(Parser* p) { { const Type* lt = cg_top2_type(p->cg); const Type* rt = cg_top_type(p->cg); + const Type* common = common_fp_type(p, lt, rt); if (lt && lt->kind == TY_PTR && rt && rt->kind == TY_PTR) { if (!pointer_pointees_compatible(p, lt, rt)) { perr(p, "comparison of incompatible pointer types"); @@ -2470,6 +2500,16 @@ static void parse_rel(Parser* p) { } else if (!type_is_arith(lt) || !type_is_arith(rt)) { perr(p, "relational operator requires arithmetic or compatible pointer operands"); } + if (common) { + coerce_fp_cmp_operands(p, common); + switch (cop) { + case CMP_LT_S: cop = CMP_LT_F; break; + case CMP_LE_S: cop = CMP_LE_F; break; + case CMP_GT_S: cop = CMP_GT_F; break; + case CMP_GE_S: cop = CMP_GE_F; break; + default: break; + } + } } cg_cmp(p->cg, cop); } @@ -2495,6 +2535,7 @@ static void parse_eq(Parser* p) { { const Type* lt = cg_top2_type(p->cg); const Type* rt = cg_top_type(p->cg); + const Type* common = common_fp_type(p, lt, rt); int lnull = lhs_null; int rnull = null_pointer_constant(p, rt); if (lt && lt->kind == TY_PTR && rt && rt->kind == TY_PTR) { @@ -2507,6 +2548,7 @@ static void parse_eq(Parser* p) { } else if (!type_is_arith(lt) || !type_is_arith(rt)) { perr(p, "equality operator requires scalar operands"); } + if (common) coerce_fp_cmp_operands(p, common); } cg_cmp(p->cg, cop); } diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -605,6 +605,34 @@ static int try_parse_addr_const(Parser* p, const Type* ty, u8* buf, u32 offset, return 1; } +static int try_parse_null_pointer_const(Parser* p) { + if (p->cur.kind == TOK_NUM) { + if (parse_int_literal(p, &p->cur) != 0) return 0; + advance(p); + return 1; + } + if (is_punct(&p->cur, '+')) { + advance(p); + return try_parse_null_pointer_const(p); + } + if (is_punct(&p->cur, '(')) { + advance(p); + if (starts_type_name(p, &p->cur)) { + const Type* cast_ty = type_unqual(p->pool, parse_type_name(p)); + expect_punct(p, ')', "')' after cast in null pointer constant"); + if (!cast_ty || (cast_ty->kind != TY_PTR && cast_ty->kind != TY_VOID && + !type_is_int(cast_ty))) { + return 0; + } + return try_parse_null_pointer_const(p); + } + if (!try_parse_null_pointer_const(p)) return 0; + expect_punct(p, ')', "')' in null pointer constant"); + return 1; + } + return 0; +} + void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset, const Type* ty) { if (ty->kind == TY_ARRAY) { @@ -690,6 +718,8 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset, if (offset + sz > buflen) perr(p, "initializer overflows object"); if (ty->kind == TY_PTR && try_parse_addr_const(p, ty, buf, offset, sz)) { /* Address constant recorded as a reloc. */ + } else if (ty->kind == TY_PTR && try_parse_null_pointer_const(p)) { + encode_int_le(buf + offset, sz, 0); } else { i64 v = eval_const_int(p, cloc); encode_int_le(buf + offset, sz, v); diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -194,6 +194,7 @@ typedef struct Parser { Sym sym_b_memmove; Sym sym_b_memcmp; Sym sym_b_memset; + Sym sym_b_isnan; Sym sym_func; /* __func__ */ Sym sym_func_gcc; /* __FUNCTION__ */ Sym sym_pretty_func_gcc; /* __PRETTY_FUNCTION__ */ diff --git a/src/arch/aa64/alloc.c b/src/arch/aa64/alloc.c @@ -106,6 +106,18 @@ static u32 cmp_to_cond(CmpOp op) { } } +static u32 fp_cmp_to_cond(CmpOp op) { + switch (op) { + case CMP_EQ: return 0x0u; /* equal; unordered is false */ + case CMP_NE: return 0x1u; /* not equal; unordered is true */ + case CMP_LT_F: return 0x4u; /* MI: less-than only */ + case CMP_LE_F: return 0x9u; /* LS: less-or-equal only */ + case CMP_GT_F: return 0xcu; /* GT excludes unordered */ + case CMP_GE_F: return 0xau; /* GE excludes unordered */ + default: return cmp_to_cond(op); + } +} + void emit_cmp_ab(CGTarget* t, Operand a_op, Operand b_op) { MCEmitter* mc = t->mc; u32 sf = type_is_64(a_op.type) ? 1u : 0u; @@ -132,8 +144,14 @@ static void aa_cmp_branch(CGTarget* t, CmpOp op, Operand a, Operand b, } static void aa_cmp(CGTarget* t, CmpOp op, Operand dst, Operand a, Operand b) { - emit_cmp_ab(t, a, b); u32 sf_dst = type_is_64(dst.type) ? 1u : 0u; + if (a.cls == RC_FP || b.cls == RC_FP) { + u32 type = type_is_fp_double(a.type) ? 1u : 0u; + aa64_emit32(t->mc, aa64_fcmp(type, reg_num(a), reg_num(b))); + aa64_emit32(t->mc, aa64_cset(sf_dst, reg_num(dst), fp_cmp_to_cond(op))); + return; + } + emit_cmp_ab(t, a, b); aa64_emit32(t->mc, aa64_cset(sf_dst, reg_num(dst), cmp_to_cond(op))); } diff --git a/src/arch/aa64/internal.h b/src/arch/aa64/internal.h @@ -184,6 +184,11 @@ static inline u32 aa64_fdiv(u32 type, u32 Rd, u32 Rn, u32 Rm) { ((Rn & 0x1f) << 5) | (Rd & 0x1f); } +static inline u32 aa64_fcmp(u32 type, u32 Rn, u32 Rm) { + return 0x1E202000u | ((type & 3) << 22) | ((Rm & 0x1f) << 16) | + ((Rn & 0x1f) << 5); +} + static inline u32 aa64_sbfm(u32 sf, u32 Rd, u32 Rn, u32 immr, u32 imms) { return 0x13000000u | (sf << 31) | (sf << 22) | ((immr & 0x3fu) << 16) | ((imms & 0x3fu) << 10) | ((Rn & 0x1f) << 5) | (Rd & 0x1f); diff --git a/src/arch/rv64/alloc.c b/src/arch/rv64/alloc.c @@ -368,12 +368,20 @@ void rv_cmp(CGTarget* t, CmpOp op, Operand dst, Operand a_op, RImpl* a = impl_of(t); u32 rd = reg_num(dst); - if (op == CMP_LT_F || op == CMP_LE_F || op == CMP_GT_F || op == CMP_GE_F) { + if (op == CMP_EQ || op == CMP_NE || op == CMP_LT_F || op == CMP_LE_F || + op == CMP_GT_F || op == CMP_GE_F) { /* FP compare in fa,fb → rd. Use FLT/FLE/FEQ depending on op. */ int is_d = type_is_fp_double(a_op.type); u32 fa = reg_num(a_op); u32 fb = reg_num(b_op); switch (op) { + case CMP_EQ: + rv64_emit32(mc, is_d ? rv_feq_d(rd, fa, fb) : rv_feq_s(rd, fa, fb)); + return; + case CMP_NE: + rv64_emit32(mc, is_d ? rv_feq_d(rd, fa, fb) : rv_feq_s(rd, fa, fb)); + rv64_emit32(mc, rv_xori(rd, rd, 1)); + return; case CMP_LT_F: rv64_emit32(mc, is_d ? rv_flt_d(rd, fa, fb) : rv_flt_s(rd, fa, fb)); return; case CMP_LE_F: rv64_emit32(mc, is_d ? rv_fle_d(rd, fa, fb) : rv_fle_s(rd, fa, fb)); return; case CMP_GT_F: rv64_emit32(mc, is_d ? rv_flt_d(rd, fb, fa) : rv_flt_s(rd, fb, fa)); return; diff --git a/src/arch/x64/alloc.c b/src/arch/x64/alloc.c @@ -289,6 +289,29 @@ static u32 cmp_to_cc(CmpOp op) { } } +static void emit_fp_setcc_ordered(CGTarget* t, CmpOp op, u32 dst) { + u32 primary; + switch (op) { + case CMP_EQ: primary = X64_CC_E; break; + case CMP_LT_F: primary = X64_CC_B; break; + case CMP_LE_F: primary = X64_CC_BE; break; + default: primary = cmp_to_cc(op); break; + } + emit_setcc(t->mc, primary, dst); + emit_movzx_r32_r8(t->mc, dst, dst); + emit_setcc(t->mc, 0xBu /* NP */, X64_R11); + emit_movzx_r32_r8(t->mc, X64_R11, X64_R11); + emit_alu_rr(t->mc, 0, 0x21, dst, X64_R11); /* AND */ +} + +static void emit_fp_setcc_unordered_ne(CGTarget* t, u32 dst) { + emit_setcc(t->mc, 0xAu /* P */, dst); + emit_movzx_r32_r8(t->mc, dst, dst); + emit_setcc(t->mc, X64_CC_NE, X64_R11); + emit_movzx_r32_r8(t->mc, X64_R11, X64_R11); + emit_alu_rr(t->mc, 0, 0x09, dst, X64_R11); /* OR */ +} + u32 x64_force_reg_int(CGTarget* t, Operand op, int w, u32 scratch) { if (op.kind == OPK_REG) return op.v.reg & 0xFu; if (op.kind == OPK_IMM) { @@ -327,6 +350,32 @@ void x_cmp_branch(CGTarget* t, CmpOp op, Operand a, Operand b, } void x_cmp(CGTarget* t, CmpOp op, Operand dst, Operand a, Operand b) { + if (a.cls == RC_FP || b.cls == RC_FP) { + u8 prefix = type_is_fp_double(a.type) ? 0x66 : 0x00; + u32 d = dst.v.reg & 0xFu; + emit_sse_rr(t->mc, prefix, 0x2E, a.v.reg & 0xFu, b.v.reg & 0xFu); + switch (op) { + case CMP_NE: + emit_fp_setcc_unordered_ne(t, d); + return; + case CMP_EQ: + case CMP_LT_F: + case CMP_LE_F: + emit_fp_setcc_ordered(t, op, d); + return; + case CMP_GT_F: + emit_setcc(t->mc, X64_CC_A, d); + break; + case CMP_GE_F: + emit_setcc(t->mc, X64_CC_AE, d); + break; + default: + emit_setcc(t->mc, cmp_to_cc(op), d); + break; + } + emit_movzx_r32_r8(t->mc, d, d); + return; + } emit_cmp_ab(t, a, b); u32 d = dst.v.reg & 0xFu; emit_setcc(t->mc, cmp_to_cc(op), d); diff --git a/test/parse/cases/6_7_9_01_static_ptr_null_void_cast.c b/test/parse/cases/6_7_9_01_static_ptr_null_void_cast.c @@ -0,0 +1,4 @@ +#define NULL ((void*)0) +static int* p = NULL; + +int test_main(void) { return 42; } diff --git a/test/parse/cases/6_7_9_01_static_ptr_null_void_cast.expected b/test/parse/cases/6_7_9_01_static_ptr_null_void_cast.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_7_9_02_static_ptr_null_read.c b/test/parse/cases/6_7_9_02_static_ptr_null_read.c @@ -0,0 +1,4 @@ +#define NULL ((void*)0) +static int* p = NULL; + +int test_main(void) { return p == 0 ? 42 : 0; } diff --git a/test/parse/cases/6_7_9_02_static_ptr_null_read.expected b/test/parse/cases/6_7_9_02_static_ptr_null_read.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_7_9_02_static_ptr_null_read.skip b/test/parse/cases/6_7_9_02_static_ptr_null_read.skip @@ -0,0 +1 @@ +red: reading a file-scope pointer object initialized from NULL crashes the JIT path diff --git a/test/parse/cases/builtin_27_isnan.c b/test/parse/cases/builtin_27_isnan.c @@ -0,0 +1,5 @@ +int test_main(void) { + double z = 0.0; + double n = z / z; + return __builtin_isnan(n) && !__builtin_isnan(1.0) ? 42 : 0; +} diff --git a/test/parse/cases/builtin_27_isnan.expected b/test/parse/cases/builtin_27_isnan.expected @@ -0,0 +1 @@ +42