kit

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

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:
Msrc/cg/arith.c | 20++++++--------------
Msrc/cg/call.c | 16++++------------
Msrc/cg/const.c | 10++--------
Msrc/cg/control.c | 132+++++++++++++++++++++++++------------------------------------------------------
Msrc/cg/data.c | 9+++------
Msrc/cg/internal.h | 30++++++++++++++++++++++++++++++
Msrc/cg/wide.c | 6++----
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; }