commit 4e8750cc367025b506861bcd0d84bfbf80a3daf4
parent 9e2ff3eaf2b7f21a6709bcfcb946cac6592372c4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 13 May 2026 04:33:35 -0700
Implement planned CG API updates
Diffstat:
7 files changed, 724 insertions(+), 418 deletions(-)
diff --git a/doc/cg-api-status.md b/doc/cg-api-status.md
@@ -1,266 +1,106 @@
-# CG API Implementation & Toy Language — Status And Plan
-
-This document separates the current implementation state from the target design
-plan. The current-status section describes what exists today, including behavior
-that the plan intentionally changes.
+# CG API And Toy Language Status
## Current Status
-### Public CG API implementation
-
-All previously-stubbed public CG API functions in `src/api/cg.c` have concrete
-implementations.
-
-| Function | Current behavior |
-|---|---|
-| `cfree_cg_push_float` | Builds `ConstBytes` union and delegates to `CGTarget.load_const`. |
-| `cfree_cg_push_bytes` | Emits anonymous bytes to `.rodata` and currently pushes a typed rodata lvalue. |
-| `cfree_cg_memcpy` | Pops dst/src addresses, forces them to regs, calls `CGTarget.copy_bytes`. |
-| `cfree_cg_memset` | Pops dst address, calls `CGTarget.set_bytes` with an immediate byte value. |
-| `cfree_cg_index` | Pops `[base, index]`, computes `base + offset + index * elemsz`, currently address-like. |
-| `cfree_cg_field_addr` | Pops base pointer, looks up field offset via `abi_record_layout`, emits field address. |
-| `cfree_cg_alloca` | Pops size and calls `CGTarget.alloca_`. |
-| `cfree_cg_va_start` | Pops `&ap`, forces reg, calls `CGTarget.va_start_`. |
-| `cfree_cg_va_arg` | Pops `&ap`, allocs dst reg, calls `CGTarget.va_arg_`. |
-| `cfree_cg_va_end` | Pops `&ap`, forces reg, calls `CGTarget.va_end_`. |
-| `cfree_cg_va_copy` | Pops `&dst, &src`, forces regs, calls `CGTarget.va_copy_`. |
-| `cfree_cg_data_decl` | Creates symbol via `obj_symbol_ex` with visibility mapping. |
-| `cfree_cg_data_begin` | Selects section, aligns it, and defines symbol. |
-| `cfree_cg_data_bytes` | Writes bytes with `obj_write`. |
-| `cfree_cg_data_zero` | Writes zero-padded bytes to section in 64-byte chunks. |
-| `cfree_cg_data_symbol` | Emits ABS32/ABS64/PC32/PC64 relocations with `obj_reloc`. |
-| `cfree_cg_data_end` | Resets data-definition state. |
-| `cfree_cg_tail_call` | Dispatches like `cfree_cg_call` with `CG_CALL_TAIL`, using a void return placeholder. |
-| `cfree_cg_continue_true` | Pops condition, emits `cmp_branch(CMP_NE, ...)` to continue label. |
-| `cfree_cg_continue_false` | Pops condition, emits `cmp_branch(CMP_EQ, ...)` to continue label. |
-| `cfree_cg_intrinsic` | Maps public intrinsic ids to `CGTarget.intrinsic`; supports scalar and overflow results. |
-| `cfree_cg_atomic_load/store/rmw/cmpxchg/fence` | Derives pointee memory access, marks atomic, maps enums, dispatches to target atomics. |
-| `cfree_cg_inline_asm` | Converts public operands to `AsmConstraint`, binds inputs/outputs/clobbers, dispatches to `CGTarget.asm_block`. |
-
-Current `CfreeCg` state additions:
-
-- `rodata_counter` for anonymous rodata symbols.
-- `data_sec`, `data_base`, and `data_size` for current data definition.
+The public CG API in `src/api/cg.c` has concrete implementations for the
+planned value, selector, control-flow, type, data, intrinsic, atomic, variadic,
+and inline-asm entry points.
-Current helper additions:
+Value categories are explicit:
-- `api_map_vis()` maps `CfreeCgVisibility` to `SymVis`.
-- `api_data_sym_kind()` maps declaration attrs to `SK_OBJ` / `SK_TLS`.
+- `cfree_cg_push_symbol` and `cfree_cg_push_bytes` push pointer/address rvalues.
+- `cfree_cg_indirect` converts a non-void pointer rvalue to a pointee lvalue.
+- `cfree_cg_load` converts lvalues to rvalues.
+- `cfree_cg_addr` converts lvalues to pointer rvalues.
+- `cfree_cg_store` is statement-like: `[lvalue, value] -> []`.
+- `cfree_cg_dup` preserves value category and gives rvalue registers independent
+ ownership.
-Current bug fix:
+Selectors are lvalue-producing:
-- `cfree_cg_func_end` resets `g->nscopes = 0`, which fixed scope state leaking
- across functions. In-function scope lifetime still needs cleanup in the plan.
-
-### Toy frontend status
-
-Toy currently has:
-
-- `let` immutable globals and `var` mutable globals.
-- Local variables, parameters, function calls, recursion, and pointer
- address/deref syntax.
-- Arithmetic, comparisons, bitwise operators, shifts, unary `~`, `&&`, and
- `||`.
-- `while`, `break`, `continue`, `if` / `else`, and `return tail f(...)`.
-- Dedicated CG API coverage builtins:
- - `typecheck()`
- - `byteconst()`
- - `alloca`, `index`, `memset`, `memcpy`
- - `atomic_load`, `atomic_store`, `atomic_add`, `atomic_sub`,
- `atomic_cas_ok`, `fence`
- - `popcount`, `ctz`, `clz`, `bswap`, `expect`
- - `fieldtest()`
- - `asmnop()`
+- `cfree_cg_index` selects an element lvalue.
+- `cfree_cg_field` selects a record field lvalue.
+- Callers use `cfree_cg_addr` after a selector when they need an address.
-Current toy lowering has several planned changes:
+Control-flow and calls:
-- Globals currently use `push_symbol + load/store`. The target model will use
- `push_symbol + indirect + load/store`.
-- `byteconst()` currently covers `push_bytes + load`. The target model will use
- `push_bytes + indirect + load`.
-- `fieldtest()` currently covers `cfree_cg_field_addr`. The target API will
- replace that with `cfree_cg_field`.
-- `asmnop()` is AArch64-specific. The target toy surface will replace it with
- general inline asm plus target-property selection.
-- Toy currently lowers `if` / `else` with raw labels because the nested-scope
- idiom was not obvious. The target plan keeps scopes valid and adds inline
- helpers for the common pattern.
+- Public scopes are stack-disciplined. `scope_end` must be LIFO and stale or
+ inactive handles are rejected.
+- Expression-valued scopes reconcile fallthrough and break results through a
+ canonical result slot.
+- Public inline helpers cover the common `if` / `else` pattern.
+- `cfree_cg_tail_call` is a terminator and pushes no result.
-### Current validation
+Types:
-Validated current behavior:
+- `CFREE_CG_BUILTIN_VA_LIST` and `CfreeCgBuiltinTypes.va_list` expose the
+ target ABI `va_list` type.
+- Pointer, array, qualified, and function type constructors intern by shape.
+- Aliases and nominal record/enum constructors remain source-identity
+ producing.
+
+Toy currently supports:
+
+- Immutable and mutable globals, locals, parameters, function calls, recursion,
+ pointers, address/deref syntax, arithmetic, comparisons, bitwise operators,
+ shifts, unary operators, `&&`, `||`, `while`, `break`, `continue`,
+ `if` / `else`, and `return tail f(...)`.
+- CG API coverage builtins: `typecheck()`, `byteconst()`, `alloca`, `index`,
+ `memset`, `memcpy`, `atomic_load`, `atomic_store`, `atomic_add`,
+ `atomic_sub`, `atomic_cas_ok`, `fence`, `popcount`, `ctz`, `clz`, `bswap`,
+ `expect`, `fieldtest()`, and `asmnop()`.
+- Lowering uses the explicit value-category API:
+ `push_symbol + indirect + load/store`, `push_bytes + indirect + load`,
+ `cfree_cg_field`, statement-like `store`, terminator tail calls, and the
+ public inline `if` / `else` helpers.
+
+Toy validation:
+
+- `test/toy/run.sh` supports:
+ - `R`: `cfree run case.toy`
+ - `L`: `cfree cc -c case.toy`, `cfree ld`, native execution
+ - `X`: opt-in Linux cross-target compile/link/execute for `aa64`, `x64`, and
+ `rv64` via `cfree cc -target`, `cfree ld`, and `test/lib/exec_target.sh`
+- Cross-arch validation intentionally has no cross-arch JIT path.
+- `asmnop()` is still AArch64-specific, so x64/rv64 cross runs skip cases that
+ use it until toy has target-selectable inline asm.
+
+Current validation:
- `make lib`
- `make bin`
- `make test-cg-api`
- `make test-cg-binder`
-- `make test-toy` — 36 pass, 0 fail
-- `make test-cg` — 1573 pass, 0 fail, 0 skip
-
-Toy CG API cases currently include:
-
-- `15_cg_api_types_bytes_globals`
-- `16_cg_api_alloca_memory_index`
-- `17_cg_api_atomics_intrinsics`
-- `18_cg_api_field_tail`
-
-The AArch64 comparison-as-value failure was fixed. The value-producing compare
-path now materializes the 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. That is a disassembly alias concern, not the
-comparison-value bug.
-
-## Target Design Decisions
-
-### Value categories
-
-The public CG API will use explicit value-category transitions.
-
-- `cfree_cg_push_symbol` is address-producing. It pushes a pointer/address value
- for the requested symbol reference form. GOT/PLT/TLS forms are
- address-generation models, not lvalue categories.
-- `cfree_cg_push_bytes` is address-producing. It pushes a pointer/address to the
- first byte of anonymous immutable bytes.
-- Add `cfree_cg_indirect(CfreeCg*)`. It converts TOS from pointer rvalue `*T`
- to an lvalue of `T`, rejecting non-pointers and `void *`.
-- Frontends that need a different pointee type should call
- `cfree_cg_convert` to the desired pointer type before `cfree_cg_indirect`.
-- `cfree_cg_load` converts lvalue to rvalue.
-- `cfree_cg_addr` converts lvalue to pointer rvalue.
-- `cfree_cg_store` becomes statement-like: `[lvalue, value] -> []`.
- Frontends that need assignment-expression semantics must preserve the value
- explicitly, usually with `cfree_cg_dup`.
-- `cfree_cg_dup` duplicates TOS while preserving value category. Rvalue
- duplicates must have independent ownership where needed; lvalue duplicates
- are two references to the same storage.
-
-### Data selection
+- `make test-toy` - 36 pass, 0 fail, 0 skip
+- `CFREE_TEST_PATHS=X test/toy/run.sh` - 52 pass, 0 fail, 2 skip
+- `make test-cg` - 1573 pass, 0 fail, 0 skip
+- `test/toy/demo.toy` compiles with `cfree cc -c`
-Data selectors are lvalue-producing.
+## Plan / TODOs
-- `cfree_cg_index` selects an element lvalue.
-- Replace `cfree_cg_field_addr` with `cfree_cg_field`, which selects a field
- lvalue.
-- Callers that need a field address use `cfree_cg_field` followed by
- `cfree_cg_addr`.
-- Raw address-producing primitives are reserved for symbol/blob materialization,
- explicit `addr`, and explicit pointer arithmetic.
-
-### Calls and control flow
-
-- `cfree_cg_tail_call` is a function terminator. It consumes callee and args,
- emits a tail dispatch, pushes no result, and terminates the current
- control-flow path. It is valid only in tail position with a return-compatible
- callee.
-- Public scopes are stack-disciplined. Every `scope_begin` must pair with
- `scope_end` in LIFO order, and `scope_end` deactivates/pops the top active
- scope. `break` / `continue` reject inactive or non-active handles.
-- Statement scopes can act as named exits. Breaks may target an outer active
- scope.
-- Expression-valued scopes stay in the public API. All result-producing exits
- must provide a value of the declared result type, and the implementation must
- reconcile exits into a canonical result location using register copies or a
- stack slot as needed.
-- Add public static inline helpers for the common `if` / `else` pattern. The
- helpers should compile down to existing scope operations rather than adding a
- second structured-control mechanism.
-
-### Types
-
-- Expose target ABI `va_list` as a public CG builtin type:
- `CFREE_CG_BUILTIN_VA_LIST` plus `CfreeCgBuiltinTypes.va_list`, backed by
- `abi_va_list_type`.
-- Intern semantic structural type constructors, including qualified types.
-- Pointer, array, qualified, and function constructors return stable ids for
- the same shape.
-- Aliases and nominal record/enum constructors remain source-identity
- producing, unless a future scoped nominal-type key is introduced.
-
-### Toy language direction
-
-- Teach toy to parse and use `va_list`, then add toy coverage for
- `cfree_cg_va_start`, `cfree_cg_va_arg`, `cfree_cg_va_end`, and
- `cfree_cg_va_copy`.
-- Replace `asmnop()` with a general toy inline-asm surface.
-- Add a small compile-time selector/switch mechanism that can branch on target
- properties such as `builtin.arch`, so toy source can choose target-specific
- asm templates.
-- Add toy error cases with expected diagnostic-message matching.
-- Keep focused regression cases in `test/toy/cases/*`.
-- Add a comprehensive human-readable demo at `test/toy/demo.toy`.
-
-### Cross-arch validation
-
-- Add toy cross-arch tests that compile with `cfree cc -target` for Linux cross
- targets, link with `cfree ld`, then execute via podman/qemu.
-- Cover `aa64`, `x64`, and `rv64`.
-- Do not add a cross-arch JIT path.
-- Allow environment-dependent skips.
-
-## Implementation Checklist
-
-1. Update public CG docs and declarations.
- - Add `cfree_cg_indirect`.
- - Replace/remove `cfree_cg_field_addr` with `cfree_cg_field`.
- - Update `push_symbol`, `push_bytes`, `store`, `dup`, `tail_call`, scope,
- and type-constructor documentation.
- - Add `CFREE_CG_BUILTIN_VA_LIST` and `CfreeCgBuiltinTypes.va_list`.
-
-2. Implement value-category changes in `src/api/cg.c`.
- - Make `push_symbol` address-producing.
- - Make `push_bytes` address-producing.
- - Implement `cfree_cg_indirect`.
- - Remove implicit pointer-rvalue store behavior.
- - Make `store` consume its value without pushing a result.
- - Preserve and test `dup` category/ownership semantics.
-
-3. Implement selector changes.
- - Make `cfree_cg_index` lvalue-producing.
- - Replace `cfree_cg_field_addr` with `cfree_cg_field`.
- - Update API users and tests to call `addr` when an address is required.
-
-4. Tighten scope and tail-call behavior.
- - Enforce LIFO scope lifetime and stale-handle diagnostics.
- - Implement expression-scope canonical result placement.
- - Add static inline `if` / `else` helpers.
- - Treat `cfree_cg_tail_call` as a terminator with no result.
-
-5. Implement type changes.
- - Add the public `va_list` builtin id and resolution.
- - Intern qualified types by `(base, quals)`.
- - Keep aliases and nominal record/enum constructors source-identity
- producing.
-
-6. Add direct API and misuse tests.
- - Keep return-value checks in `test/api/cg_type_test.c` or a sibling API
- test.
- - Add a focused panic-catching CG API misuse harness for stack underflow,
+1. Add direct CG API misuse tests.
+ - Keep type return-value checks in `test/api/cg_type_test.c` or a sibling
+ API test.
+ - Add a focused panic-catching misuse harness for stack underflow,
stale/non-LIFO scopes, invalid field indexes/base types, invalid
`indirect`, and unsupported data relocation widths.
-7. Update toy lowering and positive tests.
- - Use `push_symbol + indirect` for global loads/stores.
- - Use `push_bytes + indirect + load` for byte constants.
- - Use `cfree_cg_field` instead of `field_addr`.
- - Update assignments for statement-like `store`.
- - Update `return tail f(...)` for terminator semantics.
- - Move `if` / `else` lowering to the new inline helpers if practical.
+2. Add toy variadics.
+ - Add `va_list` syntax/usage.
+ - Add toy coverage for `cfree_cg_va_start`, `cfree_cg_va_arg`,
+ `cfree_cg_va_end`, and `cfree_cg_va_copy`.
-8. Add toy variadics, inline asm, and error tests.
- - Add `va_list` syntax/usage and variadic coverage.
- - Replace `asmnop()` with general inline asm plus `builtin.arch` selection.
- - Extend `test/toy/run.sh` with an error-case mode if needed.
- - Add diagnostic-matching toy error cases.
+3. Replace `asmnop()` with general toy inline asm.
+ - Add a toy inline-asm surface.
+ - Add a compile-time selector/switch mechanism for target properties such as
+ `builtin.arch`.
+ - Use the selector so toy source can choose target-specific asm templates.
-9. Add cross-arch toy validation.
- - Add an opt-in toy cc+ld+exec mode for `aa64`, `x64`, and `rv64`.
- - Use `cfree cc -target`, `cfree ld`, and existing podman/qemu execution
- helpers where possible.
- - Do not add a cross-arch JIT path.
+4. Add toy error tests.
+ - Extend `test/toy/run.sh` with an error-case mode if needed.
+ - Add expected diagnostic-message matching.
-10. Add `test/toy/demo.toy`.
- - Keep it readable and comprehensive.
- - Exercise toy syntax, globals, control flow, calls, memory helpers,
- atomics, variadics, tail calls, inline asm, and public CG API builtins.
+5. Complete `test/toy/demo.toy`.
+ - The demo currently covers toy syntax, globals, control flow, calls, memory
+ helpers, atomics, tail calls, inline asm, and public CG API builtins.
+ - Add variadic coverage once toy `va_list` support exists.
diff --git a/include/cfree/cg.h b/include/cfree/cg.h
@@ -30,6 +30,7 @@ typedef enum CfreeCgBuiltinType {
CFREE_CG_BUILTIN_USIZE,
CFREE_CG_BUILTIN_F32,
CFREE_CG_BUILTIN_F64,
+ CFREE_CG_BUILTIN_VA_LIST,
CFREE_CG_BUILTIN_COUNT,
} CfreeCgBuiltinType;
@@ -48,6 +49,7 @@ typedef struct CfreeCgBuiltinTypes {
CfreeCgTypeId usize;
CfreeCgTypeId f32;
CfreeCgTypeId f64;
+ CfreeCgTypeId va_list;
} CfreeCgBuiltinTypes;
typedef enum CfreeCgTypeQual {
@@ -67,9 +69,9 @@ typedef struct CfreeCgEnumValue {
int64_t value;
} CfreeCgEnumValue;
-/* 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
+/* Builtin ids are stable for the compiler. Pointer, array, qualified, and
+ * function constructors return a stable id for the same shape within one
+ * compiler; aliases, records, and enums allocate fresh user-facing
* identities. */
CfreeCgBuiltinTypes cfree_cg_builtin_types(CfreeCompiler*);
CfreeCgTypeId cfree_cg_type_ptr(CfreeCompiler*, CfreeCgTypeId pointee);
@@ -213,18 +215,22 @@ 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 an lvalue at the first byte.
- * pointee_type describes the logical value at that address, enabling
- * push_bytes + load to materialize arbitrary-width constants. */
+/* Anonymous immutable bytes in rodata; pushes a pointer rvalue to the first
+ * byte. pointee_type describes the logical value at that address, enabling
+ * push_bytes + indirect + load to materialize arbitrary-width constants. */
void cfree_cg_push_bytes(CfreeCg*, const uint8_t* str, size_t len,
CfreeCgTypeId pointee_type);
void cfree_cg_push_local(CfreeCg*, CfreeCgSlot slot);
+/* Pushes a pointer/address rvalue for the symbol. `type` is the symbol's
+ * declared object/function type, not the resulting pointer type. */
void cfree_cg_push_symbol(CfreeCg*, CfreeSym name, CfreeCgTypeId type,
CfreeCgSymbolRefKind kind, int64_t addend);
+/* Converts a pointer rvalue TOS from *T to an lvalue T. */
+void cfree_cg_indirect(CfreeCg*);
void cfree_cg_load(CfreeCg*);
void cfree_cg_addr(CfreeCg*);
-void cfree_cg_store(CfreeCg*); /* [..., lv, rv] → [rv] */
+void cfree_cg_store(CfreeCg*); /* [..., lv, rv] -> [] */
void cfree_cg_dup(CfreeCg*);
void cfree_cg_swap(CfreeCg*);
@@ -359,14 +365,15 @@ void cfree_cg_branch_false(CfreeCg*, CfreeCgLabel);
void cfree_cg_memcpy(CfreeCg*, uint32_t size, uint32_t align);
void cfree_cg_memset(CfreeCg*, uint8_t val, uint32_t size, uint32_t align);
-/* Computes base + offset + index * elemsz and pushes the element address.
+/* Computes base + offset + index * elemsz and pushes the element lvalue.
* Stack is [base, index]. elemsz is inferred from the base pointer/array
* type; index may be a constant produced by cfree_cg_push_int. */
void cfree_cg_index(CfreeCg*, uint32_t offset);
-/* Pops record base address and pushes the field address. Offset is inferred
- * from the record type and field_index. */
-void cfree_cg_field_addr(CfreeCg*, uint32_t field_index);
+/* Pops a record lvalue and pushes the field lvalue. Offset is inferred from
+ * the record type and field_index. Use cfree_cg_addr after this when an
+ * address is required. */
+void cfree_cg_field(CfreeCg*, uint32_t field_index);
void cfree_cg_call(CfreeCg*, uint32_t nargs, CfreeCgTypeId fn_type);
void cfree_cg_tail_call(CfreeCg*, uint32_t nargs, CfreeCgTypeId fn_type);
@@ -390,8 +397,7 @@ void cfree_cg_data_end(CfreeCg*);
/* Increment/decrement an lvalue in place. Stack: [lv] → [result].
* post=1 pushes the old value; post=0 pushes the new value.
* op is CFREE_CG_ADD or CFREE_CG_SUB. ty is the promoted integer type
- * of the lvalue (used for the literal 1 step).
- * Requires: cfree_cg_store pushes the stored value. */
+ * of the lvalue (used for the literal 1 step). */
static inline void cfree_cg_inc_dec(CfreeCg* cg, CfreeCgBinOp op, int post,
CfreeCgTypeId ty) {
cfree_cg_dup(cg); /* [lv, lv] */
@@ -402,15 +408,39 @@ static inline void cfree_cg_inc_dec(CfreeCg* cg, CfreeCgBinOp op, int post,
cfree_cg_binop(cg, op); /* [lv, old, new] */
cfree_cg_rot3(cg); /* [old, new, lv] */
cfree_cg_swap(cg); /* [old, lv, new] */
- cfree_cg_store(cg); /* [old, new] */
- cfree_cg_drop(cg); /* [old] */
+ cfree_cg_store(cg); /* [old] */
} else {
cfree_cg_push_int(cg, 1, ty); /* [lv, old, 1] */
cfree_cg_binop(cg, op); /* [lv, new] */
+ cfree_cg_dup(cg); /* [lv, new, new] */
+ cfree_cg_rot3(cg); /* [new, new, lv] */
+ cfree_cg_swap(cg); /* [new, lv, new] */
cfree_cg_store(cg); /* [new] */
}
}
+typedef struct CfreeCgIf {
+ CfreeCgLabel else_label;
+ CfreeCgLabel end_label;
+} CfreeCgIf;
+
+static inline CfreeCgIf cfree_cg_if_begin(CfreeCg* cg) {
+ CfreeCgIf it;
+ it.else_label = cfree_cg_label_new(cg);
+ it.end_label = cfree_cg_label_new(cg);
+ cfree_cg_branch_false(cg, it.else_label);
+ return it;
+}
+
+static inline void cfree_cg_if_else(CfreeCg* cg, CfreeCgIf it) {
+ cfree_cg_jump(cg, it.end_label);
+ cfree_cg_label_place(cg, it.else_label);
+}
+
+static inline void cfree_cg_if_end(CfreeCg* cg, CfreeCgIf it) {
+ cfree_cg_label_place(cg, it.end_label);
+}
+
/* Extract bits [lo, lo+width) from TOS as an unsigned value.
* Stack: [value] → [field]. ty is the integer type of the value.
* width < 64 masks with (1<<width)-1; width == 64 keeps all bits. */
diff --git a/lang/toy/toy.c b/lang/toy/toy.c
@@ -63,7 +63,7 @@
* cfree_cg_va_start / va_arg / va_end / va_copy
*
* Memory:
- * cfree_cg_memcpy / memset / index / field_addr
+ * cfree_cg_memcpy / memset / index / field
*
* Inline asm:
* cfree_cg_inline_asm
@@ -593,6 +593,7 @@ static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name,
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_indirect(p->cg);
cfree_cg_load(p->cg);
return p->int_type;
}
@@ -650,6 +651,7 @@ static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name,
return CFREE_CG_TYPE_NONE;
}
cfree_cg_index(p->cg, 0);
+ cfree_cg_addr(p->cg);
return p->int_ptr_type;
}
@@ -746,11 +748,12 @@ static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name,
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_indirect(p->cg);
+ cfree_cg_field(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_indirect(p->cg);
+ cfree_cg_field(p->cg, 1);
cfree_cg_load(p->cg);
return p->int_type;
}
@@ -872,6 +875,7 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) {
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_indirect(p->cg);
cfree_cg_load(p->cg);
return g->type;
}
@@ -949,7 +953,6 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) {
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);
}
}
@@ -961,6 +964,7 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) {
toy_error(p, p->cur.loc, "cannot dereference non-pointer");
return CFREE_CG_TYPE_NONE;
}
+ cfree_cg_indirect(p->cg);
cfree_cg_load(p->cg);
return p->int_type;
}
@@ -1146,13 +1150,11 @@ static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) {
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_store(p->cg);
- cfree_cg_drop(p->cg);
cfree_cg_label_place(p->cg, end_label);
cfree_cg_push_local(p->cg, result_slot);
cfree_cg_load(p->cg);
@@ -1179,13 +1181,11 @@ static CfreeCgTypeId toy_parse_expr_or(ToyParser* p) {
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_store(p->cg);
- cfree_cg_drop(p->cg);
cfree_cg_label_place(p->cg, end_label);
cfree_cg_push_local(p->cg, result_slot);
cfree_cg_load(p->cg);
@@ -1257,7 +1257,6 @@ static int toy_parse_let_stmt(ToyParser* p) {
cfree_cg_push_local(p->cg, slot);
cfree_cg_swap(p->cg);
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 let");
@@ -1267,7 +1266,7 @@ static int toy_parse_let_stmt(ToyParser* p) {
}
static int toy_parse_if_stmt(ToyParser* p) {
- CfreeCgLabel else_label, end_label;
+ CfreeCgIf it;
CfreeCgTypeId cond_ty;
toy_parser_advance(p); /* if */
cond_ty = toy_parse_expr(p);
@@ -1277,17 +1276,11 @@ static int toy_parse_if_stmt(ToyParser* p) {
return 0;
}
- 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_branch_false(p->cg, else_label);
+ it = cfree_cg_if_begin(p->cg);
if (!toy_parse_block(p)) return 0;
- cfree_cg_jump(p->cg, end_label);
- cfree_cg_label_place(p->cg, else_label);
+ cfree_cg_if_else(p->cg, it);
if (p->cur.kind == TOK_ELSE) {
toy_parser_advance(p); /* else */
@@ -1298,7 +1291,7 @@ static int toy_parse_if_stmt(ToyParser* p) {
}
}
- cfree_cg_label_place(p->cg, end_label);
+ cfree_cg_if_end(p->cg, it);
return 1;
}
@@ -1473,7 +1466,6 @@ static int toy_parse_stmt(ToyParser* p) {
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) {
@@ -1489,9 +1481,9 @@ static int toy_parse_stmt(ToyParser* p) {
return 0;
}
cfree_cg_push_symbol(p->cg, name, g->type, CFREE_CG_SYMREF_ADDR, 0);
+ cfree_cg_indirect(p->cg);
cfree_cg_swap(p->cg);
cfree_cg_store(p->cg);
- cfree_cg_drop(p->cg);
}
}
if (!toy_parser_expect(p, TOK_SEMI)) {
@@ -1511,6 +1503,7 @@ static int toy_parse_stmt(ToyParser* p) {
toy_error(p, p->cur.loc, "cannot assign through non-pointer");
return 0;
}
+ cfree_cg_indirect(p->cg);
if (!toy_parser_expect(p, TOK_EQ)) {
toy_error(p, p->cur.loc, "expected '=' in pointer assignment");
return 0;
@@ -1522,7 +1515,6 @@ static int toy_parse_stmt(ToyParser* p) {
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;
diff --git a/src/api/cg.c b/src/api/cg.c
@@ -107,6 +107,8 @@ static const Type* builtin_type(Compiler* c, CfreeCgBuiltinType t) {
return type_prim(c->global, TY_FLOAT);
case CFREE_CG_BUILTIN_F64:
return type_prim(c->global, TY_DOUBLE);
+ case CFREE_CG_BUILTIN_VA_LIST:
+ return abi_va_list_type(c->abi, c->global);
case CFREE_CG_BUILTIN_COUNT:
return NULL;
}
@@ -171,6 +173,22 @@ static CfreeCgTypeId find_array_type_id(Compiler* c, CfreeCgTypeId elem,
return CFREE_CG_TYPE_NONE;
}
+static CfreeCgTypeId find_qualified_type_id(Compiler* c, CfreeCgTypeId base,
+ u32 quals) {
+ 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_QUALIFIED && e->base == base &&
+ e->flags == quals)
+ 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)
@@ -266,6 +284,7 @@ CfreeCgBuiltinTypes cfree_cg_builtin_types(CfreeCompiler* c) {
out.usize = builtin_id(CFREE_CG_BUILTIN_USIZE);
out.f32 = builtin_id(CFREE_CG_BUILTIN_F32);
out.f64 = builtin_id(CFREE_CG_BUILTIN_F64);
+ out.va_list = builtin_id(CFREE_CG_BUILTIN_VA_LIST);
return out;
}
@@ -309,6 +328,9 @@ CfreeCgTypeId cfree_cg_type_qualified(CfreeCompiler* c, CfreeCgTypeId base,
CfreeCgTypeId id;
CgApiType* e;
if (!bty || (quals & ~known)) return CFREE_CG_TYPE_NONE;
+ if (quals == 0) return base;
+ id = find_qualified_type_id(c, base, quals);
+ if (id != CFREE_CG_TYPE_NONE) return id;
e = type_alloc(c, &id);
if (!e) return CFREE_CG_TYPE_NONE;
e->type = type_qualified(c->global, bty, quals_to_internal(quals));
@@ -549,7 +571,8 @@ typedef struct ApiSValue {
const Type* type;
u8 res;
u8 pinned;
- u8 pad[2];
+ u8 lvalue;
+ u8 pad[1];
FrameSlot spill_slot;
} ApiSValue;
@@ -558,7 +581,12 @@ typedef struct ApiSValue {
typedef struct ApiCgScope {
Label break_lbl;
Label continue_lbl;
+ CGScope target_scope;
const Type* result_type;
+ FrameSlot result_slot;
+ u32 generation;
+ u8 active;
+ u8 pad[3];
} ApiCgScope;
#define API_CG_MAX_SCOPES 64
@@ -594,6 +622,7 @@ struct CfreeCg {
ApiCgScope scopes[API_CG_MAX_SCOPES];
u32 nscopes;
+ u32 scope_generation;
u32 rodata_counter;
@@ -679,15 +708,25 @@ static ApiSValue api_make_sv(Operand op, const Type* ty) {
return sv;
}
+static ApiSValue api_make_lv(Operand op, const Type* ty) {
+ ApiSValue sv = api_make_sv(op, ty);
+ sv.lvalue = 1;
+ return sv;
+}
+
static const Type* api_sv_type(const ApiSValue* sv) {
return sv->type ? sv->type : sv->op.type;
}
-static int api_is_lvalue(const Operand* o) {
+static int api_operand_can_address(const Operand* o) {
return o->kind == OPK_LOCAL || o->kind == OPK_GLOBAL ||
o->kind == OPK_INDIRECT;
}
+static int api_is_lvalue_sv(const ApiSValue* sv) {
+ return sv->lvalue && api_operand_can_address(&sv->op);
+}
+
static void api_stack_grow(CfreeCg* g, u32 want) {
Heap* h = g->c->env->heap;
u32 cap = g->cap;
@@ -925,11 +964,13 @@ static Operand api_force_reg(CfreeCg* g, ApiSValue* v, const Type* ty) {
Operand dst = api_op_reg(r, ty);
if (v->op.kind == OPK_IMM) {
T->load_imm(T, dst, v->op.v.imm);
- } else if (api_is_lvalue(&v->op)) {
+ } else if (api_is_lvalue_sv(v)) {
T->load(T, dst, v->op, api_mem_for_lvalue(g, &v->op, ty));
if (v->op.kind == OPK_INDIRECT) {
T->free_reg(T, v->op.v.ind.base, RC_INT);
}
+ } else if (v->op.kind == OPK_GLOBAL) {
+ T->addr_of(T, dst, v->op);
} else {
compiler_panic(g->c, g->cur_loc, "CfreeCg: cannot force operand to register");
}
@@ -1223,6 +1264,7 @@ void cfree_cg_func_end(CfreeCg* g) {
g->fn_abi = NULL;
g->fn_ret_type = NULL;
g->nscopes = 0;
+ memset(g->scopes, 0, sizeof g->scopes);
}
/* ============================================================
@@ -1322,6 +1364,7 @@ void cfree_cg_push_bytes(CfreeCg* g, const uint8_t* str, size_t len,
Compiler* c;
ObjBuilder* ob;
const Type* pty;
+ const Type* ptr_ty;
Sym sec_name;
ObjSecId sec;
u32 base;
@@ -1333,6 +1376,7 @@ void cfree_cg_push_bytes(CfreeCg* g, const uint8_t* str, size_t len,
ob = g->obj;
pty = resolve_type(c, pointee_type);
if (!pty) return;
+ ptr_ty = type_ptr(c->global, pty);
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));
@@ -1340,14 +1384,14 @@ void cfree_cg_push_bytes(CfreeCg* g, const uint8_t* str, size_t 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));
+ api_push(g, api_make_sv(api_op_global(sym, 0, ptr_ty), ptr_ty));
}
void cfree_cg_push_local(CfreeCg* g, CfreeCgSlot slot) {
const Type* ty;
if (!g) return;
ty = api_slot_type(g, (FrameSlot)slot);
- api_push(g, api_make_sv(api_op_local((FrameSlot)slot, ty), ty));
+ api_push(g, api_make_lv(api_op_local((FrameSlot)slot, ty), ty));
}
void cfree_cg_push_symbol(CfreeCg* g, CfreeSym name, CfreeCgTypeId type,
@@ -1355,9 +1399,11 @@ void cfree_cg_push_symbol(CfreeCg* g, CfreeSym name, CfreeCgTypeId type,
Sym mangled;
ObjSymId sym;
const Type* ty;
+ const Type* ptr_ty;
if (!g) return;
ty = resolve_type(g->c, type);
if (!ty) return;
+ ptr_ty = type_ptr(g->c->global, ty);
mangled = api_c_mangle(g->c, name);
sym = obj_symbol_find(g->obj, mangled);
if (sym == OBJ_SYM_NONE) {
@@ -1365,7 +1411,7 @@ void cfree_cg_push_symbol(CfreeCg* g, CfreeSym name, CfreeCgTypeId type,
OBJ_SEC_NONE, 0, 0);
}
(void)kind;
- api_push(g, api_make_sv(api_op_global(sym, addend, ty), ty));
+ api_push(g, api_make_sv(api_op_global(sym, addend, ptr_ty), ptr_ty));
}
/* ============================================================
@@ -1379,21 +1425,35 @@ void cfree_cg_load(CfreeCg* g) {
if (!g) return;
v = api_pop(g);
api_ensure_reg(g, &v);
- if (!api_is_lvalue(&v.op)) {
- 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;
- }
+ if (!api_is_lvalue_sv(&v)) {
+ api_push(g, v);
+ return;
}
ty = api_sv_type(&v);
dst = api_force_reg(g, &v, ty);
api_push(g, api_make_sv(dst, ty));
}
+void cfree_cg_indirect(CfreeCg* g) {
+ ApiSValue ptr;
+ const Type* pty;
+ const Type* pointee;
+ Operand ptr_op;
+ if (!g) return;
+ ptr = api_pop(g);
+ pty = api_sv_type(&ptr);
+ if (!pty || pty->kind != TY_PTR || !pty->ptr.pointee ||
+ pty->ptr.pointee->kind == TY_VOID) {
+ compiler_panic(g->c, g->cur_loc,
+ "CfreeCg: indirect operand is not a pointer to object");
+ return;
+ }
+ pointee = pty->ptr.pointee;
+ ptr_op = api_force_reg(g, &ptr, pty);
+ api_push(g, api_make_lv(api_op_indirect(ptr_op.v.reg, 0, pointee),
+ pointee));
+}
+
void cfree_cg_addr(CfreeCg* g) {
ApiSValue v;
CGTarget* T;
@@ -1404,7 +1464,7 @@ void cfree_cg_addr(CfreeCg* g) {
T = g->target;
v = api_pop(g);
api_ensure_reg(g, &v);
- if (!api_is_lvalue(&v.op)) {
+ if (!api_is_lvalue_sv(&v)) {
compiler_panic(g->c, g->cur_loc, "CfreeCg: addr operand is not an lvalue");
return;
}
@@ -1427,13 +1487,7 @@ 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)) {
+ if (!api_is_lvalue_sv(&lv)) {
compiler_panic(g->c, g->cur_loc, "CfreeCg: store destination is not an lvalue");
return;
}
@@ -1445,7 +1499,7 @@ void cfree_cg_store(CfreeCg* g) {
}
T->store(T, lv.op, src, api_mem_for_lvalue(g, &lv.op, ty));
api_release(g, &lv);
- api_push(g, api_make_sv(src, ty));
+ api_release(g, &rv);
}
/* ============================================================
@@ -2064,7 +2118,7 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl,
} 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)) {
+ } else if (api_is_lvalue_sv(&in_svs[i])) {
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);
@@ -2235,10 +2289,77 @@ void cfree_cg_branch_false(CfreeCg* g, CfreeCgLabel label) {
* Scopes / structured control flow
* ============================================================ */
+static CfreeCgScope api_scope_handle(u32 idx, u32 generation) {
+ return (CfreeCgScope)((generation << 8) | ((idx + 1u) & 0xffu));
+}
+
+static ApiCgScope* api_scope_from_handle(CfreeCg* g, CfreeCgScope scope,
+ int require_top,
+ const char* who) {
+ u32 slot;
+ u32 generation;
+ ApiCgScope* s;
+ if (!g || scope == 0) return NULL;
+ slot = ((u32)scope & 0xffu);
+ generation = ((u32)scope >> 8);
+ if (slot == 0 || slot > API_CG_MAX_SCOPES) {
+ compiler_panic(g->c, g->cur_loc, "%s: invalid scope handle", who);
+ return NULL;
+ }
+ slot--;
+ if (slot >= g->nscopes) {
+ compiler_panic(g->c, g->cur_loc, "%s: stale scope handle", who);
+ return NULL;
+ }
+ if (require_top && slot + 1u != g->nscopes) {
+ compiler_panic(g->c, g->cur_loc, "%s: non-LIFO scope end", who);
+ return NULL;
+ }
+ s = &g->scopes[slot];
+ if (!s->active || s->generation != generation) {
+ compiler_panic(g->c, g->cur_loc, "%s: stale scope handle", who);
+ return NULL;
+ }
+ return s;
+}
+
+static int api_scope_has_result(const ApiCgScope* s) {
+ return s->result_type && s->result_type->kind != TY_VOID;
+}
+
+static void api_scope_store_result(CfreeCg* g, ApiCgScope* s,
+ ApiSValue* result) {
+ Operand dst;
+ Operand src;
+ if (!api_scope_has_result(s)) return;
+ dst = api_op_local(s->result_slot, s->result_type);
+ src = (result->op.kind == OPK_IMM || result->op.kind == OPK_REG)
+ ? result->op
+ : api_force_reg(g, result, s->result_type);
+ g->target->store(g->target, dst, src,
+ api_mem_for_lvalue(g, &dst, s->result_type));
+ api_release(g, result);
+}
+
+static void api_scope_push_result(CfreeCg* g, ApiCgScope* s) {
+ Operand dst;
+ Operand src;
+ Reg r;
+ if (!api_scope_has_result(s)) return;
+ r = api_alloc_reg_or_spill(g, api_type_class(s->result_type), s->result_type);
+ dst = api_op_reg(r, s->result_type);
+ src = api_op_local(s->result_slot, s->result_type);
+ g->target->load(g->target, dst, src,
+ api_mem_for_lvalue(g, &src, s->result_type));
+ api_push(g, api_make_sv(dst, s->result_type));
+}
+
CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) {
Label break_lbl, cont_lbl;
CGScopeDesc d;
ApiCgScope* s;
+ CGScope target_scope;
+ u32 idx;
if (!g) return 0;
break_lbl = g->target->label_new(g->target);
cont_lbl = g->target->label_new(g->target);
@@ -2248,10 +2369,14 @@ CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) {
compiler_panic(g->c, g->cur_loc, "CfreeCg: too many nested scopes");
return 0;
}
- s = &g->scopes[g->nscopes];
+ idx = g->nscopes;
+ s = &g->scopes[idx];
s->break_lbl = break_lbl;
s->continue_lbl = cont_lbl;
s->result_type = resolve_type(g->c, result_type);
+ s->generation = ++g->scope_generation;
+ if (s->generation == 0) s->generation = ++g->scope_generation;
+ s->active = 1;
g->nscopes++;
memset(&d, 0, sizeof d);
@@ -2259,154 +2384,182 @@ CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) {
d.break_label = break_lbl;
d.continue_label = cont_lbl;
d.result_type = s->result_type;
- (void)g->target->scope_begin(g->target, &d);
+ target_scope = g->target->scope_begin(g->target, &d);
+ s->target_scope = target_scope;
+ s->result_slot = FRAME_SLOT_NONE;
+ if (api_scope_has_result(s)) {
+ FrameSlotDesc fsd;
+ memset(&fsd, 0, sizeof fsd);
+ fsd.type = s->result_type;
+ fsd.size = abi_sizeof(g->c->abi, s->result_type);
+ fsd.align = abi_alignof(g->c->abi, s->result_type);
+ fsd.kind = FS_LOCAL;
+ s->result_slot = g->target->frame_slot(g->target, &fsd);
+ }
- return (CfreeCgScope)g->nscopes;
+ return api_scope_handle(idx, s->generation);
}
void cfree_cg_scope_end(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
- if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
- g->target->label_place(g->target, g->scopes[idx].break_lbl);
- g->target->scope_end(g->target, (CGScope)scope);
+ ApiCgScope* s = api_scope_from_handle(g, scope, 1, "CfreeCg: scope_end");
+ if (!s) return;
+ if (api_scope_has_result(s)) {
+ ApiSValue result = api_pop(g);
+ api_scope_store_result(g, s, &result);
+ }
+ g->target->label_place(g->target, s->break_lbl);
+ g->target->scope_end(g->target, s->target_scope);
+ api_scope_push_result(g, s);
+ s->active = 0;
+ g->nscopes--;
}
void cfree_cg_break(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
- if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
- g->target->jump(g->target, g->scopes[idx].break_lbl);
+ ApiCgScope* s = api_scope_from_handle(g, scope, 0, "CfreeCg: break");
+ if (!s) return;
+ if (api_scope_has_result(s)) {
+ ApiSValue result = api_pop(g);
+ api_scope_store_result(g, s, &result);
+ }
+ g->target->jump(g->target, s->break_lbl);
}
void cfree_cg_break_true(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
- ApiSValue v;
+ ApiCgScope* s;
+ ApiSValue cond;
CGTarget* T;
const Type* ty;
if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
+ s = api_scope_from_handle(g, scope, 0, "CfreeCg: break_true");
+ if (!s) return;
T = g->target;
- v = api_pop(g);
- ty = v.type ? v.type : type_prim(g->c->global, TY_INT);
-
- if (g->scopes[idx].result_type && g->scopes[idx].result_type->kind != TY_VOID) {
- cfree_cg_swap(g);
- {
- ApiSValue val = api_pop(g);
- api_ensure_reg(g, &val);
- Operand a = api_force_reg(g, &val, ty);
+ cond = api_pop(g);
+ ty = cond.type ? cond.type : type_prim(g->c->global, TY_INT);
+
+ if (api_scope_has_result(s)) {
+ ApiSValue result = api_pop(g);
+ if (cond.op.kind == OPK_IMM) {
+ if (cond.op.v.imm != 0) {
+ api_scope_store_result(g, s, &result);
+ T->jump(T, s->break_lbl);
+ } else {
+ api_release(g, &result);
+ }
+ api_release(g, &cond);
+ } else {
+ Label skip = T->label_new(T);
+ Operand a = api_force_reg(g, &cond, ty);
Operand zero = api_op_imm(0, ty);
- T->cmp_branch(T, CMP_NE, a, zero, g->scopes[idx].break_lbl);
- api_release(g, &val);
- }
- {
- ApiSValue result = api_pop(g);
- api_push(g, result);
+ T->cmp_branch(T, CMP_EQ, a, zero, skip);
+ api_release(g, &cond);
+ api_scope_store_result(g, s, &result);
+ T->jump(T, s->break_lbl);
+ T->label_place(T, skip);
}
} else {
- if (v.op.kind == OPK_IMM) {
- if (v.op.v.imm != 0) T->jump(T, g->scopes[idx].break_lbl);
- api_release(g, &v);
+ if (cond.op.kind == OPK_IMM) {
+ if (cond.op.v.imm != 0) T->jump(T, s->break_lbl);
+ api_release(g, &cond);
} else {
- Operand a = api_force_reg(g, &v, ty);
+ Operand a = api_force_reg(g, &cond, ty);
Operand zero = api_op_imm(0, ty);
- T->cmp_branch(T, CMP_NE, a, zero, g->scopes[idx].break_lbl);
- api_release(g, &v);
+ T->cmp_branch(T, CMP_NE, a, zero, s->break_lbl);
+ api_release(g, &cond);
}
}
}
void cfree_cg_break_false(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
- ApiSValue v;
+ ApiCgScope* s;
+ ApiSValue cond;
CGTarget* T;
const Type* ty;
if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
+ s = api_scope_from_handle(g, scope, 0, "CfreeCg: break_false");
+ if (!s) return;
T = g->target;
- v = api_pop(g);
- ty = v.type ? v.type : type_prim(g->c->global, TY_INT);
-
- if (g->scopes[idx].result_type && g->scopes[idx].result_type->kind != TY_VOID) {
- cfree_cg_swap(g);
- {
- ApiSValue val = api_pop(g);
- api_ensure_reg(g, &val);
- Operand a = api_force_reg(g, &val, ty);
+ cond = api_pop(g);
+ ty = cond.type ? cond.type : type_prim(g->c->global, TY_INT);
+
+ if (api_scope_has_result(s)) {
+ ApiSValue result = api_pop(g);
+ if (cond.op.kind == OPK_IMM) {
+ if (cond.op.v.imm == 0) {
+ api_scope_store_result(g, s, &result);
+ T->jump(T, s->break_lbl);
+ } else {
+ api_release(g, &result);
+ }
+ api_release(g, &cond);
+ } else {
+ Label skip = T->label_new(T);
+ Operand a = api_force_reg(g, &cond, ty);
Operand zero = api_op_imm(0, ty);
- T->cmp_branch(T, CMP_EQ, a, zero, g->scopes[idx].break_lbl);
- api_release(g, &val);
- }
- {
- ApiSValue result = api_pop(g);
- api_push(g, result);
+ T->cmp_branch(T, CMP_NE, a, zero, skip);
+ api_release(g, &cond);
+ api_scope_store_result(g, s, &result);
+ T->jump(T, s->break_lbl);
+ T->label_place(T, skip);
}
} else {
- if (v.op.kind == OPK_IMM) {
- if (v.op.v.imm == 0) T->jump(T, g->scopes[idx].break_lbl);
- api_release(g, &v);
+ if (cond.op.kind == OPK_IMM) {
+ if (cond.op.v.imm == 0) T->jump(T, s->break_lbl);
+ api_release(g, &cond);
} else {
- Operand a = api_force_reg(g, &v, ty);
+ Operand a = api_force_reg(g, &cond, ty);
Operand zero = api_op_imm(0, ty);
- T->cmp_branch(T, CMP_EQ, a, zero, g->scopes[idx].break_lbl);
- api_release(g, &v);
+ T->cmp_branch(T, CMP_EQ, a, zero, s->break_lbl);
+ api_release(g, &cond);
}
}
}
void cfree_cg_continue(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
- if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
- g->target->jump(g->target, g->scopes[idx].continue_lbl);
+ ApiCgScope* s = api_scope_from_handle(g, scope, 0, "CfreeCg: continue");
+ if (!s) return;
+ g->target->jump(g->target, s->continue_lbl);
}
void cfree_cg_continue_true(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
+ ApiCgScope* s;
ApiSValue v;
CGTarget* T;
const Type* ty;
if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
+ s = api_scope_from_handle(g, scope, 0, "CfreeCg: continue_true");
+ if (!s) 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);
+ if (v.op.v.imm != 0) T->jump(T, s->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);
+ T->cmp_branch(T, CMP_NE, a, zero, s->continue_lbl);
api_release(g, &v);
}
}
void cfree_cg_continue_false(CfreeCg* g, CfreeCgScope scope) {
- u32 idx;
+ ApiCgScope* s;
ApiSValue v;
CGTarget* T;
const Type* ty;
if (!g || scope == 0) return;
- idx = (u32)scope - 1;
- if (idx >= g->nscopes) return;
+ s = api_scope_from_handle(g, scope, 0, "CfreeCg: continue_false");
+ if (!s) 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);
+ if (v.op.v.imm == 0) T->jump(T, s->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);
+ T->cmp_branch(T, CMP_EQ, a, zero, s->continue_lbl);
api_release(g, &v);
}
}
@@ -2537,26 +2690,40 @@ void cfree_cg_memset(CfreeCg* g, uint8_t val, uint32_t size, uint32_t align) {
void cfree_cg_index(CfreeCg* g, uint32_t offset) {
ApiSValue idx, base;
CGTarget* T;
- const Type *base_ptr_ty, *elem_ty, *idx_ty;
+ const Type *base_ty, *base_ptr_ty, *elem_ty, *idx_ty;
u32 elemsz;
+ int free_base_op = 0;
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;
+ api_ensure_reg(g, &base);
+ base_ty = api_sv_type(&base);
+ if (base_ty && base_ty->kind == TY_PTR) {
+ elem_ty = base_ty->ptr.pointee;
+ base_ptr_ty = base_ty;
+ } else if (base_ty && base_ty->kind == TY_ARRAY && api_is_lvalue_sv(&base)) {
+ elem_ty = base_ty->arr.elem;
+ base_ptr_ty = type_ptr(g->c->global, elem_ty);
} else {
- elem_ty = type_prim(g->c->global, TY_INT);
+ compiler_panic(g->c, g->cur_loc,
+ "CfreeCg: index base is not a pointer or array lvalue");
+ return;
}
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);
+ if (base_ty && base_ty->kind == TY_ARRAY) {
+ rr = api_alloc_reg_or_spill(g, RC_INT, base_ptr_ty);
+ base_op = api_op_reg(rr, base_ptr_ty);
+ T->addr_of(T, base_op, base.op);
+ api_release(g, &base);
+ free_base_op = 1;
+ } else {
+ 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);
@@ -2573,30 +2740,32 @@ void cfree_cg_index(CfreeCg* g, uint32_t offset) {
T->binop(T, BO_IADD, result, base_op, scaled);
T->free_reg(T, sr, RC_INT);
}
- api_release(g, &base);
+ if (free_base_op) T->free_reg(T, base_op.v.reg, RC_INT);
+ if (base_ty && base_ty->kind != TY_ARRAY) api_release(g, &base);
api_release(g, &idx);
- api_push(g, api_make_sv(result, base_ptr_ty));
+ api_push(g, api_make_lv(api_op_indirect(result.v.reg, 0, elem_ty), elem_ty));
}
-void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) {
+void cfree_cg_field(CfreeCg* g, uint32_t field_index) {
ApiSValue base;
CGTarget* T;
- const Type *base_ptr_ty, *rec_ty;
- const Type* field_ptr_ty;
+ const Type* rec_ty;
+ const Type* field_ty;
+ const Type* rec_ptr_ty;
const ABIRecordLayout* layout;
u32 field_offset;
- Operand base_op, result;
+ Operand 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) {
+ api_ensure_reg(g, &base);
+ rec_ty = api_sv_type(&base);
+ if (!api_is_lvalue_sv(&base)) {
compiler_panic(g->c, g->cur_loc,
- "CfreeCg: field_addr base is not a pointer");
+ "CfreeCg: field base is not an lvalue");
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");
@@ -2607,19 +2776,43 @@ void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) {
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_ty = rec_ty->rec.fields[field_index].type;
+ rec_ptr_ty = type_ptr(g->c->global, rec_ty);
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, field_ptr_ty);
- result = api_op_reg(rr, field_ptr_ty);
- if (field_offset == 0) {
- T->copy(T, result, base_op);
+ if (base.op.kind == OPK_GLOBAL) {
+ result = api_op_global(base.op.v.global.sym,
+ base.op.v.global.addend + (i64)field_offset,
+ field_ty);
+ api_push(g, api_make_lv(result, field_ty));
+ } else if (base.op.kind == OPK_INDIRECT &&
+ field_offset <= (u32)INT32_MAX &&
+ base.op.v.ind.ofs <= INT32_MAX - (i32)field_offset) {
+ result = api_op_indirect(base.op.v.ind.base,
+ base.op.v.ind.ofs + (i32)field_offset, field_ty);
+ api_push(g, api_make_lv(result, field_ty));
} else {
- T->binop(T, BO_IADD, result, base_op,
- api_op_imm((i64)field_offset, base_ptr_ty));
+ Operand base_addr;
+ rr = api_alloc_reg_or_spill(g, RC_INT, rec_ptr_ty);
+ base_addr = api_op_reg(rr, rec_ptr_ty);
+ T->addr_of(T, base_addr, base.op);
+ api_release(g, &base);
+ if (field_offset == 0) {
+ result = base_addr;
+ } else {
+ Reg fr = api_alloc_reg_or_spill(g, RC_INT, rec_ptr_ty);
+ result = api_op_reg(fr, rec_ptr_ty);
+ T->binop(T, BO_IADD, result, base_addr,
+ api_op_imm((i64)field_offset, rec_ptr_ty));
+ T->free_reg(T, base_addr.v.reg, RC_INT);
+ }
+ api_push(g, api_make_lv(api_op_indirect(result.v.reg, 0, field_ty),
+ field_ty));
}
- api_release(g, &base);
- api_push(g, api_make_sv(result, field_ptr_ty));
+}
+
+void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) {
+ cfree_cg_field(g, field_index);
+ cfree_cg_addr(g);
}
/* ============================================================
@@ -2678,7 +2871,7 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) {
avs[idx].size = abi_sizeof(g->c->abi, aty);
} else {
avs[idx].storage =
- api_is_lvalue(&arg.op) ? api_force_reg(g, &arg, aty) : arg.op;
+ api_is_lvalue_sv(&arg) ? api_force_reg(g, &arg, aty) : arg.op;
}
}
@@ -2730,7 +2923,13 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) {
}
if (has_result) {
- api_push(g, api_make_sv(desc.ret.storage, ret_ty));
+ if (desc.ret.storage.kind == OPK_LOCAL ||
+ desc.ret.storage.kind == OPK_GLOBAL ||
+ desc.ret.storage.kind == OPK_INDIRECT) {
+ api_push(g, api_make_lv(desc.ret.storage, ret_ty));
+ } else {
+ api_push(g, api_make_sv(desc.ret.storage, ret_ty));
+ }
}
}
@@ -2759,7 +2958,7 @@ void cfree_cg_tail_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_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;
+ api_is_lvalue_sv(&arg) ? api_force_reg(g, &arg, aty) : arg.op;
}
callee = api_pop(g);
api_ensure_reg(g, &callee);
diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c
@@ -90,6 +90,7 @@ int main(void) {
EXPECT(bi.void_ != CFREE_CG_TYPE_NONE, "void builtin id is none");
EXPECT(bi.i32 != CFREE_CG_TYPE_NONE, "i32 builtin id is none");
EXPECT(bi.f64 != CFREE_CG_TYPE_NONE, "f64 builtin id is none");
+ EXPECT(bi.va_list != CFREE_CG_TYPE_NONE, "va_list builtin id is none");
EXPECT(bi.void_ != bi.i32 && bi.i32 != bi.f64, "builtin ids collide");
ptr_i32 = cfree_cg_type_ptr(c, bi.i32);
@@ -108,7 +109,11 @@ int main(void) {
EXPECT(alias != CFREE_CG_TYPE_NONE && alias != bi.i32,
"alias id should be fresh");
EXPECT(qual != CFREE_CG_TYPE_NONE && qual != alias,
- "qualified id should be fresh");
+ "qualified type failed");
+ EXPECT(cfree_cg_type_qualified(c, alias, CFREE_CG_TQ_CONST) == qual,
+ "qualified type id should be interned");
+ EXPECT(cfree_cg_type_qualified(c, alias, 0) == alias,
+ "zero qualifiers should return base id");
EXPECT(cfree_cg_type_qualified(c, alias, 1u << 12) == CFREE_CG_TYPE_NONE,
"unknown qualifier bit should fail");
diff --git a/test/toy/demo.toy b/test/toy/demo.toy
@@ -0,0 +1,74 @@
+let seed: int = 5;
+var counter: int = 3;
+var counter_ptr: *int = &counter;
+
+fn id(x: int): int {
+ return x;
+}
+
+fn id_tail(x: int): int {
+ return tail id(x);
+}
+
+fn fib(n: int): int {
+ if n < 2 {
+ return n;
+ }
+ return fib(n - 1) + fib(n - 2);
+}
+
+fn sum_to(n: int): int {
+ let i: int = 0;
+ let sum: int = 0;
+ while i < n {
+ i = i + 1;
+ if i == 3 {
+ continue;
+ }
+ if i > 6 {
+ break;
+ }
+ sum = sum + i;
+ }
+ return sum;
+}
+
+fn memory_demo(): 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);
+}
+
+fn atomic_demo(): int {
+ atomic_store(counter_ptr, 10);
+ let before: int = atomic_add(counter_ptr, 5);
+ let after: int = atomic_load(counter_ptr);
+ let ok: int = atomic_cas_ok(counter_ptr, after, 21);
+ fence();
+ return before + after + ok;
+}
+
+fn intrin_demo(x: int): int {
+ return popcount(x) + ctz(8) + clz(1) + expect(1, 1) + bswap(1);
+}
+
+fn api_demo(): int {
+ return typecheck() + byteconst() + fieldtest() + asmnop();
+}
+
+fn main(): int {
+ let local: int = seed + *counter_ptr;
+ counter = counter + 2;
+ return id_tail(local)
+ + fib(6)
+ + sum_to(8)
+ + memory_demo()
+ + atomic_demo()
+ + intrin_demo(7)
+ + api_demo();
+}
diff --git a/test/toy/run.sh b/test/toy/run.sh
@@ -4,13 +4,15 @@
# Paths per case:
# R cfree run case.toy
# L cfree cc -c case.toy -> cfree ld case.o -> native executable
+# X cfree cc -target -> cfree ld -> exec_target for Linux cross targets
#
# 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".
+# CFREE_TEST_FILTER / CFREE_TEST_PATHS, where paths is a subset of "RLX".
+# X is opt-in cross-arch cc+ld+exec for aa64, x64, and rv64.
set -u
@@ -23,11 +25,14 @@ 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
+case "$PATHS" in *X*) RUN_X=1;; *) RUN_X=0;; esac
+TOY_CROSS_ARCHS="${CFREE_TOY_CROSS_ARCHS:-aa64 x64 rv64}"
mkdir -p "$BUILD_DIR"
PASS=0
FAIL=0
+SKIP=0
FAIL_NAMES=()
color_red() { printf '\033[31m%s\033[0m' "$1"; }
@@ -44,6 +49,11 @@ note_fail() {
printf ' %s %s\n' "$(color_red FAIL)" "$1"
}
+note_skip() {
+ SKIP=$((SKIP + 1))
+ printf ' SKIP %s (%s)\n' "$1" "$2"
+}
+
expected_for() {
local src="$1" exp="${src%.toy}.expected"
if [ -f "$exp" ]; then
@@ -113,11 +123,164 @@ run_case_link() {
check_rc "$name/L" "$rc" "$expected" "$err"
}
+cross_triple_for() {
+ case "$1" in
+ aa64|aarch64) printf 'aarch64-linux-gnu' ;;
+ x64|x86_64) printf 'x86_64-linux-gnu' ;;
+ rv64|riscv64) printf 'riscv64-linux-gnu' ;;
+ *) return 1 ;;
+ esac
+}
+
+cross_tag_for() {
+ case "$1" in
+ aa64|aarch64) printf 'aarch64-linux' ;;
+ x64|x86_64) printf 'x64-linux' ;;
+ rv64|riscv64) printf 'rv64-linux' ;;
+ *) return 1 ;;
+ esac
+}
+
+cross_make_start_obj() {
+ local arch="$1" triple="$2" work="$3"
+ local start_c="$work/$arch.start.c" start_o="$work/$arch.start.o"
+ cat > "$start_c" <<'EOF_START'
+extern int main(void);
+
+__attribute__((noreturn)) static void do_exit(int code) {
+#if defined(__aarch64__)
+ register long x8 __asm__("x8") = 94;
+ register long x0 __asm__("x0") = code;
+ __asm__ volatile("svc #0" ::"r"(x8), "r"(x0) : "memory");
+#elif defined(__x86_64__)
+ register long rax __asm__("rax") = 231;
+ register long rdi __asm__("rdi") = code;
+ __asm__ volatile("syscall" ::"r"(rax), "r"(rdi) : "memory");
+#elif defined(__riscv) && __riscv_xlen == 64
+ register long a7 __asm__("a7") = 94;
+ register long a0 __asm__("a0") = code;
+ __asm__ volatile("ecall" ::"r"(a7), "r"(a0) : "memory");
+#else
+#error unsupported target
+#endif
+ __builtin_unreachable();
+}
+
+#if defined(__x86_64__)
+__attribute__((force_align_arg_pointer))
+#endif
+void _start(void) {
+ do_exit(main());
+}
+EOF_START
+ if ! clang --target="$triple" -O1 -ffreestanding -fno-stack-protector \
+ -fno-PIC -fno-pie -c "$start_c" -o "$start_o" \
+ > "$work/$arch.start.out" 2> "$work/$arch.start.err"; then
+ return 1
+ fi
+ printf '%s' "$start_o"
+}
+
+run_case_cross_one() {
+ local arch="$1" name="$2" src="$3" expected="$4" work="$5"
+ local triple tag obj exe start_obj cc_err ld_err out err rc label
+ triple="$(cross_triple_for "$arch")" || {
+ note_skip "$name/X:$arch" "unknown cross arch"
+ return
+ }
+ tag="$(cross_tag_for "$arch")" || {
+ note_skip "$name/X:$arch" "unknown cross arch"
+ return
+ }
+ label="$name/X:$arch"
+ if [ "$arch" != "aa64" ] && [ "$arch" != "aarch64" ] &&
+ grep -q 'asmnop' "$src"; then
+ note_skip "$label" "asmnop is target-specific before toy asm selectors"
+ return
+ fi
+ if ! exec_target_supported "$tag"; then
+ note_skip "$label" "no runner for $tag"
+ return
+ fi
+
+ obj="$work/$name.$arch.o"
+ exe="$work/$name.$arch.exe"
+ cc_err="$work/$arch.cc.err"
+ ld_err="$work/$arch.ld.err"
+ out="$work/$arch.out"
+ err="$work/$arch.err"
+
+ if ! "$CFREE" cc -target "$triple" -c "$src" -o "$obj" \
+ > "$work/$arch.cc.out" 2> "$cc_err"; then
+ note_fail "$label"
+ printf ' cfree cc -target %s -c failed\n' "$triple"
+ sed 's/^/ | /' "$cc_err"
+ return
+ fi
+ if [ -s "$cc_err" ]; then
+ note_fail "$label"
+ printf ' cfree cc -target %s -c wrote stderr\n' "$triple"
+ sed 's/^/ | /' "$cc_err"
+ return
+ fi
+
+ start_obj="$(cross_make_start_obj "$arch" "$triple" "$work")" || {
+ note_skip "$label" "clang --target=$triple unavailable for startup"
+ return
+ }
+
+ if ! "$CFREE" ld "$obj" "$start_obj" -o "$exe" \
+ > "$work/$arch.ld.out" 2> "$ld_err"; then
+ note_fail "$label"
+ printf ' cfree ld failed\n'
+ sed 's/^/ | /' "$ld_err"
+ return
+ fi
+ if [ -s "$ld_err" ]; then
+ note_fail "$label"
+ printf ' cfree ld wrote stderr\n'
+ sed 's/^/ | /' "$ld_err"
+ return
+ fi
+
+ chmod +x "$exe" 2>/dev/null || true
+ exec_target_run "$tag" "$exe" "$out" "$err"
+ rc=$RUN_RC
+ if [ -s "$err" ]; then
+ grep -v '^WARNING: image platform .* does not match the expected platform' \
+ "$err" > "$err.clean" || true
+ mv "$err.clean" "$err"
+ fi
+ check_rc "$label" "$rc" "$expected" "$err"
+}
+
+run_case_cross() {
+ local name="$1" src="$2" expected="$3" work="$4" arch
+ for arch in $TOY_CROSS_ARCHS; do
+ run_case_cross_one "$arch" "$name" "$src" "$expected" "$work"
+ done
+}
+
if [ ! -x "$CFREE" ]; then
printf 'missing cfree binary: %s\n' "$CFREE" >&2
exit 2
fi
+if [ $RUN_X -eq 1 ]; then
+ have_podman=0
+ command -v podman >/dev/null 2>&1 && have_podman=1
+ QEMU_BIN="${QEMU_BIN:-$(command -v qemu-aarch64 2>/dev/null || true)}"
+ have_qemu=0
+ [ -n "$QEMU_BIN" ] && have_qemu=1
+ case "$(uname -m 2>/dev/null)" in
+ aarch64|arm64) is_aarch64=1 ;;
+ *) is_aarch64=0 ;;
+ esac
+ export have_qemu QEMU_BIN have_podman is_aarch64
+ # shellcheck source=../lib/exec_target.sh
+ source "$ROOT/test/lib/exec_target.sh"
+fi
+
shopt -s nullglob
cases=("$TEST_DIR"/cases/*.toy)
if [ ${#cases[@]} -eq 0 ]; then
@@ -140,9 +303,12 @@ for src in "${cases[@]}"; do
if [ $RUN_L -eq 1 ]; then
run_case_link "$name" "$src" "$expected" "$work"
fi
+ if [ $RUN_X -eq 1 ]; then
+ run_case_cross "$name" "$src" "$expected" "$work"
+ fi
done
-printf '\nResults: %d pass, %d fail\n' "$PASS" "$FAIL"
+printf '\nResults: %d pass, %d fail, %d skip\n' "$PASS" "$FAIL" "$SKIP"
if [ $FAIL -ne 0 ]; then
printf 'Failures:\n'
for name in "${FAIL_NAMES[@]}"; do