commit 549af2838a49bbafedb372906f363bda053063b2
parent 888738afcfa1f9f65a1d03938960314974891ea7
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 15 Jun 2026 16:25:12 -0700
cg: elide API-misuse/invariant checks under NDEBUG
The public cg.h contract treats a frontend that drives the value stack,
scope handles, or operand kinds incorrectly as a kit bug, not a
recoverable condition. Those checks were unconditional compiler_panic
calls that paid their cost (condition + message formatting) in every
build. Introduce CG_REQUIRE / CG_BUG in cg/internal.h: in a debug build
they fire the same FATAL diagnostic + longjmp as before, but under
NDEBUG the whole check is elided (a sizeof keeps operands in an
unevaluated context so a diagnostic-only `who` argument doesn't trip
-Werror's unused warnings, while emitting no code).
Convert the misuse/invariant panics in arith, call, const, control,
data, and wide. Genuine resource failures (OOM, object-emission
failures), unsupported-operation guards whose elision would silently
miscompile, size/truncation limits, ODR errors, and config errors stay
as unconditional compiler_panic so they trap in every build.
Diffstat:
7 files changed, 89 insertions(+), 134 deletions(-)
diff --git a/src/cg/arith.c b/src/cg/arith.c
@@ -152,10 +152,8 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) {
can_delay = api_can_delay_int_arith(g, ty, flags);
if (iop == UO_FNEG) {
- if (!api_type_is_float(g->c, ty)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: FP negation requires floating operand");
- }
+ CG_REQUIRE(g, api_type_is_float(g->c, ty),
+ "KitCg: FP negation requires floating operand");
ra = api_force_local(g, &a, ty);
rr = api_alloc_temp_local(g, ty);
dst = api_op_local(rr, ty);
@@ -385,8 +383,7 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) {
}
api_store_f128_bytes(g, local, dty, bytes);
} else {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: unsupported 16-byte bitcast source");
+ CG_BUG(g, "KitCg: unsupported 16-byte bitcast source");
}
api_release(g, &v);
api_push(g, api_make_lv(dst_lv, dty));
@@ -1465,9 +1462,7 @@ void kit_cg_fp_binop(KitCg* g, KitCgFpBinOp op, uint32_t flags) {
void kit_cg_fp_unop(KitCg* g, KitCgFpUnOp op, uint32_t flags) {
(void)flags;
if (!g) return;
- if (op != KIT_CG_FP_NEG) {
- compiler_panic(g->c, g->cur_loc, "KitCg: FP unary op unsupported");
- }
+ CG_REQUIRE(g, op == KIT_CG_FP_NEG, "KitCg: FP unary op unsupported");
if (api_unevaluated(g)) {
api_cg_unop(g, UO_FNEG, 0);
return;
@@ -2211,10 +2206,7 @@ void kit_cg_intrinsic(KitCg* g, KitCgIntrinsic intrin, uint32_t nargs,
KitCgTypeId api_atomic_pointee(KitCg* g, KitCgTypeId pty, const char* who) {
KitCgTypeId pointee = cg_type_pointee(g->c, pty);
- if (!pointee) {
- compiler_panic(g->c, g->cur_loc, "%.*s: operand is not a pointer",
- SLICE_ARG(slice_from_cstr(who)));
- return builtin_id(KIT_CG_BUILTIN_I32);
- }
+ CG_REQUIRE(g, pointee, "%.*s: operand is not a pointer",
+ SLICE_ARG(slice_from_cstr(who)));
return pointee;
}
diff --git a/src/cg/call.c b/src/cg/call.c
@@ -34,8 +34,7 @@ static CGLocal api_materialize_call_local(KitCg* g, ApiSValue* arg,
g->target->load(g->target, dst, api_op_indirect(arg->op.v.local, 0, ty),
ma);
} else {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: aggregate call argument is not addressable");
+ CG_BUG(g, "KitCg: aggregate call argument is not addressable");
}
return r;
}
@@ -64,8 +63,7 @@ static CGLocal api_materialize_call_local(KitCg* g, ApiSValue* arg,
} else if (op.kind == OPK_LOCAL) {
g->target->copy(g->target, dst, op);
} else {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: scalar call argument is not materialized");
+ CG_BUG(g, "KitCg: scalar call argument is not materialized");
}
api_release(g, arg);
return r;
@@ -205,10 +203,7 @@ void kit_cg_call(KitCg* g, uint32_t nargs, KitCgTypeId fn_type,
fty = resolve_type(g->c, fn_type);
if (!fty) return;
- if (g->sp < (u32)nargs + 1u) {
- compiler_panic(g->c, g->cur_loc, "KitCg: call stack underflow");
- return;
- }
+ CG_REQUIRE(g, g->sp >= (u32)nargs + 1u, "KitCg: call stack underflow");
if (api_unevaluated(g)) {
u32 i;
result_type = cg_type_func_result_id(g->c, fty);
@@ -276,10 +271,7 @@ void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs,
attrs.tail == KIT_CG_TAIL_ALLOWED || attrs.tail == KIT_CG_TAIL_MUST;
fty = api_sym_type(g, sym);
if (!fty) return;
- if (g->sp < nargs) {
- compiler_panic(g->c, g->cur_loc, "KitCg: call stack underflow");
- return;
- }
+ CG_REQUIRE(g, g->sp >= nargs, "KitCg: call stack underflow");
if (api_unevaluated(g)) {
u32 i;
result_type = cg_type_func_result_id(g->c, fty);
diff --git a/src/cg/const.c b/src/cg/const.c
@@ -513,18 +513,12 @@ int api_const_fold_convert(KitCg* g, ConvKind ck, KitCgTypeId src_type,
void kit_cg_unevaluated_push(KitCg* g) {
if (!g) return;
++g->unevaluated_depth;
- if (g->unevaluated_depth == 0) {
- compiler_panic(g->c, g->cur_loc, "KitCg: unevaluated depth overflow");
- g->unevaluated_depth = UINT32_MAX;
- }
+ CG_REQUIRE(g, g->unevaluated_depth != 0, "KitCg: unevaluated depth overflow");
}
void kit_cg_unevaluated_pop(KitCg* g) {
if (!g) return;
- if (!g->unevaluated_depth) {
- compiler_panic(g->c, g->cur_loc, "KitCg: unevaluated pop underflow");
- return;
- }
+ CG_REQUIRE(g, g->unevaluated_depth, "KitCg: unevaluated pop underflow");
--g->unevaluated_depth;
}
diff --git a/src/cg/control.c b/src/cg/control.c
@@ -461,11 +461,8 @@ void kit_cg_computed_goto(KitCg* g, const KitCgLabel* valid_targets,
api_release(g, &target);
return;
}
- if (!valid_targets || ntargets == 0) {
- compiler_panic(g->c, g->cur_loc,
- "kit_cg_computed_goto: valid_targets must be non-empty");
- return;
- }
+ CG_REQUIRE(g, valid_targets && ntargets != 0,
+ "kit_cg_computed_goto: valid_targets must be non-empty");
api_local_const_control_boundary(g);
target = api_pop(g);
target_ty = api_sv_type(&target);
@@ -521,28 +518,16 @@ ApiCgScope* api_scope_from_handle(KitCg* g, KitCgScope scope, int require_top,
if (!g || scope == 0) return NULL;
scope_index = ((u32)scope & 0xffffu);
generation = ((u32)scope >> 16);
- if (scope_index == 0 || scope_index > API_CG_MAX_SCOPES) {
- compiler_panic(g->c, g->cur_loc, "%.*s: invalid scope handle",
- SLICE_ARG(slice_from_cstr(who)));
- return NULL;
- }
+ CG_REQUIRE(g, scope_index != 0 && scope_index <= API_CG_MAX_SCOPES,
+ "%.*s: invalid scope handle", SLICE_ARG(slice_from_cstr(who)));
scope_index--;
- if (scope_index >= g->nscopes) {
- compiler_panic(g->c, g->cur_loc, "%.*s: stale scope handle",
- SLICE_ARG(slice_from_cstr(who)));
- return NULL;
- }
- if (require_top && scope_index + 1u != g->nscopes) {
- compiler_panic(g->c, g->cur_loc, "%.*s: non-LIFO scope end",
- SLICE_ARG(slice_from_cstr(who)));
- return NULL;
- }
+ CG_REQUIRE(g, scope_index < g->nscopes, "%.*s: stale scope handle",
+ SLICE_ARG(slice_from_cstr(who)));
+ CG_REQUIRE(g, !require_top || scope_index + 1u == g->nscopes,
+ "%.*s: non-LIFO scope end", SLICE_ARG(slice_from_cstr(who)));
s = &g->scopes[scope_index];
- if (!s->active || s->generation != generation) {
- compiler_panic(g->c, g->cur_loc, "%.*s: stale scope handle",
- SLICE_ARG(slice_from_cstr(who)));
- return NULL;
- }
+ CG_REQUIRE(g, s->active && s->generation == generation,
+ "%.*s: stale scope handle", SLICE_ARG(slice_from_cstr(who)));
return s;
}
@@ -725,12 +710,10 @@ static KitCgScope api_scope_begin_sig_kind(KitCg* g, u8 kind,
cont_lbl =
(kind == SCOPE_LOOP) ? g->target->label_new(g->target) : LABEL_NONE;
- if (g->nscopes >= API_CG_MAX_SCOPES) {
- /* Depth past what the 16-bit handle index can address (see
- * api_scope_handle) — unreachable for any realistic source. */
- compiler_panic(g->c, g->cur_loc, "KitCg: too many nested scopes");
- return 0;
- }
+ /* Depth past what the 16-bit handle index can address (see api_scope_handle)
+ * — unreachable for any realistic source. */
+ CG_REQUIRE(g, g->nscopes < API_CG_MAX_SCOPES,
+ "KitCg: too many nested scopes");
if (!api_grow_scopes(g, g->nscopes + 1u)) {
compiler_panic(g->c, g->cur_loc, "KitCg: out of memory growing scopes");
return 0;
@@ -804,11 +787,9 @@ KitCgScope kit_cg_scope_begin_value(KitCg* g, KitCgTypeId result_type) {
memset(&sig, 0, sizeof sig);
if (!g) return 0;
resolved = resolve_type(g->c, result_type);
- if (resolved == KIT_CG_TYPE_NONE || cg_type_is_void(g->c, resolved)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: value scope requires non-void result type");
- return 0;
- }
+ CG_REQUIRE(g,
+ resolved != KIT_CG_TYPE_NONE && !cg_type_is_void(g->c, resolved),
+ "KitCg: value scope requires non-void result type");
sig.results = &result_type;
sig.nresults = 1u;
return api_scope_begin_sig_kind(g, (u8)SCOPE_LOOP, &sig);
@@ -826,11 +807,9 @@ KitCgScope kit_cg_block_begin_value(KitCg* g, KitCgTypeId result_type) {
memset(&sig, 0, sizeof sig);
if (!g) return 0;
resolved = resolve_type(g->c, result_type);
- if (resolved == KIT_CG_TYPE_NONE || cg_type_is_void(g->c, resolved)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: value block requires non-void result type");
- return 0;
- }
+ CG_REQUIRE(g,
+ resolved != KIT_CG_TYPE_NONE && !cg_type_is_void(g->c, resolved),
+ "KitCg: value block requires non-void result type");
sig.results = &result_type;
sig.nresults = 1u;
return api_scope_begin_sig_kind(g, (u8)SCOPE_BLOCK, &sig);
@@ -977,11 +956,8 @@ void kit_cg_break_false(KitCg* g, KitCgScope scope) {
* rather than emitting a jump to a nonexistent label. */
static int api_require_loop_scope(KitCg* g, const ApiCgScope* s,
const char* op) {
- if (s->continue_lbl == LABEL_NONE) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: %s is not valid on a forward-only block scope", op);
- return 0;
- }
+ CG_REQUIRE(g, s->continue_lbl != LABEL_NONE,
+ "KitCg: %s is not valid on a forward-only block scope", op);
return 1;
}
@@ -1260,13 +1236,11 @@ static void api_cg_elem(KitCg* g, u32 elem_size, int64_t offset) {
base = api_pop(g);
base_ty = api_sv_type(&base);
base_info = cg_type_get(g->c, base_ty);
- if (api_is_lvalue_sv(&base) || !base_info ||
- base_info->kind != KIT_CG_TYPE_PTR) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: elem requires a pointer value base (decay an "
- "array to a pointer first)");
- return;
- }
+ CG_REQUIRE(g,
+ !api_is_lvalue_sv(&base) && base_info &&
+ base_info->kind == KIT_CG_TYPE_PTR,
+ "KitCg: elem requires a pointer value base (decay an "
+ "array to a pointer first)");
elem_ty = base_info->ptr.pointee;
base_ptr_ty = base_ty;
elemsz = elem_size ? elem_size : (u32)abi_cg_sizeof(g->c->abi, elem_ty);
@@ -1403,18 +1377,10 @@ static void api_cg_field_at_place(KitCg* g, ApiSValue base,
if (!g) return;
T = g->target;
api_ensure_local(g, &base);
- if (!api_is_lvalue_sv(&base)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: field_at requires a place; deref a pointer first");
- api_release(g, &base);
- return;
- }
+ CG_REQUIRE(g, api_is_lvalue_sv(&base),
+ "KitCg: field_at requires a place; deref a pointer first");
field_ty = resolve_type(g->c, field_ty);
- if (!field_ty) {
- compiler_panic(g->c, g->cur_loc, "KitCg: field_at has invalid field type");
- api_release(g, &base);
- return;
- }
+ CG_REQUIRE(g, field_ty, "KitCg: field_at has invalid field type");
if (api_unevaluated(g)) {
api_release(g, &base);
api_push(g, api_uneval_place(g, field_ty));
@@ -1483,26 +1449,18 @@ void kit_cg_field(KitCg* g, uint32_t field_index) {
base = api_pop(g);
api_ensure_local(g, &base);
base_ty = api_sv_type(&base);
- if (!api_is_lvalue_sv(&base)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: field requires a record place; deref a pointer "
- "first");
- api_release(g, &base);
- return;
- }
+ CG_REQUIRE(g, api_is_lvalue_sv(&base),
+ "KitCg: field requires a record place; deref a pointer first");
rec_ty = base_ty;
rec_ptr_ty = cg_type_ptr_to(g->c, rec_ty);
layout = abi_cg_record_layout(g->c->abi, rec_ty);
- if (!layout || field_index >= layout->nfields) {
- compiler_panic(g->c, g->cur_loc, "KitCg: invalid field index");
- return;
- }
+ CG_REQUIRE(g, layout && field_index < layout->nfields,
+ "KitCg: invalid field index");
rec_info = cg_type_get(g->c, rec_ty);
- if (!rec_info || rec_info->kind != KIT_CG_TYPE_RECORD ||
- field_index >= rec_info->record.nfields) {
- compiler_panic(g->c, g->cur_loc, "KitCg: invalid record base");
- return;
- }
+ CG_REQUIRE(g,
+ rec_info && rec_info->kind == KIT_CG_TYPE_RECORD &&
+ field_index < rec_info->record.nfields,
+ "KitCg: invalid record base");
field_ty = rec_info->record.fields[field_index].type;
field_offset = layout->fields[field_index].offset;
if (api_unevaluated(g)) {
@@ -1526,11 +1484,8 @@ void kit_cg_field(KitCg* g, uint32_t field_index) {
0) {
Operand base_addr;
ApiSValue sv;
- if (layout->fields[field_index].bit_width == 0) {
- compiler_panic(g->c, g->cur_loc, "KitCg: zero-width bit-field access");
- api_release(g, &base);
- return;
- }
+ CG_REQUIRE(g, layout->fields[field_index].bit_width != 0,
+ "KitCg: zero-width bit-field access");
/* Project to a bit-field PLACE: the place addresses the enclosing storage
* unit and carries the bit-field geometry from the record layout. A plain
* load/store on this place performs the extract/insert; there is no
@@ -1554,11 +1509,8 @@ void kit_cg_field_bits(KitCg* g, uint16_t bit_offset, uint16_t bit_width,
ApiSValue* top;
if (!g || g->sp == 0) return;
top = &g->stack[g->sp - 1u];
- if (!api_is_lvalue_sv(top)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: field_bits requires a place destination");
- return;
- }
+ CG_REQUIRE(g, api_is_lvalue_sv(top),
+ "KitCg: field_bits requires a place destination");
top->bitfield.bit_offset = bit_offset;
top->bitfield.bit_width = bit_width;
top->bitfield.bit_storage_size = bit_storage_size;
diff --git a/src/cg/data.c b/src/cg/data.c
@@ -610,12 +610,9 @@ void kit_cg_data_label_addr(KitCg* g, KitCgLabel target, int64_t addend,
(void)address_space;
if (!g) return;
if (g->data_discard) return;
- if (!width || width > sizeof(pad)) {
- compiler_panic(g->c, g->cur_loc,
- "kit_cg_data_label_addr: width must be 1..%u, got %u",
- (unsigned)sizeof(pad), (unsigned)width);
- return;
- }
+ CG_REQUIRE(g, width && width <= sizeof(pad),
+ "kit_cg_data_label_addr: width must be 1..%u, got %u",
+ (unsigned)sizeof(pad), (unsigned)width);
if (g->data_local_static_target) {
g->target->local_static_data_label_addr(g->target, (Label)target, addend,
width, address_space);
diff --git a/src/cg/internal.h b/src/cg/internal.h
@@ -352,6 +352,36 @@ struct KitCg {
u32 data_tls_relocs_cap;
};
+/* ------------------------------------------------------------------
+ * API-misuse / invariant checks (debug-only)
+ *
+ * The cg.h contract treats a frontend that drives the value stack, scope
+ * handles, or operand kinds incorrectly as a kit bug, not a recoverable
+ * condition. CG_REQUIRE / CG_BUG state those invariants: in a debug build they
+ * fire the same rich FATAL diagnostic + longjmp as a hand-written
+ * compiler_panic, but under NDEBUG the whole check -- condition, message, and
+ * argument evaluation -- is elided, so the release hot path pays nothing.
+ *
+ * CG_REQUIRE(g, cond, fmt, ...) assert the OK condition `cond` holds
+ * CG_BUG(g, fmt, ...) mark an unreachable else/default arm
+ *
+ * Use ONLY for invariants a correct caller can never violate. Genuine resource
+ * failures (OOM, object-emission failures), unsupported-operation guards whose
+ * elision would silently miscompile, size/truncation limits, and configuration
+ * errors stay as unconditional compiler_panic so they trap in every build.
+ * ------------------------------------------------------------------ */
+#ifdef NDEBUG
+/* Elided: the sizeof keeps every operand in an unevaluated context so a value
+ * or `who`-style diagnostic argument used only by the check does not become an
+ * unused variable/parameter under -Werror, while emitting no runtime code. */
+#define CG_REQUIRE(g, cond, ...) ((void)sizeof((g), (cond), __VA_ARGS__, 0))
+#define CG_BUG(g, ...) ((void)sizeof((g), __VA_ARGS__, 0))
+#else
+#define CG_REQUIRE(g, cond, ...) \
+ ((cond) ? (void)0 : compiler_panic((g)->c, (g)->cur_loc, __VA_ARGS__))
+#define CG_BUG(g, ...) compiler_panic((g)->c, (g)->cur_loc, __VA_ARGS__)
+#endif
+
void cg_api_fini(Compiler*);
void api_cg_binop(KitCg* g, BinOp iop, u32 flags);
diff --git a/src/cg/wide.c b/src/cg/wide.c
@@ -291,10 +291,8 @@ ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v,
if (v->op.kind == OPK_IMM) {
return api_make_wide16_int_const(g, v->op.v.imm, ty);
}
- compiler_panic(
- g->c, g->cur_loc,
- "KitCg: 16-byte scalar value is not addressable (kind %u, op %u)",
- (unsigned)api_sv_kind(v), (unsigned)v->op.kind);
+ CG_BUG(g, "KitCg: 16-byte scalar value is not addressable (kind %u, op %u)",
+ (unsigned)api_sv_kind(v), (unsigned)v->op.kind);
return *v;
}