kit

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

commit 04332745f571e228baecdc3f3a9803532fc98106
parent 9c17016648794d30aae14b0b408054d45187a567
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 11 Jun 2026 17:08:23 -0700

refactor(cg): give type.c sole ownership of type classification

The type-property predicates (api_type_is_float / api_is_{f128,i128,wide8,
wide16}_type), the wide-class classifier (api_wide_kind_for), and the packed
api_type_class memo were split across value.c (predicates) and type.c (memo) —
yet they are all pure queries over a CgType + target ABI, the same kind of thing
as cg_type_* / api_unalias_type, which already live in the type module.

Move them into type.c/type.h so the type module is the single owner of "what is
this type and how does codegen treat it", leaving value.c to just the value
stack and operand constructors. Sharper boundary, no behavior change:

- api_is_*_type / api_type_is_float impls + decls: value.c/internal.h -> type.c
  /type.h (beside cg_type_is_* and api_unalias_type).
- ApiWideKind enum + API_TYPE_CLASS_* format bits: internal.h -> type.h.
- api_wide_kind_for: now static in type.c (its only caller is the classifier
  there) — one fewer cg-internal symbol.
- api_type_class now takes Compiler* not KitCg* (the memo lives on c->cg_api,
  not the value stack), matching every other type query and dropping the KitCg
  dependency from type.h.

Pure relocation: byte-identical across the full 60-category gate; test-cg-api
/test-toy green.

Diffstat:
Msrc/cg/arith.c | 4++--
Msrc/cg/internal.h | 40++++++----------------------------------
Msrc/cg/type.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/cg/type.h | 34++++++++++++++++++++++++++++++++++
Msrc/cg/value.c | 87+------------------------------------------------------------------------------
5 files changed, 114 insertions(+), 124 deletions(-)

diff --git a/src/cg/arith.c b/src/cg/arith.c @@ -319,7 +319,7 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { * ============================================================ */ /* The wide / soft-float dispatch class cached on the value-stack node at - * `depth` below TOS (api_push computes it via api_wide_kind_for). The five + * `depth` below TOS (api_push stamps it from api_type_class). The five * stack-top predicates below are now a bounds check plus a tag compare; the * type-get + alias-chase gauntlet that used to run per operand happens once at * push instead. WK_NARROW for an out-of-range depth keeps every predicate false @@ -337,7 +337,7 @@ int api_i128_stack_top(KitCg* g, u32 depth) { * backend handles add/sub/and/or/xor on such values as register pairs, but * mul/div/shift must be lowered to a __*di3 runtime call (see * api_wideint64_binop). i128 routes through its own ti3 path (api_i128_*), so - * the WK_WIDE8 class (computed in api_wide_kind_for) excludes it. */ + * the WK_WIDE8 class excludes it. */ static int api_wide64_stack_top(KitCg* g, u32 depth) { return api_stack_wide_kind(g, depth) == WK_WIDE8; } diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -71,32 +71,10 @@ typedef union ApiDelayed { union ApiDelayed* next_free; } ApiDelayed; -/* Scalar wide-class tag cached on every value-stack node (api_push). The wide / - * soft-float dispatch in arith.c re-derived this on every operand with a string - * of type-get + alias-chase round trips; it is a pure function of the value type - * (and the target's fixed float-ABI / split-lane policy), and the type never - * changes after a value is pushed, so it is computed once at push time and the - * stack-top predicates become a load and compare. The tags are mutually - * exclusive (a type matches at most one; see api_wide_kind_for). WK_I128 is kept - * distinct from WK_WIDE8 because the int wide64 path explicitly excludes i128 - * (i128 routes through its own ti3 lowering). WK_NARROW == 0 so a zeroed node - * (api_make_sv memsets) defaults to the common narrow case. */ -typedef enum ApiWideKind { - WK_NARROW = 0, /* not wide / not soft-float: the common fast path */ - WK_I128, /* 128-bit integer (api_is_i128_type) */ - WK_F128, /* 128-bit float / long double (api_is_f128_type) */ - WK_WIDE8, /* int split into two 32-bit lanes (api_int_is_wide64) */ - WK_SOFT_DOUBLE, /* f64 on a target without hardware double */ - WK_SOFT_SINGLE, /* f32 on a pure-soft target (no FP unit) */ -} ApiWideKind; - -/* Packed per-type classification memoized for the value stack. The wide/soft - * class and the aggregate-place property are both pure functions of (type, - * target), so they are computed once per type id (builtins precomputed at cg_api - * init, user types filled lazily) instead of re-derived on every api_push. The - * low bits hold the ApiWideKind; bit 3 marks an aggregate (record) type. */ -#define API_TYPE_CLASS_WIDE_MASK 0x07u -#define API_TYPE_CLASS_AGGREGATE 0x08u +/* The scalar wide-class tag (ApiWideKind) and the packed per-type + * classification it feeds (api_type_class) live in cg/type.h, with the rest of + * the type-property queries. The value stack only stores the result (the + * one-byte tag below) and reads it back. */ /* Bit-field geometry carried by a bit-field PLACE. `kit_cg_field` fills this * from the record layout when it projects to a bit-field; a plain load/store on @@ -446,14 +424,8 @@ KitCgDecl api_sym_attrs(KitCg* g, KitCgSym sym); int api_sym_is_tls(KitCg* g, KitCgSym sym); RelocKind api_data_reloc_kind(int pcrel, uint32_t width); SrcLoc api_no_loc(void); -int api_type_is_float(Compiler* c, KitCgTypeId ty); -int api_is_f128_type(Compiler* c, KitCgTypeId ty); -int api_is_i128_type(Compiler* c, KitCgTypeId ty); -int api_is_wide16_scalar_type(Compiler* c, KitCgTypeId ty); -int api_is_wide8_scalar_type(Compiler* c, KitCgTypeId ty); -u8 api_wide_kind_for(Compiler* c, KitCgTypeId ty); -/* Memoized fetch of the packed API_TYPE_CLASS_* byte for `ty` (see above). */ -u8 api_type_class(KitCg* g, KitCgTypeId ty); +/* Type-property predicates (api_type_is_float / api_is_*_type) and the type + * classification (api_type_class) are declared in cg/type.h. */ /* Delayed cmp/arith payload pool (fold.c). alloc returns a payload from the * per-function freelist or the arena; free returns it for reuse; reset drops diff --git a/src/cg/type.c b/src/cg/type.c @@ -163,6 +163,76 @@ static void builtin_cg_type_init(Compiler* c, CgType* out, KitCgBuiltinType t) { static CgApiState* cg_api_get(Compiler* c); static CgApiType* api_type_from_id(Compiler* c, KitCgTypeId id); +/* ---- codegen scalar type-property predicates ---- + * Thin queries over the resolved CgType / target ABI; pure functions of + * (type, target). The wide-class predicates below feed api_wide_kind_for, which + * feeds the memoized api_type_class. */ + +int api_type_is_float(Compiler* c, KitCgTypeId ty) { + const CgType* cg; + ty = api_unalias_type(c, ty); + cg = cg_type_get(c, ty); + return cg && cg->kind == KIT_CG_TYPE_FLOAT; +} + +int api_is_f128_type(Compiler* c, KitCgTypeId ty) { + const CgType* cg; + ty = api_unalias_type(c, ty); + cg = cg_type_get(c, ty); + return cg && cg->kind == KIT_CG_TYPE_FLOAT && cg->fp.width == 128; +} + +int api_is_i128_type(Compiler* c, KitCgTypeId ty) { + const CgType* cg; + ty = api_unalias_type(c, ty); + cg = cg_type_get(c, ty); + return cg && cg->kind == KIT_CG_TYPE_INT && cg->integer.width == 128; +} + +int api_is_wide16_scalar_type(Compiler* c, KitCgTypeId ty) { + return api_is_f128_type(c, ty) || api_is_i128_type(c, ty); +} + +/* 8-byte scalar split into two 4-byte lanes by the selected ABI. This covers + * 32-bit native ABIs whose generic CG path cannot keep the value in a single + * scalar register/value. Such values are forced memory-resident so operations + * can be legalized as lane sequences or runtime calls. */ +int api_is_wide8_scalar_type(Compiler* c, KitCgTypeId ty) { + ABITypeInfo ti; + if (!c || !c->abi || !ty) return 0; + if (abi_cg_scalar_split_lane_size(c->abi, ty) != 4u) return 0; + ti = abi_cg_type_info(c->abi, ty); + return ti.size == 8u && + (ti.scalar_kind == ABI_SC_INT || ti.scalar_kind == ABI_SC_FLOAT); +} + +/* Classify a scalar into its mutually-exclusive wide / soft-float dispatch + * class. Reproduces the predicates exactly: WK_I128/WK_F128 are the 128-bit + * scalars; WK_WIDE8 is an int-width-bearing split-lane scalar (excluding i128, + * handled above; a split-lane *float* bears int width 0 and falls through to a + * soft class); WK_SOFT_DOUBLE/WK_SOFT_SINGLE are f64/f32 with no hardware FP of + * that width. Only api_compute_type_class calls this. */ +static u8 api_wide_kind_for(Compiler* c, KitCgTypeId ty) { + u8 fa; + if (!c || !ty) return WK_NARROW; + if (api_is_i128_type(c, ty)) return WK_I128; + if (api_is_f128_type(c, ty)) return WK_F128; + if (kit_cg_type_int_width((KitCompiler*)c, ty) != 0 && + api_is_wide8_scalar_type(c, ty)) { + return WK_WIDE8; + } + fa = c->target.float_abi; + if ((fa == KIT_FLOAT_ABI_SOFT || fa == KIT_FLOAT_ABI_SINGLE) && + kit_cg_type_float_width((KitCompiler*)c, ty) == 64) { + return WK_SOFT_DOUBLE; + } + if (fa == KIT_FLOAT_ABI_SOFT && + kit_cg_type_float_width((KitCompiler*)c, ty) == 32) { + return WK_SOFT_SINGLE; + } + return WK_NARROW; +} + /* Compute the packed API_TYPE_CLASS_* byte for `ty` from scratch: the wide / * soft-float dispatch class plus the aggregate-place bit. Both are pure * functions of (type, target), so callers memoize the result (per builtin at @@ -187,8 +257,7 @@ static void cg_api_init_builtins(Compiler* c, CgApiState* s) { s->builtins_init = 1; } -u8 api_type_class(KitCg* g, KitCgTypeId ty) { - Compiler* c = g->c; +u8 api_type_class(Compiler* c, KitCgTypeId ty) { CgApiState* s; CgApiType* e; u32 seg; diff --git a/src/cg/type.h b/src/cg/type.h @@ -87,6 +87,40 @@ KitCgTypeId resolve_type(Compiler*, KitCgTypeId); KitCgTypeId api_unalias_type(Compiler*, KitCgTypeId); int cg_type_is_void(Compiler*, KitCgTypeId); int cg_type_is_aggregate(Compiler*, KitCgTypeId); + +/* Codegen-relevant scalar predicates: a value of this type is a float, a + * 128-bit scalar (f128 / i128), or a wide 8-byte scalar the selected ABI splits + * into two 32-bit lanes (forced memory-resident; legalized as lane sequences / + * runtime calls). Pure functions of (type, target ABI). */ +int api_type_is_float(Compiler*, KitCgTypeId); +int api_is_f128_type(Compiler*, KitCgTypeId); +int api_is_i128_type(Compiler*, KitCgTypeId); +int api_is_wide16_scalar_type(Compiler*, KitCgTypeId); /* f128 || i128 */ +int api_is_wide8_scalar_type(Compiler*, KitCgTypeId); + +/* The wide / soft-float dispatch class of a scalar type — mutually exclusive, + * cached once per value on the value-stack node so the per-operand wide/soft + * predicates become a one-byte compare instead of a fresh type-query gauntlet. + * Pure function of the type and the target's fixed float-ABI / split-lane + * policy. WK_NARROW == 0 is the common fast path. */ +typedef enum ApiWideKind { + WK_NARROW = 0, /* not wide / not soft-float: the common fast path */ + WK_I128, /* 128-bit integer (api_is_i128_type) */ + WK_F128, /* 128-bit float / long double (api_is_f128_type) */ + WK_WIDE8, /* int split into two 32-bit lanes (api_is_wide8_scalar_type) */ + WK_SOFT_DOUBLE, /* f64 on a target without hardware double */ + WK_SOFT_SINGLE, /* f32 on a pure-soft target (no FP unit) */ +} ApiWideKind; + +/* api_type_class packs a type's codegen classification into one byte: the + * ApiWideKind in the low bits plus an aggregate-place bit. Both are pure + * functions of (type, target), so the result is memoized per type id (builtins + * precomputed at cg_api init, user types lazily) rather than re-derived on every + * value push. */ +#define API_TYPE_CLASS_WIDE_MASK 0x07u +#define API_TYPE_CLASS_AGGREGATE 0x08u +u8 api_type_class(Compiler*, KitCgTypeId); + KitCgTypeId cg_type_ptr_to(Compiler*, KitCgTypeId); KitCgTypeId cg_type_pointee(Compiler*, KitCgTypeId); /* The function's result type, or the VOID builtin when it returns nothing. */ diff --git a/src/cg/value.c b/src/cg/value.c @@ -14,91 +14,6 @@ _Static_assert(offsetof(ApiSValue, bitfield) + sizeof(ApiBitField) == _Static_assert(sizeof(ApiSValue) <= 64, "delayed payload must be off-node; ApiSValue stays small"); -int api_type_is_float(Compiler* c, KitCgTypeId ty) { - const CgType* cg; - ty = api_unalias_type(c, ty); - cg = cg_type_get(c, ty); - return cg && cg->kind == KIT_CG_TYPE_FLOAT; -} - -int api_is_f128_type(Compiler* c, KitCgTypeId ty) { - const CgType* cg; - ty = api_unalias_type(c, ty); - cg = cg_type_get(c, ty); - return cg && cg->kind == KIT_CG_TYPE_FLOAT && cg->fp.width == 128; -} - -int api_is_i128_type(Compiler* c, KitCgTypeId ty) { - const CgType* cg; - ty = api_unalias_type(c, ty); - cg = cg_type_get(c, ty); - return cg && cg->kind == KIT_CG_TYPE_INT && cg->integer.width == 128; -} - -int api_is_wide16_scalar_type(Compiler* c, KitCgTypeId ty) { - return api_is_f128_type(c, ty) || api_is_i128_type(c, ty); -} - -/* 8-byte scalar split into two 4-byte lanes by the selected ABI. This covers - * 32-bit native ABIs whose generic CG path cannot keep the value in a single - * scalar register/value. Such values are forced memory-resident so operations - * can be legalized as lane sequences or runtime calls. */ -int api_is_wide8_scalar_type(Compiler* c, KitCgTypeId ty) { - ABITypeInfo ti; - if (!c || !c->abi || !ty) return 0; - if (abi_cg_scalar_split_lane_size(c->abi, ty) != 4u) return 0; - ti = abi_cg_type_info(c->abi, ty); - return ti.size == 8u && - (ti.scalar_kind == ABI_SC_INT || ti.scalar_kind == ABI_SC_FLOAT); -} - -/* Classify a value type into its wide / soft-float dispatch class once, so the - * stack-top predicates in arith.c become a cached-byte compare instead of a - * fresh gauntlet of type-get + alias-chase round trips per operand. Called from - * api_push (the sole stack writer); the result rides on the node (the bitfield - * slack) for the value's lifetime, which is sound because the class is a pure - * function of the type plus the target's fixed float-ABI / split-lane policy, - * and the type never changes after a push. - * - * The classes are mutually exclusive and reproduce the original predicates - * exactly: - * WK_I128 == api_is_i128_type - * WK_F128 == api_is_f128_type - * WK_WIDE8 == api_int_is_wide64 (int-width-bearing wide8, excluding i128) - * WK_SOFT_DOUBLE == api_type_is_soft_double (f64 under SOFT/SINGLE float-abi) - * WK_SOFT_SINGLE == api_type_is_soft_single (f32 under SOFT float-abi) - * They cannot overlap: i128/wide8 are integer, f128/soft-double/soft-single are - * float, and the three float classes have distinct widths (128/64/32). A wide8 - * float (e.g. double on a 32-bit ABI) bears int width 0 so it is not WK_WIDE8 — - * it falls through to WK_SOFT_DOUBLE, exactly as the original int-width guard in - * api_int_is_wide64 intended. */ -u8 api_wide_kind_for(Compiler* c, KitCgTypeId ty) { - u8 fa; - if (!c || !ty) return WK_NARROW; - if (api_is_i128_type(c, ty)) return WK_I128; - if (api_is_f128_type(c, ty)) return WK_F128; - /* Int split into two 32-bit lanes by the ABI. The original api_int_is_wide64 - * excludes i128 (handled above) and requires a nonzero int-like width, so a - * split-lane *float* never matches here. */ - if (kit_cg_type_int_width((KitCompiler*)c, ty) != 0 && - api_is_wide8_scalar_type(c, ty)) { - return WK_WIDE8; - } - fa = c->target.float_abi; - /* Soft double: f64 where the target has no hardware double (SOFT or SINGLE; - * under SINGLE only single-precision is in FP regs, double is always soft). */ - if ((fa == KIT_FLOAT_ABI_SOFT || fa == KIT_FLOAT_ABI_SINGLE) && - kit_cg_type_float_width((KitCompiler*)c, ty) == 64) { - return WK_SOFT_DOUBLE; - } - /* Soft single: f32 under pure-soft float-abi (no FP unit at all). */ - if (fa == KIT_FLOAT_ABI_SOFT && - kit_cg_type_float_width((KitCompiler*)c, ty) == 32) { - return WK_SOFT_SINGLE; - } - return WK_NARROW; -} - /* The operand/value constructors run on the hottest per-operand codegen path. * Each uses a designated compound literal rather than memset + field stores: it * is value-identical (the unnamed fields zero-fill) but lets the compiler emit @@ -245,7 +160,7 @@ void api_push(KitCg* g, ApiSValue v) { * wide/soft-float dispatch class — both pure functions of the type, computed * once per type id rather than re-derived (cg_type_get + alias-chase + ABI * round trips) on every push. */ - u8 cls = api_type_class(g, ty); + u8 cls = api_type_class(g->c, ty); /* An aggregate (record) can only ever be a PLACE: it is addressed, loaded, * and passed by SRET/BYVAL/BYREF, never materialized as a scalar VALUE. Catch * any aggregate VALUE at the point it would enter the stack. i128/f128 are