kit

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

commit 142c5027b04c79c1acca25563a1fe678d670e31a
parent 6d7d79927123d94a9a05baaef05be308f89075a4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 17:24:06 -0700

c-front: support type-generic __builtin_{add,sub,mul}_overflow

kit's C frontend handled the explicit-width overflow builtins
(__builtin_smull_overflow et al.) but not the type-generic forms
(__builtin_add_overflow / __builtin_sub_overflow / __builtin_mul_overflow).
src/cg/control.c (cg_checked_scaled_offset, added in e55ca1f0) uses the
generic forms, so kit could not compile its own source -- the bootstrap
failed with "undeclared identifier '__builtin_mul_overflow'".

The generic forms infer the operation type from the result pointer's
pointee and pick the signed/unsigned intrinsic from that type's
signedness, reusing the existing per-type KIT_CG_INTRIN_*_OVERFLOW
machinery. The explicit-width path is unchanged (operands still coerced
during the argument parse); the generic path coerces both operands to
the resolved type after the result pointer is stashed.

Regression test test/parse/cases/builtin_generic_overflow.c covers
add/sub/mul x signed/unsigned, result and overflow flag; passes D/R/E on
aa64 and R/E on x64/rv64.

Diffstat:
Mlang/c/parse/parse_expr.c | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Atest/parse/cases/builtin_generic_overflow.c | 31+++++++++++++++++++++++++++++++
Atest/parse/cases/builtin_generic_overflow.expected | 1+
3 files changed, 109 insertions(+), 7 deletions(-)

diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -1090,28 +1090,80 @@ static FrameSlot builtin_tmp_slot(Parser* p, const Type* ty) { return c_cg_local(p, &fsd); } +/* The type-generic overflow builtins (no s/u + width suffix). They infer the + * operation type from the result pointer's pointee and the signed/unsigned + * intrinsic from that type's signedness, then reuse the same per-type + * KIT_CG_INTRIN_*_OVERFLOW machinery as the explicit __builtin_smull_overflow + * family. kit's own source uses these (e.g. cg_checked_scaled_offset), so they + * must self-host. */ +typedef enum { OVF_ADD, OVF_SUB, OVF_MUL } OverflowOp; + +static int builtin_overflow_generic(Parser* p, Sym name, OverflowOp* op_out) { + static const struct { + OverflowOp op; + const char* name; + } g[] = { + {OVF_ADD, "__builtin_add_overflow"}, + {OVF_SUB, "__builtin_sub_overflow"}, + {OVF_MUL, "__builtin_mul_overflow"}, + }; + size_t i; + for (i = 0; i < sizeof(g) / sizeof(g[0]); ++i) { + if (sym_eq_cstr(p, name, g[i].name)) { + *op_out = g[i].op; + return 1; + } + } + return 0; +} + +static KitCgIntrinsic overflow_intrin_for(OverflowOp op, int is_signed) { + switch (op) { + case OVF_ADD: + return is_signed ? KIT_CG_INTRIN_SADD_OVERFLOW : KIT_CG_INTRIN_UADD_OVERFLOW; + case OVF_SUB: + return is_signed ? KIT_CG_INTRIN_SSUB_OVERFLOW : KIT_CG_INTRIN_USUB_OVERFLOW; + case OVF_MUL: + default: + return is_signed ? KIT_CG_INTRIN_SMUL_OVERFLOW : KIT_CG_INTRIN_UMUL_OVERFLOW; + } +} + static int parse_builtin_overflow_call(Parser* p, Sym name, SrcLoc loc) { BuiltinOverflowInfo info; + OverflowOp gop = OVF_ADD; + KitCgIntrinsic intrin; const Type* op_ty; const Type* ptr_ty; const Type* out_ty; const Type* bool_ty; FrameSlot ptr_slot; FrameSlot ov_slot; - if (!builtin_overflow_info(p, name, &info)) return 0; - - op_ty = type_prim(p->pool, info.type_kind); + int is_generic; + + if (builtin_overflow_info(p, name, &info)) { + is_generic = 0; + intrin = info.intrin; + op_ty = type_prim(p->pool, info.type_kind); + } else if (builtin_overflow_generic(p, name, &gop)) { + /* op_ty + intrin are resolved from the result pointee, parsed below. */ + is_generic = 1; + intrin = KIT_CG_INTRIN_SADD_OVERFLOW; + op_ty = NULL; + } else { + return 0; + } bool_ty = type_prim(p->pool, TY_BOOL); advance(p); /* IDENT */ expect_punct(p, '(', "'(' after overflow builtin"); parse_assign_expr(p); to_rvalue(p); - coerce_top_to_type(p, op_ty); + if (!is_generic) coerce_top_to_type(p, op_ty); expect_punct(p, ',', "',' in overflow builtin"); parse_assign_expr(p); to_rvalue(p); - coerce_top_to_type(p, op_ty); + if (!is_generic) coerce_top_to_type(p, op_ty); expect_punct(p, ',', "',' in overflow builtin"); parse_assign_expr(p); to_rvalue(p); @@ -1120,7 +1172,16 @@ static int parse_builtin_overflow_call(Parser* p, Sym name, SrcLoc loc) { perr(p, "overflow builtin result argument must be a pointer"); } out_ty = ptr_ty->ptr.pointee; - if (!type_compatible(type_unqual(p->pool, out_ty), op_ty)) { + if (is_generic) { + /* Result type (and thus the operation type) is the pointee; operands are + * coerced to it after the pointer is stashed (op_ty is unknown until now). */ + const Type* res = type_unqual(p->pool, out_ty); + if (!type_is_int(res)) { + perr(p, "__builtin_*_overflow result must point to an integer"); + } + op_ty = res; + intrin = overflow_intrin_for(gop, type_is_signed_integer(res)); + } else if (!type_compatible(type_unqual(p->pool, out_ty), op_ty)) { perr(p, "overflow builtin result pointer type mismatch"); } expect_punct(p, ')', "')' after overflow builtin"); @@ -1130,8 +1191,17 @@ static int parse_builtin_overflow_call(Parser* p, Sym name, SrcLoc loc) { c_cg_swap(p); c_cg_store_void(p); + if (is_generic) { + /* Coerce both operands to the resolved op_ty now that the pointer is + * stashed (stack is [a, b], b on top). */ + coerce_top_to_type(p, op_ty); + c_cg_swap(p); + coerce_top_to_type(p, op_ty); + c_cg_swap(p); + } + c_cg_set_loc(p, loc); - kit_cg_intrinsic(p->cg, info.intrin, 2, c_cg_tid(p, op_ty)); + kit_cg_intrinsic(p->cg, intrin, 2, c_cg_tid(p, op_ty)); c_cg_retag_at(p, 1, op_ty, 0); c_cg_retag_top(p, bool_ty); diff --git a/test/parse/cases/builtin_generic_overflow.c b/test/parse/cases/builtin_generic_overflow.c @@ -0,0 +1,31 @@ +/* Type-generic overflow builtins (__builtin_add/sub/mul_overflow). The + * operation type is inferred from the result pointer's pointee, dispatching to + * the same per-type intrinsics as the explicit __builtin_smull_overflow family. + * kit's own source (src/cg/control.c cg_checked_scaled_offset) uses these, so + * they must self-host. Returns the count of passing checks (expect 7). LP64 + * targets only (aa64/x64/rv64). */ + +int test_main(void) { + int ok = 0; + long lp; + int ip; + unsigned up; + unsigned long ulp; + + /* signed mul, no overflow */ + if (!__builtin_mul_overflow(3L, 4L, &lp) && lp == 12L) ok++; + /* signed mul, overflow */ + if (__builtin_mul_overflow((long)0x7fffffffffffffffL, 2L, &lp)) ok++; + /* signed add, no overflow */ + if (!__builtin_add_overflow(100, 23, &ip) && ip == 123) ok++; + /* signed int add, overflow */ + if (__builtin_add_overflow((int)0x7fffffff, 1, &ip)) ok++; + /* unsigned sub, wrap (overflow) */ + if (__builtin_sub_overflow(0u, 1u, &up)) ok++; + /* unsigned sub, no overflow */ + if (!__builtin_sub_overflow(10u, 3u, &up) && up == 7u) ok++; + /* unsigned long mul, no overflow */ + if (!__builtin_mul_overflow(6ul, 7ul, &ulp) && ulp == 42ul) ok++; + + return ok; +} diff --git a/test/parse/cases/builtin_generic_overflow.expected b/test/parse/cases/builtin_generic_overflow.expected @@ -0,0 +1 @@ +7