kit

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

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:
Mdoc/cg-api-status.md | 324++++++++++++++++++++-----------------------------------------------------------
Minclude/cfree/cg.h | 60+++++++++++++++++++++++++++++++++++++++++++++---------------
Mlang/toy/toy.c | 38+++++++++++++++-----------------------
Msrc/api/cg.c | 469++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mtest/api/cg_type_test.c | 7++++++-
Atest/toy/demo.toy | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/toy/run.sh | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
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