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:
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();
+}