kit

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

commit fed0b77d70da076c5963cb78b8aa7fc8e45cd3f1
parent a3c3bfa0236ef2364ffa372f085db1c64eb901c0
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 13 May 2026 03:03:01 -0700

Complete public CG API wrappers

Diffstat:
Mdoc/cg-api-status.md | 61++++++++++++++++++++++++++++++++++++++-----------------------
Minclude/cfree/cg.h | 2--
Mlang/toy/toy.c | 395+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/api/cg.c | 541+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Atest/toy/cases/15_cg_api_types_bytes_globals.expected | 1+
Atest/toy/cases/15_cg_api_types_bytes_globals.toy | 6++++++
Atest/toy/cases/16_cg_api_alloca_memory_index.expected | 1+
Atest/toy/cases/16_cg_api_alloca_memory_index.toy | 10++++++++++
Atest/toy/cases/17_cg_api_atomics_intrinsics.expected | 1+
Atest/toy/cases/17_cg_api_atomics_intrinsics.toy | 9+++++++++
Atest/toy/cases/18_cg_api_field_tail.expected | 1+
Atest/toy/cases/18_cg_api_field_tail.toy | 11+++++++++++
12 files changed, 987 insertions(+), 52 deletions(-)

diff --git a/doc/cg-api-status.md b/doc/cg-api-status.md @@ -26,6 +26,9 @@ All previously-stubbed public CG API functions now have real implementations: | `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 | +| `cfree_cg_intrinsic` | Done | Maps public intrinsic ids to `CGTarget.intrinsic`; supports scalar result and overflow two-result forms | +| `cfree_cg_atomic_load/store/rmw/cmpxchg/fence` | Done | Derives pointee memory access, sets `MF_ATOMIC`, maps op/order enums, dispatches to target atomics | +| `cfree_cg_inline_asm` | Done | Converts public operands to `AsmConstraint`, binds inputs/outputs/clobbers, dispatches to `CGTarget.asm_block` | Struct additions to `CfreeCg`: @@ -45,7 +48,7 @@ Bug fix: ### New tokens -`TOK_VAR`, `TOK_PIPE` (`|`), `TOK_CARET` (`^`), `TOK_TILDE` (`~`), `TOK_SHL` (`<<`), `TOK_SHR` (`>>`) +`TOK_VAR`, `TOK_TAIL`, `TOK_PIPE` (`|`), `TOK_CARET` (`^`), `TOK_TILDE` (`~`), `TOK_SHL` (`<<`), `TOK_SHR` (`>>`) ### New expression precedence levels (C-standard order) @@ -64,6 +67,21 @@ Bug fix: - 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` +- Constant global initializers now emit bytes directly. +- Address global initializers (`&name`) emit `cfree_cg_data_symbol`. + +### CG API test builtins + +Toy now includes small builtins dedicated to public CG API coverage: + +- `typecheck()` covers type constructors and queries. +- `byteconst()` covers `push_bytes + load`. +- `alloca`, `index`, `memset`, `memcpy` cover dynamic allocation and memory helpers. +- `atomic_load`, `atomic_store`, `atomic_add`, `atomic_sub`, `atomic_cas_ok`, `fence` cover atomics. +- `popcount`, `ctz`, `clz`, `bswap`, `expect` cover public intrinsic dispatch. +- `fieldtest()` covers record construction, layout queries, and `field_addr`. +- `asmnop()` covers public inline asm dispatch on the AArch64 toy test target. +- `return tail f(...)` covers `cfree_cg_tail_call`. ### if/else fix @@ -103,15 +121,27 @@ Changes made: slots to the scalar spill pool. - `cfree_cg_push_bytes` pushes a typed rodata lvalue, matching the intended `push_bytes + load` materialization flow. +- `cfree_cg_field_addr` now pushes a pointer to the selected field type instead + of preserving the base record-pointer type. +- `cfree_cg_store` accepts pointer rvalues as store destinations by converting + them to indirect lvalues. +- `cfree_cg_tail_call` marks return storage as a void placeholder so targets do + not try to copy a return value after a tail-call-shaped dispatch. Validation: - `make lib` - `make bin` - `make test-cg-api` -- `make test-toy` — 28 pass, 0 fail +- `make test-cg-binder` +- `make test-toy` — 36 pass, 0 fail - `make test-cg` — 1573 pass, 0 fail, 0 skip - Toy smoke tests for store and short-circuit `&&` / `||` +- Toy CG API cases: + - `15_cg_api_types_bytes_globals` + - `16_cg_api_alloca_memory_index` + - `17_cg_api_atomics_intrinsics` + - `18_cg_api_field_tail` - AArch64 object dump for `return 1 != 2` materializes the compare result in a real register and returns it through `x0`. @@ -122,36 +152,21 @@ 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_data_symbol` relocations in data initializers. -6. Test `cfree_cg_alloca`, `cfree_cg_va_*` (variadics) -7. Test `cfree_cg_tail_call` -8. Test memory/address helpers: `cfree_cg_memcpy`, `cfree_cg_memset`, - `cfree_cg_index`, and `cfree_cg_field_addr`. -9. Implement and test remaining public stubs: - - `cfree_cg_intrinsic` - - `cfree_cg_atomic_load`, `cfree_cg_atomic_store`, - `cfree_cg_atomic_rmw`, `cfree_cg_atomic_cmpxchg`, - `cfree_cg_atomic_fence` - - `cfree_cg_inline_asm` -10. Add negative/error tests for public API misuse: +1. Add public CG API variadic coverage. The wrappers are implemented, but toy + still lacks a way to name the target ABI `va_list` type. +2. Add negative/error tests for public API misuse: - stack underflow - invalid type ids - invalid field indexes / wrong record base - unsupported data relocation widths -11. Run targeted cross-arch validation for the public CG API and toy frontend +3. Run targeted cross-arch validation for the public CG API and toy frontend on `aa64`, `x64`, and `rv64`, especially ABI-sensitive paths such as variadics, tail calls, atomics, and inline asm. -12. Tighten and document API semantics: +4. Tighten and document API semantics: - whether `cfree_cg_push_symbol` always pushes an lvalue or can model GOT/PLT/TLS reference forms directly - expression-scope value semantics, since current target scope support is label-oriented rather than phi-like - whether qualified type ids should be interned or remain fresh source identities -13. Write comprehensive `demo.toy` that exercises all features +5. Write comprehensive `demo.toy` that exercises all features. diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -278,8 +278,6 @@ typedef enum CfreeCgIntrinsic { CFREE_CG_INTRIN_CTZ, CFREE_CG_INTRIN_POPCOUNT, CFREE_CG_INTRIN_BSWAP, - CFREE_CG_INTRIN_FRAME_ADDRESS, - CFREE_CG_INTRIN_RETURN_ADDRESS, CFREE_CG_INTRIN_SETJMP, /* pop &buf; push i32 */ CFREE_CG_INTRIN_LONGJMP, /* pop &buf, val; no return */ CFREE_CG_INTRIN_ADD_OVERFLOW, /* pop a, b; push (result, ok_i1) */ diff --git a/lang/toy/toy.c b/lang/toy/toy.c @@ -84,6 +84,7 @@ typedef enum ToyTokenKind { TOK_BREAK, TOK_CONTINUE, TOK_RETURN, + TOK_TAIL, TOK_TYPE, TOK_INT, TOK_IDENT, @@ -331,6 +332,9 @@ static ToyToken toy_lexer_next(ToyLexer* lex) { start[2] == 't' && start[3] == 'u' && start[4] == 'r' && start[5] == 'n') kind = TOK_RETURN; + else if (len == 4 && start[0] == 't' && start[1] == 'a' && + start[2] == 'i' && start[3] == 'l') + kind = TOK_TAIL; else if (len == 4 && start[0] == 't' && start[1] == 'y' && start[2] == 'p' && start[3] == 'e') kind = TOK_TYPE; @@ -398,6 +402,8 @@ typedef struct ToyParser { CfreeCgBuiltinTypes types; CfreeCgTypeId int_type; CfreeCgTypeId int_ptr_type; + CfreeCgTypeId pair_type; + CfreeCgTypeId pair_ptr_type; ToyVar vars[TOY_MAX_VARS]; size_t nvars; @@ -425,6 +431,17 @@ 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); + { + CfreeCgField fields[2]; + memset(fields, 0, sizeof fields); + fields[0].name = cfree_sym_intern(c, "a"); + fields[0].type = p->int_type; + fields[1].name = cfree_sym_intern(c, "b"); + fields[1].type = p->int_type; + p->pair_type = cfree_cg_type_record(c, cfree_sym_intern(c, "Pair"), + fields, 2); + p->pair_ptr_type = cfree_cg_type_ptr(c, p->pair_type); + } p->nvars = 0; p->nfns = 0; p->nglobals = 0; @@ -532,6 +549,255 @@ static CfreeCgTypeId toy_parse_type(ToyParser* p) { static CfreeCgTypeId toy_parse_expr(ToyParser* p); +static int toy_sym_is(ToyParser* p, CfreeSym sym, const char* name) { + return sym == cfree_sym_intern(p->c, name); +} + +static void toy_emit_int_bytes(ToyParser* p, uint64_t v, uint8_t* buf, + size_t n) { + size_t i; + (void)p; + for (i = 0; i < n; ++i) buf[i] = (uint8_t)(v >> (i * 8u)); +} + +static int toy_expect_comma(ToyParser* p) { + if (!toy_parser_expect(p, TOK_COMMA)) { + toy_error(p, p->cur.loc, "expected ','"); + return 0; + } + return 1; +} + +static int toy_parse_number_arg(ToyParser* p, int64_t* out) { + if (p->cur.kind != TOK_NUMBER) { + toy_error(p, p->cur.loc, "expected numeric constant"); + return 0; + } + *out = p->cur.int_value; + toy_parser_advance(p); + return 1; +} + +static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, + int* recognized) { + *recognized = 1; + + if (toy_sym_is(p, name, "byteconst")) { + uint8_t buf[16]; + size_t n = (size_t)cfree_cg_type_size(p->c, p->int_type); + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected byteconst()"); + return CFREE_CG_TYPE_NONE; + } + if (n > sizeof buf) n = sizeof buf; + toy_emit_int_bytes(p, 42, buf, n); + cfree_cg_push_bytes(p->cg, buf, n, p->int_type); + cfree_cg_load(p->cg); + return p->int_type; + } + + if (toy_sym_is(p, name, "popcount") || toy_sym_is(p, name, "ctz") || + toy_sym_is(p, name, "clz") || toy_sym_is(p, name, "bswap")) { + CfreeCgIntrinsic intrin = CFREE_CG_INTRIN_POPCOUNT; + CfreeCgTypeId ty; + if (toy_sym_is(p, name, "ctz")) intrin = CFREE_CG_INTRIN_CTZ; + else if (toy_sym_is(p, name, "clz")) intrin = CFREE_CG_INTRIN_CLZ; + else if (toy_sym_is(p, name, "bswap")) intrin = CFREE_CG_INTRIN_BSWAP; + toy_parser_advance(p); /* ( */ + ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')'"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_intrinsic(p->cg, intrin, 1, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "expect")) { + CfreeCgTypeId a, b; + toy_parser_advance(p); + 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; + cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_EXPECT, 2, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "alloca")) { + CfreeCgTypeId ty; + toy_parser_advance(p); + ty = toy_parse_expr(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_alloca(p->cg, p->int_ptr_type, 8); + return p->int_ptr_type; + } + + if (toy_sym_is(p, name, "index")) { + CfreeCgTypeId base, idx; + toy_parser_advance(p); + base = toy_parse_expr(p); + if (base == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + idx = toy_parse_expr(p); + if (idx == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (base != p->int_ptr_type || idx != p->int_type) { + toy_error(p, p->cur.loc, "index expects (*int, int)"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_index(p->cg, 0); + return p->int_ptr_type; + } + + if (toy_sym_is(p, name, "memset")) { + CfreeCgTypeId dst; + int64_t val, size; + toy_parser_advance(p); + dst = toy_parse_expr(p); + if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &val) || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_memset(p->cg, (uint8_t)val, (uint32_t)size, 1); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "memcpy")) { + CfreeCgTypeId dst, src; + int64_t size; + toy_parser_advance(p); + dst = toy_parse_expr(p); + if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + src = toy_parse_expr(p); + if (src == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_memcpy(p->cg, (uint32_t)size, 1); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "atomic_load")) { + CfreeCgTypeId ty; + toy_parser_advance(p); + ty = toy_parse_expr(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_atomic_load(p->cg, CFREE_CG_MO_SEQ_CST); + return p->int_type; + } + + if (toy_sym_is(p, name, "atomic_store") || + toy_sym_is(p, name, "atomic_add") || + toy_sym_is(p, name, "atomic_sub")) { + CfreeCgTypeId ptr_ty, val_ty; + int is_store = toy_sym_is(p, name, "atomic_store"); + CfreeCgAtomicOp op = toy_sym_is(p, name, "atomic_sub") + ? CFREE_CG_ATOMIC_SUB + : CFREE_CG_ATOMIC_ADD; + toy_parser_advance(p); + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + val_ty = toy_parse_expr(p); + if (val_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (is_store) { + cfree_cg_atomic_store(p->cg, CFREE_CG_MO_SEQ_CST); + cfree_cg_push_int(p->cg, 0, p->int_type); + } else { + cfree_cg_atomic_rmw(p->cg, op, CFREE_CG_MO_SEQ_CST); + } + return p->int_type; + } + + if (toy_sym_is(p, name, "atomic_cas_ok")) { + CfreeCgTypeId t0, t1, t2; + toy_parser_advance(p); + t0 = toy_parse_expr(p); + if (t0 == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + t1 = toy_parse_expr(p); + if (t1 == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + t2 = toy_parse_expr(p); + if (t2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_atomic_cmpxchg(p->cg, CFREE_CG_MO_SEQ_CST, CFREE_CG_MO_RELAXED); + cfree_cg_swap(p->cg); + cfree_cg_drop(p->cg); + return p->int_type; + } + + if (toy_sym_is(p, name, "fence")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_atomic_fence(p->cg, CFREE_CG_MO_SEQ_CST); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + 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) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int(p->cg, sz, p->int_type); + cfree_cg_alloca(p->cg, p->pair_ptr_type, cfree_cg_type_align(p->c, p->pair_type)); + cfree_cg_dup(p->cg); + cfree_cg_field_addr(p->cg, 1); + cfree_cg_push_int(p->cg, 42, p->int_type); + cfree_cg_store(p->cg); + cfree_cg_drop(p->cg); + cfree_cg_field_addr(p->cg, 1); + cfree_cg_load(p->cg); + return p->int_type; + } + + if (toy_sym_is(p, name, "asmnop")) { + CfreeSym tmpl = cfree_sym_intern(p->c, "nop"); + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + 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, "typecheck")) { + CfreeCgTypeId arr, qual, alias, enm, fnty; + CfreeCgEnumValue ev; + int ok = 1; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + arr = cfree_cg_type_array(p->c, p->int_type, 4); + qual = cfree_cg_type_qualified(p->c, p->int_type, CFREE_CG_TQ_CONST); + alias = cfree_cg_type_alias(p->c, cfree_sym_intern(p->c, "Word"), p->int_type); + ev.name = cfree_sym_intern(p->c, "E"); + ev.value = 7; + enm = cfree_cg_type_enum(p->c, cfree_sym_intern(p->c, "Enum"), + p->int_type, &ev, 1); + fnty = cfree_cg_type_func(p->c, p->int_type, &p->int_type, 1, 0); + ok = ok && arr && qual && alias && enm && fnty; + ok = ok && cfree_cg_type_is_ptr(p->c, p->int_ptr_type); + ok = ok && cfree_cg_type_ptr_pointee(p->c, p->int_ptr_type) == p->int_type; + ok = ok && cfree_cg_type_is_record(p->c, p->pair_type); + ok = ok && cfree_cg_type_record_nfields(p->c, p->pair_type) == 2; + ok = ok && cfree_cg_type_is_func(p->c, fnty); + ok = ok && cfree_cg_type_func_ret(p->c, fnty) == p->int_type; + ok = ok && cfree_cg_type_func_nparams(p->c, fnty) == 1; + ok = ok && cfree_cg_type_func_param(p->c, fnty, 0) == p->int_type; + ok = ok && cfree_cg_type_size(p->c, arr) >= 4; + ok = ok && cfree_cg_type_align(p->c, qual) >= 1; + cfree_cg_push_int(p->cg, ok ? 1 : 0, p->int_type); + return p->int_type; + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} + static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { toy_set_loc(p); if (p->cur.kind == TOK_NUMBER) { @@ -547,6 +813,10 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { if (p->cur.kind == TOK_LPAREN) { /* Function call */ + int recognized = 0; + CfreeCgTypeId builtin_ty = toy_parse_builtin_call(p, name, &recognized); + if (recognized) return builtin_ty; + ToyFn* fn = toy_find_fn(p, name); if (!fn) { toy_error(p, ident_tok.loc, "undefined function '%s'", @@ -667,14 +937,21 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { } 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"); - return CFREE_CG_TYPE_NONE; - } toy_parser_advance(p); - cfree_cg_push_local(p->cg, v->slot); - cfree_cg_addr(p->cg); - return p->int_ptr_type; + if (v) { + cfree_cg_push_local(p->cg, v->slot); + cfree_cg_addr(p->cg); + return cfree_cg_type_ptr(p->c, v->type); + } else { + ToyGlobal* g = toy_find_global(p, name); + if (!g) { + toy_error(p, p->cur.loc, "undefined variable"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_push_symbol(p->cg, name, g->type, CFREE_CG_SYMREF_ADDR, 0); + cfree_cg_addr(p->cg); + return cfree_cg_type_ptr(p->c, g->type); + } } if (toy_parser_match(p, TOK_STAR)) { @@ -1096,6 +1373,44 @@ static int toy_parse_continue_stmt(ToyParser* p) { static int toy_parse_return_stmt(ToyParser* p) { CfreeCgTypeId ty; toy_parser_advance(p); /* return */ + if (toy_parser_match(p, TOK_TAIL)) { + CfreeSym name; + ToyFn* fn; + size_t nargs = 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected function name after tail"); + return 0; + } + name = toy_tok_sym(p, p->cur); + fn = toy_find_fn(p, name); + if (!fn) { + toy_error(p, p->cur.loc, "undefined function in tail call"); + 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); + if (p->cur.kind != TOK_RPAREN) { + for (;;) { + CfreeCgTypeId arg_ty = toy_parse_expr(p); + if (arg_ty == CFREE_CG_TYPE_NONE) return 0; + if (nargs >= fn->nparams || arg_ty != fn->params[nargs]) { + toy_error(p, p->cur.loc, "tail call argument mismatch"); + return 0; + } + nargs++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + } + if (!toy_parser_expect(p, TOK_RPAREN)) return 0; + if (nargs != fn->nparams || fn->ret != p->cur_fn_ret) { + toy_error(p, p->cur.loc, "tail call signature mismatch"); + return 0; + } + cfree_cg_tail_call(p->cg, (uint32_t)nargs, fn->type); + if (!toy_parser_expect(p, TOK_SEMI)) return 0; + return 1; + } if (p->cur.kind == TOK_SEMI) { toy_parser_advance(p); if (p->cur_fn_ret != p->types.void_) { @@ -1186,6 +1501,35 @@ static int toy_parse_stmt(ToyParser* p) { return 1; } + if (p->cur.kind == TOK_STAR) { + CfreeCgTypeId ptr_ty; + CfreeCgTypeId expr_ty; + toy_parser_advance(p); /* * */ + ptr_ty = toy_parse_expr_unary(p); + if (ptr_ty == CFREE_CG_TYPE_NONE) return 0; + if (!cfree_cg_type_is_ptr(p->c, ptr_ty)) { + toy_error(p, p->cur.loc, "cannot assign through non-pointer"); + return 0; + } + if (!toy_parser_expect(p, TOK_EQ)) { + toy_error(p, p->cur.loc, "expected '=' in pointer assignment"); + return 0; + } + expr_ty = toy_parse_expr(p); + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + if (expr_ty != cfree_cg_type_ptr_pointee(p->c, ptr_ty)) { + toy_error(p, p->cur.loc, "type mismatch in pointer assignment"); + return 0; + } + 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; + } + return 1; + } + return toy_parse_expr_stmt(p); } @@ -1282,6 +1626,7 @@ static int toy_parse_fn(ToyParser* p) { attrs.visibility = CFREE_CG_VIS_DEFAULT; attrs.tls_model = CFREE_CG_TLS_DEFAULT; attrs.flags = CFREE_CG_DECL_DEFINED; + cfree_cg_func_decl(p->cg, name, fn_ty, attrs); cfree_cg_func_begin(p->cg, name, fn_ty, attrs); /* Setup parameter slots */ @@ -1362,17 +1707,35 @@ static int toy_parse_global_var(ToyParser* p) { 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"); + if (p->cur.kind == TOK_NUMBER) { + uint8_t buf[16]; + size_t n = (size_t)cfree_cg_type_size(p->c, ty); + if (n > sizeof buf) { + toy_error(p, p->cur.loc, "initializer too large"); + return 0; + } + toy_emit_int_bytes(p, (uint64_t)p->cur.int_value, buf, n); + toy_parser_advance(p); + cfree_cg_data_begin(p->cg, name, ty, attrs); + cfree_cg_data_bytes(p->cg, buf, n); + cfree_cg_data_end(p->cg); + } else if (p->cur.kind == TOK_AMPERSAND) { + CfreeSym target; + uint32_t nbytes = (uint32_t)cfree_cg_type_size(p->c, ty); + toy_parser_advance(p); + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected identifier after '&'"); + return 0; + } + target = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + cfree_cg_data_begin(p->cg, name, ty, attrs); + cfree_cg_data_symbol(p->cg, CFREE_CG_SYMREF_ADDR, target, 0, nbytes); + cfree_cg_data_end(p->cg); + } else { + toy_error(p, p->cur.loc, "expected constant 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); diff --git a/src/api/cg.c b/src/api/cg.c @@ -1011,6 +1011,40 @@ static CmpOp api_map_cmp(CfreeCgCmpOp op) { return CMP_EQ; } +static AtomicOp api_map_atomic_op(CfreeCgAtomicOp op) { + switch (op) { + case CFREE_CG_ATOMIC_XCHG: return AO_XCHG; + case CFREE_CG_ATOMIC_ADD: return AO_ADD; + case CFREE_CG_ATOMIC_SUB: return AO_SUB; + case CFREE_CG_ATOMIC_AND: return AO_AND; + case CFREE_CG_ATOMIC_OR: return AO_OR; + case CFREE_CG_ATOMIC_XOR: return AO_XOR; + case CFREE_CG_ATOMIC_NAND: return AO_NAND; + } + return AO_XCHG; +} + +static MemOrder api_map_mem_order(CfreeCgMemOrder order) { + switch (order) { + case CFREE_CG_MO_RELAXED: return MO_RELAXED; + case CFREE_CG_MO_CONSUME: return MO_CONSUME; + case CFREE_CG_MO_ACQUIRE: return MO_ACQUIRE; + case CFREE_CG_MO_RELEASE: return MO_RELEASE; + case CFREE_CG_MO_ACQ_REL: return MO_ACQ_REL; + case CFREE_CG_MO_SEQ_CST: return MO_SEQ_CST; + } + return MO_RELAXED; +} + +static AsmDir api_map_asm_dir(uint8_t dir) { + switch ((CfreeCgAsmDir)dir) { + case CFREE_CG_ASM_IN: return ASM_IN; + case CFREE_CG_ASM_OUT: return ASM_OUT; + case CFREE_CG_ASM_INOUT: return ASM_INOUT; + } + return ASM_IN; +} + /* ---- C-symbol mangling ---- */ static Sym api_c_mangle(Compiler* c, CfreeSym name) { @@ -1393,6 +1427,12 @@ void cfree_cg_store(CfreeCg* g) { lv = api_pop(g); api_ensure_reg(g, &lv); api_ensure_reg(g, &rv); + if (!api_is_lvalue(&lv.op) && type_is_ptr(api_sv_type(&lv))) { + const Type* pty = api_sv_type(&lv); + const Type* pointee = pty->ptr.pointee; + Operand ptr = api_force_reg(g, &lv, pty); + lv = api_make_sv(api_op_indirect(ptr.v.reg, 0, pointee), pointee); + } if (!api_is_lvalue(&lv.op)) { compiler_panic(g->c, g->cur_loc, "CfreeCg: store destination is not an lvalue"); return; @@ -1616,47 +1656,518 @@ void cfree_cg_convert(CfreeCg* g, CfreeCgTypeId dst_type) { * Intrinsics (stub) * ============================================================ */ +static IntrinKind api_map_intrinsic(CfreeCg* g, CfreeCgIntrinsic intrin, + const Type* result_type) { + u32 size = result_type ? abi_sizeof(g->c->abi, result_type) : 0; + switch (intrin) { + case CFREE_CG_INTRIN_TRAP: return INTRIN_TRAP; + case CFREE_CG_INTRIN_UNREACHABLE: return INTRIN_UNREACHABLE; + case CFREE_CG_INTRIN_CLZ: return INTRIN_CLZ; + case CFREE_CG_INTRIN_CTZ: return INTRIN_CTZ; + case CFREE_CG_INTRIN_POPCOUNT: return INTRIN_POPCOUNT; + case CFREE_CG_INTRIN_BSWAP: + if (size <= 2) return INTRIN_BSWAP16; + if (size <= 4) return INTRIN_BSWAP32; + return INTRIN_BSWAP64; + case CFREE_CG_INTRIN_SETJMP: return INTRIN_SETJMP; + case CFREE_CG_INTRIN_LONGJMP: return INTRIN_LONGJMP; + case CFREE_CG_INTRIN_ADD_OVERFLOW: return INTRIN_ADD_OVERFLOW; + case CFREE_CG_INTRIN_SUB_OVERFLOW: return INTRIN_SUB_OVERFLOW; + case CFREE_CG_INTRIN_MUL_OVERFLOW: return INTRIN_MUL_OVERFLOW; + case CFREE_CG_INTRIN_PREFETCH: return INTRIN_PREFETCH; + case CFREE_CG_INTRIN_EXPECT: return INTRIN_EXPECT; + case CFREE_CG_INTRIN_ASSUME_ALIGNED: return INTRIN_ASSUME_ALIGNED; + } + return INTRIN_NONE; +} + +static int api_intrinsic_is_void(CfreeCgIntrinsic intrin) { + return intrin == CFREE_CG_INTRIN_TRAP || + intrin == CFREE_CG_INTRIN_UNREACHABLE || + intrin == CFREE_CG_INTRIN_LONGJMP || + intrin == CFREE_CG_INTRIN_PREFETCH; +} + +static int api_intrinsic_is_overflow(CfreeCgIntrinsic intrin) { + return intrin == CFREE_CG_INTRIN_ADD_OVERFLOW || + intrin == CFREE_CG_INTRIN_SUB_OVERFLOW || + intrin == CFREE_CG_INTRIN_MUL_OVERFLOW; +} + void cfree_cg_intrinsic(CfreeCg* g, CfreeCgIntrinsic intrin, uint32_t nargs, CfreeCgTypeId result_type) { - (void)g; (void)intrin; (void)nargs; (void)result_type; + CGTarget* T; + const Type* rty; + const Type* int_ty; + IntrinKind kind; + ApiSValue* svs; + Operand* args; + Operand dsts[2]; + u32 ndst = 0; + Heap* h; + if (!g) return; + T = g->target; + h = g->c->env->heap; + rty = resolve_type(g->c, result_type); + int_ty = type_prim(g->c->global, TY_INT); + kind = api_map_intrinsic(g, intrin, rty); + if (kind == INTRIN_NONE) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: unsupported intrinsic"); + return; + } + + svs = NULL; + args = NULL; + if (nargs) { + svs = (ApiSValue*)h->alloc(h, sizeof(*svs) * nargs, _Alignof(ApiSValue)); + args = (Operand*)h->alloc(h, sizeof(*args) * nargs, _Alignof(Operand)); + memset(args, 0, sizeof(*args) * nargs); + for (u32 i = 0; i < nargs; ++i) { + u32 idx = nargs - 1u - i; + const Type* aty; + svs[idx] = api_pop(g); + aty = api_sv_type(&svs[idx]); + if (svs[idx].op.kind == OPK_IMM && + (intrin == CFREE_CG_INTRIN_EXPECT || + intrin == CFREE_CG_INTRIN_ASSUME_ALIGNED || + intrin == CFREE_CG_INTRIN_PREFETCH)) { + args[idx] = svs[idx].op; + } else { + args[idx] = api_force_reg(g, &svs[idx], aty); + } + } + } + + if (api_intrinsic_is_overflow(intrin)) { + const Type* vty = rty ? rty : (nargs ? api_sv_type(&svs[0]) : int_ty); + Reg rr = api_alloc_reg_or_spill(g, api_type_class(vty), vty); + Reg ok = api_alloc_reg_or_spill(g, RC_INT, int_ty); + dsts[0] = api_op_reg(rr, vty); + dsts[1] = api_op_reg(ok, int_ty); + ndst = 2; + } else if (!api_intrinsic_is_void(intrin) && rty && + rty->kind != TY_VOID) { + Reg rr = api_alloc_reg_or_spill(g, api_type_class(rty), rty); + dsts[0] = api_op_reg(rr, rty); + ndst = 1; + } + + T->intrinsic(T, kind, ndst ? dsts : NULL, ndst, args, nargs); + + for (u32 i = 0; i < nargs; ++i) api_release(g, &svs[i]); + if (svs) h->free(h, svs, sizeof(*svs) * nargs); + if (args) h->free(h, args, sizeof(*args) * nargs); + + if (api_intrinsic_is_overflow(intrin)) { + api_push(g, api_make_sv(dsts[0], dsts[0].type)); + api_push(g, api_make_sv(dsts[1], int_ty)); + } else if (ndst == 1) { + api_push(g, api_make_sv(dsts[0], rty)); + } } /* ============================================================ * Atomics (stub) * ============================================================ */ +static const Type* api_atomic_pointee(CfreeCg* g, const Type* pty, + const char* who) { + if (!pty || pty->kind != TY_PTR) { + compiler_panic(g->c, g->cur_loc, "%s: operand is not a pointer", who); + return type_prim(g->c->global, TY_INT); + } + return pty->ptr.pointee; +} + +static MemAccess api_mem_for_atomic(CfreeCg* g, const Type* val_ty) { + MemAccess ma; + memset(&ma, 0, sizeof ma); + ma.type = val_ty; + ma.size = val_ty ? abi_sizeof(g->c->abi, val_ty) : 0; + ma.align = val_ty ? abi_alignof(g->c->abi, val_ty) : 0; + ma.flags = MF_ATOMIC; + ma.alias.kind = (u8)ALIAS_UNKNOWN; + return ma; +} + void cfree_cg_atomic_load(CfreeCg* g, CfreeCgMemOrder order) { - (void)g; (void)order; + ApiSValue ptr; + const Type *pty, *val_ty; + Operand addr, dst; + Reg rr; + if (!g) return; + ptr = api_pop(g); + pty = api_sv_type(&ptr); + val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_load"); + addr = api_force_reg(g, &ptr, pty); + rr = api_alloc_reg_or_spill(g, api_type_class(val_ty), val_ty); + dst = api_op_reg(rr, val_ty); + g->target->atomic_load(g->target, dst, addr, api_mem_for_atomic(g, val_ty), + api_map_mem_order(order)); + api_release(g, &ptr); + api_push(g, api_make_sv(dst, val_ty)); } void cfree_cg_atomic_store(CfreeCg* g, CfreeCgMemOrder order) { - (void)g; (void)order; + ApiSValue val, ptr; + const Type *pty, *val_ty; + Operand addr, src; + if (!g) return; + val = api_pop(g); + ptr = api_pop(g); + pty = api_sv_type(&ptr); + val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_store"); + addr = api_force_reg(g, &ptr, pty); + src = (val.op.kind == OPK_IMM || val.op.kind == OPK_REG) + ? val.op + : api_force_reg(g, &val, val_ty); + g->target->atomic_store(g->target, addr, src, api_mem_for_atomic(g, val_ty), + api_map_mem_order(order)); + api_release(g, &val); + api_release(g, &ptr); } void cfree_cg_atomic_rmw(CfreeCg* g, CfreeCgAtomicOp op, CfreeCgMemOrder order) { - (void)g; (void)op; (void)order; + ApiSValue val, ptr; + const Type *pty, *val_ty; + Operand addr, vop, dst; + Reg rr; + if (!g) return; + val = api_pop(g); + ptr = api_pop(g); + pty = api_sv_type(&ptr); + val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_rmw"); + addr = api_force_reg(g, &ptr, pty); + vop = (val.op.kind == OPK_IMM || val.op.kind == OPK_REG) + ? val.op + : api_force_reg(g, &val, val_ty); + rr = api_alloc_reg_or_spill(g, api_type_class(val_ty), val_ty); + dst = api_op_reg(rr, val_ty); + g->target->atomic_rmw(g->target, api_map_atomic_op(op), dst, addr, vop, + api_mem_for_atomic(g, val_ty), + api_map_mem_order(order)); + api_release(g, &val); + api_release(g, &ptr); + api_push(g, api_make_sv(dst, val_ty)); } void cfree_cg_atomic_cmpxchg(CfreeCg* g, CfreeCgMemOrder success, CfreeCgMemOrder failure) { - (void)g; (void)success; (void)failure; + ApiSValue desired, expected, ptr; + const Type *pty, *val_ty, *int_ty; + Operand addr, exp_op, des_op, prior, ok; + Reg pr, kr; + if (!g) return; + desired = api_pop(g); + expected = api_pop(g); + ptr = api_pop(g); + pty = api_sv_type(&ptr); + val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_cmpxchg"); + int_ty = type_prim(g->c->global, TY_INT); + addr = api_force_reg(g, &ptr, pty); + exp_op = (expected.op.kind == OPK_IMM || expected.op.kind == OPK_REG) + ? expected.op + : api_force_reg(g, &expected, val_ty); + des_op = (desired.op.kind == OPK_IMM || desired.op.kind == OPK_REG) + ? desired.op + : api_force_reg(g, &desired, val_ty); + pr = api_alloc_reg_or_spill(g, api_type_class(val_ty), val_ty); + kr = api_alloc_reg_or_spill(g, RC_INT, int_ty); + prior = api_op_reg(pr, val_ty); + ok = api_op_reg(kr, int_ty); + g->target->atomic_cas(g->target, prior, ok, addr, exp_op, des_op, + api_mem_for_atomic(g, val_ty), + api_map_mem_order(success), + api_map_mem_order(failure)); + api_release(g, &desired); + api_release(g, &expected); + api_release(g, &ptr); + api_push(g, api_make_sv(prior, val_ty)); + api_push(g, api_make_sv(ok, int_ty)); } void cfree_cg_atomic_fence(CfreeCg* g, CfreeCgMemOrder order) { - (void)g; (void)order; + if (!g) return; + g->target->fence(g->target, api_map_mem_order(order)); } /* ============================================================ * Inline asm (stub) * ============================================================ */ +static const char* api_sym_cstr(CfreeCg* g, CfreeSym sym) { + size_t len; + const char* s; + if (!sym) return ""; + s = pool_str(g->c->global, (Sym)sym, &len); + (void)len; + return s ? s : ""; +} + +static int api_asm_parse_match_index(const char* s) { + int n; + if (!s || s[0] < '0' || s[0] > '9') return -1; + n = 0; + for (const char* p = s; *p >= '0' && *p <= '9'; ++p) { + n = n * 10 + (*p - '0'); + } + return n; +} + +static const char* api_asm_constraint_body(const char* s) { + if (!s) return ""; + if (s[0] == '=' && s[1] == '&') return s + 2; + if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; + return s; +} + +static int api_asm_is_early_clobber(const char* s) { + if (!s) return 0; + return (s[0] == '=' && s[1] == '&') || s[0] == '&'; +} + +static void api_asm_spill_sv(CfreeCg* g, ApiSValue* sv, Reg phys, + RegClass cls) { + FrameSlot slot = api_take_spill_slot(g, cls); + Operand victim_reg = api_op_reg(phys, api_owned_reg_type(g, sv)); + g->target->spill_reg(g->target, victim_reg, slot, api_mem_for_spill(g, sv)); + g->target->free_reg(g->target, phys, cls); + sv->spill_slot = slot; + sv->res = RES_SPILLED; + api_set_owned_reg(sv, (Reg)REG_NONE); +} + void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, const CfreeCgAsmOperand* outputs, uint32_t noutputs, const CfreeCgAsmOperand* inputs, uint32_t ninputs, const CfreeSym* clobbers, uint32_t nclobbers, uint32_t flags) { - (void)g; (void)tmpl; (void)outputs; (void)noutputs; - (void)inputs; (void)ninputs; (void)clobbers; (void)nclobbers; (void)flags; + CGTarget* T; + Heap* h; + const Type* fallback_ty; + AsmConstraint* outs; + AsmConstraint* ins; + Sym* clobs; + ApiSValue* in_svs; + Operand* in_ops; + Operand* out_ops; + u8* out_reg_owned; + const char* tmpl_str; + Sym sym_memory; + int has_memory_clobber; + (void)flags; + if (!g) return; + T = g->target; + h = g->c->env->heap; + fallback_ty = type_prim(g->c->global, TY_LLONG); + tmpl_str = api_sym_cstr(g, tmpl); + + outs = NULL; + ins = NULL; + clobs = NULL; + in_svs = NULL; + in_ops = NULL; + out_ops = NULL; + out_reg_owned = NULL; + + if (noutputs) { + outs = (AsmConstraint*)h->alloc(h, sizeof(*outs) * noutputs, + _Alignof(AsmConstraint)); + memset(outs, 0, sizeof(*outs) * noutputs); + for (u32 i = 0; i < noutputs; ++i) { + outs[i].str = api_sym_cstr(g, outputs[i].constraint); + outs[i].name = (Sym)outputs[i].name; + outs[i].type = resolve_type(g->c, outputs[i].type); + outs[i].dir = (u8)api_map_asm_dir(outputs[i].dir); + if (!outs[i].type) outs[i].type = fallback_ty; + } + out_ops = (Operand*)h->alloc(h, sizeof(*out_ops) * noutputs, + _Alignof(Operand)); + memset(out_ops, 0, sizeof(*out_ops) * noutputs); + out_reg_owned = (u8*)h->alloc(h, noutputs, 1); + memset(out_reg_owned, 0, noutputs); + } + + if (ninputs) { + ins = (AsmConstraint*)h->alloc(h, sizeof(*ins) * ninputs, + _Alignof(AsmConstraint)); + memset(ins, 0, sizeof(*ins) * ninputs); + in_svs = (ApiSValue*)h->alloc(h, sizeof(*in_svs) * ninputs, + _Alignof(ApiSValue)); + in_ops = (Operand*)h->alloc(h, sizeof(*in_ops) * ninputs, + _Alignof(Operand)); + memset(in_ops, 0, sizeof(*in_ops) * ninputs); + for (u32 i = 0; i < ninputs; ++i) { + u32 idx = ninputs - 1u - i; + ins[idx].str = api_sym_cstr(g, inputs[idx].constraint); + ins[idx].name = (Sym)inputs[idx].name; + ins[idx].type = resolve_type(g->c, inputs[idx].type); + ins[idx].dir = (u8)api_map_asm_dir(inputs[idx].dir); + if (!ins[idx].type) ins[idx].type = fallback_ty; + in_svs[idx] = api_pop(g); + api_ensure_reg(g, &in_svs[idx]); + } + } + + if (nclobbers) { + clobs = (Sym*)h->alloc(h, sizeof(*clobs) * nclobbers, _Alignof(Sym)); + for (u32 i = 0; i < nclobbers; ++i) clobs[i] = (Sym)clobbers[i]; + } + + for (u32 i = 0; i < noutputs; ++i) { + const char* body = api_asm_constraint_body(outs[i].str); + if (api_asm_is_early_clobber(outs[i].str)) continue; + if (body[0] == 'r') { + const Type* oty = outs[i].type ? outs[i].type : fallback_ty; + Reg r = api_alloc_reg_or_spill(g, api_type_class(oty), oty); + out_ops[i] = api_op_reg(r, oty); + out_reg_owned[i] = 1; + } else { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: unsupported asm output constraint"); + } + } + + for (u32 i = 0; i < ninputs; ++i) { + const char* s = ins[i].str ? ins[i].str : ""; + int matched = api_asm_parse_match_index(s); + const Type* ity = api_sv_type(&in_svs[i]); + if (matched >= 0) { + Operand bound; + if ((u32)matched >= noutputs) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm matching constraint out of range"); + continue; + } + if (api_asm_is_early_clobber(outs[matched].str)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm matching input uses early-clobber output"); + continue; + } + bound = out_ops[matched]; + if (in_svs[i].op.kind == OPK_REG && in_svs[i].op.v.reg == bound.v.reg) { + } else if (in_svs[i].op.kind == OPK_IMM) { + T->load_imm(T, bound, in_svs[i].op.v.imm); + } else { + Operand src = api_force_reg(g, &in_svs[i], ity); + T->copy(T, bound, src); + } + in_ops[i] = bound; + } else if (s[0] == 'r') { + in_ops[i] = api_force_reg(g, &in_svs[i], ity); + } else if (s[0] == 'i') { + if (in_svs[i].op.kind != OPK_IMM) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm 'i' constraint requires an immediate"); + } + in_ops[i] = in_svs[i].op; + } else if (s[0] == 'm') { + if (in_svs[i].op.kind == OPK_INDIRECT) { + in_ops[i] = in_svs[i].op; + } else if (api_is_lvalue(&in_svs[i].op)) { + const Type* pty = type_ptr(g->c->global, ity ? ity : type_void(g->c->global)); + Reg r = api_alloc_reg_or_spill(g, RC_INT, pty); + Operand dst = api_op_reg(r, pty); + T->addr_of(T, dst, in_svs[i].op); + in_svs[i].op = api_op_indirect(r, 0, ity); + in_svs[i].res = RES_REG; + in_ops[i] = in_svs[i].op; + } else { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm 'm' constraint requires an lvalue"); + } + } else { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: unsupported asm input constraint"); + } + } + + for (u32 i = 0; i < noutputs; ++i) { + const char* body; + const Type* oty; + Reg r; + if (!api_asm_is_early_clobber(outs[i].str)) continue; + body = api_asm_constraint_body(outs[i].str); + if (body[0] != 'r') { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: unsupported early-clobber asm output"); + continue; + } + oty = outs[i].type ? outs[i].type : fallback_ty; + r = api_alloc_reg_or_spill(g, api_type_class(oty), oty); + for (u32 k = 0; k < ninputs; ++k) { + if ((in_ops[k].kind == OPK_REG && in_ops[k].v.reg == r) || + (in_ops[k].kind == OPK_INDIRECT && in_ops[k].v.ind.base == r)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm early-clobber register collision"); + } + } + out_ops[i] = api_op_reg(r, oty); + out_reg_owned[i] = 1; + } + + sym_memory = pool_intern_cstr(g->c->global, "memory"); + has_memory_clobber = 0; + for (u32 i = 0; i < nclobbers; ++i) { + if (clobs[i] == sym_memory) has_memory_clobber = 1; + } + if (has_memory_clobber) { + for (u32 i = 0; i < g->sp; ++i) { + ApiSValue* sv = &g->stack[i]; + Reg phys; + RegClass cls; + if (sv->res != RES_REG) continue; + phys = api_reg_of_sv(sv); + cls = (RegClass)api_class_of_sv(sv); + api_asm_spill_sv(g, sv, phys, cls); + } + } else if (T->resolve_reg_name) { + for (u32 i = 0; i < nclobbers; ++i) { + Reg phys; + RegClass cls; + if (T->resolve_reg_name(T, clobs[i], &phys, &cls) != 0) continue; + for (u32 k = 0; k < noutputs; ++k) { + if (out_ops[k].kind == OPK_REG && out_ops[k].cls == cls && + (Reg)out_ops[k].v.reg == phys) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm clobber overlaps output"); + } + } + for (u32 k = 0; k < ninputs; ++k) { + if (in_ops[k].kind == OPK_REG && in_ops[k].cls == cls && + (Reg)in_ops[k].v.reg == phys) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm clobber overlaps input"); + } + } + for (u32 k = 0; k < g->sp; ++k) { + ApiSValue* sv = &g->stack[k]; + if (sv->res != RES_REG) continue; + if (api_class_of_sv(sv) != (u8)cls) continue; + if ((Reg)api_reg_of_sv(sv) != phys) continue; + api_asm_spill_sv(g, sv, phys, cls); + } + } + } + + T->asm_block(T, tmpl_str, outs, noutputs, out_ops, ins, ninputs, in_ops, + clobs, nclobbers); + + for (u32 i = 0; i < ninputs; ++i) api_release(g, &in_svs[i]); + for (u32 i = 0; i < noutputs; ++i) { + const Type* oty = outs[i].type ? outs[i].type : fallback_ty; + ApiSValue sv = api_make_sv(out_ops[i], oty); + if (!out_reg_owned[i] && sv.res == RES_REG) sv.res = RES_INHERENT; + api_push(g, sv); + } + + if (outs) h->free(h, outs, sizeof(*outs) * noutputs); + if (ins) h->free(h, ins, sizeof(*ins) * ninputs); + if (clobs) h->free(h, clobs, sizeof(*clobs) * nclobbers); + if (in_svs) h->free(h, in_svs, sizeof(*in_svs) * ninputs); + if (in_ops) h->free(h, in_ops, sizeof(*in_ops) * ninputs); + if (out_ops) h->free(h, out_ops, sizeof(*out_ops) * noutputs); + if (out_reg_owned) h->free(h, out_reg_owned, noutputs); } /* ============================================================ @@ -2071,6 +2582,7 @@ void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) { ApiSValue base; CGTarget* T; const Type *base_ptr_ty, *rec_ty; + const Type* field_ptr_ty; const ABIRecordLayout* layout; u32 field_offset; Operand base_op, result; @@ -2090,10 +2602,16 @@ void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) { compiler_panic(g->c, g->cur_loc, "CfreeCg: invalid field index"); return; } + if (!rec_ty || (rec_ty->kind != TY_STRUCT && rec_ty->kind != TY_UNION) || + field_index >= rec_ty->rec.nfields) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: invalid record base"); + return; + } + field_ptr_ty = type_ptr(g->c->global, rec_ty->rec.fields[field_index].type); 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); + rr = api_alloc_reg_or_spill(g, RC_INT, field_ptr_ty); + result = api_op_reg(rr, field_ptr_ty); if (field_offset == 0) { T->copy(T, result, base_op); } else { @@ -2101,7 +2619,7 @@ void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) { api_op_imm((i64)field_offset, base_ptr_ty)); } api_release(g, &base); - api_push(g, api_make_sv(result, base_ptr_ty)); + api_push(g, api_make_sv(result, field_ptr_ty)); } /* ============================================================ @@ -2257,6 +2775,7 @@ void cfree_cg_tail_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) { desc.flags = CG_CALL_TAIL; desc.ret.type = fty->fn.ret; desc.ret.abi = &abi->ret; + desc.ret.storage = api_op_imm(0, type_void(g->c->global)); T->call(T, &desc); for (u32 i = 0; i < nargs; ++i) { api_release_arg_storage(g, &avs[i].storage); diff --git a/test/toy/cases/15_cg_api_types_bytes_globals.expected b/test/toy/cases/15_cg_api_types_bytes_globals.expected @@ -0,0 +1 @@ +92 diff --git a/test/toy/cases/15_cg_api_types_bytes_globals.toy b/test/toy/cases/15_cg_api_types_bytes_globals.toy @@ -0,0 +1,6 @@ +let g: int = 5; +var gp: *int = &g; + +fn main(): int { + return typecheck() * 40 + byteconst() + g + *gp; +} diff --git a/test/toy/cases/16_cg_api_alloca_memory_index.expected b/test/toy/cases/16_cg_api_alloca_memory_index.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/16_cg_api_alloca_memory_index.toy b/test/toy/cases/16_cg_api_alloca_memory_index.toy @@ -0,0 +1,10 @@ +fn main(): int { + let p: *int = alloca(16); + *p = 10; + *index(p, 1) = 32; + + let q: *int = alloca(16); + memset(q, 0, 16); + memcpy(q, p, 16); + return *q + *index(q, 1); +} diff --git a/test/toy/cases/17_cg_api_atomics_intrinsics.expected b/test/toy/cases/17_cg_api_atomics_intrinsics.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/17_cg_api_atomics_intrinsics.toy b/test/toy/cases/17_cg_api_atomics_intrinsics.toy @@ -0,0 +1,9 @@ +fn main(): int { + let x: int = 0; + atomic_store(&x, 40); + atomic_add(&x, 2); + let ok: int = atomic_cas_ok(&x, 42, 43); + fence(); + return atomic_load(&x) - ok + popcount(255) + ctz(128) + + bswap(1297036692682702848) + clz(255) - 89; +} diff --git a/test/toy/cases/18_cg_api_field_tail.expected b/test/toy/cases/18_cg_api_field_tail.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/18_cg_api_field_tail.toy b/test/toy/cases/18_cg_api_field_tail.toy @@ -0,0 +1,11 @@ +fn id2(x: int): int { + return x; +} + +fn id(x: int): int { + return tail id2(x); +} + +fn main(): int { + return fieldtest() + id(0) + asmnop(); +}