kit

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

commit 4501d77e4915f2b6d863c0261d53e4436dfca962
parent ca43ec6ce767098be6c91b8579236b798676fe62
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 12 Jun 2026 09:50:11 -0700

feat(c): add the __builtin_nan family and __builtin_constant_p

__builtin_nan/nanf/nanl produce a quiet NaN of the matching width (the expansion
of <math.h>'s NAN, which SQLite's shell needs); the tag-string argument is parsed
and ignored, matching the "" case. __builtin_constant_p(expr) folds to 1 when its
operand is a compile-time integer constant and 0 otherwise.

Diffstat:
Mlang/c/parse/parse.c | 5+++++
Mlang/c/parse/parse_expr.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mlang/c/parse/parse_priv.h | 4++++
Atest/parse/cases/6_5_2_2_01_builtin_nan.c | 9+++++++++
Atest/parse/cases/6_5_2_2_01_builtin_nan.expected | 1+
5 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -1679,6 +1679,9 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_huge_valf")); p.sym_b_huge_vall = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_huge_vall")); + p.sym_b_nan = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_nan")); + p.sym_b_nanf = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_nanf")); + p.sym_b_nanl = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_nanl")); p.sym_b_isless = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_isless")); p.sym_b_islessequal = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_islessequal")); @@ -1697,6 +1700,8 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, p.sym_b_expect = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_expect")); p.sym_b_offsetof = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_offsetof")); + p.sym_b_constant_p = + kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_constant_p")); p.sym_b_va_list = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_va_list")); p.sym_b_va_start = diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -972,6 +972,19 @@ static CConstInt cexpr_unary(Parser* p, SrcLoc loc) { expect_punct(p, ')', "')' after __builtin_offsetof"); return cint_make_u64(p, ty_size_t(p), off); } + if (name == p->sym_b_constant_p) { + /* In a constant expression too: fold to 1 iff the operand folds to a + * compile-time constant (see parse_builtin_constant_p_call). */ + i64 cval; + int is_const; + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after __builtin_constant_p"); + parse_assign_expr(p); + is_const = pcg_emit_enabled(p) && kit_cg_top_const_int(p->cg, &cval); + pcg_drop(p); + expect_punct(p, ')', "')' after __builtin_constant_p"); + return cint_bool(p, is_const); + } { SymEntry* e = scope_lookup(p, name); if (e && e->kind == SEK_ENUM_CST) { @@ -1564,11 +1577,11 @@ static int parse_builtin_fp_cmp_call(Parser* p, Sym name, SrcLoc loc) { static const Type* builtin_math_fp_type(Parser* p, Sym name) { if (name == p->sym_b_fabsf || name == p->sym_b_inff || - name == p->sym_b_huge_valf) { + name == p->sym_b_huge_valf || name == p->sym_b_nanf) { return type_prim(p->pool, TY_FLOAT); } if (name == p->sym_b_fabsl || name == p->sym_b_infl || - name == p->sym_b_huge_vall) { + name == p->sym_b_huge_vall || name == p->sym_b_nanl) { return type_prim(p->pool, TY_LDOUBLE); } return type_prim(p->pool, TY_DOUBLE); @@ -1591,6 +1604,51 @@ static int parse_builtin_inf_call(Parser* p, Sym name, SrcLoc loc) { return 1; } +/* __builtin_nan / nanf / nanl: produce a quiet NaN of the matching width. + * The argument is a (constant) tag string selecting the NaN payload; kit + * ignores the payload and always emits the default quiet NaN — matching the + * `""` tag that <math.h>'s NAN expands to (__builtin_nanf("")). */ +static int parse_builtin_nan_call(Parser* p, Sym name, SrcLoc loc) { + const Type* ty; + if (name != p->sym_b_nan && name != p->sym_b_nanf && name != p->sym_b_nanl) { + return 0; + } + ty = builtin_math_fp_type(p, name); + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after __builtin_nan"); + parse_assign_expr(p); /* the tag string — evaluated and discarded */ + to_rvalue(p); + pcg_drop(p); + expect_punct(p, ')', "')' after __builtin_nan"); + pcg_set_loc(p, loc); + pcg_push_float(p, __builtin_nan(""), ty); + return 1; +} + +/* __builtin_constant_p(expr): 1 if expr folds to a compile-time integer + * constant, else 0. kit answers from what its single-pass front end can fold at + * parse time — which matches GCC at -O0, where expressions GCC would fold only + * under optimization also report 0. The operand's value is never used: it is + * parsed and immediately dropped. kit's value stack is lazy, so a constant or + * otherwise side-effect-free operand emits no code; only an operand containing + * an eager side effect (a call) emits, and such an operand is never a constant + * (so the result — 0 — is still correct). The result is a pushed integer + * constant, usable anywhere a constant expression is. */ +static int parse_builtin_constant_p_call(Parser* p, Sym name, SrcLoc loc) { + i64 cval; + int is_const; + if (name != p->sym_b_constant_p) return 0; + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after __builtin_constant_p"); + parse_assign_expr(p); + is_const = pcg_emit_enabled(p) && kit_cg_top_const_int(p->cg, &cval); + pcg_drop(p); + expect_punct(p, ')', "')' after __builtin_constant_p"); + pcg_set_loc(p, loc); + pcg_push_int(p, is_const ? 1 : 0, ty_int(p)); + return 1; +} + static int parse_builtin_fabs_call(Parser* p, Sym name, SrcLoc loc) { const Type* ty; FrameSlot slot; @@ -1734,6 +1792,8 @@ static int try_parse_builtin_call(Parser* p) { if (parse_builtin_isnan_call(p, name, loc)) return 1; if (parse_builtin_fp_cmp_call(p, name, loc)) return 1; if (parse_builtin_inf_call(p, name, loc)) return 1; + if (parse_builtin_nan_call(p, name, loc)) return 1; + if (parse_builtin_constant_p_call(p, name, loc)) return 1; if (parse_builtin_fabs_call(p, name, loc)) return 1; if (parse_builtin_abs_call(p, name, loc)) return 1; if (parse_builtin_clear_cache_call(p, name, loc)) return 1; diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -269,6 +269,9 @@ typedef struct Parser { Sym sym_b_huge_val; Sym sym_b_huge_valf; Sym sym_b_huge_vall; + Sym sym_b_nan; /* __builtin_nan */ + Sym sym_b_nanf; /* __builtin_nanf */ + Sym sym_b_nanl; /* __builtin_nanl */ Sym sym_b_isless; /* __builtin_isless */ Sym sym_b_islessequal; /* __builtin_islessequal */ Sym sym_b_isgreater; /* __builtin_isgreater */ @@ -283,6 +286,7 @@ typedef struct Parser { const Type* cur_func_ret; Sym sym_b_expect; Sym sym_b_offsetof; + Sym sym_b_constant_p; /* __builtin_constant_p */ Sym sym_b_va_list; /* Cached singleton for __builtin_va_list — built lazily on first * mention so every occurrence resolves to the same Type* (and the diff --git a/test/parse/cases/6_5_2_2_01_builtin_nan.c b/test/parse/cases/6_5_2_2_01_builtin_nan.c @@ -0,0 +1,9 @@ +/* __builtin_nan / __builtin_nanf produce quiet NaNs (the expansion of + * <math.h>'s NAN). A NaN compares unequal to itself, so both self-comparisons + * are true. No sysroot needed. */ +int test_main(void) { + double n = __builtin_nan(""); + float nf = __builtin_nanf(""); + /* NaN != NaN must hold for both widths. */ + return (n != n && nf != nf) ? 42 : 0; +} diff --git a/test/parse/cases/6_5_2_2_01_builtin_nan.expected b/test/parse/cases/6_5_2_2_01_builtin_nan.expected @@ -0,0 +1 @@ +42