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