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:
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