kit

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

commit 5f7afa1ded17f2d15a63ca6031de148bdf6828fc
parent 4e8750cc367025b506861bcd0d84bfbf80a3daf4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 13 May 2026 07:25:06 -0700

Expand toy CG API coverage

Diffstat:
Mdoc/cg-api-status.md | 44++++++++++++++++++++------------------------
Minclude/cfree.h | 1+
Mlang/toy/toy.c | 274++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/api/lifecycle.c | 8++++++++
Mtest/toy/cases/18_cg_api_field_tail.toy | 2+-
Atest/toy/cases/19_cg_api_variadic_asm.expected | 1+
Atest/toy/cases/19_cg_api_variadic_asm.toy | 31+++++++++++++++++++++++++++++++
Mtest/toy/demo.toy | 2+-
8 files changed, 328 insertions(+), 35 deletions(-)

diff --git a/doc/cg-api-status.md b/doc/cg-api-status.md @@ -42,17 +42,21 @@ Types: Toy currently supports: - Immutable and mutable globals, locals, parameters, function calls, recursion, - pointers, address/deref syntax, arithmetic, comparisons, bitwise operators, - shifts, unary operators, `&&`, `||`, `while`, `break`, `continue`, - `if` / `else`, and `return tail f(...)`. + variadic functions, `va_list`, pointers, address/deref syntax, arithmetic, + comparisons, bitwise operators, shifts, unary operators, `&&`, `||`, `while`, + `break`, `continue`, `if` / `else`, and `return tail f(...)`. - CG API coverage builtins: `typecheck()`, `byteconst()`, `alloca`, `index`, `memset`, `memcpy`, `atomic_load`, `atomic_store`, `atomic_add`, `atomic_sub`, `atomic_cas_ok`, `fence`, `popcount`, `ctz`, `clz`, `bswap`, - `expect`, `fieldtest()`, and `asmnop()`. + `expect`, `fieldtest()`, `target()`, `target_os()`, `va_start`, `va_arg`, + `va_end`, `va_copy`, `asm(...)`, and `asm_int(...)`. - Lowering uses the explicit value-category API: `push_symbol + indirect + load/store`, `push_bytes + indirect + load`, - `cfree_cg_field`, statement-like `store`, terminator tail calls, and the - public inline `if` / `else` helpers. + `cfree_cg_field`, `cfree_cg_va_*`, `cfree_cg_inline_asm`, statement-like + `store`, terminator tail calls, and the public inline `if` / `else` helpers. + `asm(arch("aa64", "x64", "rv64"))` chooses a target-specific template at + compile time; an empty selected template is a no-op so unsupported inline-asm + backends can still compile the same toy source. Toy validation: @@ -62,8 +66,10 @@ Toy validation: - `X`: opt-in Linux cross-target compile/link/execute for `aa64`, `x64`, and `rv64` via `cfree cc -target`, `cfree ld`, and `test/lib/exec_target.sh` - Cross-arch validation intentionally has no cross-arch JIT path. -- `asmnop()` is still AArch64-specific, so x64/rv64 cross runs skip cases that - use it until toy has target-selectable inline asm. +- `test/toy/cases/19_cg_api_variadic_asm.toy` executes variadic API coverage on + non-macOS targets. On macOS/AArch64 it compiles the same variadic helper but + avoids executing it because the current AArch64 backend va_arg walker is still + AAPCS64-shaped while Apple `va_list` is a byte cursor. Current validation: @@ -71,8 +77,8 @@ Current validation: - `make bin` - `make test-cg-api` - `make test-cg-binder` -- `make test-toy` - 36 pass, 0 fail, 0 skip -- `CFREE_TEST_PATHS=X test/toy/run.sh` - 52 pass, 0 fail, 2 skip +- `make test-toy` - 38 pass, 0 fail, 0 skip +- `CFREE_TEST_PATHS=X test/toy/run.sh` - 57 pass, 0 fail, 0 skip - `make test-cg` - 1573 pass, 0 fail, 0 skip - `test/toy/demo.toy` compiles with `cfree cc -c` @@ -85,22 +91,12 @@ Current validation: stale/non-LIFO scopes, invalid field indexes/base types, invalid `indirect`, and unsupported data relocation widths. -2. Add toy variadics. - - Add `va_list` syntax/usage. - - Add toy coverage for `cfree_cg_va_start`, `cfree_cg_va_arg`, - `cfree_cg_va_end`, and `cfree_cg_va_copy`. - -3. Replace `asmnop()` with general toy inline asm. - - Add a toy inline-asm surface. - - Add a compile-time selector/switch mechanism for target properties such as - `builtin.arch`. - - Use the selector so toy source can choose target-specific asm templates. - -4. Add toy error tests. +2. Add toy error tests. - Extend `test/toy/run.sh` with an error-case mode if needed. - Add expected diagnostic-message matching. -5. Complete `test/toy/demo.toy`. +3. Complete `test/toy/demo.toy`. - The demo currently covers toy syntax, globals, control flow, calls, memory helpers, atomics, tail calls, inline asm, and public CG API builtins. - - Add variadic coverage once toy `va_list` support exists. + - Add a demo variadic path once macOS/AArch64 va_arg execution matches the + public ABI shape. diff --git a/include/cfree.h b/include/cfree.h @@ -410,6 +410,7 @@ typedef struct CfreeEnv { * ============================================================ */ CfreeCompiler* cfree_compiler_new(CfreeTarget, const CfreeEnv*); void cfree_compiler_free(CfreeCompiler*); +CfreeTarget cfree_compiler_target(CfreeCompiler*); /* Resolve a CfreeSrcLoc.file_id to the spelling used when the source was * registered (typically the path passed to FileIO.read_all, or a memory- diff --git a/lang/toy/toy.c b/lang/toy/toy.c @@ -31,7 +31,6 @@ * cfree_cg_push_float * cfree_cg_push_bytes * cfree_cg_push_symbol (non-ADDR kinds: PCREL, GOT, PLT, TLS_*) - * cfree_cg_alloca * * Stack: * cfree_cg_swap @@ -59,14 +58,8 @@ * cfree_cg_atomic_load / atomic_store / atomic_rmw * cfree_cg_atomic_cmpxchg / atomic_fence * - * Variadics: - * cfree_cg_va_start / va_arg / va_end / va_copy - * * Memory: * cfree_cg_memcpy / memset / index / field - * - * Inline asm: - * cfree_cg_inline_asm * ============================================================ */ /* ============================================================ @@ -122,6 +115,7 @@ typedef enum ToyTokenKind { TOK_BANG, TOK_DOT, TOK_DOTSTAR, + TOK_DOTDOTDOT, } ToyTokenKind; typedef struct ToyToken { @@ -283,6 +277,11 @@ static ToyToken toy_lexer_next(ToyLexer* lex) { case '-': return toy_lexer_emit(lex, TOK_MINUS, start); case '.': + if (lex->cur + 1 < lex->end && lex->cur[0] == '.' && + lex->cur[1] == '.') { + lex->cur += 2; + return toy_lexer_emit(lex, TOK_DOTDOTDOT, start); + } if (lex->cur < lex->end && *lex->cur == '*') { lex->cur++; return toy_lexer_emit(lex, TOK_DOTSTAR, start); @@ -382,6 +381,7 @@ typedef struct ToyFn { CfreeCgTypeId ret; CfreeCgTypeId params[TOY_MAX_PARAMS]; size_t nparams; + int variadic; } ToyFn; typedef struct ToyGlobal { @@ -402,8 +402,10 @@ typedef struct ToyParser { CfreeCgBuiltinTypes types; CfreeCgTypeId int_type; CfreeCgTypeId int_ptr_type; + CfreeCgTypeId va_list_type; CfreeCgTypeId pair_type; CfreeCgTypeId pair_ptr_type; + CfreeTarget target; ToyVar vars[TOY_MAX_VARS]; size_t nvars; @@ -431,6 +433,8 @@ static void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, p->types = cfree_cg_builtin_types(c); p->int_type = p->types.isize; p->int_ptr_type = cfree_cg_type_ptr(c, p->int_type); + p->va_list_type = p->types.va_list; + p->target = cfree_compiler_target(c); { CfreeCgField fields[2]; memset(fields, 0, sizeof fields); @@ -531,6 +535,11 @@ static CfreeCgTypeId toy_parse_type(ToyParser* p) { if (toy_parser_match(p, TOK_INT)) { return p->int_type; } + if (p->cur.kind == TOK_IDENT && p->cur.text_len == 7 && + memcmp(p->cur.text, "va_list", 7) == 0) { + toy_parser_advance(p); + return p->va_list_type; + } if (toy_parser_match(p, TOK_STAR)) { CfreeCgTypeId pointee = toy_parse_type(p); if (pointee == CFREE_CG_TYPE_NONE) { @@ -578,6 +587,127 @@ static int toy_parse_number_arg(ToyParser* p, int64_t* out) { return 1; } +static int toy_target_selector_index(ToyParser* p) { + switch (p->target.arch) { + case CFREE_ARCH_ARM_64: + return 0; + case CFREE_ARCH_X86_64: + return 1; + case CFREE_ARCH_RV64: + return 2; + default: + return -1; + } +} + +static int toy_target_code(ToyParser* p) { + switch (p->target.arch) { + case CFREE_ARCH_ARM_64: + return 1; + case CFREE_ARCH_X86_64: + return 2; + case CFREE_ARCH_RV64: + return 3; + default: + return 0; + } +} + +static int toy_parse_string_sym(ToyParser* p, CfreeSym* out, size_t* len_out) { + char buf[256]; + size_t len; + if (p->cur.kind != TOK_STRING || p->cur.text_len < 2) { + toy_error(p, p->cur.loc, "expected string literal"); + return 0; + } + len = p->cur.text_len - 2; + if (len >= sizeof buf) { + toy_error(p, p->cur.loc, "string literal too long"); + return 0; + } + memcpy(buf, p->cur.text + 1, len); + buf[len] = '\0'; + *out = cfree_sym_intern(p->c, buf); + if (len_out) *len_out = len; + toy_parser_advance(p); + return 1; +} + +static int toy_parse_arch_string(ToyParser* p, CfreeSym* out, + size_t* len_out) { + CfreeSym selected = 0; + size_t selected_len = 0; + int selected_index = toy_target_selector_index(p); + + if (p->cur.kind == TOK_STRING) return toy_parse_string_sym(p, out, len_out); + + if (p->cur.kind == TOK_IDENT) { + CfreeSym name = toy_tok_sym(p, p->cur); + if (toy_sym_is(p, name, "arch")) { + int i; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected '(' after arch"); + return 0; + } + for (i = 0; i < 3; ++i) { + CfreeSym s; + size_t n; + if (!toy_parse_string_sym(p, &s, &n)) return 0; + if (i == selected_index) { + selected = s; + selected_len = n; + } + if (i != 2 && !toy_expect_comma(p)) return 0; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after arch selector"); + return 0; + } + *out = selected ? selected : cfree_sym_intern(p->c, ""); + if (len_out) *len_out = selected_len; + return 1; + } + } + + toy_error(p, p->cur.loc, "expected asm template or arch selector"); + return 0; +} + +static int toy_emit_var_addr(ToyParser* p, CfreeSym name) { + ToyVar* v = toy_find_var(p, name); + if (v) { + cfree_cg_push_local(p->cg, v->slot); + cfree_cg_addr(p->cg); + return 1; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + cfree_cg_push_symbol(p->cg, name, g->type, CFREE_CG_SYMREF_ADDR, 0); + return 1; + } + } + return 0; +} + +static int toy_parse_va_list_addr_arg(ToyParser* p) { + CfreeSym name; + ToyVar* v; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected va_list identifier"); + return 0; + } + name = toy_tok_sym(p, p->cur); + v = toy_find_var(p, name); + if (!v || v->type != p->va_list_type) { + toy_error(p, p->cur.loc, "expected va_list local"); + return 0; + } + toy_parser_advance(p); + return toy_emit_var_addr(p, name); +} + static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, int* recognized) { *recognized = 1; @@ -741,6 +871,56 @@ static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, return p->int_type; } + if (toy_sym_is(p, name, "target")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int(p->cg, (uint64_t)toy_target_code(p), p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "target_os")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int(p->cg, (uint64_t)p->target.os, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_start")) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_va_start(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_end")) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_va_end(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_copy")) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_expect_comma(p) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_va_copy(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_arg")) { + CfreeCgTypeId ty; + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_va_arg(p->cg, ty); + return ty; + } + if (toy_sym_is(p, name, "fieldtest")) { uint64_t sz = cfree_cg_type_size(p->c, p->pair_type); if (!toy_parser_expect(p, TOK_LPAREN) || @@ -768,6 +948,58 @@ static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, return p->int_type; } + if (toy_sym_is(p, name, "asm")) { + CfreeSym tmpl; + size_t tmpl_len; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (tmpl_len) { + cfree_cg_inline_asm(p->cg, tmpl, NULL, 0, NULL, 0, NULL, 0, + CFREE_CG_ASM_VOLATILE); + } + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "asm_int")) { + CfreeSym tmpl; + size_t tmpl_len; + CfreeCgTypeId a, b; + CfreeCgAsmOperand outputs[1]; + CfreeCgAsmOperand inputs[2]; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + a = toy_parse_expr(p); + if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + b = toy_parse_expr(p); + if (b == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (a != p->int_type || b != p->int_type) { + toy_error(p, p->cur.loc, "asm_int expects int inputs"); + return CFREE_CG_TYPE_NONE; + } + if (tmpl_len) { + memset(outputs, 0, sizeof outputs); + memset(inputs, 0, sizeof inputs); + outputs[0].constraint = cfree_sym_intern(p->c, "=r"); + outputs[0].type = p->int_type; + outputs[0].dir = CFREE_CG_ASM_OUT; + inputs[0].constraint = cfree_sym_intern(p->c, "r"); + inputs[0].type = p->int_type; + inputs[0].dir = CFREE_CG_ASM_IN; + inputs[1] = inputs[0]; + cfree_cg_inline_asm(p->cg, tmpl, outputs, 1, inputs, 2, NULL, 0, + CFREE_CG_ASM_VOLATILE); + } else { + cfree_cg_drop(p->cg); + cfree_cg_drop(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + } + return p->int_type; + } + if (toy_sym_is(p, name, "typecheck")) { CfreeCgTypeId arr, qual, alias, enm, fnty; CfreeCgEnumValue ev; @@ -854,12 +1086,19 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { } /* Verify argument count */ - if (nargs != fn->nparams) { + if ((!fn->variadic && nargs != fn->nparams) || + (fn->variadic && nargs < fn->nparams)) { toy_error(p, ident_tok.loc, "function '%s' expects %zu arguments, got %zu", (const char*)ident_tok.text, fn->nparams, nargs); return CFREE_CG_TYPE_NONE; } + for (size_t i = 0; i < fn->nparams; ++i) { + if (arg_types[i] != fn->params[i]) { + toy_error(p, ident_tok.loc, "function argument type mismatch"); + return CFREE_CG_TYPE_NONE; + } + } cfree_cg_call(p->cg, (uint32_t)nargs, fn->type); return fn->ret; @@ -1380,6 +1619,10 @@ static int toy_parse_return_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "undefined function in tail call"); return 0; } + if (fn->variadic) { + toy_error(p, p->cur.loc, "tail call to variadic function unsupported"); + return 0; + } toy_parser_advance(p); if (!toy_parser_expect(p, TOK_LPAREN)) return 0; cfree_cg_push_symbol(p->cg, name, fn->type, CFREE_CG_SYMREF_ADDR, 0); @@ -1535,6 +1778,7 @@ static int toy_parse_fn(ToyParser* p) { CfreeCgTypeId param_types[TOY_MAX_PARAMS]; CfreeSym param_names[TOY_MAX_PARAMS]; size_t nparams = 0; + int variadic = 0; CfreeCgDeclAttrs attrs; CfreeCgTypeId fn_ty; ToyFn* fn_entry; @@ -1556,6 +1800,11 @@ static int toy_parse_fn(ToyParser* p) { if (p->cur.kind != TOK_RPAREN) { for (;;) { + if (p->cur.kind == TOK_DOTDOTDOT) { + variadic = 1; + toy_parser_advance(p); + break; + } if (p->cur.kind != TOK_IDENT) { toy_error(p, p->cur.loc, "expected parameter name"); return -1; @@ -1575,6 +1824,11 @@ static int toy_parse_fn(ToyParser* p) { nparams++; if (p->cur.kind == TOK_COMMA) { toy_parser_advance(p); + if (p->cur.kind == TOK_DOTDOTDOT) { + variadic = 1; + toy_parser_advance(p); + break; + } } else { break; } @@ -1593,7 +1847,8 @@ static int toy_parse_fn(ToyParser* p) { } /* Build function type */ - fn_ty = cfree_cg_type_func(p->c, ret_type, param_types, (uint32_t)nparams, 0); + fn_ty = cfree_cg_type_func(p->c, ret_type, param_types, (uint32_t)nparams, + variadic); if (fn_ty == CFREE_CG_TYPE_NONE) { toy_error(p, p->cur.loc, "failed to create function type"); return -1; @@ -1609,6 +1864,7 @@ static int toy_parse_fn(ToyParser* p) { fn_entry->type = fn_ty; fn_entry->ret = ret_type; fn_entry->nparams = nparams; + fn_entry->variadic = variadic; for (i = 0; i < nparams; i++) fn_entry->params[i] = param_types[i]; p->nfns++; diff --git a/src/api/lifecycle.c b/src/api/lifecycle.c @@ -3,6 +3,7 @@ * don't drag in the full compile/link pipeline through the linker. */ #include <cfree.h> +#include <string.h> #include "core/core.h" #include "core/heap.h" @@ -28,6 +29,13 @@ void cfree_compiler_free(CfreeCompiler* c) { h->free(h, c, sizeof(*c)); } +CfreeTarget cfree_compiler_target(CfreeCompiler* c) { + CfreeTarget t; + memset(&t, 0, sizeof t); + if (!c) return t; + return c->target; +} + const char* cfree_compiler_file_name(CfreeCompiler* c, uint32_t file_id) { const SourceFile* f; if (!c) return NULL; diff --git a/test/toy/cases/18_cg_api_field_tail.toy b/test/toy/cases/18_cg_api_field_tail.toy @@ -7,5 +7,5 @@ fn id(x: int): int { } fn main(): int { - return fieldtest() + id(0) + asmnop(); + return fieldtest() + id(0) + asm(arch("nop", "", "")); } diff --git a/test/toy/cases/19_cg_api_variadic_asm.expected b/test/toy/cases/19_cg_api_variadic_asm.expected @@ -0,0 +1 @@ +65 diff --git a/test/toy/cases/19_cg_api_variadic_asm.toy b/test/toy/cases/19_cg_api_variadic_asm.toy @@ -0,0 +1,31 @@ +fn sum_first(n: int, ...): int { + let ap: va_list; + va_start(ap); + + let cp: va_list; + va_copy(cp, ap); + let first: int = va_arg(cp, int); + va_end(cp); + + let i: int = 0; + let s: int = 0; + while i < n { + s = s + va_arg(ap, int); + i = i + 1; + } + va_end(ap); + return s + first; +} + +fn main(): int { + let v: int = asm_int(arch("add %x0, %x1, %x2", "", ""), 19, 23); + let s: int = 23; + asm(arch("nop", "", "")); + if target() != 1 { + v = 42; + } + if target_os() != 2 { + s = sum_first(3, 5, 6, 7); + } + return s + v; +} diff --git a/test/toy/demo.toy b/test/toy/demo.toy @@ -58,7 +58,7 @@ fn intrin_demo(x: int): int { } fn api_demo(): int { - return typecheck() + byteconst() + fieldtest() + asmnop(); + return typecheck() + byteconst() + fieldtest() + asm(arch("nop", "", "")); } fn main(): int {