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