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