kit

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

commit 244f103980455c31f5f0ef75d128bd5fad50bc0a
parent 64afd07c476c8aaca38a511fdc9177b830390630
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 13:22:52 -0700

cg: carry source signedness and enums into debug info

Integer CG storage is width-only by design, so locals/params/fields
debugged as their CG type lost C signedness (all emitted DW_ATE_signed)
and enums collapsed to a bare signed int with no enumerators.

Add a source-facing nominal scalar, KIT_CG_TYPE_SOURCE_BASE: a spelling
(name + KitCgDebugEncoding) over a width-only storage builtin. It reports
its exact kind and feeds debug info, but unaliases to the storage builtin
for every ABI/codegen/storage query (api_unalias_type strips it like an
alias), so frontends pass one type id and get faithful signed/unsigned
debug for free. The C frontend lowers each integer/char scalar to a
source-base spelling; the ABI classifiers, fold width, and int/float
width queries see through it.

Enums now thread their enumerators: the C parser collects (name,value)
onto the enum Type and lowering passes them to kit_cg_type_enum;
api_debug_type emits DW_TAG_enumeration_type with DW_TAG_enumerator
children over the (now source-base) underlying type instead of a faked
"int". cg_type_set_enum validates its base through unalias so a
source-base underlying type is accepted.

Verified end to end: kit cc -g emits DW_ATE_unsigned / signed_char /
unsigned_char for the matching C types plus real enumerator DIEs.
test-cg-api 291/0, abi_classify 384/0, test-opt, smoke-x64/rv64, parse,
toy all green.

Diffstat:
Minclude/kit/cg.h | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mlang/c/parse/parse_type.c | 19+++++++++++++++++++
Mlang/c/type/type.c | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mlang/c/type/type.h | 9+++++++++
Mlang/toy/types.c | 1+
Msrc/abi/abi.c | 2++
Msrc/abi/abi_aapcs64.c | 3+++
Msrc/abi/abi_rv64.c | 6++++++
Msrc/abi/abi_sysv_x64.c | 6++++++
Msrc/abi/abi_win64_x64.c | 3+++
Msrc/arch/wasm/abi.c | 3+++
Msrc/cg/debug.c | 41+++++++++++++++++++++++++++++++++++++----
Msrc/cg/fold.c | 4++++
Msrc/cg/type.c | 154++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/cg/type.h | 5+++++
Mtest/api/cg_type_test.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
16 files changed, 437 insertions(+), 40 deletions(-)

diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -55,8 +55,25 @@ typedef enum KitCgTypeKind { KIT_CG_TYPE_ENUM, KIT_CG_TYPE_ALIAS, KIT_CG_TYPE_VARARG_STATE, + /* A source-facing primitive spelling (sign + name) over a width-only storage + * builtin. Storage/ABI/codegen see the builtin (the id unaliases to it); the + * exact kind, debug info, and the C-source backend see the name + encoding. */ + KIT_CG_TYPE_SOURCE_BASE, } KitCgTypeKind; +/* Debug base-type encoding carried by a source-base type. Mirrors DWARF + * DW_ATE_* (and the producer's DEBUG_BE_* set) so kit_cg_local/param/field can + * round-trip C signedness into debug info without storage carrying it. */ +typedef enum KitCgDebugEncoding { + KIT_CG_DEBUG_ENC_NONE, + KIT_CG_DEBUG_ENC_BOOL, + KIT_CG_DEBUG_ENC_SIGNED, + KIT_CG_DEBUG_ENC_UNSIGNED, + KIT_CG_DEBUG_ENC_SIGNED_CHAR, + KIT_CG_DEBUG_ENC_UNSIGNED_CHAR, + KIT_CG_DEBUG_ENC_FLOAT, +} KitCgDebugEncoding; + typedef enum KitCgCallConv { /* Backend-selected C ABI for the target triple. Frontends should use this * unless source semantics or ABI interop explicitly require another @@ -222,6 +239,16 @@ static inline KitCgTypeId kit_cg_type_record(KitCompiler* c, KIT_API KitCgTypeId kit_cg_type_alias(KitCompiler*, KitSym name, KitCgTypeId base); +/* A source-facing primitive spelling over a width-only storage builtin. storage + * must be a scalar builtin (bool/int/float); the result reports + * KIT_CG_TYPE_SOURCE_BASE for the exact kind, carries name + encoding for debug + * info, and unaliases to storage for all ABI/codegen/storage queries. This lets + * frontends pass one type id and get faithful signed/unsigned debug info + * without storage builtins splitting by sign. */ +KIT_API KitCgTypeId kit_cg_type_source_base(KitCompiler*, KitSym name, + KitCgTypeId storage, + KitCgDebugEncoding encoding); + /* Type queries. * * kit_cg_type_kind is exact: alias ids report KIT_CG_TYPE_ALIAS. Layout and @@ -229,13 +256,47 @@ KIT_API KitCgTypeId kit_cg_type_alias(KitCompiler*, KitSym name, * queries are exact: call kit_cg_type_resolve_alias first when storage shape is * desired. */ KIT_API KitStatus kit_cg_type_info(KitCompiler*, KitCgTypeId, KitCgTypeInfo*); -KIT_API KitCgTypeId kit_cg_type_resolve_alias(KitCompiler*, KitCgTypeId); + +/* Stable per-id identity view. Returns a pointer to an interned KitCgTypeInfo + * whose IDENTITY fields are authoritative for the lifetime of the compiler: + * id, storage_id (the alias-resolved terminal id), kind (exact), and the + * BUILTIN / NOMINAL flag bits. The layout substruct and the COMPLETE / SIZED + * flag bits are NOT maintained on this view — completeness and sizing can + * change as records complete, and layout is computed lazily — so call + * kit_cg_type_info for an authoritative full snapshot. Returns NULL for an + * invalid id. The cheap identity queries below are inline projections over + * this one extern. */ +KIT_API const KitCgTypeInfo* kit_cg_type_view(KitCompiler*, KitCgTypeId); + KIT_API int kit_cg_type_same_storage(KitCompiler*, KitCgTypeId, KitCgTypeId); -KIT_API int kit_cg_type_is_void(KitCompiler*, KitCgTypeId); KIT_API int kit_cg_type_is_complete(KitCompiler*, KitCgTypeId); KIT_API int kit_cg_type_is_sized(KitCompiler*, KitCgTypeId); -KIT_API int kit_cg_func_result_has_value(KitCompiler*, KitCgFuncResult); -KIT_API KitCgTypeKind kit_cg_type_kind(KitCompiler*, KitCgTypeId); + +/* Exact kind: alias ids report KIT_CG_TYPE_ALIAS. */ +static inline KitCgTypeKind kit_cg_type_kind(KitCompiler* c, KitCgTypeId id) { + const KitCgTypeInfo* t = kit_cg_type_view(c, id); + return t ? t->kind : KIT_CG_TYPE_VOID; +} +/* The alias-resolved terminal (storage) id, or KIT_CG_TYPE_NONE for an + * invalid id. */ +static inline KitCgTypeId kit_cg_type_resolve_alias(KitCompiler* c, + KitCgTypeId id) { + const KitCgTypeInfo* t = kit_cg_type_view(c, id); + return t ? t->storage_id : KIT_CG_TYPE_NONE; +} +/* True iff the type resolves through aliases to void. */ +static inline int kit_cg_type_is_void(KitCompiler* c, KitCgTypeId id) { + const KitCgTypeInfo* t = kit_cg_type_view(c, id); + if (!t) return 0; + if (t->storage_id != t->id) t = kit_cg_type_view(c, t->storage_id); + return t && t->kind == KIT_CG_TYPE_VOID; +} +/* A function result carries a value unless it is absent or the void builtin. */ +static inline int kit_cg_func_result_has_value(KitCompiler* c, + KitCgFuncResult result) { + return result.type != KIT_CG_TYPE_NONE && + !kit_cg_type_is_void(c, result.type); +} KIT_API uint64_t kit_cg_type_size(KitCompiler*, KitCgTypeId); KIT_API uint32_t kit_cg_type_align(KitCompiler*, KitCgTypeId); KIT_API uint32_t kit_cg_type_int_width(KitCompiler*, KitCgTypeId); @@ -271,6 +332,11 @@ KIT_API KitStatus kit_cg_type_enum_value(KitCompiler*, KitCgTypeId, KIT_API KitSym kit_cg_type_alias_name(KitCompiler*, KitCgTypeId); KIT_API KitCgTypeId kit_cg_type_alias_base(KitCompiler*, KitCgTypeId); +KIT_API KitSym kit_cg_type_source_base_name(KitCompiler*, KitCgTypeId); +KIT_API KitCgTypeId kit_cg_type_source_base_storage(KitCompiler*, KitCgTypeId); +KIT_API KitCgDebugEncoding kit_cg_type_source_base_encoding(KitCompiler*, + KitCgTypeId); + typedef enum KitCgSymbolFeature { KIT_CG_SYMFEAT_WEAK, KIT_CG_SYMFEAT_PROTECTED_VISIBILITY, diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -1190,6 +1190,11 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { const Type* et = type_enum(p->pool, tid, tag_name, underlying); expect_punct(p, '{', "'{'"); i64 next_val = 0; + /* Collect enumerators so the enum Type can carry them into CG/debug info + * (DW_TAG_enumerator). Geometric growth from the arena keeps it allocation- + * light; the arena never frees, but enum bodies are small. */ + EnumConst* consts = NULL; + u32 nconsts = 0, consts_cap = 0; for (;;) { Sym name; SrcLoc nloc = tok_loc(p, &p->cur); @@ -1210,10 +1215,24 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { e = scope_define(p, name, SEK_ENUM_CST, et); e->v.enum_value = val; next_val = val + 1; + if (nconsts == consts_cap) { + u32 newcap = consts_cap ? consts_cap * 2u : 8u; + EnumConst* grown = arena_array(p->pool->arena, EnumConst, newcap); + if (grown && consts) memcpy(grown, consts, nconsts * sizeof(EnumConst)); + consts = grown; + consts_cap = grown ? newcap : 0; + } + if (consts && nconsts < UINT16_MAX) { + consts[nconsts].name = name; + consts[nconsts].value = val; + nconsts++; + } if (!accept_punct(p, ',')) break; if (is_punct(&p->cur, '}')) break; } expect_punct(p, '}', "'}' after enumerator list"); + ((Type*)et)->enm.consts = consts; + ((Type*)et)->enm.nconsts = (u16)nconsts; parse_attrs_into(p, &rec_attrs); if (tag_name) { TagEntry* existing = tag_lookup_local(p, tag_name); diff --git a/lang/c/type/type.c b/lang/c/type/type.c @@ -743,6 +743,48 @@ static KitCgTypeId type_cg_builtin(KitCompiler* c, TypeKind kind) { return KIT_CG_TYPE_NONE; } +/* Source spelling + debug encoding per integer/char TypeKind. CG integer + * storage is width-only, so the frontend wraps each scalar lowering in a + * source-base type (kit_cg_type_source_base) that carries the C signedness and + * name into debug info while still unaliasing to the width-only storage builtin + * for all ABI/codegen. Kinds with a NULL name (void/bool/float/double) keep the + * bare builtin: the CG debug producer already names them correctly. */ +typedef struct ScalarDbgSpec { + const char* name; + u8 enc; /* KitCgDebugEncoding */ +} ScalarDbgSpec; + +static const ScalarDbgSpec kScalarDbg[TY_ENUM + 1] = { + [TY_CHAR] = {"char", KIT_CG_DEBUG_ENC_SIGNED_CHAR}, + [TY_SCHAR] = {"signed char", KIT_CG_DEBUG_ENC_SIGNED_CHAR}, + [TY_UCHAR] = {"unsigned char", KIT_CG_DEBUG_ENC_UNSIGNED_CHAR}, + [TY_SHORT] = {"short", KIT_CG_DEBUG_ENC_SIGNED}, + [TY_USHORT] = {"unsigned short", KIT_CG_DEBUG_ENC_UNSIGNED}, + [TY_INT] = {"int", KIT_CG_DEBUG_ENC_SIGNED}, + [TY_UINT] = {"unsigned int", KIT_CG_DEBUG_ENC_UNSIGNED}, + [TY_LONG] = {"long", KIT_CG_DEBUG_ENC_SIGNED}, + [TY_ULONG] = {"unsigned long", KIT_CG_DEBUG_ENC_UNSIGNED}, + [TY_LLONG] = {"long long", KIT_CG_DEBUG_ENC_SIGNED}, + [TY_ULLONG] = {"unsigned long long", KIT_CG_DEBUG_ENC_UNSIGNED}, + [TY_INT128] = {"__int128", KIT_CG_DEBUG_ENC_SIGNED}, + [TY_UINT128] = {"unsigned __int128", KIT_CG_DEBUG_ENC_UNSIGNED}, +}; + +/* Lower a scalar TypeKind to its CG type id: a source-base spelling over the + * width-only storage builtin for integer/char kinds, the bare builtin for + * void/bool/float, or NONE for non-scalar kinds (caller falls through to the + * structural lowering). Pure function of (kind, target), like type_cg_builtin. */ +static KitCgTypeId type_cg_scalar(KitCompiler* c, TypeKind kind) { + KitCgTypeId base = type_cg_builtin(c, kind); + const ScalarDbgSpec* s; + if (base == KIT_CG_TYPE_NONE) return KIT_CG_TYPE_NONE; + if ((unsigned)kind > (unsigned)TY_ENUM) return base; + s = &kScalarDbg[kind]; + if (!s->name) return base; + return kit_cg_type_source_base(c, kit_sym_intern(c, kit_slice_cstr(s->name)), + base, (KitCgDebugEncoding)s->enc); +} + typedef enum TypeCgMode { TYPE_CG_VALUE, } TypeCgMode; @@ -889,11 +931,11 @@ static KitCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, cacheable = (mode == TYPE_CG_VALUE); if (cacheable && t->cg_id != KIT_CG_TYPE_NONE) return t->cg_id; /* cached => was incomplete-free => still stable */ - id = type_cg_builtin(l->c, (TypeKind)t->kind); + id = type_cg_scalar(l->c, (TypeKind)t->kind); if (id != KIT_CG_TYPE_NONE) { - /* type_cg_builtin is a pure function of t->kind (and the fixed target), so - * a builtin id is mode-independent and incomplete-free: stamp it on the - * node so the next VALUE-mode crossing short-circuits above. Builtins are + /* type_cg_scalar is a pure function of t->kind (and the fixed target), so + * a scalar id is mode-independent and incomplete-free: stamp it on the + * node so the next VALUE-mode crossing short-circuits above. Scalar ids are * never 0, so they can't alias the NONE sentinel. */ if (cacheable) ((Type*)t)->cg_id = id; return id; @@ -956,10 +998,24 @@ static KitCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, case TY_UNION: id = type_cg_record_layout(l, t); break; - case TY_ENUM: - id = kit_cg_type_enum(l->c, t->enm.tag, - type_cg_lower(l, t->enm.base, mode), NULL, 0); + case TY_ENUM: { + KitCgTypeId ebase = type_cg_lower(l, t->enm.base, mode); + KitCgEnumValue* evs = NULL; + u32 nv = t->enm.nconsts; + if (nv) { + evs = arena_array(l->p->arena, KitCgEnumValue, nv); + if (!evs) { + nv = 0; + } else { + for (u32 i = 0; i < nv; ++i) { + evs[i].name = t->enm.consts[i].name; + evs[i].value = (u64)t->enm.consts[i].value; + } + } + } + id = kit_cg_type_enum(l->c, t->enm.tag, ebase, evs, nv); break; + } default: id = KIT_CG_TYPE_NONE; break; diff --git a/lang/c/type/type.h b/lang/c/type/type.h @@ -87,6 +87,13 @@ typedef struct Field { u8 packed; } Field; +/* One enumerator (name = value), collected by parse_enum and carried on the + * enum Type so CG/debug lowering can emit DW_TAG_enumerator children. */ +typedef struct EnumConst { + Sym name; + i64 value; +} EnumConst; + struct Type { u16 kind; u16 qual; @@ -134,6 +141,8 @@ struct Type { TagId tag_id; Sym tag; const Type* base; + const EnumConst* consts; /* enumerators, NULL until the body is parsed */ + u16 nconsts; } enm; }; }; diff --git a/lang/toy/types.c b/lang/toy/types.c @@ -465,6 +465,7 @@ ToyTypeId toy_type_from_cg(ToyParser* p, KitCgTypeId cg) { case KIT_CG_TYPE_INT: case KIT_CG_TYPE_FLOAT: case KIT_CG_TYPE_VARARG_STATE: + case KIT_CG_TYPE_SOURCE_BASE: type.kind = TOY_TYPE_BUILTIN; break; case KIT_CG_TYPE_PTR: diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -32,6 +32,8 @@ static ABITypeInfo abi_cg_type_info_compute(TargetABI* a, KitCgTypeId id) { switch (t->kind) { case KIT_CG_TYPE_ALIAS: return abi_cg_type_info(a, t->alias.base); + case KIT_CG_TYPE_SOURCE_BASE: + return abi_cg_type_info(a, t->source_base.base); case KIT_CG_TYPE_PTR: r.size = a->c->target.ptr_size ? a->c->target.ptr_size : 8; r.align = a->c->target.ptr_align ? a->c->target.ptr_align : 8; diff --git a/src/abi/abi_aapcs64.c b/src/abi/abi_aapcs64.c @@ -79,6 +79,9 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, case KIT_CG_TYPE_ALIAS: classify_one(a, ty->alias.base, out, is_return); return; + case KIT_CG_TYPE_SOURCE_BASE: + classify_one(a, ty->source_base.base, out, is_return); + return; default: classify_scalar(a, t, out); return; diff --git a/src/abi/abi_rv64.c b/src/abi/abi_rv64.c @@ -96,6 +96,9 @@ static u32 riscv_collect_leaves(TargetABI* a, KitCgTypeId tid, u32 base_off, if (!t) return written + 1u; /* poison: treat as too-many */ if (t->kind == KIT_CG_TYPE_ALIAS) return riscv_collect_leaves(a, t->alias.base, base_off, out, cap, written); + if (t->kind == KIT_CG_TYPE_SOURCE_BASE) + return riscv_collect_leaves(a, t->source_base.base, base_off, out, cap, + written); if (t->kind == KIT_CG_TYPE_RECORD) { if (t->record.is_union) return cap + 1u; /* unions: bail */ for (u32 i = 0; i < t->record.nfields; ++i) { @@ -273,6 +276,9 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, case KIT_CG_TYPE_ALIAS: classify_one(a, ty->alias.base, out, is_return); return; + case KIT_CG_TYPE_SOURCE_BASE: + classify_one(a, ty->source_base.base, out, is_return); + return; default: classify_scalar(a, t, out); return; diff --git a/src/abi/abi_sysv_x64.c b/src/abi/abi_sysv_x64.c @@ -73,6 +73,9 @@ static int classify_range(TargetABI* a, KitCgTypeId t, u32 base, if (ty->kind == KIT_CG_TYPE_ALIAS) { return classify_range(a, ty->alias.base, base, cls); } + if (ty->kind == KIT_CG_TYPE_SOURCE_BASE) { + return classify_range(a, ty->source_base.base, base, cls); + } if (ty->kind == KIT_CG_TYPE_ENUM) { return classify_range(a, ty->enum_.base, base, cls); } @@ -182,6 +185,9 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, case KIT_CG_TYPE_ALIAS: classify_one(a, ty->alias.base, out, is_return); return; + case KIT_CG_TYPE_SOURCE_BASE: + classify_one(a, ty->source_base.base, out, is_return); + return; default: classify_scalar(a, t, out, is_return); return; diff --git a/src/abi/abi_win64_x64.c b/src/abi/abi_win64_x64.c @@ -111,6 +111,9 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, case KIT_CG_TYPE_ALIAS: classify_one(a, ty->alias.base, out, is_return); return; + case KIT_CG_TYPE_SOURCE_BASE: + classify_one(a, ty->source_base.base, out, is_return); + return; default: classify_scalar(a, t, out, is_return); return; diff --git a/src/arch/wasm/abi.c b/src/arch/wasm/abi.c @@ -116,6 +116,9 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, case KIT_CG_TYPE_ALIAS: classify_one(a, ty->alias.base, out, is_return); return; + case KIT_CG_TYPE_SOURCE_BASE: + classify_one(a, ty->source_base.base, out, is_return); + return; default: classify_scalar(a, t, out); return; diff --git a/src/cg/debug.c b/src/cg/debug.c @@ -1,5 +1,27 @@ #include "cg/internal.h" +/* Map the public source-base encoding onto the DWARF producer's base-type + * encoding. KitCgDebugEncoding deliberately mirrors the DEBUG_BE_* set the + * frontends care about; NONE has no DWARF spelling, so fall back to signed. */ +static DebugBaseEncoding debug_base_encoding(u8 enc) { + switch ((KitCgDebugEncoding)enc) { + case KIT_CG_DEBUG_ENC_BOOL: + return DEBUG_BE_BOOL; + case KIT_CG_DEBUG_ENC_UNSIGNED: + return DEBUG_BE_UNSIGNED; + case KIT_CG_DEBUG_ENC_SIGNED_CHAR: + return DEBUG_BE_SIGNED_CHAR; + case KIT_CG_DEBUG_ENC_UNSIGNED_CHAR: + return DEBUG_BE_UNSIGNED_CHAR; + case KIT_CG_DEBUG_ENC_FLOAT: + return DEBUG_BE_FLOAT; + case KIT_CG_DEBUG_ENC_NONE: + case KIT_CG_DEBUG_ENC_SIGNED: + return DEBUG_BE_SIGNED; + } + return DEBUG_BE_SIGNED; +} + DebugTypeId api_debug_type(KitCg* g, KitCgTypeId id) { const CgType* ty; if (!g || !g->debug) return DEBUG_TYPE_NONE; @@ -71,15 +93,26 @@ DebugTypeId api_debug_type(KitCg* g, KitCgTypeId id) { if (!b) return DEBUG_TYPE_NONE; return debug_type_record_end(b); } - case KIT_CG_TYPE_ENUM: - return debug_type_base(g->debug, - pool_intern_slice(g->c->global, SLICE_LIT("int")), - DEBUG_BE_SIGNED, ty->size ? (u32)ty->size : 4u); + case KIT_CG_TYPE_ENUM: { + DebugTypeId base = api_debug_type(g, ty->enum_.base); + DebugEnumBuilder* eb = debug_type_enum_begin(g->debug, + (Sym)ty->enum_.tag, base); + if (!eb) return DEBUG_TYPE_NONE; + for (u32 i = 0; i < ty->enum_.nvalues; ++i) { + debug_type_enum_value(eb, (Sym)ty->enum_.values[i].name, + (i64)ty->enum_.values[i].value); + } + return debug_type_enum_end(eb); + } case KIT_CG_TYPE_ALIAS: { DebugTypeId base = api_debug_type(g, ty->alias.base); if (base == DEBUG_TYPE_NONE) base = debug_type_void(g->debug); return debug_type_typedef(g->debug, (Sym)ty->alias.name, base); } + case KIT_CG_TYPE_SOURCE_BASE: + return debug_type_base(g->debug, (Sym)ty->source_base.name, + debug_base_encoding(ty->source_base.encoding), + ty->size ? (u32)ty->size : 1u); case KIT_CG_TYPE_VARARG_STATE: return debug_type_void(g->debug); } diff --git a/src/cg/fold.c b/src/cg/fold.c @@ -56,6 +56,8 @@ u32 api_int_like_width(Compiler* c, KitCgTypeId id) { if (!ty) return 0; if (ty->kind == KIT_CG_TYPE_ALIAS) return api_int_like_width(c, ty->alias.base); + if (ty->kind == KIT_CG_TYPE_SOURCE_BASE) + return api_int_like_width(c, ty->source_base.base); if (ty->kind == KIT_CG_TYPE_INT || ty->kind == KIT_CG_TYPE_BOOL) return ty->integer.width; if (ty->kind == KIT_CG_TYPE_ENUM) return (u32)(ty->size * 8u); @@ -67,6 +69,8 @@ int api_type_is_bool(Compiler* c, KitCgTypeId id) { const CgType* ty = cg_type_get(c, id); if (!ty) return 0; if (ty->kind == KIT_CG_TYPE_ALIAS) return api_type_is_bool(c, ty->alias.base); + if (ty->kind == KIT_CG_TYPE_SOURCE_BASE) + return api_type_is_bool(c, ty->source_base.base); return ty->kind == KIT_CG_TYPE_BOOL; } diff --git a/src/cg/type.c b/src/cg/type.c @@ -10,9 +10,18 @@ typedef enum CgApiTypeKind { CG_API_TYPE_RECORD, CG_API_TYPE_ENUM, CG_API_TYPE_FUNC, + CG_API_TYPE_SOURCE_BASE, } CgApiTypeKind; typedef struct CgApiType { + /* Public identity view (kit/cg.h). Kept as the FIRST member so a pointer to + * the entry is a valid `const KitCgTypeInfo*` and kit_cg_type_view can hand + * it back without a copy. Identity (id/storage_id/kind/flags) is filled once, + * lazily, by kit_cg_type_view; layout and the COMPLETE/SIZED flags are not + * maintained here (see the view's contract) — kit_cg_type_info computes + * those. info.id == 0 marks the entry as not yet filled (a valid id is + * never 0, and segvec slots are zero-initialized). */ + KitCgTypeInfo info; CgType cg; KitCgTypeId base; KitSym name; @@ -96,6 +105,10 @@ typedef struct CgApiState { CgArraySet array_index; CgFuncSet func_index; CgType builtins[KIT_CG_BUILTIN_COUNT]; + /* Per-builtin identity view returned by kit_cg_type_view: builtins are stored + * as bare CgType (no CgApiType entry to point at), so their stable + * KitCgTypeInfo backing lives here, precomputed at init. */ + KitCgTypeInfo builtin_info[KIT_CG_BUILTIN_COUNT]; /* Packed API_TYPE_CLASS_* per builtin, precomputed at init so the hottest * operand types (i32/i64/f64/ptr/...) classify with a single indexed load. */ u8 builtin_class[KIT_CG_BUILTIN_COUNT]; @@ -303,6 +316,12 @@ static void cg_api_init_builtins(Compiler* c, CgApiState* s) { * exact, final value — classify the node once here, then every predicate * query is one indexed load (see api_type_pred). */ s->builtin_pred[i] = api_pred_bits_for_kind(&s->builtins[i]); + /* Stable identity view for kit_cg_type_view. Builtins are never aliases, so + * storage_id == id; layout/complete/sized are left to kit_cg_type_info. */ + s->builtin_info[i].id = builtin_id((KitCgBuiltinType)i); + s->builtin_info[i].storage_id = s->builtin_info[i].id; + s->builtin_info[i].kind = s->builtins[i].kind; + s->builtin_info[i].flags = KIT_CG_TYPEF_BUILTIN; } s->builtins_init = 1; } @@ -655,8 +674,12 @@ KitCgTypeId api_unalias_type(Compiler* c, KitCgTypeId id) { e = api_type_from_id(c, id); if (e && e->unalias_filled) return e->unaliased; ty = cg_type_get(c, id); - while (ty && ty->kind == KIT_CG_TYPE_ALIAS) { - id = ty->alias.base; + /* Both aliases and source-base spellings are transparent to storage: strip + * them to the terminal storage type so every ABI/codegen/predicate site that + * unaliases sees the underlying builtin with no source-base special-casing. */ + while (ty && (ty->kind == KIT_CG_TYPE_ALIAS || + ty->kind == KIT_CG_TYPE_SOURCE_BASE)) { + id = ty->kind == KIT_CG_TYPE_ALIAS ? ty->alias.base : ty->source_base.base; ty = cg_type_get(c, id); } id = ty ? id : KIT_CG_TYPE_NONE; @@ -911,6 +934,29 @@ static int cg_type_set_alias(Compiler* c, CgApiType* e, KitSym name, return 1; } +/* A source-base wraps a scalar storage builtin (bool/int/float) with a source + * spelling + debug encoding. It mirrors the builtin's size/align so width/size + * queries answer directly, and (like an alias) unaliases to the builtin for all + * storage/ABI/codegen. */ +static int cg_type_set_source_base(Compiler* c, CgApiType* e, KitSym name, + KitCgTypeId base, u8 encoding) { + const CgType* bty = cg_type_get(c, base); + ABITypeInfo ti; + if (!bty || !(bty->kind == KIT_CG_TYPE_INT || bty->kind == KIT_CG_TYPE_BOOL || + bty->kind == KIT_CG_TYPE_FLOAT)) { + return 0; + } + ti = abi_cg_type_info(c->abi, base); + memset(&e->cg, 0, sizeof(e->cg)); + e->cg.kind = KIT_CG_TYPE_SOURCE_BASE; + e->cg.size = ti.size; + e->cg.align = ti.align; + e->cg.source_base.name = name; + e->cg.source_base.base = base; + e->cg.source_base.encoding = encoding; + return 1; +} + static int cg_type_set_record(Compiler* c, CgApiType* e, KitSym tag, const KitCgField* fields, u32 nfields, int is_union, u32 align_override, u32 flags) { @@ -930,12 +976,15 @@ static int cg_type_set_record(Compiler* c, CgApiType* e, KitSym tag, static int cg_type_set_enum(Compiler* c, CgApiType* e, KitSym tag, KitCgTypeId base, KitCgEnumValue* values, u32 nvalues) { - const CgType* bty; + const CgType* sty; ABITypeInfo ti; if (base == KIT_CG_TYPE_NONE) base = builtin_id(KIT_CG_BUILTIN_I32); - bty = cg_type_get(c, base); - if (!bty || - !(bty->kind == KIT_CG_TYPE_INT || bty->kind == KIT_CG_TYPE_BOOL)) { + /* The base may be a source-base spelling (e.g. an `enum E : unsigned int`) + * or an alias; validate the underlying storage kind but keep the exact base + * id so debug emits the spelled underlying type. */ + sty = cg_type_get(c, api_unalias_type(c, base)); + if (!sty || + !(sty->kind == KIT_CG_TYPE_INT || sty->kind == KIT_CG_TYPE_BOOL)) { return 0; } ti = abi_cg_type_info(c->abi, base); @@ -1028,6 +1077,25 @@ KitCgTypeId kit_cg_type_alias(KitCompiler* c, KitSym name, KitCgTypeId base) { return cg_type_set_alias(c, e, name, base) ? id : KIT_CG_TYPE_NONE; } +KitCgTypeId kit_cg_type_source_base(KitCompiler* c, KitSym name, + KitCgTypeId storage, + KitCgDebugEncoding encoding) { + KitCgTypeId id; + CgApiType* e; + if (!cg_type_get(c, storage)) return KIT_CG_TYPE_NONE; + /* Source-base ids are fresh (not interned), like aliases/enums. Frontends + * cache the lowered id on their own canonical type node, so the handful of C + * primitive spellings yield a bounded set of entries in practice. */ + e = type_alloc(c, &id); + if (!e) return KIT_CG_TYPE_NONE; + e->base = storage; + e->name = name; + e->kind = CG_API_TYPE_SOURCE_BASE; + return cg_type_set_source_base(c, e, name, storage, (u8)encoding) + ? id + : KIT_CG_TYPE_NONE; +} + KitCgTypeId kit_cg_type_record_decl(KitCompiler* c, KitSym tag, int is_union) { KitCgTypeId id; CgApiType* e; @@ -1150,14 +1218,31 @@ uint32_t kit_cg_type_align(KitCompiler* c, KitCgTypeId id) { return cg_type_align(c, id); } -KitCgTypeId kit_cg_type_resolve_alias(KitCompiler* c, KitCgTypeId id) { - return api_unalias_type(c, id); -} - -int kit_cg_type_is_void(KitCompiler* c, KitCgTypeId id) { - KitCgTypeId u = api_unalias_type(c, id); - const CgType* ty = cg_type_get(c, u); - return ty && ty->kind == KIT_CG_TYPE_VOID; +/* Identity view backing the inline kit_cg_type_{kind,resolve_alias,is_void} and + * kit_cg_func_result_has_value queries in kit/cg.h. Builtins read the + * precomputed table; user entries fill their identity prefix once (immutable + * thereafter: kind and storage are stable, NOMINAL is fixed at creation), then + * hand back the entry's first member. Layout and the COMPLETE/SIZED flags are + * deliberately left to kit_cg_type_info. */ +const KitCgTypeInfo* kit_cg_type_view(KitCompiler* kc, KitCgTypeId id) { + Compiler* c = (Compiler*)kc; + CgApiState* s; + CgApiType* e; + if (!c || id == KIT_CG_TYPE_NONE) return NULL; + s = c->cg_api ? (CgApiState*)c->cg_api : cg_api_get(c); + if (!s) return NULL; + if (id <= KIT_CG_BUILTIN_COUNT) return &s->builtin_info[id - 1u]; + e = api_type_from_id(c, id); + if (!e) return NULL; + if (e->info.id == 0) { + e->info.id = id; + e->info.storage_id = api_unalias_type(c, id); + e->info.kind = e->cg.kind; + if (e->cg.kind == KIT_CG_TYPE_RECORD || e->cg.kind == KIT_CG_TYPE_ENUM || + e->cg.kind == KIT_CG_TYPE_ALIAS) + e->info.flags = KIT_CG_TYPEF_NOMINAL; + } + return &e->info; } int kit_cg_type_is_complete(KitCompiler* c, KitCgTypeId id) { @@ -1168,16 +1253,6 @@ int kit_cg_type_is_sized(KitCompiler* c, KitCgTypeId id) { return cg_type_sized_id(c, id); } -int kit_cg_func_result_has_value(KitCompiler* c, KitCgFuncResult result) { - return result.type != KIT_CG_TYPE_NONE && - !kit_cg_type_is_void(c, result.type); -} - -KitCgTypeKind kit_cg_type_kind(KitCompiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - return ty ? ty->kind : KIT_CG_TYPE_VOID; -} - static KitCgStorageKind cg_storage_kind_from_abi(const CgType* ty, ABITypeInfo ti) { switch ((ABIScalarKind)ti.scalar_kind) { @@ -1233,7 +1308,8 @@ KitStatus kit_cg_type_info(KitCompiler* kc, KitCgTypeId id, out->kind = exact->kind; if (id <= KIT_CG_BUILTIN_COUNT) out->flags |= KIT_CG_TYPEF_BUILTIN; if (exact->kind == KIT_CG_TYPE_RECORD || exact->kind == KIT_CG_TYPE_ENUM || - exact->kind == KIT_CG_TYPE_ALIAS) + exact->kind == KIT_CG_TYPE_ALIAS || + exact->kind == KIT_CG_TYPE_SOURCE_BASE) out->flags |= KIT_CG_TYPEF_NOMINAL; if (cg_type_complete_id(c, id)) out->flags |= KIT_CG_TYPEF_COMPLETE; if (cg_type_sized_id(c, id)) out->flags |= KIT_CG_TYPEF_SIZED; @@ -1301,7 +1377,10 @@ static int cg_type_same_storage_rec(Compiler* c, KitCgTypeId a, KitCgTypeId b) { return 1; case KIT_CG_TYPE_RECORD: return 0; + /* a and b were unaliased before this switch, so ALIAS/SOURCE_BASE are + * unreachable here; the cases keep the switch exhaustive. */ case KIT_CG_TYPE_ALIAS: + case KIT_CG_TYPE_SOURCE_BASE: return 0; case KIT_CG_TYPE_ENUM: return 0; @@ -1330,6 +1409,9 @@ uint32_t kit_cg_type_int_width(KitCompiler* c, KitCgTypeId id) { if (ty->kind == KIT_CG_TYPE_ALIAS) { return kit_cg_type_int_width(c, ty->alias.base); } + if (ty->kind == KIT_CG_TYPE_SOURCE_BASE) { + return kit_cg_type_int_width(c, ty->source_base.base); + } return 0; } @@ -1340,6 +1422,9 @@ uint32_t kit_cg_type_float_width(KitCompiler* c, KitCgTypeId id) { if (ty->kind == KIT_CG_TYPE_ALIAS) { return kit_cg_type_float_width(c, ty->alias.base); } + if (ty->kind == KIT_CG_TYPE_SOURCE_BASE) { + return kit_cg_type_float_width(c, ty->source_base.base); + } return 0; } @@ -1478,6 +1563,25 @@ KitCgTypeId kit_cg_type_alias_base(KitCompiler* c, KitCgTypeId id) { : KIT_CG_TYPE_NONE; } +KitSym kit_cg_type_source_base_name(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_SOURCE_BASE) ? ty->source_base.name : 0; +} + +KitCgTypeId kit_cg_type_source_base_storage(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_SOURCE_BASE) ? ty->source_base.base + : KIT_CG_TYPE_NONE; +} + +KitCgDebugEncoding kit_cg_type_source_base_encoding(KitCompiler* c, + KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_SOURCE_BASE) + ? (KitCgDebugEncoding)ty->source_base.encoding + : KIT_CG_DEBUG_ENC_NONE; +} + int kit_cg_target_supports_call_conv(KitCompiler* c, KitCgCallConv cc) { const ArchImpl* a; if (!c) return 0; diff --git a/src/cg/type.h b/src/cg/type.h @@ -63,6 +63,11 @@ typedef struct CgType { KitSym name; KitCgTypeId base; } alias; + struct { + KitSym name; + KitCgTypeId base; /* width-only storage builtin */ + u8 encoding; /* KitCgDebugEncoding */ + } source_base; }; } CgType; diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -1739,6 +1739,9 @@ int main(void) { KitCgTypeId alias; KitCgTypeId alias_ptr_i32; KitCgTypeId ptr_alias_i32; + KitCgTypeId src_uint; + KitCgTypeId src_uchar; + KitCgTypeId ptr_src_uint; KitCgTypeInfo info; KitCgTypeId rec; KitCgTypeId rec_ex; @@ -1828,6 +1831,57 @@ int main(void) { EXPECT(kit_cg_type_same_storage(c, ptr_alias_i32, ptr_i32), "ptr(alias(i32)) should share storage with ptr(i32)"); + /* Source-base scalars: a source-facing primitive spelling (sign/name) over a + * width-only storage builtin. Exact kind and debug see the spelling; storage, + * ABI, and codegen see the underlying builtin (the id unaliases to it). */ + src_uint = kit_cg_type_source_base( + c, kit_sym_intern(c, KIT_SLICE_LIT("unsigned int")), i32_ty, + KIT_CG_DEBUG_ENC_UNSIGNED); + EXPECT(src_uint != KIT_CG_TYPE_NONE && src_uint != i32_ty, + "source-base id should be fresh and distinct from its storage"); + EXPECT(kit_cg_type_kind(c, src_uint) == KIT_CG_TYPE_SOURCE_BASE, + "source-base exact kind mismatch"); + EXPECT(kit_cg_type_source_base_storage(c, src_uint) == i32_ty, + "source-base storage accessor mismatch"); + EXPECT(kit_cg_type_source_base_encoding(c, src_uint) == + KIT_CG_DEBUG_ENC_UNSIGNED, + "source-base encoding accessor mismatch"); + EXPECT(kit_cg_type_resolve_alias(c, src_uint) == i32_ty, + "source-base should unalias to its storage builtin"); + EXPECT(kit_cg_type_same_storage(c, src_uint, i32_ty), + "source-base should share storage with its builtin"); + EXPECT(kit_cg_type_size(c, src_uint) == kit_cg_type_size(c, i32_ty) && + kit_cg_type_int_width(c, src_uint) == 32, + "source-base size/width should follow its storage"); + EXPECT(kit_cg_type_is_sized(c, src_uint), + "source-base over a sized builtin should be sized"); + EXPECT(kit_cg_type_info(c, src_uint, &info) == KIT_OK, + "source-base info failed"); + EXPECT(info.kind == KIT_CG_TYPE_SOURCE_BASE && info.storage_id == i32_ty && + (info.flags & KIT_CG_TYPEF_NOMINAL) && + info.layout.storage_kind == KIT_CG_STORAGE_INT && + info.layout.scalar_width == 32, + "source-base info exact/storage/layout mismatch"); + /* A composite built over a source-base preserves the spelling (debug fidelity + * for `unsigned int *`) yet stays storage-equivalent to the builtin form. */ + ptr_src_uint = kit_cg_type_ptr(c, src_uint, 0); + EXPECT(ptr_src_uint != KIT_CG_TYPE_NONE && + kit_cg_type_ptr_pointee(c, ptr_src_uint) == src_uint, + "ptr(source-base) should preserve the source-base pointee"); + EXPECT(kit_cg_type_same_storage(c, ptr_src_uint, ptr_i32), + "ptr(source-base) should share storage with ptr(i32)"); + src_uchar = kit_cg_type_source_base( + c, kit_sym_intern(c, KIT_SLICE_LIT("unsigned char")), i8_ty, + KIT_CG_DEBUG_ENC_UNSIGNED_CHAR); + EXPECT(src_uchar != KIT_CG_TYPE_NONE && + kit_cg_type_same_storage(c, src_uchar, i8_ty), + "unsigned-char source-base should share storage with i8"); + /* The storage operand must be a real scalar builtin. */ + EXPECT(kit_cg_type_source_base( + c, kit_sym_intern(c, KIT_SLICE_LIT("bad")), ptr_i32, + KIT_CG_DEBUG_ENC_SIGNED) == KIT_CG_TYPE_NONE, + "source-base over a non-scalar storage should be rejected"); + memset(fields, 0, sizeof fields); fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("a")); fields[0].type = i32_ty; @@ -1950,6 +2004,29 @@ int main(void) { enm = kit_cg_type_enum(c, kit_sym_intern(c, KIT_SLICE_LIT("E")), KIT_CG_TYPE_NONE, vals, 2); EXPECT(enm != KIT_CG_TYPE_NONE, "enum type failed"); + /* Enumerators must be preserved so debug info can emit DW_TAG_enumerator + * children rather than collapsing the enum to a bare integer. */ + EXPECT(kit_cg_type_enum_nvalues(c, enm) == 2, + "enum should preserve its enumerator count"); + { + KitCgEnumValue ev; + EXPECT(kit_cg_type_enum_value(c, enm, 1, &ev) == KIT_OK && + ev.name == vals[1].name && ev.value == 2, + "enum should preserve enumerator name/value pairs"); + } + /* An enum whose underlying type is a source-base spelling (e.g. + * `enum E : unsigned int`) must construct: the base validates through its + * storage, and the source-facing base id is retained for debug. */ + { + KitCgTypeId enm_u = kit_cg_type_enum( + c, kit_sym_intern(c, KIT_SLICE_LIT("EU")), src_uint, vals, 2); + EXPECT(enm_u != KIT_CG_TYPE_NONE, + "enum over a source-base underlying type should construct"); + EXPECT(kit_cg_type_enum_base(c, enm_u) == src_uint, + "enum should retain its source-base underlying type id"); + EXPECT(kit_cg_type_int_width(c, enm_u) == 32, + "enum width should follow its source-base underlying type"); + } memset(params, 0, sizeof(params)); params[0].type = i32_ty;