kit

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

commit 010526be2280ffc15afe52166e607b6ff447594b
parent cf6f708e3c566f50f7f625d7e08d6718f0e2fdda
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 12 May 2026 22:29:23 -0700

Complete public CG API toy coverage

Diffstat:
Adoc/cg-api-status.md | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/cfree/cg.h | 11++++++-----
Mlang/toy/toy.c | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/api/cg.c | 685++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mtest/api/cg_type_test.c | 10++++++++--
Mtest/test.mk | 7+++++--
Atest/toy/cases/01_return_const.expected | 1+
Atest/toy/cases/01_return_const.toy | 3+++
Atest/toy/cases/02_arith_precedence.expected | 1+
Atest/toy/cases/02_arith_precedence.toy | 3+++
Atest/toy/cases/03_bitwise_shift.expected | 1+
Atest/toy/cases/03_bitwise_shift.toy | 3+++
Atest/toy/cases/04_cmp_values.expected | 1+
Atest/toy/cases/04_cmp_values.toy | 3+++
Atest/toy/cases/05_if_else.expected | 1+
Atest/toy/cases/05_if_else.toy | 9+++++++++
Atest/toy/cases/06_while_sum.expected | 1+
Atest/toy/cases/06_while_sum.toy | 9+++++++++
Atest/toy/cases/07_break_continue.expected | 1+
Atest/toy/cases/07_break_continue.toy | 15+++++++++++++++
Atest/toy/cases/08_recursion_fib.expected | 1+
Atest/toy/cases/08_recursion_fib.toy | 10++++++++++
Atest/toy/cases/09_function_params.expected | 1+
Atest/toy/cases/09_function_params.toy | 7+++++++
Atest/toy/cases/10_pointer_addr_deref.expected | 1+
Atest/toy/cases/10_pointer_addr_deref.toy | 5+++++
Atest/toy/cases/11_global_mutation.expected | 1+
Atest/toy/cases/11_global_mutation.toy | 6++++++
Atest/toy/cases/12_short_circuit_and_skip.expected | 1+
Atest/toy/cases/12_short_circuit_and_skip.toy | 13+++++++++++++
Atest/toy/cases/13_short_circuit_or_skip.expected | 1+
Atest/toy/cases/13_short_circuit_or_skip.toy | 13+++++++++++++
Atest/toy/cases/14_pointer_param.expected | 1+
Atest/toy/cases/14_pointer_param.toy | 8++++++++
Atest/toy/run.sh | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
35 files changed, 1351 insertions(+), 115 deletions(-)

diff --git a/doc/cg-api-status.md b/doc/cg-api-status.md @@ -0,0 +1,133 @@ +# CG API Implementation & Toy Language — Status + +## Completed Implementations (`src/api/cg.c`) + +All previously-stubbed public CG API functions now have real implementations: + +| Function | Status | Mechanism | +|---|---|---| +| `cfree_cg_push_float` | Done | Builds `ConstBytes` union, delegates to `CGTarget.load_const` | +| `cfree_cg_push_bytes` | Done | Emits to `.rodata` section via `ObjBuilder`, pushes `OPK_GLOBAL` pointer | +| `cfree_cg_memcpy` | Done | Pops dst/src addrs, forces to regs, calls `CGTarget.copy_bytes` | +| `cfree_cg_memset` | Done | Pops dst addr, calls `CGTarget.set_bytes` with imm byte value | +| `cfree_cg_index` | Done | Pops `[base, index]`, computes `base + offset + index * elemsz` | +| `cfree_cg_field_addr` | Done | Pops base ptr, looks up field offset via `abi_record_layout`, emits add | +| `cfree_cg_alloca` | Done | Pops size, calls `CGTarget.alloca_` | +| `cfree_cg_va_start` | Done | Pops `&ap`, forces reg, calls `CGTarget.va_start_` | +| `cfree_cg_va_arg` | Done | Pops `&ap`, allocs dst reg, calls `CGTarget.va_arg_` | +| `cfree_cg_va_end` | Done | Pops `&ap`, forces reg, calls `CGTarget.va_end_` | +| `cfree_cg_va_copy` | Done | Pops `&dst, &src`, forces regs, calls `CGTarget.va_copy_` | +| `cfree_cg_data_decl` | Done | Creates symbol via `obj_symbol_ex` with visibility mapping | +| `cfree_cg_data_begin` | Done | Selects section (data/rodata/bss/tdata), aligns, defines symbol | +| `cfree_cg_data_bytes` | Done | `obj_write` to current section | +| `cfree_cg_data_zero` | Done | Writes zero-padded bytes to section in 64-byte chunks | +| `cfree_cg_data_symbol` | Done | Emits reloc via `obj_reloc` (ABS32/ABS64/PC32/PC64) | +| `cfree_cg_data_end` | Done | Resets data state | +| `cfree_cg_tail_call` | Done | Same as `cfree_cg_call` but with `CG_CALL_TAIL` flag | +| `cfree_cg_continue_true` | Done | Pops condition, `cmp_branch(CMP_NE, ...)` to continue label | +| `cfree_cg_continue_false` | Done | Pops condition, `cmp_branch(CMP_EQ, ...)` to continue label | + +Struct additions to `CfreeCg`: + +- `rodata_counter` — unique names for anonymous rodata symbols +- `data_sec`, `data_base`, `data_size` — tracks current data definition + +Helper additions: + +- `api_map_vis()` — maps `CfreeCgVisibility` to `SymVis` +- `api_data_sym_kind()` — maps decl attrs to `SK_OBJ` / `SK_TLS` + +Bug fix: + +- `cfree_cg_func_end` now resets `g->nscopes = 0` (was leaking scope state across functions) + +## Toy Language Additions (`lang/toy/toy.c`) + +### New tokens + +`TOK_VAR`, `TOK_PIPE` (`|`), `TOK_CARET` (`^`), `TOK_TILDE` (`~`), `TOK_SHL` (`<<`), `TOK_SHR` (`>>`) + +### New expression precedence levels (C-standard order) + +- shift: `<<`, `>>` -> `CFREE_CG_SHL`, `CFREE_CG_SHR_S` +- bitwise AND: `&` -> `CFREE_CG_AND` +- bitwise XOR: `^` -> `CFREE_CG_XOR` +- bitwise OR: `|` -> `CFREE_CG_OR` +- unary `~` -> `CFREE_CG_BNOT` + +### Global variables + +`let` = immutable, `var` = mutable: + +- `ToyGlobal` struct, `ToyGlobal globals[]` in parser +- `toy_find_global()` lookup +- Primary expression: globals resolved via `cfree_cg_push_symbol` + `cfree_cg_load` +- Assignment: mutable globals stored via `cfree_cg_push_symbol` + `cfree_cg_store` +- Global `let`/`var` declarations emit `cfree_cg_data_begin/zero/end` + +### if/else fix + +Switched from `cfree_cg_scope_begin/break_false/break/scope_end` to raw +`cfree_cg_label_new/branch_false/jump/label_place` — the scope model cannot +express "jump to else" vs "jump to end" with distinct targets. + +## Test Results + +All passing via `cfree run`: + +- Basic arithmetic, function calls, recursion (fib) +- while loops, break/continue +- `&&`, `||` short-circuit (via scopes with `break_true`/`break_false`) +- Bitwise `&`, `|`, `^`, `~`, `<<`, `>>` +- Pointer deref (`*p`), address-of (`&var`) +- Multi-function programs with if/else, local vars, params +- GCD (Euclidean algorithm), sum-to-N, is_even + +## Fixed: API stack/register ownership bugs + +The AArch64 comparison-as-value failure was fixed without adding another +record kind; the CG API remains struct-only for records. + +Changes made: + +- `cfree_cg_store` again follows the public contract: stack is + `[... lvalue, rvalue] -> [rvalue]`. +- `cfree_cg_push_local` now remembers the declared slot type from + `local_slot` / `param_slot`, so loads and stores use the correct ABI size. +- `cfree_cg_dup` now duplicates register-owned values into a fresh register + and pins the source during allocation, avoiding double-free/stale-register + ownership. +- Spill/reload now uses the type of the owned register, including pointer-sized + storage for `OPK_INDIRECT` bases and 16-byte FP spill slots. +- In-flight call argument spill cleanup no longer returns aggregate lvalue + slots to the scalar spill pool. +- `cfree_cg_push_bytes` pushes a typed rodata lvalue, matching the intended + `push_bytes + load` materialization flow. + +Validation: + +- `make lib` +- `make bin` +- `make test-cg-api` +- `make test-toy` — 28 pass, 0 fail +- `make test-cg` — 1573 pass, 0 fail, 0 skip +- Toy smoke tests for store and short-circuit `&&` / `||` +- AArch64 object dump for `return 1 != 2` materializes the compare result in a + real register and returns it through `x0`. + +Note: branch lowering may still disassemble `cmp xN, #0` as `subs sp, xN, #0` +because register 31 is the architectural zero-register destination for the +flag-setting compare encoding. The fixed failure was the value-producing +comparison path losing its destination register. + +## Remaining TODOs + +1. Test `cfree_cg_push_bytes` + `cfree_cg_load` (arbitrary-width constants) +2. Toy global declarations parse non-zero initializers but still emit zero-filled + data; `let g: int = 5; fn main(): int { return g; }` returns 0 today. +3. Toy assignment only accepts identifier lvalues; pointer stores like + `*p = 7;` fail to parse. +4. Test data definitions with non-zero initializers once compile-time eval lands. +5. Test `cfree_cg_alloca`, `cfree_cg_va_*` (variadics) +6. Test `cfree_cg_tail_call` +7. Write comprehensive `demo.toy` that exercises all features diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -67,9 +67,10 @@ typedef struct CfreeCgEnumValue { int64_t value; } CfreeCgEnumValue; -/* Builtin ids are stable for the compiler. All constructors below allocate a - * fresh user-facing type id; construct aliases/qualified types to describe - * distinct source-language identities over the same ABI layout. */ +/* Builtin ids are stable for the compiler. Pointer, array, and function + * constructors return a stable id for the same shape within one compiler; + * aliases, qualified types, records, and enums allocate fresh user-facing + * identities. */ CfreeCgBuiltinTypes cfree_cg_builtin_types(CfreeCompiler*); CfreeCgTypeId cfree_cg_type_ptr(CfreeCompiler*, CfreeCgTypeId pointee); CfreeCgTypeId cfree_cg_type_array(CfreeCompiler*, CfreeCgTypeId elem, @@ -212,8 +213,8 @@ void cfree_cg_va_copy(CfreeCg*); /* pop &dst, &src void cfree_cg_push_int(CfreeCg*, uint64_t value, CfreeCgTypeId type); void cfree_cg_push_float(CfreeCg*, double value, CfreeCgTypeId type); -/* Anonymous immutable bytes in rodata; pushes a pointer to the first byte. - * pointee_type describes the logical value at the pushed address, enabling +/* Anonymous immutable bytes in rodata; pushes an lvalue at the first byte. + * pointee_type describes the logical value at that address, enabling * push_bytes + load to materialize arbitrary-width constants. */ void cfree_cg_push_bytes(CfreeCg*, const uint8_t* str, size_t len, CfreeCgTypeId pointee_type); diff --git a/lang/toy/toy.c b/lang/toy/toy.c @@ -77,6 +77,7 @@ typedef enum ToyTokenKind { TOK_EOF = 0, TOK_FN, TOK_LET, + TOK_VAR, TOK_IF, TOK_ELSE, TOK_WHILE, @@ -111,8 +112,13 @@ typedef enum ToyTokenKind { TOK_NE, TOK_ANDAND, TOK_PIPEPIPE, - TOK_BANG, TOK_AMPERSAND, + TOK_PIPE, + TOK_CARET, + TOK_TILDE, + TOK_SHL, + TOK_SHR, + TOK_BANG, TOK_DOT, TOK_DOTSTAR, } ToyTokenKind; @@ -236,7 +242,11 @@ static ToyToken toy_lexer_next(ToyLexer* lex) { lex->cur++; return toy_lexer_emit(lex, TOK_PIPEPIPE, start); } - break; + return toy_lexer_emit(lex, TOK_PIPE, start); + case '^': + return toy_lexer_emit(lex, TOK_CARET, start); + case '~': + return toy_lexer_emit(lex, TOK_TILDE, start); case '=': if (lex->cur < lex->end && *lex->cur == '=') { lex->cur++; @@ -250,12 +260,20 @@ static ToyToken toy_lexer_next(ToyLexer* lex) { } return toy_lexer_emit(lex, TOK_BANG, start); case '<': + if (lex->cur < lex->end && *lex->cur == '<') { + lex->cur++; + return toy_lexer_emit(lex, TOK_SHL, start); + } if (lex->cur < lex->end && *lex->cur == '=') { lex->cur++; return toy_lexer_emit(lex, TOK_LE, start); } return toy_lexer_emit(lex, TOK_LT, start); case '>': + if (lex->cur < lex->end && *lex->cur == '>') { + lex->cur++; + return toy_lexer_emit(lex, TOK_SHR, start); + } if (lex->cur < lex->end && *lex->cur == '=') { lex->cur++; return toy_lexer_emit(lex, TOK_GE, start); @@ -288,6 +306,8 @@ static ToyToken toy_lexer_next(ToyLexer* lex) { ToyTokenKind kind = TOK_IDENT; if (len == 2 && start[0] == 'f' && start[1] == 'n') kind = TOK_FN; + else if (len == 3 && start[0] == 'v' && start[1] == 'a' && start[2] == 'r') + kind = TOK_VAR; else if (len == 3 && start[0] == 'i' && start[1] == 'n' && start[2] == 't') kind = TOK_INT; else if (len == 3 && start[0] == 'l' && start[1] == 'e' && start[2] == 't') @@ -343,6 +363,7 @@ static ToyToken toy_lexer_peek(const ToyLexer* lex) { #define TOY_MAX_FNS 32 #define TOY_MAX_SCOPES 16 #define TOY_MAX_PARAMS 16 +#define TOY_MAX_GLOBALS 32 typedef struct ToyVar { CfreeSym name; @@ -359,6 +380,12 @@ typedef struct ToyFn { size_t nparams; } ToyFn; +typedef struct ToyGlobal { + CfreeSym name; + CfreeCgTypeId type; + int mutable; +} ToyGlobal; + typedef struct ToyScope { CfreeCgScope cg_scope; } ToyScope; @@ -378,6 +405,9 @@ typedef struct ToyParser { ToyFn fns[TOY_MAX_FNS]; size_t nfns; + ToyGlobal globals[TOY_MAX_GLOBALS]; + size_t nglobals; + ToyScope scopes[TOY_MAX_SCOPES]; size_t nscopes; @@ -397,6 +427,7 @@ static void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, p->int_ptr_type = cfree_cg_type_ptr(c, p->int_type); p->nvars = 0; p->nfns = 0; + p->nglobals = 0; p->nscopes = 0; p->cur_fn_ret = p->types.void_; p->diag = cfree_compiler_diag_sink(c); @@ -467,6 +498,14 @@ static ToyFn* toy_find_fn(ToyParser* p, CfreeSym name) { return NULL; } +static ToyGlobal* toy_find_global(ToyParser* p, CfreeSym name) { + size_t i; + for (i = p->nglobals; i > 0; --i) { + if (p->globals[i - 1].name == name) return &p->globals[i - 1]; + } + return NULL; +} + /* ============================================================ * Type parsing * ============================================================ */ @@ -555,14 +594,20 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { /* Variable reference */ ToyVar* v = toy_find_var(p, name); - if (!v) { - toy_error(p, ident_tok.loc, "undefined variable '%s'", - (const char*)ident_tok.text); - return CFREE_CG_TYPE_NONE; + if (v) { + cfree_cg_push_local(p->cg, v->slot); + cfree_cg_load(p->cg); + return v->type; } - cfree_cg_push_local(p->cg, v->slot); - cfree_cg_load(p->cg); - return v->type; + ToyGlobal* g = toy_find_global(p, name); + if (g) { + cfree_cg_push_symbol(p->cg, name, g->type, CFREE_CG_SYMREF_ADDR, 0); + cfree_cg_load(p->cg); + return g->type; + } + toy_error(p, ident_tok.loc, "undefined variable '%s'", + (const char*)ident_tok.text); + return CFREE_CG_TYPE_NONE; } if (p->cur.kind == TOK_LPAREN) { @@ -604,6 +649,17 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { return p->int_type; } + if (toy_parser_match(p, TOK_TILDE)) { + CfreeCgTypeId ty = toy_parse_expr_unary(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type) { + toy_error(p, p->cur.loc, "invalid operand for '~'"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_unop(p->cg, CFREE_CG_BNOT); + return p->int_type; + } + if (toy_parser_match(p, TOK_AMPERSAND)) { if (p->cur.kind != TOK_IDENT) { toy_error(p, p->cur.loc, "expected identifier after '&'"); @@ -729,25 +785,100 @@ static CfreeCgTypeId toy_parse_expr_cmp(ToyParser* p) { return ty; } -static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) { +static CfreeCgTypeId toy_parse_expr_shift(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_cmp(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_SHL || p->cur.kind == TOK_SHR) { + ToyTokenKind op = p->cur.kind; + CfreeCgBinOp binop = (op == TOK_SHL) ? CFREE_CG_SHL : CFREE_CG_SHR_S; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_cmp(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "shift operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_binop(p->cg, binop); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_band(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_shift(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_AMPERSAND) { + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_shift(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "bitwise operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_binop(p->cg, CFREE_CG_AND); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_bxor(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_band(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_CARET) { + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_band(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "bitwise operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_binop(p->cg, CFREE_CG_XOR); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_bor(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_bxor(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_PIPE) { + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_bxor(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "bitwise operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_binop(p->cg, CFREE_CG_OR); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_bor(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_ANDAND) { - CfreeCgScope scope; + CfreeCgLabel false_label; + CfreeCgLabel end_label; + CfreeCgSlot result_slot; toy_parser_advance(p); - scope = cfree_cg_scope_begin(p->cg, p->int_type); - cfree_cg_push_int(p->cg, 0, p->int_type); - cfree_cg_cmp(p->cg, CFREE_CG_NE); - cfree_cg_break_false(p->cg, scope); - cfree_cg_drop(p->cg); - ty = toy_parse_expr_cmp(p); + false_label = cfree_cg_label_new(p->cg); + end_label = cfree_cg_label_new(p->cg); + result_slot = cfree_cg_local_slot(p->cg, p->int_type, 0); + cfree_cg_branch_false(p->cg, false_label); + ty = toy_parse_expr_bor(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + cfree_cg_branch_false(p->cg, false_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_push_int(p->cg, 1, p->int_type); + cfree_cg_store(p->cg); + cfree_cg_drop(p->cg); + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, false_label); + cfree_cg_push_local(p->cg, result_slot); cfree_cg_push_int(p->cg, 0, p->int_type); - cfree_cg_cmp(p->cg, CFREE_CG_NE); - cfree_cg_break_false(p->cg, scope); + cfree_cg_store(p->cg); cfree_cg_drop(p->cg); - cfree_cg_push_int(p->cg, 1, p->int_type); - cfree_cg_scope_end(p->cg, scope); + cfree_cg_label_place(p->cg, end_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_load(p->cg); ty = p->int_type; } return ty; @@ -757,21 +888,30 @@ static CfreeCgTypeId toy_parse_expr_or(ToyParser* p) { CfreeCgTypeId ty = toy_parse_expr_and(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; while (p->cur.kind == TOK_PIPEPIPE) { - CfreeCgScope scope; + CfreeCgLabel true_label; + CfreeCgLabel end_label; + CfreeCgSlot result_slot; toy_parser_advance(p); - scope = cfree_cg_scope_begin(p->cg, p->int_type); - cfree_cg_push_int(p->cg, 1, p->int_type); - cfree_cg_cmp(p->cg, CFREE_CG_NE); - cfree_cg_break_true(p->cg, scope); - cfree_cg_drop(p->cg); + true_label = cfree_cg_label_new(p->cg); + end_label = cfree_cg_label_new(p->cg); + result_slot = cfree_cg_local_slot(p->cg, p->int_type, 0); + cfree_cg_branch_true(p->cg, true_label); ty = toy_parse_expr_and(p); if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + cfree_cg_branch_true(p->cg, true_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_push_int(p->cg, 0, p->int_type); + cfree_cg_store(p->cg); + cfree_cg_drop(p->cg); + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, true_label); + cfree_cg_push_local(p->cg, result_slot); cfree_cg_push_int(p->cg, 1, p->int_type); - cfree_cg_cmp(p->cg, CFREE_CG_NE); - cfree_cg_break_true(p->cg, scope); + cfree_cg_store(p->cg); cfree_cg_drop(p->cg); - cfree_cg_push_int(p->cg, 0, p->int_type); - cfree_cg_scope_end(p->cg, scope); + cfree_cg_label_place(p->cg, end_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_load(p->cg); ty = p->int_type; } return ty; @@ -838,6 +978,7 @@ static int toy_parse_let_stmt(ToyParser* p) { return 0; } cfree_cg_push_local(p->cg, slot); + cfree_cg_swap(p->cg); cfree_cg_store(p->cg); cfree_cg_drop(p->cg); } @@ -849,7 +990,7 @@ static int toy_parse_let_stmt(ToyParser* p) { } static int toy_parse_if_stmt(ToyParser* p) { - CfreeCgScope scope; + CfreeCgLabel else_label, end_label; CfreeCgTypeId cond_ty; toy_parser_advance(p); /* if */ cond_ty = toy_parse_expr(p); @@ -859,15 +1000,19 @@ static int toy_parse_if_stmt(ToyParser* p) { return 0; } - scope = cfree_cg_scope_begin(p->cg, CFREE_CG_TYPE_NONE); + else_label = cfree_cg_label_new(p->cg); + end_label = cfree_cg_label_new(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); cfree_cg_cmp(p->cg, CFREE_CG_NE); - cfree_cg_break_false(p->cg, scope); + cfree_cg_branch_false(p->cg, else_label); if (!toy_parse_block(p)) return 0; + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, else_label); + if (p->cur.kind == TOK_ELSE) { - cfree_cg_break(p->cg, scope); toy_parser_advance(p); /* else */ if (p->cur.kind == TOK_LBRACE) { if (!toy_parse_block(p)) return 0; @@ -875,7 +1020,8 @@ static int toy_parse_if_stmt(ToyParser* p) { if (!toy_parse_stmt(p)) return 0; } } - cfree_cg_scope_end(p->cg, scope); + + cfree_cg_label_place(p->cg, end_label); return 1; } @@ -998,22 +1144,41 @@ static int toy_parse_stmt(ToyParser* p) { if (p->cur.kind == TOK_IDENT && toy_lexer_peek(&p->lex).kind == TOK_EQ) { CfreeSym name = toy_tok_sym(p, p->cur); - ToyVar* v = toy_find_var(p, name); - if (!v) { - toy_error(p, p->cur.loc, "undefined variable in assignment"); - return 0; - } toy_parser_advance(p); /* ident */ toy_parser_advance(p); /* = */ CfreeCgTypeId expr_ty = toy_parse_expr(p); if (expr_ty == CFREE_CG_TYPE_NONE) return 0; - if (expr_ty != v->type) { - toy_error(p, p->cur.loc, "type mismatch in assignment"); - return 0; + { + ToyVar* v = toy_find_var(p, name); + if (v) { + if (expr_ty != v->type) { + toy_error(p, p->cur.loc, "type mismatch in assignment"); + return 0; + } + cfree_cg_push_local(p->cg, v->slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg); + cfree_cg_drop(p->cg); + } else { + ToyGlobal* g = toy_find_global(p, name); + if (!g) { + toy_error(p, p->cur.loc, "undefined variable in assignment"); + return 0; + } + if (!g->mutable) { + toy_error(p, p->cur.loc, "cannot assign to immutable global"); + return 0; + } + if (expr_ty != g->type) { + toy_error(p, p->cur.loc, "type mismatch in assignment"); + return 0; + } + cfree_cg_push_symbol(p->cg, name, g->type, CFREE_CG_SYMREF_ADDR, 0); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg); + cfree_cg_drop(p->cg); + } } - cfree_cg_push_local(p->cg, v->slot); - cfree_cg_store(p->cg); - cfree_cg_drop(p->cg); if (!toy_parser_expect(p, TOK_SEMI)) { toy_error(p, p->cur.loc, "expected ';' after assignment"); return 0; @@ -1150,15 +1315,95 @@ static int toy_parse_fn(ToyParser* p) { } /* ============================================================ + * Global variable declarations + * ============================================================ */ + +static int toy_parse_global_var(ToyParser* p) { + CfreeSym name; + CfreeCgTypeId ty; + CfreeCgDeclAttrs attrs; + int mutable; + + if (p->cur.kind == TOK_VAR) { + mutable = 1; + toy_parser_advance(p); + } else { + mutable = 0; + toy_parser_advance(p); /* let */ + } + + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected identifier"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after identifier"); + return 0; + } + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE) return 0; + + if (p->nglobals >= TOY_MAX_GLOBALS) { + toy_error(p, p->cur.loc, "too many globals"); + return 0; + } + p->globals[p->nglobals].name = name; + p->globals[p->nglobals].type = ty; + p->globals[p->nglobals].mutable = mutable; + p->nglobals++; + + memset(&attrs, 0, sizeof(attrs)); + attrs.bind = CFREE_SB_GLOBAL; + attrs.visibility = CFREE_CG_VIS_DEFAULT; + attrs.tls_model = CFREE_CG_TLS_DEFAULT; + attrs.flags = CFREE_CG_DECL_DEFINED; + if (!mutable) attrs.flags |= CFREE_CG_DECL_READONLY; + + if (toy_parser_match(p, TOK_EQ)) { + CfreeCgTypeId expr_ty = toy_parse_expr(p); + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + if (expr_ty != ty) { + toy_error(p, p->cur.loc, "type mismatch in global initializer"); + return 0; + } + /* Emit data definition with initializer */ + if (!mutable) attrs.flags |= CFREE_CG_DECL_READONLY; + cfree_cg_data_begin(p->cg, name, ty, attrs); + cfree_cg_data_zero(p->cg, cfree_cg_type_size(p->c, ty)); + cfree_cg_data_end(p->cg); + } else { + /* Zero-initialized */ + cfree_cg_data_begin(p->cg, name, ty, attrs); + cfree_cg_data_zero(p->cg, cfree_cg_type_size(p->c, ty)); + cfree_cg_data_end(p->cg); + } + + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';'"); + return 0; + } + return 1; +} + +/* ============================================================ * Program parsing * ============================================================ */ static int toy_parse_program(ToyParser* p) { while (p->cur.kind != TOK_EOF) { - int r = toy_parse_fn(p); - if (r < 0) return 0; - if (r == 0) { - toy_error(p, p->cur.loc, "expected function declaration"); + if (p->cur.kind == TOK_FN) { + int r = toy_parse_fn(p); + if (r < 0) return 0; + if (r == 0) { + toy_error(p, p->cur.loc, "expected function declaration"); + return 0; + } + } else if (p->cur.kind == TOK_LET || p->cur.kind == TOK_VAR) { + if (!toy_parse_global_var(p)) return 0; + } else { + toy_error(p, p->cur.loc, "expected function or global declaration"); return 0; } } diff --git a/src/api/cg.c b/src/api/cg.c @@ -1,5 +1,7 @@ #include <cfree/cg.h> +#include <stdarg.h> #include <stdint.h> +#include <stdio.h> #include <string.h> #include "api/cg_api.h" @@ -31,8 +33,7 @@ typedef struct CgApiType { const CfreeCgEnumValue* values; const CfreeCgTypeId* params; u8 kind; - u8 is_union; - u8 pad[2]; + u8 pad[3]; } CgApiType; SEGVEC_DEFINE(CgApiTypes, CgApiType, CG_API_TYPE_SEG_SHIFT); @@ -70,6 +71,10 @@ static CfreeCgTypeId user_id_from_index(u32 index) { return type_id_from_tuple(raw_seg + CG_API_TYPE_USER_SEG_BIAS, off); } +static CfreeCgTypeId type_id_for_user_index(u32 index) { + return user_id_from_index(index); +} + static const Type* builtin_type(Compiler* c, CfreeCgBuiltinType t) { switch (t) { case CFREE_CG_BUILTIN_VOID: @@ -136,6 +141,62 @@ static CgApiType* type_alloc(Compiler* c, CfreeCgTypeId* id_out) { return e; } +static CfreeCgTypeId find_ptr_type_id(Compiler* c, CfreeCgTypeId pointee) { + CgApiState* s; + u32 n; + if (!c || !c->cg_api) return CFREE_CG_TYPE_NONE; + s = (CgApiState*)c->cg_api; + n = CgApiTypes_count(&s->types); + for (u32 i = 0; i < n; ++i) { + CgApiType* e = CgApiTypes_at(&s->types, i); + if (e && e->kind == CG_API_TYPE_PTR && e->base == pointee) + return type_id_for_user_index(i); + } + return CFREE_CG_TYPE_NONE; +} + +static CfreeCgTypeId find_array_type_id(Compiler* c, CfreeCgTypeId elem, + u32 count) { + CgApiState* s; + u32 n; + if (!c || !c->cg_api) return CFREE_CG_TYPE_NONE; + s = (CgApiState*)c->cg_api; + n = CgApiTypes_count(&s->types); + for (u32 i = 0; i < n; ++i) { + CgApiType* e = CgApiTypes_at(&s->types, i); + if (e && e->kind == CG_API_TYPE_ARRAY && e->base == elem && + e->count == count) + return type_id_for_user_index(i); + } + return CFREE_CG_TYPE_NONE; +} + +static int type_id_arrays_eq(const CfreeCgTypeId* a, const CfreeCgTypeId* b, + u32 n) { + for (u32 i = 0; i < n; ++i) + if (a[i] != b[i]) return 0; + return 1; +} + +static CfreeCgTypeId find_func_type_id(Compiler* c, CfreeCgTypeId ret, + const CfreeCgTypeId* params, + u32 nparams, int variadic) { + CgApiState* s; + u32 n; + if (!c || !c->cg_api) return CFREE_CG_TYPE_NONE; + s = (CgApiState*)c->cg_api; + n = CgApiTypes_count(&s->types); + for (u32 i = 0; i < n; ++i) { + CgApiType* e = CgApiTypes_at(&s->types, i); + if (!e || e->kind != CG_API_TYPE_FUNC) continue; + if (e->base != ret || e->count != nparams) continue; + if ((e->flags != 0) != (variadic != 0)) continue; + if (nparams && !type_id_arrays_eq(e->params, params, nparams)) continue; + return type_id_for_user_index(i); + } + return CFREE_CG_TYPE_NONE; +} + static const Type* resolve_type(Compiler* c, CfreeCgTypeId id) { u32 seg; u32 off; @@ -213,6 +274,8 @@ CfreeCgTypeId cfree_cg_type_ptr(CfreeCompiler* c, CfreeCgTypeId pointee) { CfreeCgTypeId id; CgApiType* e; if (!pty) return CFREE_CG_TYPE_NONE; + id = find_ptr_type_id(c, pointee); + if (id != CFREE_CG_TYPE_NONE) return id; e = type_alloc(c, &id); if (!e) return CFREE_CG_TYPE_NONE; e->type = type_ptr(c->global, pty); @@ -227,6 +290,8 @@ CfreeCgTypeId cfree_cg_type_array(CfreeCompiler* c, CfreeCgTypeId elem, CfreeCgTypeId id; CgApiType* e; if (!ety) return CFREE_CG_TYPE_NONE; + id = find_array_type_id(c, elem, count); + if (id != CFREE_CG_TYPE_NONE) return id; e = type_alloc(c, &id); if (!e) return CFREE_CG_TYPE_NONE; e->type = type_array(c->global, ety, count, 0); @@ -310,7 +375,6 @@ CfreeCgTypeId cfree_cg_type_record(CfreeCompiler* c, CfreeSym tag, e->count = nfields; e->fields = copied; e->kind = CG_API_TYPE_RECORD; - e->is_union = 0; return e->type ? id : CFREE_CG_TYPE_NONE; } @@ -357,6 +421,8 @@ CfreeCgTypeId cfree_cg_type_func(CfreeCompiler* c, CfreeCgTypeId ret, if (!c || !rty || (nparams && !params) || nparams > UINT16_MAX) { return CFREE_CG_TYPE_NONE; } + id = find_func_type_id(c, ret, params, nparams, variadic); + if (id != CFREE_CG_TYPE_NONE) return id; h = (Heap*)c->env->heap; if (nparams) { ptypes = (const Type**)h->alloc(h, sizeof(*ptypes) * nparams, @@ -507,6 +573,9 @@ struct CfreeCg { u32 sp; u32 cap; + const Type** slot_types; + u32 slot_types_cap; + struct { FrameSlot* free; u32 n; @@ -525,6 +594,12 @@ struct CfreeCg { ApiCgScope scopes[API_CG_MAX_SCOPES]; u32 nscopes; + + u32 rodata_counter; + + ObjSecId data_sec; + u32 data_base; + u64 data_size; }; /* ---- value stack helpers ---- */ @@ -578,6 +653,17 @@ static Operand api_op_global(ObjSymId sym, i64 addend, const Type* ty) { return o; } +static Operand api_op_indirect(Reg base, i32 ofs, const Type* ty) { + Operand o; + memset(&o, 0, sizeof o); + o.kind = OPK_INDIRECT; + o.cls = RC_INT; + o.type = ty; + o.v.ind.base = base; + o.v.ind.ofs = ofs; + return o; +} + static u8 api_residency_for(const Operand* o) { if (o->kind == OPK_REG || o->kind == OPK_INDIRECT) return RES_REG; return RES_INHERENT; @@ -629,12 +715,40 @@ static ApiSValue api_pop(CfreeCg* g) { return g->stack[--g->sp]; } +static void api_remember_slot_type(CfreeCg* g, FrameSlot slot, const Type* ty) { + Heap* h = g->c->env->heap; + const Type** nb; + u32 cap; + if (slot == FRAME_SLOT_NONE) return; + if (slot < g->slot_types_cap) { + g->slot_types[slot] = ty; + return; + } + cap = g->slot_types_cap ? g->slot_types_cap : 16; + while (cap <= slot) cap *= 2u; + nb = (const Type**)h->alloc(h, sizeof(*nb) * cap, _Alignof(const Type*)); + if (!nb) return; + memset(nb, 0, sizeof(*nb) * cap); + if (g->slot_types) { + memcpy(nb, g->slot_types, sizeof(*nb) * g->slot_types_cap); + h->free(h, g->slot_types, sizeof(*g->slot_types) * g->slot_types_cap); + } + g->slot_types = nb; + g->slot_types_cap = cap; + g->slot_types[slot] = ty; +} + +static const Type* api_slot_type(CfreeCg* g, FrameSlot slot) { + if (slot == FRAME_SLOT_NONE || slot >= g->slot_types_cap) return NULL; + return g->slot_types[slot]; +} + /* ---- register class helpers ---- */ static u8 api_class_of_sv(const ApiSValue* sv) { - if (sv->op.kind == OPK_IMM || sv->op.kind == OPK_REG) - return sv->op.cls; - return RC_INT; + if (sv->op.kind == OPK_INDIRECT) return RC_INT; + if (sv->op.kind == OPK_IMM || sv->op.kind == OPK_REG) return sv->op.cls; + return api_type_class(api_sv_type(sv)); } static Reg api_reg_of_sv(const ApiSValue* sv) { @@ -648,6 +762,14 @@ static void api_set_owned_reg(ApiSValue* sv, Reg r) { else if (sv->op.kind == OPK_INDIRECT) sv->op.v.ind.base = r; } +static const Type* api_owned_reg_type(CfreeCg* g, const ApiSValue* sv) { + if (sv->op.kind == OPK_INDIRECT) { + const Type* base = sv->type ? sv->type : type_void(g->c->global); + return type_ptr(g->c->global, base); + } + return api_sv_type(sv); +} + /* ---- spill slot management ---- */ static void api_take_spill_slot_alloc(CfreeCg* g, u8 cls, FrameSlot* out) { @@ -655,8 +777,8 @@ static void api_take_spill_slot_alloc(CfreeCg* g, u8 cls, FrameSlot* out) { FrameSlotDesc fsd; memset(&fsd, 0, sizeof fsd); fsd.kind = FS_SPILL; - fsd.size = (cls == RC_FP) ? 8 : 8; - fsd.align = (cls == RC_FP) ? 8 : 8; + fsd.size = (cls == RC_FP) ? 16 : 8; + fsd.align = fsd.size; *out = T->frame_slot(T, &fsd); } @@ -671,6 +793,7 @@ static FrameSlot api_take_spill_slot(CfreeCg* g, u8 cls) { static void api_return_spill_slot(CfreeCg* g, FrameSlot s, u8 cls) { Heap* h; + if (s == FRAME_SLOT_NONE) return; if (cls >= 3) return; h = g->c->env->heap; if (g->slot_pools[cls].n >= g->slot_pools[cls].cap) { @@ -698,19 +821,18 @@ static ApiSValue* api_pick_victim(CfreeCg* g, u8 cls) { return NULL; } +static MemAccess api_mem_for_spill(CfreeCg* g, const ApiSValue* sv); + static int api_spill_avs_victim(CfreeCg* g, u8 cls) { CGTarget* T = g->target; + if (!g->avs_in_flight) return 0; for (u32 i = 0; i < g->avs_in_flight_n; ++i) { CGABIValue* av = &g->avs_in_flight[i]; if (av->storage.kind != OPK_REG) continue; if (av->storage.cls != cls) continue; FrameSlot slot = api_take_spill_slot(g, cls); - MemAccess ma; - memset(&ma, 0, sizeof ma); - ma.type = av->type; - ma.size = av->type ? abi_sizeof(g->c->abi, av->type) : 8; - ma.align = av->type ? abi_alignof(g->c->abi, av->type) : 8; - T->spill_reg(T, av->storage, slot, ma); + ApiSValue tmp = api_make_sv(av->storage, av->type); + T->spill_reg(T, av->storage, slot, api_mem_for_spill(g, &tmp)); Operand local = api_op_local(slot, av->type); local.cls = cls; av->storage = local; @@ -739,7 +861,7 @@ static MemAccess api_mem_for_lvalue(CfreeCg* g, const Operand* lv, const Type* t } static MemAccess api_mem_for_spill(CfreeCg* g, const ApiSValue* sv) { - const Type* ty = api_sv_type(sv); + const Type* ty = api_owned_reg_type(g, sv); MemAccess m; memset(&m, 0, sizeof m); m.type = ty; @@ -757,7 +879,8 @@ static Reg api_alloc_reg_or_spill(CfreeCg* g, u8 cls, const Type* ty) { ApiSValue* victim = api_pick_victim(g, cls); if (victim) { FrameSlot slot = api_take_spill_slot(g, cls); - Operand victim_reg = api_op_reg((Reg)api_reg_of_sv(victim), victim->type); + const Type* rty = api_owned_reg_type(g, victim); + Operand victim_reg = api_op_reg((Reg)api_reg_of_sv(victim), rty); T->spill_reg(T, victim_reg, slot, api_mem_for_spill(g, victim)); victim->spill_slot = slot; victim->res = RES_SPILLED; @@ -781,7 +904,7 @@ static void api_ensure_reg(CfreeCg* g, ApiSValue* sv) { if (sv->res != RES_SPILLED) return; CGTarget* T = g->target; u8 cls = api_class_of_sv(sv); - const Type* ty = api_sv_type(sv); + const Type* ty = api_owned_reg_type(g, sv); Reg r = api_alloc_reg_or_spill(g, cls, ty ? ty : type_prim(g->c->global, TY_INT)); T->reload_reg(T, api_op_reg(r, ty), sv->spill_slot, api_mem_for_spill(g, sv)); api_return_spill_slot(g, sv->spill_slot, cls); @@ -834,7 +957,11 @@ static void api_release_arg_storage(CfreeCg* g, Operand* storage) { if (storage->kind == OPK_REG) { g->target->free_reg(g->target, storage->v.reg, storage->cls); } else if (storage->kind == OPK_LOCAL && storage->cls < 3) { + const Type* ty = storage->type; + if (ty && (ty->kind == TY_STRUCT || ty->kind == TY_UNION)) return; api_return_spill_slot(g, storage->v.frame_slot, storage->cls); + } else if (storage->kind == OPK_INDIRECT) { + g->target->free_reg(g->target, storage->v.ind.base, RC_INT); } } @@ -902,6 +1029,20 @@ static SymBind api_map_bind(CfreeSymBind b) { return SB_LOCAL; } +static SymVis api_map_vis(CfreeCgVisibility v) { + switch (v) { + case CFREE_CG_VIS_DEFAULT: return SV_DEFAULT; + case CFREE_CG_VIS_HIDDEN: return SV_HIDDEN; + case CFREE_CG_VIS_PROTECTED: return SV_PROTECTED; + } + return SV_DEFAULT; +} + +static SymKind api_data_sym_kind(CfreeCgDeclAttrs attrs) { + if (attrs.flags & CFREE_CG_DECL_TLS) return SK_TLS; + return SK_OBJ; +} + /* ============================================================ * Public API: CfreeCg lifecycle * ============================================================ */ @@ -942,6 +1083,9 @@ void cfree_cg_free(CfreeCg* g) { mc_free(g->mc); h = g->c->env->heap; if (g->stack) h->free(h, g->stack, sizeof(ApiSValue) * g->cap); + if (g->slot_types) { + h->free(h, g->slot_types, sizeof(*g->slot_types) * g->slot_types_cap); + } for (u32 c = 0; c < 3; ++c) { if (g->slot_pools[c].free) { h->free(h, g->slot_pools[c].free, sizeof(FrameSlot) * g->slot_pools[c].cap); @@ -1044,6 +1188,7 @@ void cfree_cg_func_end(CfreeCg* g) { g->target->func_end(g->target); g->fn_abi = NULL; g->fn_ret_type = NULL; + g->nscopes = 0; } /* ============================================================ @@ -1053,6 +1198,7 @@ void cfree_cg_func_end(CfreeCg* g) { CfreeCgSlot cfree_cg_local_slot(CfreeCg* g, CfreeCgTypeId type, CfreeSym name) { const Type* ty; FrameSlotDesc fsd; + FrameSlot slot; if (!g) return 0; ty = resolve_type(g->c, type); if (!ty) return 0; @@ -1063,7 +1209,9 @@ CfreeCgSlot cfree_cg_local_slot(CfreeCg* g, CfreeCgTypeId type, CfreeSym name) { fsd.size = abi_sizeof(g->c->abi, ty); fsd.align = abi_alignof(g->c->abi, ty); fsd.kind = FS_LOCAL; - return (CfreeCgSlot)g->target->frame_slot(g->target, &fsd); + slot = g->target->frame_slot(g->target, &fsd); + api_remember_slot_type(g, slot, ty); + return (CfreeCgSlot)slot; } CfreeCgSlot cfree_cg_param_slot(CfreeCg* g, uint32_t index, CfreeCgTypeId type, @@ -1084,6 +1232,7 @@ CfreeCgSlot cfree_cg_param_slot(CfreeCg* g, uint32_t index, CfreeCgTypeId type, fsd.align = abi_alignof(g->c->abi, ty); fsd.kind = FS_PARAM; slot = g->target->frame_slot(g->target, &fsd); + api_remember_slot_type(g, slot, ty); memset(&pd, 0, sizeof pd); pd.index = index; @@ -1112,17 +1261,59 @@ void cfree_cg_push_int(CfreeCg* g, uint64_t value, CfreeCgTypeId type) { } void cfree_cg_push_float(CfreeCg* g, double value, CfreeCgTypeId type) { - (void)g; (void)value; (void)type; + const Type* ty; + CGTarget* T; + ConstBytes cb; + union { double d; float f; uint8_t b[8]; } u; + Reg r; + Operand dst; + if (!g) return; + ty = resolve_type(g->c, type); + if (!ty) return; + T = g->target; + cb.type = ty; + cb.size = (u32)abi_sizeof(g->c->abi, ty); + cb.align = (u32)abi_alignof(g->c->abi, ty); + if (ty->kind == TY_FLOAT) u.f = (float)value; + else u.d = value; + cb.bytes = u.b; + r = api_alloc_reg_or_spill(g, api_type_class(ty), ty); + dst = api_op_reg(r, ty); + T->load_const(T, dst, cb); + api_push(g, api_make_sv(dst, ty)); } void cfree_cg_push_bytes(CfreeCg* g, const uint8_t* str, size_t len, CfreeCgTypeId pointee_type) { - (void)g; (void)str; (void)len; (void)pointee_type; + Compiler* c; + ObjBuilder* ob; + const Type* pty; + Sym sec_name; + ObjSecId sec; + u32 base; + char name_buf[32]; + Sym anon_name; + ObjSymId sym; + if (!g) return; + c = g->c; + ob = g->obj; + pty = resolve_type(c, pointee_type); + if (!pty) return; + sec_name = pool_intern_cstr(c->global, ".rodata"); + sec = obj_section(ob, sec_name, SEC_RODATA, SF_ALLOC, 1); + base = obj_align_to(ob, sec, (u32)abi_alignof(c->abi, pty)); + obj_write(ob, sec, str, len); + snprintf(name_buf, sizeof(name_buf), ".Lcfree_ro.%u", g->rodata_counter++); + anon_name = pool_intern_cstr(c->global, name_buf); + sym = obj_symbol(ob, anon_name, SB_LOCAL, SK_OBJ, sec, base, (u64)len); + api_push(g, api_make_sv(api_op_global(sym, 0, pty), pty)); } void cfree_cg_push_local(CfreeCg* g, CfreeCgSlot slot) { + const Type* ty; if (!g) return; - api_push(g, api_make_sv(api_op_local((FrameSlot)slot, NULL), NULL)); + ty = api_slot_type(g, (FrameSlot)slot); + api_push(g, api_make_sv(api_op_local((FrameSlot)slot, ty), ty)); } void cfree_cg_push_symbol(CfreeCg* g, CfreeSym name, CfreeCgTypeId type, @@ -1155,8 +1346,14 @@ void cfree_cg_load(CfreeCg* g) { v = api_pop(g); api_ensure_reg(g, &v); if (!api_is_lvalue(&v.op)) { - api_push(g, v); - return; + if (type_is_ptr(api_sv_type(&v))) { + const Type* pty = api_sv_type(&v)->ptr.pointee; + Operand ptr = api_force_reg(g, &v, api_sv_type(&v)); + v = api_make_sv(api_op_indirect(ptr.v.reg, 0, pty), pty); + } else { + api_push(g, v); + return; + } } ty = api_sv_type(&v); dst = api_force_reg(g, &v, ty); @@ -1192,10 +1389,8 @@ void cfree_cg_store(CfreeCg* g) { Operand src; if (!g) return; T = g->target; - /* Toy frontend pushes rvalue then lvalue: stack is [..., rv, lv]. - * Pop lv first (TOS), then rv. */ - lv = api_pop(g); rv = api_pop(g); + lv = api_pop(g); api_ensure_reg(g, &lv); api_ensure_reg(g, &rv); if (!api_is_lvalue(&lv.op)) { @@ -1218,17 +1413,32 @@ void cfree_cg_store(CfreeCg* g) { * ============================================================ */ void cfree_cg_dup(CfreeCg* g) { - ApiSValue v; + ApiSValue v, dup; + ApiSValue* top; + const Type* ty; + Reg r; + Operand dst; if (!g || g->sp == 0) return; - v = g->stack[g->sp - 1]; - if (v.op.kind == OPK_REG && v.res == RES_REG) { - Reg r = api_alloc_reg_or_spill(g, v.op.cls, v.type); - Operand dst = api_op_reg(r, v.type); - g->target->copy(g->target, dst, v.op); - api_push(g, api_make_sv(dst, v.type)); - } else { + top = &g->stack[g->sp - 1]; + api_ensure_reg(g, top); + v = *top; + if (v.res != RES_REG) { api_push(g, v); + return; } + top->pinned = 1; + ty = api_owned_reg_type(g, &v); + r = api_alloc_reg_or_spill(g, api_class_of_sv(&v), ty); + dst = api_op_reg(r, ty); + g->target->copy(g->target, dst, + api_op_reg((Reg)api_reg_of_sv(&v), ty)); + g->stack[g->sp - 1].pinned = 0; + dup = v; + api_set_owned_reg(&dup, r); + dup.res = RES_REG; + dup.pinned = 0; + dup.spill_slot = FRAME_SLOT_NONE; + api_push(g, dup); } void cfree_cg_swap(CfreeCg* g) { @@ -1647,11 +1857,47 @@ void cfree_cg_continue(CfreeCg* g, CfreeCgScope scope) { } void cfree_cg_continue_true(CfreeCg* g, CfreeCgScope scope) { - (void)g; (void)scope; + u32 idx; + ApiSValue v; + CGTarget* T; + const Type* ty; + if (!g || scope == 0) return; + idx = (u32)scope - 1; + if (idx >= g->nscopes) return; + T = g->target; + v = api_pop(g); + ty = v.type ? v.type : type_prim(g->c->global, TY_INT); + if (v.op.kind == OPK_IMM) { + if (v.op.v.imm != 0) T->jump(T, g->scopes[idx].continue_lbl); + api_release(g, &v); + } else { + Operand a = api_force_reg(g, &v, ty); + Operand zero = api_op_imm(0, ty); + T->cmp_branch(T, CMP_NE, a, zero, g->scopes[idx].continue_lbl); + api_release(g, &v); + } } void cfree_cg_continue_false(CfreeCg* g, CfreeCgScope scope) { - (void)g; (void)scope; + u32 idx; + ApiSValue v; + CGTarget* T; + const Type* ty; + if (!g || scope == 0) return; + idx = (u32)scope - 1; + if (idx >= g->nscopes) return; + T = g->target; + v = api_pop(g); + ty = v.type ? v.type : type_prim(g->c->global, TY_INT); + if (v.op.kind == OPK_IMM) { + if (v.op.v.imm == 0) T->jump(T, g->scopes[idx].continue_lbl); + api_release(g, &v); + } else { + Operand a = api_force_reg(g, &v, ty); + Operand zero = api_op_imm(0, ty); + T->cmp_branch(T, CMP_EQ, a, zero, g->scopes[idx].continue_lbl); + api_release(g, &v); + } } /* ============================================================ @@ -1659,32 +1905,203 @@ void cfree_cg_continue_false(CfreeCg* g, CfreeCgScope scope) { * ============================================================ */ void cfree_cg_alloca(CfreeCg* g, CfreeCgTypeId result_ptr_type, uint32_t align) { - (void)g; (void)result_ptr_type; (void)align; + ApiSValue sz; + CGTarget* T; + const Type* pty; + Operand sz_op; + Reg rr; + Operand dst; + if (!g) return; + T = g->target; + sz = api_pop(g); + pty = resolve_type(g->c, result_ptr_type); + if (!pty) pty = type_ptr(g->c->global, type_void(g->c->global)); + sz_op = (sz.op.kind == OPK_IMM) ? sz.op : api_force_reg(g, &sz, api_sv_type(&sz)); + rr = api_alloc_reg_or_spill(g, RC_INT, pty); + dst = api_op_reg(rr, pty); + T->alloca_(T, dst, sz_op, align ? align : 16); + api_release(g, &sz); + api_push(g, api_make_sv(dst, pty)); } -void cfree_cg_va_start(CfreeCg* g) { (void)g; } -void cfree_cg_va_arg(CfreeCg* g, CfreeCgTypeId type) { (void)g; (void)type; } -void cfree_cg_va_end(CfreeCg* g) { (void)g; } -void cfree_cg_va_copy(CfreeCg* g) { (void)g; } +void cfree_cg_va_start(CfreeCg* g) { + ApiSValue ap; + CGTarget* T; + Operand ap_op; + if (!g) return; + T = g->target; + ap = api_pop(g); + ap_op = api_force_reg(g, &ap, api_sv_type(&ap)); + T->va_start_(T, ap_op); + api_release(g, &ap); +} + +void cfree_cg_va_arg(CfreeCg* g, CfreeCgTypeId type) { + ApiSValue ap; + CGTarget* T; + const Type* ty; + Operand ap_op; + Reg rr; + Operand dst; + if (!g) return; + T = g->target; + ty = resolve_type(g->c, type); + if (!ty) return; + ap = api_pop(g); + ap_op = api_force_reg(g, &ap, api_sv_type(&ap)); + rr = api_alloc_reg_or_spill(g, api_type_class(ty), ty); + dst = api_op_reg(rr, ty); + T->va_arg_(T, dst, ap_op, ty); + api_release(g, &ap); + api_push(g, api_make_sv(dst, ty)); +} + +void cfree_cg_va_end(CfreeCg* g) { + ApiSValue ap; + CGTarget* T; + Operand ap_op; + if (!g) return; + T = g->target; + ap = api_pop(g); + ap_op = api_force_reg(g, &ap, api_sv_type(&ap)); + T->va_end_(T, ap_op); + api_release(g, &ap); +} + +void cfree_cg_va_copy(CfreeCg* g) { + ApiSValue src, dst; + CGTarget* T; + Operand src_op, dst_op; + if (!g) return; + T = g->target; + src = api_pop(g); + dst = api_pop(g); + src_op = api_force_reg(g, &src, api_sv_type(&src)); + dst_op = api_force_reg(g, &dst, api_sv_type(&dst)); + T->va_copy_(T, dst_op, src_op); + api_release(g, &src); + api_release(g, &dst); +} /* ============================================================ * Memory operations (stubs) * ============================================================ */ void cfree_cg_memcpy(CfreeCg* g, uint32_t size, uint32_t align) { - (void)g; (void)size; (void)align; + ApiSValue src, dst; + CGTarget* T; + AggregateAccess agg; + Operand dst_op, src_op; + if (!g) return; + T = g->target; + src = api_pop(g); + dst = api_pop(g); + dst_op = api_force_reg(g, &dst, api_sv_type(&dst)); + src_op = api_force_reg(g, &src, api_sv_type(&src)); + memset(&agg, 0, sizeof agg); + agg.size = size; + agg.align = align ? align : size; + T->copy_bytes(T, dst_op, src_op, agg); + api_release(g, &dst); + api_release(g, &src); } void cfree_cg_memset(CfreeCg* g, uint8_t val, uint32_t size, uint32_t align) { - (void)g; (void)val; (void)size; (void)align; + ApiSValue dst; + CGTarget* T; + AggregateAccess agg; + Operand dst_op, byte_val; + if (!g) return; + T = g->target; + dst = api_pop(g); + dst_op = api_force_reg(g, &dst, api_sv_type(&dst)); + byte_val = api_op_imm((i64)val, NULL); + memset(&agg, 0, sizeof agg); + agg.size = size; + agg.align = align ? align : size; + T->set_bytes(T, dst_op, byte_val, agg); + api_release(g, &dst); } void cfree_cg_index(CfreeCg* g, uint32_t offset) { - (void)g; (void)offset; + ApiSValue idx, base; + CGTarget* T; + const Type *base_ptr_ty, *elem_ty, *idx_ty; + u32 elemsz; + Operand base_op, idx_op, result; + Reg rr; + if (!g) return; + T = g->target; + idx = api_pop(g); + base = api_pop(g); + base_ptr_ty = api_sv_type(&base); + if (base_ptr_ty && base_ptr_ty->kind == TY_PTR) { + elem_ty = base_ptr_ty->ptr.pointee; + } else if (base_ptr_ty && base_ptr_ty->kind == TY_ARRAY) { + elem_ty = base_ptr_ty->arr.elem; + } else { + elem_ty = type_prim(g->c->global, TY_INT); + } + elemsz = (u32)abi_sizeof(g->c->abi, elem_ty); + idx_ty = idx.type ? idx.type : idx.op.type; + if (!idx_ty) idx_ty = type_prim(g->c->global, TY_INT); + base_op = api_force_reg(g, &base, base_ptr_ty); + idx_op = api_force_reg_unless_imm(g, &idx, idx_ty); + rr = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty); + result = api_op_reg(rr, base_ptr_ty); + if (idx_op.kind == OPK_IMM) { + i64 total_offset = idx_op.v.imm * (i64)elemsz + (i64)offset; + T->binop(T, BO_IADD, result, base_op, api_op_imm(total_offset, base_ptr_ty)); + } else { + Reg sr = api_alloc_reg_or_spill(g, RC_INT, idx_ty); + Operand scaled = api_op_reg(sr, idx_ty); + T->binop(T, BO_IMUL, scaled, idx_op, api_op_imm((i64)elemsz, idx_ty)); + if (offset > 0) { + T->binop(T, BO_IADD, scaled, scaled, api_op_imm((i64)offset, idx_ty)); + } + T->binop(T, BO_IADD, result, base_op, scaled); + T->free_reg(T, sr, RC_INT); + } + api_release(g, &base); + api_release(g, &idx); + api_push(g, api_make_sv(result, base_ptr_ty)); } void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) { - (void)g; (void)field_index; + ApiSValue base; + CGTarget* T; + const Type *base_ptr_ty, *rec_ty; + const ABIRecordLayout* layout; + u32 field_offset; + Operand base_op, result; + Reg rr; + if (!g) return; + T = g->target; + base = api_pop(g); + base_ptr_ty = api_sv_type(&base); + if (!base_ptr_ty || base_ptr_ty->kind != TY_PTR) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: field_addr base is not a pointer"); + return; + } + rec_ty = base_ptr_ty->ptr.pointee; + layout = abi_record_layout(g->c->abi, rec_ty); + if (!layout || field_index >= layout->nfields) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: invalid field index"); + return; + } + field_offset = layout->fields[field_index].offset; + base_op = api_force_reg(g, &base, base_ptr_ty); + rr = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty); + result = api_op_reg(rr, base_ptr_ty); + if (field_offset == 0) { + T->copy(T, result, base_op); + } else { + T->binop(T, BO_IADD, result, base_op, + api_op_imm((i64)field_offset, base_ptr_ty)); + } + api_release(g, &base); + api_push(g, api_make_sv(result, base_ptr_ty)); } /* ============================================================ @@ -1800,7 +2217,53 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) { } void cfree_cg_tail_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) { - (void)g; (void)nargs; (void)fn_type; + CGTarget* T; + const Type* fty; + const ABIFuncInfo* abi; + CGABIValue* avs; + CGCallDesc desc; + ApiSValue callee; + if (!g) return; + T = g->target; + fty = resolve_type(g->c, fn_type); + if (!fty) return; + abi = abi_func_info(g->c->abi, fty); + avs = NULL; + if (nargs) { + avs = arena_array(g->c->tu, CGABIValue, nargs); + memset(avs, 0, sizeof(CGABIValue) * nargs); + } + for (u32 i = 0; i < nargs; ++i) { + u32 idx = nargs - 1u - i; + ApiSValue arg = api_pop(g); + api_ensure_reg(g, &arg); + const Type* aty = fty->fn.params ? fty->fn.params[idx] : arg.type; + avs[idx].type = aty; + avs[idx].abi = idx < abi->nparams ? &abi->params[idx] : NULL; + avs[idx].storage = + api_is_lvalue(&arg.op) ? api_force_reg(g, &arg, aty) : arg.op; + } + callee = api_pop(g); + api_ensure_reg(g, &callee); + Operand callee_op = (callee.op.kind == OPK_GLOBAL) + ? callee.op + : api_force_reg(g, &callee, fty); + memset(&desc, 0, sizeof desc); + desc.fn_type = fty; + desc.abi = abi; + desc.callee = callee_op; + desc.args = avs; + desc.nargs = nargs; + desc.flags = CG_CALL_TAIL; + desc.ret.type = fty->fn.ret; + desc.ret.abi = &abi->ret; + T->call(T, &desc); + for (u32 i = 0; i < nargs; ++i) { + api_release_arg_storage(g, &avs[i].storage); + } + if (callee.op.kind != OPK_GLOBAL) { + T->free_reg(T, callee_op.v.reg, RC_INT); + } } void cfree_cg_ret(CfreeCg* g) { @@ -1844,26 +2307,138 @@ void cfree_cg_ret_void(CfreeCg* g) { * ============================================================ */ void cfree_cg_data_decl(CfreeCg* g, CfreeSym name, CfreeCgTypeId type, - CfreeCgDeclAttrs attrs) { - (void)g; (void)name; (void)type; (void)attrs; + CfreeCgDeclAttrs attrs) { + Compiler* c; + ObjBuilder* ob; + Sym mangled; + ObjSymId sym; + if (!g) return; + c = g->c; + ob = g->obj; + mangled = api_c_mangle(c, name); + sym = obj_symbol_find(ob, mangled); + if (sym == OBJ_SYM_NONE) { + sym = obj_symbol_ex(ob, mangled, api_map_bind(attrs.bind), + api_map_vis(attrs.visibility), + api_data_sym_kind(attrs), OBJ_SEC_NONE, 0, 0, 0); + } + (void)type; } void cfree_cg_data_begin(CfreeCg* g, CfreeSym name, CfreeCgTypeId type, - CfreeCgDeclAttrs attrs) { - (void)g; (void)name; (void)type; (void)attrs; + CfreeCgDeclAttrs attrs) { + Compiler* c; + ObjBuilder* ob; + Sym mangled; + ObjSymId sym; + const Type* ty; + u32 align; + SecKind sec_kind; + u16 sec_flags; + Sym sec_name_sym; + ObjSecId sec; + if (!g) return; + c = g->c; + ob = g->obj; + ty = resolve_type(c, type); + if (!ty) return; + mangled = api_c_mangle(c, name); + sym = obj_symbol_find(ob, mangled); + align = attrs.align ? attrs.align : (u32)abi_alignof(c->abi, ty); + if (attrs.section) { + sec_name_sym = (Sym)attrs.section; + sec_kind = SEC_OTHER; + sec_flags = SF_ALLOC | SF_WRITE; + } else if (attrs.flags & CFREE_CG_DECL_READONLY) { + sec_kind = SEC_RODATA; + sec_flags = SF_ALLOC; + sec_name_sym = pool_intern_cstr(c->global, ".rodata"); + } else if (attrs.flags & CFREE_CG_DECL_TLS) { + sec_kind = SEC_DATA; + sec_flags = SF_ALLOC | SF_WRITE | SF_TLS; + sec_name_sym = pool_intern_cstr(c->global, ".tdata"); + } else if (attrs.flags & CFREE_CG_DECL_COMMON) { + sec_kind = SEC_BSS; + sec_flags = SF_ALLOC | SF_WRITE; + sec_name_sym = pool_intern_cstr(c->global, ".bss"); + } else { + sec_kind = SEC_DATA; + sec_flags = SF_ALLOC | SF_WRITE; + sec_name_sym = pool_intern_cstr(c->global, ".data"); + } + sec = obj_section(ob, sec_name_sym, sec_kind, sec_flags, align); + g->data_sec = sec; + g->data_base = obj_align_to(ob, sec, align); + g->data_size = 0; + if (sym == OBJ_SYM_NONE) { + sym = obj_symbol_ex(ob, mangled, api_map_bind(attrs.bind), + api_map_vis(attrs.visibility), + api_data_sym_kind(attrs), sec, + (u64)g->data_base, (u64)abi_sizeof(c->abi, ty), 0); + } else { + obj_symbol_define(ob, sym, sec, (u64)g->data_base, + (u64)abi_sizeof(c->abi, ty)); + } } void cfree_cg_data_bytes(CfreeCg* g, const uint8_t* data, size_t len) { - (void)g; (void)data; (void)len; + if (!g || !len) return; + obj_write(g->obj, g->data_sec, data, len); + g->data_size += len; } void cfree_cg_data_zero(CfreeCg* g, uint64_t size) { - (void)g; (void)size; + if (!g || !size) return; + { + u8 pad[64]; + memset(pad, 0, sizeof pad); + u64 remaining = size; + while (remaining >= sizeof pad) { + obj_write(g->obj, g->data_sec, pad, sizeof pad); + remaining -= sizeof pad; + } + if (remaining) obj_write(g->obj, g->data_sec, pad, (size_t)remaining); + } + g->data_size += size; } void cfree_cg_data_symbol(CfreeCg* g, CfreeCgSymbolRefKind kind, - CfreeSym target, int64_t addend, uint32_t nbytes) { - (void)g; (void)kind; (void)target; (void)addend; (void)nbytes; + CfreeSym target, int64_t addend, uint32_t nbytes) { + Compiler* c; + ObjBuilder* ob; + Sym mangled; + ObjSymId sym; + RelocKind rk; + u8 pad[8]; + if (!g) return; + c = g->c; + ob = g->obj; + mangled = api_c_mangle(c, target); + sym = obj_symbol_find(ob, mangled); + if (sym == OBJ_SYM_NONE) { + sym = obj_symbol(ob, mangled, SB_GLOBAL, SK_OBJ, OBJ_SEC_NONE, 0, 0); + } + switch (nbytes) { + case 4: + rk = (kind == CFREE_CG_SYMREF_PCREL) ? R_PC32 : R_ABS32; + break; + case 8: + rk = (kind == CFREE_CG_SYMREF_PCREL) ? R_PC64 : R_ABS64; + break; + default: + rk = R_ABS64; + break; + } + memset(pad, 0, sizeof pad); + obj_write(ob, g->data_sec, pad, nbytes); + obj_reloc(ob, g->data_sec, g->data_base + (u32)g->data_size, rk, sym, + addend); + g->data_size += nbytes; } -void cfree_cg_data_end(CfreeCg* g) { (void)g; } +void cfree_cg_data_end(CfreeCg* g) { + if (!g) return; + g->data_sec = OBJ_SEC_NONE; + g->data_base = 0; + g->data_size = 0; +} diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -96,6 +96,10 @@ int main(void) { 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, bi.i32) == ptr_i32, + "ptr type id should be interned"); + EXPECT(cfree_cg_type_array(c, bi.i32, 4) == array_i32, + "array type id should be interned"); EXPECT(cfree_cg_type_ptr(c, CFREE_CG_TYPE_NONE) == CFREE_CG_TYPE_NONE, "invalid pointer pointee should fail"); @@ -114,9 +118,9 @@ int main(void) { 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); + rec = cfree_cg_type_record(c, cfree_sym_intern(c, "R"), 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) != + EXPECT(cfree_cg_type_record(c, cfree_sym_intern(c, "Bad"), fields, 0) != CFREE_CG_TYPE_NONE, "empty record type failed"); @@ -132,6 +136,8 @@ int main(void) { 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, 2, 1) == fn, + "function type id should be interned"); EXPECT(cfree_cg_type_func(c, bi.i64, params, 70000, 0) == CFREE_CG_TYPE_NONE, "oversized function param list should fail"); diff --git a/test/test.mk b/test/test.mk @@ -29,9 +29,9 @@ # 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-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 +.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-toy 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 +test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-cg-binder test-toy test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-lib-deps test-lex: bin @CFREE=$(abspath $(BIN)) test/lex/run.sh @@ -125,6 +125,9 @@ $(CG_BINDER_TEST_BIN): test/cg/binder_test.c $(LIB_AR) @mkdir -p $(dir $@) $(CC) $(DRIVER_CFLAGS) -Isrc test/cg/binder_test.c $(LIB_AR) -o $@ +test-toy: bin + @CFREE=$(abspath $(BIN)) test/toy/run.sh + # Phase-4b Track-C: aarch64 inline-asm backend unit test (doc/INLINEASM.md # §7.1 "Track C"). Drives aa_asm_block (CGTarget vtable) directly with # hand-rolled Operand arrays and asserts the emitted .text bytes match diff --git a/test/toy/cases/01_return_const.expected b/test/toy/cases/01_return_const.expected @@ -0,0 +1 @@ +7 diff --git a/test/toy/cases/01_return_const.toy b/test/toy/cases/01_return_const.toy @@ -0,0 +1,3 @@ +fn main(): int { + return 7; +} diff --git a/test/toy/cases/02_arith_precedence.expected b/test/toy/cases/02_arith_precedence.expected @@ -0,0 +1 @@ +7 diff --git a/test/toy/cases/02_arith_precedence.toy b/test/toy/cases/02_arith_precedence.toy @@ -0,0 +1,3 @@ +fn main(): int { + return 1 + 2 * 3 - 4 / 2 + 5 % 3; +} diff --git a/test/toy/cases/03_bitwise_shift.expected b/test/toy/cases/03_bitwise_shift.expected @@ -0,0 +1 @@ +13 diff --git a/test/toy/cases/03_bitwise_shift.toy b/test/toy/cases/03_bitwise_shift.toy @@ -0,0 +1,3 @@ +fn main(): int { + return ((5 & 3) | (8 >> 1)) ^ (1 << 3); +} diff --git a/test/toy/cases/04_cmp_values.expected b/test/toy/cases/04_cmp_values.expected @@ -0,0 +1 @@ +11 diff --git a/test/toy/cases/04_cmp_values.toy b/test/toy/cases/04_cmp_values.toy @@ -0,0 +1,3 @@ +fn main(): int { + return (1 < 2) + (2 <= 2) * 2 + (3 > 4) * 4 + (5 != 6) * 8; +} diff --git a/test/toy/cases/05_if_else.expected b/test/toy/cases/05_if_else.expected @@ -0,0 +1 @@ +7 diff --git a/test/toy/cases/05_if_else.toy b/test/toy/cases/05_if_else.toy @@ -0,0 +1,9 @@ +fn main(): int { + let x: int = 3; + if x > 2 { + x = x + 4; + } else { + x = x + 40; + } + return x; +} diff --git a/test/toy/cases/06_while_sum.expected b/test/toy/cases/06_while_sum.expected @@ -0,0 +1 @@ +15 diff --git a/test/toy/cases/06_while_sum.toy b/test/toy/cases/06_while_sum.toy @@ -0,0 +1,9 @@ +fn main(): int { + let i: int = 0; + let s: int = 0; + while i < 6 { + s = s + i; + i = i + 1; + } + return s; +} diff --git a/test/toy/cases/07_break_continue.expected b/test/toy/cases/07_break_continue.expected @@ -0,0 +1 @@ +25 diff --git a/test/toy/cases/07_break_continue.toy b/test/toy/cases/07_break_continue.toy @@ -0,0 +1,15 @@ +fn main(): int { + let i: int = 0; + let s: int = 0; + while i < 10 { + i = i + 1; + if i == 3 { + continue; + } + if i == 8 { + break; + } + s = s + i; + } + return s; +} diff --git a/test/toy/cases/08_recursion_fib.expected b/test/toy/cases/08_recursion_fib.expected @@ -0,0 +1 @@ +13 diff --git a/test/toy/cases/08_recursion_fib.toy b/test/toy/cases/08_recursion_fib.toy @@ -0,0 +1,10 @@ +fn fib(n: int): int { + if n < 2 { + return n; + } + return fib(n - 1) + fib(n - 2); +} + +fn main(): int { + return fib(7); +} diff --git a/test/toy/cases/09_function_params.expected b/test/toy/cases/09_function_params.expected @@ -0,0 +1 @@ +49 diff --git a/test/toy/cases/09_function_params.toy b/test/toy/cases/09_function_params.toy @@ -0,0 +1,7 @@ +fn mix(a: int, b: int, c: int): int { + return a * 10 + b * 3 - c; +} + +fn main(): int { + return mix(4, 5, 6); +} diff --git a/test/toy/cases/10_pointer_addr_deref.expected b/test/toy/cases/10_pointer_addr_deref.expected @@ -0,0 +1 @@ +22 diff --git a/test/toy/cases/10_pointer_addr_deref.toy b/test/toy/cases/10_pointer_addr_deref.toy @@ -0,0 +1,5 @@ +fn main(): int { + let x: int = 21; + let p: *int = &x; + return *p + 1; +} diff --git a/test/toy/cases/11_global_mutation.expected b/test/toy/cases/11_global_mutation.expected @@ -0,0 +1 @@ +6 diff --git a/test/toy/cases/11_global_mutation.toy b/test/toy/cases/11_global_mutation.toy @@ -0,0 +1,6 @@ +var g: int; + +fn main(): int { + g = 4; + return g + 2; +} diff --git a/test/toy/cases/12_short_circuit_and_skip.expected b/test/toy/cases/12_short_circuit_and_skip.expected @@ -0,0 +1 @@ +0 diff --git a/test/toy/cases/12_short_circuit_and_skip.toy b/test/toy/cases/12_short_circuit_and_skip.toy @@ -0,0 +1,13 @@ +var g: int; + +fn set_g(): int { + g = 9; + return 1; +} + +fn main(): int { + if 0 && set_g() { + g = 5; + } + return g; +} diff --git a/test/toy/cases/13_short_circuit_or_skip.expected b/test/toy/cases/13_short_circuit_or_skip.expected @@ -0,0 +1 @@ +0 diff --git a/test/toy/cases/13_short_circuit_or_skip.toy b/test/toy/cases/13_short_circuit_or_skip.toy @@ -0,0 +1,13 @@ +var g: int; + +fn set_g(): int { + g = 9; + return 1; +} + +fn main(): int { + if 1 || set_g() { + g = g + 0; + } + return g; +} diff --git a/test/toy/cases/14_pointer_param.expected b/test/toy/cases/14_pointer_param.expected @@ -0,0 +1 @@ +33 diff --git a/test/toy/cases/14_pointer_param.toy b/test/toy/cases/14_pointer_param.toy @@ -0,0 +1,8 @@ +fn read(p: *int): int { + return *p; +} + +fn main(): int { + let x: int = 33; + return read(&x); +} diff --git a/test/toy/run.sh b/test/toy/run.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# test/toy/run.sh — .toy frontend end-to-end tests. +# +# Paths per case: +# R cfree run case.toy +# L cfree cc -c case.toy -> cfree ld case.o -> native executable +# +# Sidecars: +# <name>.expected expected process exit code, default 0 +# +# Filtering: +# ./run.sh [name_filter] [paths] +# CFREE_TEST_FILTER / CFREE_TEST_PATHS, where paths is a subset of "RL". + +set -u + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +TEST_DIR="$ROOT/test/toy" +BUILD_DIR="$ROOT/build/test/toy" +CFREE="${CFREE:-$ROOT/build/cfree}" + +FILTER="${1:-${CFREE_TEST_FILTER:-}}" +PATHS="${2:-${CFREE_TEST_PATHS:-RL}}" +case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac +case "$PATHS" in *L*) RUN_L=1;; *) RUN_L=0;; esac + +mkdir -p "$BUILD_DIR" + +PASS=0 +FAIL=0 +FAIL_NAMES=() + +color_red() { printf '\033[31m%s\033[0m' "$1"; } +color_grn() { printf '\033[32m%s\033[0m' "$1"; } + +note_pass() { + PASS=$((PASS + 1)) + printf ' %s %s\n' "$(color_grn PASS)" "$1" +} + +note_fail() { + FAIL=$((FAIL + 1)) + FAIL_NAMES+=("$1") + printf ' %s %s\n' "$(color_red FAIL)" "$1" +} + +expected_for() { + local src="$1" exp="${src%.toy}.expected" + if [ -f "$exp" ]; then + tr -d '[:space:]' < "$exp" + else + printf '0' + fi +} + +check_rc() { + local label="$1" got="$2" expected="$3" stderr_file="$4" + expected=$((expected & 255)) + if [ "$got" -eq "$expected" ] && [ ! -s "$stderr_file" ]; then + note_pass "$label" + else + note_fail "$label" + printf ' expected rc %d, got %d\n' "$expected" "$got" + if [ -s "$stderr_file" ]; then + sed 's/^/ | /' "$stderr_file" + fi + fi +} + +run_case_run() { + local name="$1" src="$2" expected="$3" work="$4" + local out="$work/run.out" err="$work/run.err" rc + "$CFREE" run "$src" > "$out" 2> "$err" + rc=$? + check_rc "$name/R" "$rc" "$expected" "$err" +} + +run_case_link() { + local name="$1" src="$2" expected="$3" work="$4" + local obj="$work/$name.o" exe="$work/$name.exe" + local cc_err="$work/cc.err" ld_err="$work/ld.err" out="$work/exe.out" + local err="$work/exe.err" rc + + if ! "$CFREE" cc -c "$src" -o "$obj" > "$work/cc.out" 2> "$cc_err"; then + note_fail "$name/L" + printf ' cfree cc -c failed\n' + sed 's/^/ | /' "$cc_err" + return + fi + if [ -s "$cc_err" ]; then + note_fail "$name/L" + printf ' cfree cc -c wrote stderr\n' + sed 's/^/ | /' "$cc_err" + return + fi + + if ! "$CFREE" ld "$obj" -o "$exe" > "$work/ld.out" 2> "$ld_err"; then + note_fail "$name/L" + printf ' cfree ld failed\n' + sed 's/^/ | /' "$ld_err" + return + fi + if [ -s "$ld_err" ]; then + note_fail "$name/L" + printf ' cfree ld wrote stderr\n' + sed 's/^/ | /' "$ld_err" + return + fi + + chmod +x "$exe" 2>/dev/null || true + "$exe" > "$out" 2> "$err" + rc=$? + check_rc "$name/L" "$rc" "$expected" "$err" +} + +if [ ! -x "$CFREE" ]; then + printf 'missing cfree binary: %s\n' "$CFREE" >&2 + exit 2 +fi + +shopt -s nullglob +cases=("$TEST_DIR"/cases/*.toy) +if [ ${#cases[@]} -eq 0 ]; then + printf 'no toy cases found under %s/cases\n' "$TEST_DIR" >&2 + exit 2 +fi + +for src in "${cases[@]}"; do + name="$(basename "$src" .toy)" + if [ -n "$FILTER" ] && [[ "$name" != *"$FILTER"* ]]; then + continue + fi + expected="$(expected_for "$src")" + work="$BUILD_DIR/$name" + rm -rf "$work" + mkdir -p "$work" + if [ $RUN_R -eq 1 ]; then + run_case_run "$name" "$src" "$expected" "$work" + fi + if [ $RUN_L -eq 1 ]; then + run_case_link "$name" "$src" "$expected" "$work" + fi +done + +printf '\nResults: %d pass, %d fail\n' "$PASS" "$FAIL" +if [ $FAIL -ne 0 ]; then + printf 'Failures:\n' + for name in "${FAIL_NAMES[@]}"; do + printf ' %s\n' "$name" + done + exit 1 +fi