commit 13cc44e82229698acad65407f1cfb9b525ac6ebd
parent e37202a809c3df4009250b2820d444be4817a61f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 12 May 2026 17:12:43 -0700
cg type allocator
Diffstat:
7 files changed, 584 insertions(+), 2 deletions(-)
diff --git a/include/cfree/cg.h b/include/cfree/cg.h
@@ -15,6 +15,24 @@ typedef uint32_t CfreeCgTypeId;
#define CFREE_CG_SCOPE_NONE 0u
#define CFREE_CG_TYPE_NONE 0u
+typedef enum CfreeCgBuiltinType {
+ CFREE_CG_BUILTIN_VOID,
+ CFREE_CG_BUILTIN_BOOL,
+ CFREE_CG_BUILTIN_I8,
+ CFREE_CG_BUILTIN_U8,
+ CFREE_CG_BUILTIN_I16,
+ CFREE_CG_BUILTIN_U16,
+ CFREE_CG_BUILTIN_I32,
+ CFREE_CG_BUILTIN_U32,
+ CFREE_CG_BUILTIN_I64,
+ CFREE_CG_BUILTIN_U64,
+ CFREE_CG_BUILTIN_ISIZE,
+ CFREE_CG_BUILTIN_USIZE,
+ CFREE_CG_BUILTIN_F32,
+ CFREE_CG_BUILTIN_F64,
+ CFREE_CG_BUILTIN_COUNT,
+} CfreeCgBuiltinType;
+
typedef struct CfreeCgBuiltinTypes {
CfreeCgTypeId void_;
CfreeCgTypeId bool_;
diff --git a/src/api/cg.c b/src/api/cg.c
@@ -0,0 +1,390 @@
+#include <cfree/cg.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "api/cg_api.h"
+#include "core/arena.h"
+#include "core/heap.h"
+#include "core/segvec.h"
+#include "type/type.h"
+
+typedef enum CgApiTypeKind {
+ CG_API_TYPE_PTR,
+ CG_API_TYPE_ARRAY,
+ CG_API_TYPE_QUALIFIED,
+ CG_API_TYPE_ALIAS,
+ CG_API_TYPE_RECORD,
+ CG_API_TYPE_ENUM,
+ CG_API_TYPE_FUNC,
+} CgApiTypeKind;
+
+typedef struct CgApiType {
+ const Type* type;
+ CfreeCgTypeId base;
+ CfreeSym name;
+ u32 count;
+ u32 flags;
+ const CfreeCgField* fields;
+ const CfreeCgEnumValue* values;
+ const CfreeCgTypeId* params;
+ u8 kind;
+ u8 is_union;
+ u8 pad[2];
+} CgApiType;
+
+SEGVEC_DEFINE(CgApiTypes, CgApiType, CG_API_TYPE_SEG_SHIFT);
+
+typedef struct CgApiState {
+ Heap* heap;
+ CgApiTypes types;
+} CgApiState;
+
+static CfreeCgTypeId type_id_from_tuple(u32 seg, u32 index) {
+ return (CfreeCgTypeId)((seg << CG_API_TYPE_SEG_SHIFT) |
+ (index & CG_API_TYPE_SEG_MASK));
+}
+
+static CfreeCgTypeId builtin_id(CfreeCgBuiltinType t) {
+ return type_id_from_tuple(CG_API_TYPE_BUILTIN_SEG, (u32)t);
+}
+
+static int decode_user_id(CfreeCgTypeId id, u32* index_out) {
+ u32 seg = id >> CG_API_TYPE_SEG_SHIFT;
+ u32 off = id & CG_API_TYPE_SEG_MASK;
+ if (seg < CG_API_TYPE_USER_SEG_BIAS) return 0;
+ *index_out =
+ ((seg - CG_API_TYPE_USER_SEG_BIAS) << CG_API_TYPE_SEG_SHIFT) | off;
+ return 1;
+}
+
+static CfreeCgTypeId user_id_from_index(u32 index) {
+ u32 raw_seg = index >> CG_API_TYPE_SEG_SHIFT;
+ u32 off = index & CG_API_TYPE_SEG_MASK;
+ u32 seg_limit = UINT32_MAX >> CG_API_TYPE_SEG_SHIFT;
+ if (raw_seg > seg_limit - CG_API_TYPE_USER_SEG_BIAS) {
+ return CFREE_CG_TYPE_NONE;
+ }
+ return type_id_from_tuple(raw_seg + CG_API_TYPE_USER_SEG_BIAS, off);
+}
+
+static const Type* builtin_type(Compiler* c, CfreeCgBuiltinType t) {
+ switch (t) {
+ case CFREE_CG_BUILTIN_VOID:
+ return type_void(c->global);
+ case CFREE_CG_BUILTIN_BOOL:
+ return type_prim(c->global, TY_BOOL);
+ case CFREE_CG_BUILTIN_I8:
+ return type_prim(c->global, TY_SCHAR);
+ case CFREE_CG_BUILTIN_U8:
+ return type_prim(c->global, TY_UCHAR);
+ case CFREE_CG_BUILTIN_I16:
+ return type_prim(c->global, TY_SHORT);
+ case CFREE_CG_BUILTIN_U16:
+ return type_prim(c->global, TY_USHORT);
+ case CFREE_CG_BUILTIN_I32:
+ return type_prim(c->global, TY_INT);
+ case CFREE_CG_BUILTIN_U32:
+ return type_prim(c->global, TY_UINT);
+ case CFREE_CG_BUILTIN_I64:
+ return type_prim(c->global, TY_LLONG);
+ case CFREE_CG_BUILTIN_U64:
+ return type_prim(c->global, TY_ULLONG);
+ case CFREE_CG_BUILTIN_ISIZE:
+ return c->target.ptr_size == 8 ? type_prim(c->global, TY_LLONG)
+ : type_prim(c->global, TY_INT);
+ case CFREE_CG_BUILTIN_USIZE:
+ return c->target.ptr_size == 8 ? type_prim(c->global, TY_ULLONG)
+ : type_prim(c->global, TY_UINT);
+ case CFREE_CG_BUILTIN_F32:
+ return type_prim(c->global, TY_FLOAT);
+ case CFREE_CG_BUILTIN_F64:
+ return type_prim(c->global, TY_DOUBLE);
+ case CFREE_CG_BUILTIN_COUNT:
+ return NULL;
+ }
+ return NULL;
+}
+
+static CgApiState* cg_api_get(Compiler* c) {
+ Heap* h;
+ CgApiState* s;
+ if (!c) return NULL;
+ if (c->cg_api) return (CgApiState*)c->cg_api;
+ h = (Heap*)c->env->heap;
+ s = (CgApiState*)h->alloc(h, sizeof(*s), _Alignof(CgApiState));
+ if (!s) return NULL;
+ memset(s, 0, sizeof(*s));
+ s->heap = h;
+ CgApiTypes_init(&s->types, h);
+ c->cg_api = s;
+ c->cg_api_free = cg_api_fini;
+ return s;
+}
+
+static CgApiType* type_alloc(Compiler* c, CfreeCgTypeId* id_out) {
+ CgApiState* s = cg_api_get(c);
+ CgApiType* e;
+ u32 index;
+ if (!s) return NULL;
+ e = CgApiTypes_push(&s->types, &index);
+ if (!e) return NULL;
+ *id_out = user_id_from_index(index);
+ if (*id_out == CFREE_CG_TYPE_NONE) return NULL;
+ return e;
+}
+
+static const Type* resolve_type(Compiler* c, CfreeCgTypeId id) {
+ u32 seg;
+ u32 off;
+ u32 index;
+ CgApiState* s;
+ CgApiType* e;
+ if (!c || id == CFREE_CG_TYPE_NONE) return NULL;
+ seg = id >> CG_API_TYPE_SEG_SHIFT;
+ off = id & CG_API_TYPE_SEG_MASK;
+ if (seg == CG_API_TYPE_BUILTIN_SEG) {
+ if (off >= CFREE_CG_BUILTIN_COUNT) return NULL;
+ return builtin_type(c, (CfreeCgBuiltinType)off);
+ }
+ if (!decode_user_id(id, &index)) return NULL;
+ s = (CgApiState*)c->cg_api;
+ if (!s) return NULL;
+ e = CgApiTypes_at(&s->types, index);
+ return e ? e->type : NULL;
+}
+
+static u16 quals_to_internal(u32 quals) {
+ u16 q = 0;
+ if (quals & CFREE_CG_TQ_CONST) q |= Q_CONST;
+ if (quals & CFREE_CG_TQ_VOLATILE) q |= Q_VOLATILE;
+ if (quals & CFREE_CG_TQ_RESTRICT) q |= Q_RESTRICT;
+ return q;
+}
+
+static CfreeCgTypeId* copy_type_ids(Compiler* c, const CfreeCgTypeId* src,
+ u32 n) {
+ CfreeCgTypeId* dst;
+ if (!n) return NULL;
+ if (!src) return NULL;
+ dst = arena_array(&c->global->arena, CfreeCgTypeId, n);
+ if (!dst) return NULL;
+ memcpy(dst, src, sizeof(*dst) * n);
+ return dst;
+}
+
+CfreeCgBuiltinTypes cfree_cg_builtin_types(CfreeCompiler* c) {
+ CfreeCgBuiltinTypes out;
+ (void)c;
+ out.void_ = builtin_id(CFREE_CG_BUILTIN_VOID);
+ out.bool_ = builtin_id(CFREE_CG_BUILTIN_BOOL);
+ out.i8 = builtin_id(CFREE_CG_BUILTIN_I8);
+ out.u8 = builtin_id(CFREE_CG_BUILTIN_U8);
+ out.i16 = builtin_id(CFREE_CG_BUILTIN_I16);
+ out.u16 = builtin_id(CFREE_CG_BUILTIN_U16);
+ out.i32 = builtin_id(CFREE_CG_BUILTIN_I32);
+ out.u32 = builtin_id(CFREE_CG_BUILTIN_U32);
+ out.i64 = builtin_id(CFREE_CG_BUILTIN_I64);
+ out.u64 = builtin_id(CFREE_CG_BUILTIN_U64);
+ out.isize = builtin_id(CFREE_CG_BUILTIN_ISIZE);
+ out.usize = builtin_id(CFREE_CG_BUILTIN_USIZE);
+ out.f32 = builtin_id(CFREE_CG_BUILTIN_F32);
+ out.f64 = builtin_id(CFREE_CG_BUILTIN_F64);
+ return out;
+}
+
+CfreeCgTypeId cfree_cg_type_ptr(CfreeCompiler* c, CfreeCgTypeId pointee) {
+ const Type* pty = resolve_type(c, pointee);
+ CfreeCgTypeId id;
+ CgApiType* e;
+ if (!pty) return CFREE_CG_TYPE_NONE;
+ e = type_alloc(c, &id);
+ if (!e) return CFREE_CG_TYPE_NONE;
+ e->type = type_ptr(c->global, pty);
+ e->base = pointee;
+ e->kind = CG_API_TYPE_PTR;
+ return e->type ? id : CFREE_CG_TYPE_NONE;
+}
+
+CfreeCgTypeId cfree_cg_type_array(CfreeCompiler* c, CfreeCgTypeId elem,
+ uint32_t count) {
+ const Type* ety = resolve_type(c, elem);
+ CfreeCgTypeId id;
+ CgApiType* e;
+ if (!ety) return CFREE_CG_TYPE_NONE;
+ e = type_alloc(c, &id);
+ if (!e) return CFREE_CG_TYPE_NONE;
+ e->type = type_array(c->global, ety, count, 0);
+ e->base = elem;
+ e->count = count;
+ e->kind = CG_API_TYPE_ARRAY;
+ return e->type ? id : CFREE_CG_TYPE_NONE;
+}
+
+CfreeCgTypeId cfree_cg_type_qualified(CfreeCompiler* c, CfreeCgTypeId base,
+ uint32_t quals) {
+ const u32 known =
+ CFREE_CG_TQ_CONST | CFREE_CG_TQ_VOLATILE | CFREE_CG_TQ_RESTRICT;
+ const Type* bty = resolve_type(c, base);
+ CfreeCgTypeId id;
+ CgApiType* e;
+ if (!bty || (quals & ~known)) return CFREE_CG_TYPE_NONE;
+ e = type_alloc(c, &id);
+ if (!e) return CFREE_CG_TYPE_NONE;
+ e->type = type_qualified(c->global, bty, quals_to_internal(quals));
+ e->base = base;
+ e->flags = quals;
+ e->kind = CG_API_TYPE_QUALIFIED;
+ return e->type ? id : CFREE_CG_TYPE_NONE;
+}
+
+CfreeCgTypeId cfree_cg_type_alias(CfreeCompiler* c, CfreeSym name,
+ CfreeCgTypeId base) {
+ const Type* bty = resolve_type(c, base);
+ CfreeCgTypeId id;
+ CgApiType* e;
+ if (!bty) return CFREE_CG_TYPE_NONE;
+ e = type_alloc(c, &id);
+ if (!e) return CFREE_CG_TYPE_NONE;
+ e->type = bty;
+ e->base = base;
+ e->name = name;
+ e->kind = CG_API_TYPE_ALIAS;
+ return id;
+}
+
+CfreeCgTypeId cfree_cg_type_record(CfreeCompiler* c, CfreeSym tag, int is_union,
+ const CfreeCgField* fields,
+ uint32_t nfields) {
+ CfreeCgTypeId id;
+ CgApiType* e;
+ TypeRecordBuilder* b;
+ CfreeCgField* copied = NULL;
+ TagDeclKind tag_kind = is_union ? TAG_UNION : TAG_STRUCT;
+ TagId tag_id;
+ if (!c || (nfields && !fields) || nfields > UINT16_MAX) {
+ return CFREE_CG_TYPE_NONE;
+ }
+ if (nfields) {
+ copied = arena_array(&c->global->arena, CfreeCgField, nfields);
+ if (!copied) return CFREE_CG_TYPE_NONE;
+ }
+ tag_id = type_tag_new(c->global, tag_kind, tag, (SrcLoc){0, 0, 0});
+ b = type_record_begin(c->global, is_union ? TY_UNION : TY_STRUCT, tag_id,
+ tag);
+ if (!tag_id || !b) return CFREE_CG_TYPE_NONE;
+ for (u32 i = 0; i < nfields; ++i) {
+ const Type* fty = resolve_type(c, fields[i].type);
+ Field f;
+ if (!fty) return CFREE_CG_TYPE_NONE;
+ copied[i] = fields[i];
+ memset(&f, 0, sizeof(f));
+ f.name = fields[i].name;
+ f.type = fty;
+ if (fields[i].align_override == 1u) {
+ f.packed = 1;
+ } else if (fields[i].align_override > 1u) {
+ if (fields[i].align_override > UINT16_MAX) return CFREE_CG_TYPE_NONE;
+ f.align_override = (u16)fields[i].align_override;
+ }
+ type_record_field(b, f);
+ }
+ e = type_alloc(c, &id);
+ if (!e) return CFREE_CG_TYPE_NONE;
+ e->type = type_record_end(c->global, b);
+ e->name = tag;
+ e->count = nfields;
+ e->fields = copied;
+ e->kind = CG_API_TYPE_RECORD;
+ e->is_union = is_union ? 1u : 0u;
+ return e->type ? id : CFREE_CG_TYPE_NONE;
+}
+
+CfreeCgTypeId cfree_cg_type_enum(CfreeCompiler* c, CfreeSym tag,
+ CfreeCgTypeId base,
+ const CfreeCgEnumValue* values,
+ uint32_t nvalues) {
+ const Type* bty;
+ CfreeCgEnumValue* copied = NULL;
+ CfreeCgTypeId id;
+ CgApiType* e;
+ TagId tag_id;
+ if (!c || (nvalues && !values)) return CFREE_CG_TYPE_NONE;
+ bty = base == CFREE_CG_TYPE_NONE ? type_prim(c->global, TY_INT)
+ : resolve_type(c, base);
+ if (!bty || !type_is_int(bty)) return CFREE_CG_TYPE_NONE;
+ if (nvalues) {
+ copied = arena_array(&c->global->arena, CfreeCgEnumValue, nvalues);
+ if (!copied) return CFREE_CG_TYPE_NONE;
+ memcpy(copied, values, sizeof(*copied) * nvalues);
+ }
+ tag_id = type_tag_new(c->global, TAG_ENUM, tag, (SrcLoc){0, 0, 0});
+ if (!tag_id) return CFREE_CG_TYPE_NONE;
+ e = type_alloc(c, &id);
+ if (!e) return CFREE_CG_TYPE_NONE;
+ e->type = type_enum(c->global, tag_id, tag, bty);
+ e->base = base;
+ e->name = tag;
+ e->count = nvalues;
+ e->values = copied;
+ e->kind = CG_API_TYPE_ENUM;
+ return e->type ? id : CFREE_CG_TYPE_NONE;
+}
+
+CfreeCgTypeId cfree_cg_type_func(CfreeCompiler* c, CfreeCgTypeId ret,
+ const CfreeCgTypeId* params, uint32_t nparams,
+ int variadic) {
+ Heap* h;
+ const Type* rty = resolve_type(c, ret);
+ const Type** ptypes = NULL;
+ CfreeCgTypeId* copied = NULL;
+ CfreeCgTypeId id;
+ CgApiType* e;
+ if (!c || !rty || (nparams && !params) || nparams > UINT16_MAX) {
+ return CFREE_CG_TYPE_NONE;
+ }
+ h = (Heap*)c->env->heap;
+ if (nparams) {
+ ptypes = (const Type**)h->alloc(h, sizeof(*ptypes) * nparams,
+ _Alignof(const Type*));
+ if (!ptypes) return CFREE_CG_TYPE_NONE;
+ copied = copy_type_ids(c, params, nparams);
+ if (!copied) {
+ h->free(h, ptypes, sizeof(*ptypes) * nparams);
+ return CFREE_CG_TYPE_NONE;
+ }
+ for (u32 i = 0; i < nparams; ++i) {
+ ptypes[i] = resolve_type(c, params[i]);
+ if (!ptypes[i]) {
+ h->free(h, ptypes, sizeof(*ptypes) * nparams);
+ return CFREE_CG_TYPE_NONE;
+ }
+ }
+ }
+ e = type_alloc(c, &id);
+ if (!e) {
+ if (ptypes) h->free(h, ptypes, sizeof(*ptypes) * nparams);
+ return CFREE_CG_TYPE_NONE;
+ }
+ e->type = type_func(c->global, rty, ptypes, (u16)nparams, variadic);
+ if (ptypes) h->free(h, ptypes, sizeof(*ptypes) * nparams);
+ e->base = ret;
+ e->count = nparams;
+ e->params = copied;
+ e->flags = variadic ? 1u : 0u;
+ e->kind = CG_API_TYPE_FUNC;
+ return e->type ? id : CFREE_CG_TYPE_NONE;
+}
+
+const Type* cg_api_type_resolve(Compiler* c, CfreeCgTypeId id) {
+ return resolve_type(c, id);
+}
+
+void cg_api_fini(Compiler* c) {
+ CgApiState* s;
+ if (!c || !c->cg_api) return;
+ s = (CgApiState*)c->cg_api;
+ CgApiTypes_fini(&s->types);
+ s->heap->free(s->heap, s, sizeof(*s));
+ c->cg_api = NULL;
+ c->cg_api_free = NULL;
+}
diff --git a/src/api/cg_api.h b/src/api/cg_api.h
@@ -0,0 +1,20 @@
+#ifndef CFREE_API_CG_API_H
+#define CFREE_API_CG_API_H
+
+#include <cfree/cg.h>
+
+#include "core/core.h"
+#include "type/type.h"
+
+enum {
+ CG_API_TYPE_SEG_SHIFT = 6,
+ CG_API_TYPE_SEG_SIZE = 1u << CG_API_TYPE_SEG_SHIFT,
+ CG_API_TYPE_SEG_MASK = CG_API_TYPE_SEG_SIZE - 1u,
+ CG_API_TYPE_BUILTIN_SEG = 1u,
+ CG_API_TYPE_USER_SEG_BIAS = 2u,
+};
+
+const Type* cg_api_type_resolve(Compiler*, CfreeCgTypeId);
+void cg_api_fini(Compiler*);
+
+#endif
diff --git a/src/core/core.c b/src/core/core.c
@@ -66,6 +66,11 @@ void compiler_fini(Compiler* c) {
* Run the stack defensively so memory still gets released. */
compiler_run_cleanups(c);
+ if (c->cg_api_free) {
+ c->cg_api_free(c);
+ c->cg_api_free = NULL;
+ }
+
if (c->abi) {
abi_free(c->abi);
c->abi = NULL;
diff --git a/src/core/core.h b/src/core/core.h
@@ -126,7 +126,8 @@ struct CfreeCompiler {
Target target;
CompilerCleanup* cleanup; /* top of LIFO cleanup stack */
CfreeCompileFn frontends[CFREE_LANG_COUNT];
- void* reserved;
+ void* cg_api; /* public cfree/cg.h adapter state */
+ void (*cg_api_free)(Compiler*);
};
void compiler_init(Compiler*, Target, const CfreeEnv*);
diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c
@@ -0,0 +1,140 @@
+#include <cfree.h>
+#include <cfree/cg.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void* h_alloc(CfreeHeap* h, size_t n, size_t a) {
+ (void)h;
+ (void)a;
+ return n ? malloc(n) : NULL;
+}
+
+static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) {
+ (void)h;
+ (void)o;
+ (void)a;
+ return realloc(p, n);
+}
+
+static void h_free(CfreeHeap* h, void* p, size_t n) {
+ (void)h;
+ (void)n;
+ free(p);
+}
+
+static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
+
+static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
+ const char* fmt, va_list ap) {
+ (void)s;
+ (void)loc;
+ fprintf(stderr, "diag %d: ", (int)k);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+
+static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0};
+
+static int g_fail;
+
+#define EXPECT(cond, ...) \
+ do { \
+ if (!(cond)) { \
+ ++g_fail; \
+ fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fputc('\n', stderr); \
+ } \
+ } while (0)
+
+int main(void) {
+ CfreeTarget target;
+ CfreeEnv env;
+ CfreeCompiler* c;
+ CfreeCgBuiltinTypes bi;
+ CfreeCgTypeId ptr_i32;
+ CfreeCgTypeId array_i32;
+ CfreeCgTypeId params[2];
+ CfreeCgTypeId fn;
+ CfreeCgTypeId alias;
+ CfreeCgTypeId qual;
+ CfreeCgTypeId rec;
+ CfreeCgTypeId enm;
+ CfreeCgField fields[2];
+ CfreeCgEnumValue vals[2];
+
+ memset(&target, 0, sizeof(target));
+ target.arch = CFREE_ARCH_ARM_64;
+ target.os = CFREE_OS_LINUX;
+ target.obj = CFREE_OBJ_ELF;
+ target.ptr_size = 8;
+ target.ptr_align = 8;
+
+ env.heap = &g_heap;
+ env.file_io = NULL;
+ env.diag = &g_diag;
+ env.execmem = NULL;
+ env.dbg_os = NULL;
+ env.jit_tls = NULL;
+ env.now = 0;
+
+ c = cfree_compiler_new(target, &env);
+ if (!c) {
+ fprintf(stderr, "compiler_new failed\n");
+ return 2;
+ }
+
+ bi = cfree_cg_builtin_types(c);
+ EXPECT(bi.void_ != CFREE_CG_TYPE_NONE, "void builtin id is none");
+ EXPECT(bi.i32 != CFREE_CG_TYPE_NONE, "i32 builtin id is none");
+ EXPECT(bi.f64 != CFREE_CG_TYPE_NONE, "f64 builtin id is none");
+ EXPECT(bi.void_ != bi.i32 && bi.i32 != bi.f64, "builtin ids collide");
+
+ ptr_i32 = cfree_cg_type_ptr(c, bi.i32);
+ array_i32 = cfree_cg_type_array(c, bi.i32, 4);
+ EXPECT(ptr_i32 != CFREE_CG_TYPE_NONE, "ptr type failed");
+ EXPECT(array_i32 != CFREE_CG_TYPE_NONE, "array type failed");
+ EXPECT(cfree_cg_type_ptr(c, CFREE_CG_TYPE_NONE) == CFREE_CG_TYPE_NONE,
+ "invalid pointer pointee should fail");
+
+ alias = cfree_cg_type_alias(c, cfree_sym_intern(c, "I"), bi.i32);
+ qual = cfree_cg_type_qualified(c, alias, CFREE_CG_TQ_CONST);
+ EXPECT(alias != CFREE_CG_TYPE_NONE && alias != bi.i32,
+ "alias id should be fresh");
+ EXPECT(qual != CFREE_CG_TYPE_NONE && qual != alias,
+ "qualified id should be fresh");
+ EXPECT(cfree_cg_type_qualified(c, alias, 1u << 12) == CFREE_CG_TYPE_NONE,
+ "unknown qualifier bit should fail");
+
+ fields[0].name = cfree_sym_intern(c, "a");
+ fields[0].type = bi.i32;
+ fields[0].align_override = 0;
+ fields[1].name = cfree_sym_intern(c, "b");
+ fields[1].type = ptr_i32;
+ fields[1].align_override = 1;
+ rec = cfree_cg_type_record(c, cfree_sym_intern(c, "R"), 0, fields, 2);
+ EXPECT(rec != CFREE_CG_TYPE_NONE, "record type failed");
+ EXPECT(cfree_cg_type_record(c, cfree_sym_intern(c, "Bad"), 0, fields, 0) !=
+ CFREE_CG_TYPE_NONE,
+ "empty record type failed");
+
+ vals[0].name = cfree_sym_intern(c, "A");
+ vals[0].value = 1;
+ vals[1].name = cfree_sym_intern(c, "B");
+ vals[1].value = 2;
+ enm = cfree_cg_type_enum(c, cfree_sym_intern(c, "E"), CFREE_CG_TYPE_NONE,
+ vals, 2);
+ EXPECT(enm != CFREE_CG_TYPE_NONE, "enum type failed");
+
+ params[0] = bi.i32;
+ params[1] = ptr_i32;
+ fn = cfree_cg_type_func(c, bi.i64, params, 2, 1);
+ EXPECT(fn != CFREE_CG_TYPE_NONE, "function type failed");
+ EXPECT(cfree_cg_type_func(c, bi.i64, params, 70000, 0) == CFREE_CG_TYPE_NONE,
+ "oversized function param list should fail");
+
+ cfree_compiler_free(c);
+ return g_fail ? 1 : 0;
+}
diff --git a/test/test.mk b/test/test.mk
@@ -29,7 +29,7 @@
# parse_asm / cfree_disasm_iter_* are still stubs; the harness builds
# and runs end-to-end so the wiring stays exercised. See doc/ASM.md.
-.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-cg-binder test-opt test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-libc test-musl test-glibc test-lib-deps test-smoke-x64 test-smoke-rv64
+.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-cg-api test-cg-binder test-opt test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-libc test-musl test-glibc test-lib-deps test-smoke-x64 test-smoke-rv64
test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-cg-binder test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-lib-deps
@@ -109,6 +109,14 @@ $(AA64_ISA_TEST_BIN): test/arch/aa64_isa_test.c $(LIB_AR)
# clobber spill behaviour, register-name passthrough, and "cc" no-op.
# Internal cg/ + arch/ surface — needs -Isrc.
CG_BINDER_TEST_BIN = build/test/cg_binder_test
+CG_API_TEST_BIN = build/test/cg_api_test
+
+test-cg-api: $(CG_API_TEST_BIN)
+ $(CG_API_TEST_BIN)
+
+$(CG_API_TEST_BIN): test/api/cg_type_test.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(DRIVER_CFLAGS) test/api/cg_type_test.c $(LIB_AR) -o $@
test-cg-binder: $(CG_BINDER_TEST_BIN)
$(CG_BINDER_TEST_BIN)