kit

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

commit 7993dec9c21f1f66f79a0050c6579f5dd11d8ff6
parent cd74c03124a6e88ff34e2f142cea6a1905c0871a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat,  9 May 2026 16:59:55 -0700

test/parse: scaffold file-driven C parser harness

Adds test/parse/, a file-driven analogue of test/cg/ that exercises the
full lex+pp+parse_c+cg+emit pipeline on .c source fixtures and checks
test_main's exit code (mod 256) against an .expected sidecar. Parses
nothing today since parse_c/decl_new/cg_new are still stubs; the spine
cases will turn green as those land. Reuses cfree-roundtrip,
link-exe-runner, and jit-runner verbatim. CORPUS.md tracks the §6.x
matrix so subsequent landings flip · → ★ without case-authoring churn.

Diffstat:
Atest/parse/CORPUS.md | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/parse/cases/6_5_01_return_const.c | 1+
Atest/parse/cases/6_5_01_return_const.expected | 1+
Atest/parse/cases/6_5_02_add.c | 1+
Atest/parse/cases/6_5_02_add.expected | 1+
Atest/parse/cases/6_5_03_sub_mul.c | 1+
Atest/parse/cases/6_5_03_sub_mul.expected | 1+
Atest/parse/cases/6_8_01_if_else.c | 8++++++++
Atest/parse/cases/6_8_01_if_else.expected | 1+
Atest/parse/cases/6_8_02_while_sum.c | 8++++++++
Atest/parse/cases/6_8_02_while_sum.expected | 1+
Atest/parse/cases/6_8_03_for_sum.c | 5+++++
Atest/parse/cases/6_8_03_for_sum.expected | 1+
Atest/parse/cases_err/6_5_undeclared.c | 1+
Atest/parse/harness/parse_runner.c | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/parse/run.sh | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/parse/run_errors.sh | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/test.mk | 20++++++++++++++++++--
18 files changed, 1088 insertions(+), 2 deletions(-)

diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -0,0 +1,210 @@ +# C-parser test corpus + +Coverage matrix for `test/parse/`. Each `cases/<name>.c` is one row; the +behavioral oracle is `test_main`'s return value mod 256, identical to +`test/cg/`. Group prefixes track C11 §6.x section numbers; the file +name itself is the case identifier. + +This corpus extends `test/cg/CORPUS.md` upward: cg fixtures drive `cg` +directly with hand-built `Operand`s and prove codegen primitives in +isolation; the parser cases prove the C front-end issues *the right cg +sequence* end-to-end. Anything visible at runtime that the parser is +expected to lower must have a parse-level row here. + +Test paths per case (`run.sh`, sourced from `test/cg/run.sh`): + +- **D** in-process JIT (aarch64 host only) — `parse-runner --jit`. +- **R** ELF roundtrip (host-arch agnostic) — `parse-runner --emit` → + `cfree-roundtrip` → `readelf` + `normalize.py` diff. +- **E** exec via qemu/podman — `parse-runner --emit` + `start.o` → + `link-exe-runner` → run. +- **J** jit-via-file (aarch64 host only) — `parse-runner --emit` → + `jit-runner`. + +`W` (DWARF directives) is reserved; once the parser drives `Debug` it +will read `<name>.dwarf` sidecars and reuse `cg-check-dwarf`. + +Sidecars (each missing file uses the documented default): + +- `<name>.expected` — integer; default `0`. Compared mod 256 to + `test_main`'s return value. +- `<name>.skip` — single-line reason; the runner SKIPs (treated as + failure unless `CFREE_TEST_ALLOW_SKIP=1`). +- `cases_err/<name>.errpat` — optional substring assertion against the + parser's stderr. Negative cases without an errpat assert only that + parse-runner exited nonzero. + +## Status legend + +- ★ landed (case present, parses, exit code matches) +- · planned (case registered, expected value fixed, parser doesn't + accept it yet) +- (deferred) — explicit non-goal for the current pass + +Until the parser lands, every row that exercises a parser feature is +expected to FAIL — the build's `parse_c` stub panics on entry, so paths +D and `--emit` both report nonzero. The corpus is fixed in advance so +each parser-feature landing flips a known set of `·` rows to `★` with no +case authoring required mid-implementation. + +## Spine + +Initial landing — proves the harness wiring, diagnostic flow, and +exit-code oracle end to end. Every spine row reduces to a single +`int test_main(void) { return EXPR; }` with no declarations beyond the +function definition itself. + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_5_01_return_const` | · | `return 42;` | 42 | +| `6_5_02_add` | · | `return 1 + 2;` | 3 | +| `6_5_03_sub_mul` | · | `return 7 * 3 - 4;` | 17 | +| `6_8_01_if_else` | · | `int x; if (1) x = 7; else x = 99; return x;` | 7 | +| `6_8_02_while_sum` | · | `int s=0,i=0; while (i<10) { s+=i; i++; } return s;` | 45 | +| `6_8_03_for_sum` | · | `int s=0; for (int i=1; i<=10; i++) s+=i; return s;` | 55 | + +Negative spine (one case per spec area; expands as the parser learns +to diagnose more): + +| Case | Status | Surface | Notes | +|---|---|---|---| +| `cases_err/6_5_undeclared` | · | identifier resolution | `return q;` — no such identifier | + +## §6.5 Expressions + +Drives the entire expression grammar. One row per production where the +behavior is observable through `test_main`. Spine rows above repeat +here for completeness once they're real cases. + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_5_01_return_const` | · | `return 42;` | 42 | +| `6_5_02_add` | · | `return 1 + 2;` | 3 | +| `6_5_03_sub_mul` | · | `return 7 * 3 - 4;` | 17 | +| `6_5_04_div_mod` | · | `return 23 / 4 + 23 % 4;` | 8 | +| `6_5_05_bitwise_and` | · | `return (~3) & 0xff;` | 252 | +| `6_5_06_bitwise_or_xor` | · | `return (0xa5 ^ 0x5a) & 0xff;` | 255 | +| `6_5_07_shift` | · | `return (1<<5) \| (16>>1);` | 40 | +| `6_5_08_unary_neg` | · | `return -7;` | 249 | +| `6_5_09_logical_not` | · | `return !0 + !!5;` | 2 | +| `6_5_10_cmp_eq` | · | `return (5 == 5) + (5 == 6);` | 1 | +| `6_5_11_cmp_lt` | · | `return (-1 < 1);` | 1 | +| `6_5_12_logical_and_skip` | · | `int s=0; (0) && (s=99); return s;` | 0 | +| `6_5_13_logical_or_skip` | · | `int s=0; (1) \|\| (s=99); return s;` | 0 | +| `6_5_14_ternary` | · | `return (5>3) ? 42 : 7;` | 42 | +| `6_5_15_comma` | · | `int x; return (x=1, x=42, x);` | 42 | +| `6_5_16_assign` | · | `int x; x = 42; return x;` | 42 | +| `6_5_17_compound_assign` | · | `int x = 40; x += 2; return x;` | 42 | +| `6_5_18_pre_inc` | · | `int x = 41; return ++x;` | 42 | +| `6_5_19_post_inc` | · | `int x = 42; x++; return x;` | 43; reads as 43 | +| `6_5_20_addr_deref` | · | `int x = 42; int *p = &x; return *p;` | 42 | +| `6_5_21_sizeof_int` | · | `return (int)sizeof(int);` | 4 | +| `6_5_22_sizeof_expr` | · | `int a[7]; return (int)(sizeof(a)/sizeof(int));` | 7 | +| `6_5_23_cast` | · | `return (int)(unsigned char)(-1);` | 255 | +| `6_5_24_func_call` | · | helper `int id(int x){return x;}` + `return id(42);` | 42 | + +## §6.6 Constant expressions + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_6_01_enum_const` | · | `enum { K = 42 }; return K;` | 42 | +| `6_6_02_const_expr_init` | · | `int x = 1+2*3; return x;` | 7 | +| `6_6_03_array_size_const` | · | `int a[3+4] = {0}; return (int)sizeof a / (int)sizeof a[0];` | 7 | + +## §6.7 Declarations + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_7_01_typedef` | · | `typedef int I; I x = 42; return x;` | 42 | +| `6_7_02_static_local` | · | `static int s = 42; return s;` | 42 | +| `6_7_03_static_global` | · | `static int g = 42; int test_main(void){return g;}` | 42 | +| `6_7_04_extern_resolved` | · | `extern int g; int g = 42; return g;` | 42 | +| `6_7_05_const_qualifier` | · | `const int c = 42; return c;` | 42 | +| `6_7_06_struct_basic` | · | `struct S { int a, b; } s = {10, 32}; return s.a + s.b;` | 42 | +| `6_7_07_union_basic` | · | `union U { int i; char c[4]; } u; u.i = 42; return u.i;` | 42 | +| `6_7_08_enum_basic` | · | `enum E { A = 40, B }; return B + 1;` | 42 | +| `6_7_09_alignof` | · | `return (int)_Alignof(double);` | 8 | + +## §6.7.9 Initialization + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_7_9_01_scalar_init` | · | `int x = 42; return x;` | 42 | +| `6_7_9_02_array_brace` | · | `int a[3] = {10, 20, 12}; return a[0]+a[1]+a[2];` | 42 | +| `6_7_9_03_partial_zero` | · | `int a[5] = {42}; return a[0] + a[4];` | 42 | +| `6_7_9_04_designated` | · | `int a[5] = {[2] = 42}; return a[2];` | 42 | +| `6_7_9_05_struct_init` | · | `struct S {int a,b;} s={40,2}; return s.a+s.b;` | 42 | +| `6_7_9_06_string_init` | · | `char s[] = "hi"; return s[0]+s[1]+s[2];` | 'h'+'i' | + +## §6.8 Statements + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_8_01_if_else` | · | `int x; if (1) x=7; else x=99; return x;` | 7 | +| `6_8_02_while_sum` | · | sum 0..9 with `while` | 45 | +| `6_8_03_for_sum` | · | sum 1..10 with `for` | 55 | +| `6_8_04_do_while` | · | `int i=0; do { i=42; } while (0); return i;` | 42 | +| `6_8_05_break` | · | `for (i=0;;i++) if (i==42) break; return i;` | 42 | +| `6_8_06_continue` | · | sum of evens in `[0,20)` via `continue` | 90 | +| `6_8_07_switch_case` | · | three-arm switch returns 42 on case 2 | 42 | +| `6_8_08_switch_fallthrough` | · | `case 1: r+=10; case 2: r+=20;` on input 1 | 30 | +| `6_8_09_switch_default` | · | unmatched switch hits `default` | 7 | +| `6_8_10_goto_forward` | · | `goto L; r=99; L: return 42;` | 42 | +| `6_8_11_goto_backward` | · | counter loop built with `goto` | 10 | +| `6_8_12_block_scope` | · | inner `{ int x=42; }` shadows outer | 42 | +| `6_8_13_compound_decl_mix` | · | declarations interleaved with statements (C99) | 42 | +| `6_8_14_return_void` | · | `void f(void){return;}; f(); return 42;` | 42 | +| `6_8_15_null_statement` | · | `for (int i=0;i<42;i++) ; return i;` | 42 | + +## §6.9 External definitions + +| Case | Status | Body | Expected | +|---|---|---|---| +| `6_9_01_two_functions` | · | helper + caller in one TU | 42 | +| `6_9_02_recursive_function` | · | `factorial(5)` | 120 | +| `6_9_03_tentative_def` | · | file-scope `int g;` (tentative) + use | 0 | +| `6_9_04_static_func` | · | `static int helper(...)` + caller | 42 | +| `6_9_05_proto_then_def` | · | forward declaration before body | 42 | + +## Builtins + +The freestanding runtime advertises a handful of `__builtin_*` entries +(see `doc/builtins.md`). Cases here verify the parser routes them +through `cg`'s intrinsic / asm machinery rather than treating them as +ordinary calls. + +| Case | Status | Body | Expected | +|---|---|---|---| +| `builtin_01_alloca` | · | `int *p = __builtin_alloca(4); *p=42; return *p;` | 42 | +| `builtin_02_expect` | · | `if (__builtin_expect(1, 1)) return 42; return 0;` | 42 | + +## Negative cases (cases_err/) + +| Case | Status | Surface | Notes | +|---|---|---|---| +| `6_5_undeclared` | · | identifier resolution | `return q;` | +| `6_5_lvalue_required` | · | assignment LHS | `1 = 2;` | +| `6_5_type_mismatch` | · | implicit conversion | assigning `int*` to `int` | +| `6_7_redefinition` | · | linkage / scope | two `int x = …` at file scope | +| `6_8_break_outside_loop` | · | break/continue scope | `break;` outside iteration | +| `6_9_redefinition_function` | · | external definition | two definitions of `f` | + +## Multi-TU (deferred) + +Multi-TU cases live under `cases/<name>/{a.c,b.c}` (test/link convention) +and exercise inter-TU resolution. Deferred until the single-TU corpus is +green; the harness already builds an `ObjBuilder*` per TU, so wiring is +mainly extending `run.sh` to compile each file in a case directory and +hand both objects to `link_add_obj`. + +## Non-goals + +- **Snapshot/golden assembly.** Behavioral exit codes only, matching + `test/cg`. Encoding lock-ins land surgically when a specific + instruction-selection guarantee matters. +- **Diagnostic golden output.** `cases_err/` only asserts nonzero exit + plus optional substring pattern. Full diagnostic snapshot tests are a + separate effort. +- **Cross-arch.** AArch64 only; the harness selects target via + `target_aarch64_linux` in `parse_runner.c`. Future x86_64 / RISC-V + passes duplicate the runner with the target swapped. diff --git a/test/parse/cases/6_5_01_return_const.c b/test/parse/cases/6_5_01_return_const.c @@ -0,0 +1 @@ +int test_main(void) { return 42; } diff --git a/test/parse/cases/6_5_01_return_const.expected b/test/parse/cases/6_5_01_return_const.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_02_add.c b/test/parse/cases/6_5_02_add.c @@ -0,0 +1 @@ +int test_main(void) { return 1 + 2; } diff --git a/test/parse/cases/6_5_02_add.expected b/test/parse/cases/6_5_02_add.expected @@ -0,0 +1 @@ +3 diff --git a/test/parse/cases/6_5_03_sub_mul.c b/test/parse/cases/6_5_03_sub_mul.c @@ -0,0 +1 @@ +int test_main(void) { return 7 * 3 - 4; } diff --git a/test/parse/cases/6_5_03_sub_mul.expected b/test/parse/cases/6_5_03_sub_mul.expected @@ -0,0 +1 @@ +17 diff --git a/test/parse/cases/6_8_01_if_else.c b/test/parse/cases/6_8_01_if_else.c @@ -0,0 +1,8 @@ +int test_main(void) { + int x; + if (1) + x = 7; + else + x = 99; + return x; +} diff --git a/test/parse/cases/6_8_01_if_else.expected b/test/parse/cases/6_8_01_if_else.expected @@ -0,0 +1 @@ +7 diff --git a/test/parse/cases/6_8_02_while_sum.c b/test/parse/cases/6_8_02_while_sum.c @@ -0,0 +1,8 @@ +int test_main(void) { + int s = 0, i = 0; + while (i < 10) { + s += i; + i++; + } + return s; +} diff --git a/test/parse/cases/6_8_02_while_sum.expected b/test/parse/cases/6_8_02_while_sum.expected @@ -0,0 +1 @@ +45 diff --git a/test/parse/cases/6_8_03_for_sum.c b/test/parse/cases/6_8_03_for_sum.c @@ -0,0 +1,5 @@ +int test_main(void) { + int s = 0; + for (int i = 1; i <= 10; i++) s += i; + return s; +} diff --git a/test/parse/cases/6_8_03_for_sum.expected b/test/parse/cases/6_8_03_for_sum.expected @@ -0,0 +1 @@ +55 diff --git a/test/parse/cases_err/6_5_undeclared.c b/test/parse/cases_err/6_5_undeclared.c @@ -0,0 +1 @@ +int test_main(void) { return q; } diff --git a/test/parse/harness/parse_runner.c b/test/parse/harness/parse_runner.c @@ -0,0 +1,436 @@ +/* parse-runner — file-driven C front-end test runner. + * + * parse-runner --emit FILE.c OUT.o # full pipeline → ELF .o + * parse-runner --jit FILE.c # full pipeline → JIT, call test_main + * + * Exclusively uses the public cfree.h surface: this is the same path real + * driver consumers take. Built once; the shell runner walks + * `test/parse/cases` for `.c` files and invokes one of the two modes per + * case. The JIT + * mode's exit status is `test_main`'s return value (mod 256); the emit + * mode's exit status is 0 on success and nonzero on any failure (the + * shell harness compares the resulting .o against the expected exit code + * via the cg/link harness binaries cfree-roundtrip / link-exe-runner / + * jit-runner). + * + * The execmem boilerplate mirrors test/cg/harness/cg_runner.c — strict + * W^X dual mapping on Apple/Linux, single mapping elsewhere. */ + +#include <cfree.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +/* ---- env: heap, diag ---- */ + +static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { + (void)h; + (void)a; + return n ? malloc(n) : NULL; +} +static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) { + (void)h; + (void)o; + (void)a; + return realloc(p, n); +} +static void h_free(CfreeHeap* h, void* p, size_t n) { + (void)h; + (void)n; + free(p); +} +static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; + +static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, + const char* fmt, va_list ap) { + static const char* names[] = {"note", "warning", "error", "fatal"}; + (void)s; + fprintf(stderr, "[%u]:%u:%u: %s: ", + loc.file_id, loc.line, loc.col, names[k]); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); +} +static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0}; + +/* ---- env: execmem (W^X) — copied verbatim from cg_runner ---- */ + +#if defined(__APPLE__) +#include <mach/mach.h> +#include <mach/mach_vm.h> +#define XM_DUAL_APPLE 1 +#else +#define XM_DUAL_APPLE 0 +#endif +#if defined(__linux__) +#include <sys/syscall.h> +#define XM_DUAL_LINUX 1 +#else +#define XM_DUAL_LINUX 0 +#endif + +static int xm_to_posix(int p) { + int q = 0; + if (p & CFREE_PROT_READ) q |= PROT_READ; + if (p & CFREE_PROT_WRITE) q |= PROT_WRITE; + if (p & CFREE_PROT_EXEC) q |= PROT_EXEC; + return q; +} +typedef struct XmTok { + void* w; + void* r; + size_t n; +} XmTok; +static int xm_reserve_single(size_t n, CfreeExecMemRegion* out) { + void* p = + mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (p == MAP_FAILED) return 1; + out->write = out->runtime = p; + out->size = n; + out->token = NULL; + return 0; +} +static int xm_reserve(void* u, size_t n, int p, CfreeExecMemRegion* out) { + (void)u; + if (!out || !n) return 1; + if (!(p & CFREE_PROT_EXEC)) return xm_reserve_single(n, out); +#if XM_DUAL_APPLE + { + void* w = + mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + mach_vm_address_t r = 0; + vm_prot_t cur = 0, max = 0; + XmTok* tok; + if (w == MAP_FAILED) return 1; + if (mach_vm_remap(mach_task_self(), &r, (mach_vm_size_t)n, 0, + VM_FLAGS_ANYWHERE, mach_task_self(), + (mach_vm_address_t)(uintptr_t)w, FALSE, &cur, &max, + VM_INHERIT_NONE) != KERN_SUCCESS) { + munmap(w, n); + return 1; + } + if (mprotect((void*)(uintptr_t)r, n, PROT_READ) != 0) { + munmap((void*)(uintptr_t)r, n); + munmap(w, n); + return 1; + } + tok = (XmTok*)malloc(sizeof(*tok)); + if (!tok) { + munmap((void*)(uintptr_t)r, n); + munmap(w, n); + return 1; + } + tok->w = w; + tok->r = (void*)(uintptr_t)r; + tok->n = n; + out->write = w; + out->runtime = (void*)(uintptr_t)r; + out->size = n; + out->token = tok; + return 0; + } +#elif XM_DUAL_LINUX + { + int fd = (int)syscall(SYS_memfd_create, "cfree-jit-test", 0u); + void *w, *r; + XmTok* tok; + if (fd < 0) return 1; + if (ftruncate(fd, (off_t)n) != 0) { + close(fd); + return 1; + } + w = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (w == MAP_FAILED) { + close(fd); + return 1; + } + r = mmap(NULL, n, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (r == MAP_FAILED) { + munmap(w, n); + return 1; + } + tok = (XmTok*)malloc(sizeof(*tok)); + if (!tok) { + munmap(r, n); + munmap(w, n); + return 1; + } + tok->w = w; + tok->r = r; + tok->n = n; + out->write = w; + out->runtime = r; + out->size = n; + out->token = tok; + return 0; + } +#else + return xm_reserve_single(n, out); +#endif +} +static int xm_protect(void* u, void* a, size_t n, int p) { + (void)u; + return mprotect(a, n, xm_to_posix(p)); +} +static void xm_release(void* u, CfreeExecMemRegion* region) { + (void)u; + if (!region || !region->size) return; + if (region->token) { + XmTok* tok = (XmTok*)region->token; + if (tok->r && tok->r != tok->w) munmap(tok->r, tok->n); + if (tok->w) munmap(tok->w, tok->n); + free(tok); + } else if (region->write) { + munmap(region->write, region->size); + } + region->write = region->runtime = NULL; + region->size = 0; + region->token = NULL; +} +static void xm_flush(void* u, void* a, size_t n) { + (void)u; +#if defined(__aarch64__) || defined(__arm__) + __builtin___clear_cache((char*)a, (char*)a + n); +#else + (void)a; + (void)n; +#endif +} +static CfreeExecMem g_execmem = { + 16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL, +}; + +/* ---- helpers ---- */ + +static void target_aarch64_linux(CfreeTarget* t) { + memset(t, 0, sizeof *t); + t->arch = CFREE_ARCH_ARM_64; + t->os = CFREE_OS_LINUX; + t->obj = CFREE_OBJ_ELF; + t->ptr_size = 8; + t->ptr_align = 8; + t->big_endian = 0; +} + +static int read_file(const char* path, uint8_t** out, size_t* out_len) { + FILE* f = fopen(path, "rb"); + long n; + uint8_t* buf; + size_t got; + if (!f) return 1; + if (fseek(f, 0, SEEK_END) != 0) { + fclose(f); + return 1; + } + n = ftell(f); + if (n < 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return 1; + } + buf = (uint8_t*)malloc((size_t)n + 1); + if (!buf) { + fclose(f); + return 1; + } + got = fread(buf, 1, (size_t)n, f); + fclose(f); + if (got != (size_t)n) { + free(buf); + return 1; + } + buf[n] = 0; + *out = buf; + *out_len = (size_t)n; + return 0; +} + +static void env_init(CfreeEnv* env) { + memset(env, 0, sizeof *env); + env->heap = &g_heap; + env->diag = &g_diag; + env->execmem = &g_execmem; + env->now = -1; +} + +/* ---- modes ---- */ + +static int mode_emit(const char* src_path, const char* out_path) { + uint8_t* src = NULL; + size_t src_len = 0; + CfreeTarget tgt; + CfreeEnv env; + CfreeCompiler* c; + CfreeBytesInput in; + CfreeCompileOptions opts; + CfreeWriter* w; + int rc = 0; + size_t len = 0; + const uint8_t* data; + int fd; + + if (read_file(src_path, &src, &src_len)) { + fprintf(stderr, "parse-runner: cannot read %s\n", src_path); + return 2; + } + target_aarch64_linux(&tgt); + env_init(&env); + c = cfree_compiler_new(tgt, &env); + if (!c) { + free(src); + return 2; + } + + memset(&in, 0, sizeof in); + in.name = src_path; + in.data = src; + in.len = src_len; + in.lang = CFREE_LANG_C; + + memset(&opts, 0, sizeof opts); + + w = cfree_writer_mem(&g_heap); + if (cfree_compile_obj_emit(c, &opts, &in, w) != 0) { + cfree_writer_close(w); + cfree_compiler_free(c); + free(src); + return 1; + } + + data = cfree_writer_mem_bytes(w, &len); + fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror(out_path); + rc = 2; + } else { + size_t off = 0; + while (off < len) { + ssize_t k = write(fd, data + off, len - off); + if (k <= 0) { + perror("write"); + rc = 2; + break; + } + off += (size_t)k; + } + close(fd); + } + cfree_writer_close(w); + cfree_compiler_free(c); + free(src); + return rc; +} + +/* On AArch64 host, set up TLS Local-Exec image before invoking JITed + * code. Mirrors test/cg/harness/cg_runner.c: msr → call() must be + * back-to-back with no libc invocations in between (Darwin libc clobbers + * TPIDR_EL0 via the dyld stub binder). Cases that don't use TLS see the + * lookups fail and the block stays zeroed — harmless. */ +#if defined(__aarch64__) || defined(__arm64__) +static char g_tls_block[8192] __attribute__((aligned(16))); +#endif + +static int mode_jit(const char* src_path) { + uint8_t* src = NULL; + size_t src_len = 0; + CfreeTarget tgt; + CfreeEnv env; + CfreeCompiler* c; + CfreeBytesInput in; + CfreeCompileOptions opts; + CfreeObjBuilder* ob = NULL; + CfreeLinkOptions lopts; + CfreeObjBuilder* arr[1]; + CfreeJit* jit = NULL; + int (*fn)(void); + int result; + + if (read_file(src_path, &src, &src_len)) { + fprintf(stderr, "parse-runner: cannot read %s\n", src_path); + return 2; + } + target_aarch64_linux(&tgt); + env_init(&env); + c = cfree_compiler_new(tgt, &env); + if (!c) { + free(src); + return 2; + } + + memset(&in, 0, sizeof in); + in.name = src_path; + in.data = src; + in.len = src_len; + in.lang = CFREE_LANG_C; + memset(&opts, 0, sizeof opts); + + if (cfree_compile_obj(c, &opts, &in, &ob) != 0 || !ob) { + cfree_compiler_free(c); + free(src); + return 1; + } + + memset(&lopts, 0, sizeof lopts); + arr[0] = ob; + lopts.inputs.objs = arr; + lopts.inputs.nobjs = 1; + lopts.inputs.entry = "test_main"; + + if (cfree_link_jit(c, &lopts, &jit) != 0 || !jit) { + cfree_compiler_free(c); + free(src); + return 1; + } + + fn = (int (*)(void))cfree_jit_lookup(jit, "test_main"); + +#if defined(__aarch64__) || defined(__arm64__) + { + char* td_start = (char*)cfree_jit_lookup(jit, "__tdata_start"); + char* td_end = (char*)cfree_jit_lookup(jit, "__tdata_end"); + unsigned long bs_n = + (unsigned long)(unsigned long long)cfree_jit_lookup(jit, "__tbss_size"); + if (td_start && td_end) { + unsigned long td_n = (unsigned long)(td_end - td_start); + unsigned long i; + for (i = 0; i < td_n; ++i) g_tls_block[16 + i] = td_start[i]; + for (i = 0; i < bs_n; ++i) g_tls_block[16 + td_n + i] = 0; + } + } +#endif + + if (fn) { +#if defined(__aarch64__) || defined(__arm64__) + __asm__ volatile("msr tpidr_el0, %0" ::"r"(g_tls_block) : "memory"); +#endif + result = fn(); + } else { + result = 1; + } + + cfree_jit_free(jit); + cfree_compiler_free(c); + free(src); + return result; +} + +static int usage(void) { + fprintf(stderr, + "usage: parse-runner --emit FILE.c OUT.o\n" + " parse-runner --jit FILE.c\n"); + return 2; +} + +int main(int argc, char** argv) { + long ps = sysconf(_SC_PAGESIZE); + if (ps > 0) g_execmem.page_size = (size_t)ps; + if (argc < 2) return usage(); + if (!strcmp(argv[1], "--emit") && argc == 4) return mode_emit(argv[2], argv[3]); + if (!strcmp(argv[1], "--jit") && argc == 3) return mode_jit(argv[2]); + return usage(); +} diff --git a/test/parse/run.sh b/test/parse/run.sh @@ -0,0 +1,325 @@ +#!/usr/bin/env bash +# test/parse/run.sh — file-driven C-parser test harness. +# +# For each test/parse/cases/*.c, runs up to four paths (the test/cg path +# matrix minus W; DWARF directives may be added later via .dwarf sidecars): +# +# D in-process JIT — parse-runner --jit FILE.c → exit code matches +# expected. No file I/O. aarch64 host only. +# R ELF roundtrip — parse-runner --emit + cfree-roundtrip + readelf +# normalize diff. Validates emitter+reader fidelity. +# E exec via qemu — parse-runner --emit + start.o → link-exe-runner → +# qemu/podman → exit code. Cross-host friendly. +# J jit-via-file — parse-runner --emit + jit-runner. aarch64 host. +# +# Reuses the test/link harness binaries (cfree-roundtrip, link-exe-runner, +# jit-runner) and test/link/harness/start.c verbatim. +# +# Sidecar conventions (each missing file uses the documented default): +# <name>.expected — integer; default 0. Compared mod 256 to test_main. +# <name>.skip — single-line reason. Treated as failure unless +# CFREE_TEST_ALLOW_SKIP=1 (matching the rest of +# the test suite). +# +# Filtering: +# ./run.sh [name_filter] [paths] +# name_filter substring match against case basename +# paths subset of "DREJ" (default "DREJ") +# Equivalent env vars: CFREE_TEST_FILTER, CFREE_TEST_PATHS. + +set -u + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +TEST_DIR="$ROOT/test/parse" +LINK_TEST_DIR="$ROOT/test/link" +BUILD_DIR="$ROOT/build/test" +LIB_AR="$ROOT/build/libcfree.a" + +PARSE_RUNNER="$BUILD_DIR/parse-runner" +ROUNDTRIP_BIN="$BUILD_DIR/cfree-roundtrip" +LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner" +JIT_RUNNER="$BUILD_DIR/jit-runner" +NORMALIZE="$ROOT/test/elf/normalize.py" + +CLANG_TARGET="--target=aarch64-linux-gnu" +CC="${CC:-cc}" +HARNESS_CFLAGS="-std=c11 -Wall -Wextra -I$ROOT/include" +ALLOW_SKIP="${CFREE_TEST_ALLOW_SKIP:-0}" + +FILTER="${1:-${CFREE_TEST_FILTER:-}}" +PATHS="${2:-${CFREE_TEST_PATHS:-DREJ}}" +case "$PATHS" in *D*) RUN_D=1;; *) RUN_D=0;; esac +case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac +case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac +case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac +T_D=0; T_R=0; T_E=0; T_J=0 +now_ms() { python3 -c 'import time;print(int(time.time()*1000))'; } + +mkdir -p "$BUILD_DIR" "$BUILD_DIR/parse" + +PASS=0; FAIL=0; SKIP=0 +FAIL_NAMES=(); SKIP_NAMES=() + +color_red() { printf '\033[31m%s\033[0m' "$1"; } +color_grn() { printf '\033[32m%s\033[0m' "$1"; } +color_yel() { printf '\033[33m%s\033[0m' "$1"; } + +note_pass() { PASS=$((PASS+1)); printf ' %s %s\n' "$(color_grn PASS)" "$1"; } +note_fail() { FAIL=$((FAIL+1)); FAIL_NAMES+=("$1"); printf ' %s %s\n' "$(color_red FAIL)" "$1"; } +note_skip() { SKIP=$((SKIP+1)); SKIP_NAMES+=("$1"); printf ' %s %s — %s\n' "$(color_yel SKIP)" "$1" "$2"; } + +# ---- tool detection (mirrors test/cg/run.sh) ------------------------------- + +have_clang_cross=0 +have_readelf=0 +have_python3=0 +have_qemu=0 +have_podman=0 +have_runner=0 +have_roundtrip=0 +have_exe_runner=0 +have_jit_runner=0 +is_aarch64=0 + +if clang $CLANG_TARGET -c -x c - -o /dev/null < /dev/null 2>/dev/null; then + have_clang_cross=1 +fi +command -v llvm-readelf >/dev/null 2>&1 && have_readelf=1 +command -v readelf >/dev/null 2>&1 && have_readelf=1 +command -v python3 >/dev/null 2>&1 && have_python3=1 + +QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)" +[ -n "$QEMU_BIN" ] && have_qemu=1 +command -v podman >/dev/null 2>&1 && have_podman=1 +{ [ $have_qemu -eq 1 ] || [ $have_podman -eq 1 ]; } && have_runner=1 + +arch_raw="$(uname -m 2>/dev/null || true)" +{ [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1 + +READELF_BIN="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)" +RUN_AARCH64_IMAGE="${RUN_AARCH64_IMAGE:-alpine:latest}" + +run_aarch64() { + local exe="$1" out="$2" err="$3" + if [ $have_qemu -eq 1 ]; then + "$QEMU_BIN" "$exe" >"$out" 2>"$err"; RUN_RC=$?; return + fi + if [ $have_podman -eq 1 ]; then + local dir base platform_flag=() + dir="$(cd "$(dirname "$exe")" && pwd)"; base="$(basename "$exe")" + [ $is_aarch64 -eq 0 ] && platform_flag=(--platform linux/arm64) + podman run --rm "${platform_flag[@]}" --net=none \ + -v "$dir":/work:Z -w /work "$RUN_AARCH64_IMAGE" "./$base" >"$out" 2>"$err" + RUN_RC=$?; return + fi + RUN_RC=127 +} + +# ---- build harness binaries ------------------------------------------------ + +printf 'Building harness...\n' + +if [ ! -f "$LIB_AR" ]; then + printf ' FATAL: %s not found — run "make lib" first\n' "$LIB_AR" >&2 + exit 1 +fi + +# parse-runner +if $CC $HARNESS_CFLAGS \ + "$TEST_DIR/harness/parse_runner.c" \ + "$LIB_AR" -o "$PARSE_RUNNER" 2>"$BUILD_DIR/parse-runner.err"; then + printf ' %s parse-runner\n' "$(color_grn built)" +else + printf ' %s parse-runner (see %s)\n' \ + "$(color_red FATAL)" "$BUILD_DIR/parse-runner.err" >&2 + exit 1 +fi + +# cfree-roundtrip — for path R. +if [ ! -x "$ROUNDTRIP_BIN" ]; then + if $CC -I"$ROOT/include" "$ROOT/test/elf/cfree-roundtrip.c" "$LIB_AR" \ + -o "$ROUNDTRIP_BIN" 2>"$BUILD_DIR/cfree-roundtrip.err"; then + have_roundtrip=1 + printf ' %s cfree-roundtrip\n' "$(color_grn built)" + else + printf ' %s cfree-roundtrip (see %s)\n' \ + "$(color_yel warn)" "$BUILD_DIR/cfree-roundtrip.err" >&2 + fi +else + have_roundtrip=1 +fi + +# link-exe-runner — for path E. +if [ ! -x "$LINK_EXE_RUNNER" ]; then + if $CC -I"$ROOT/include" "$LINK_TEST_DIR/harness/link_exe_runner.c" \ + "$LIB_AR" -o "$LINK_EXE_RUNNER" 2>"$BUILD_DIR/link-exe-runner.err"; then + have_exe_runner=1 + printf ' %s link-exe-runner\n' "$(color_grn built)" + else + printf ' %s link-exe-runner (see %s)\n' \ + "$(color_yel warn)" "$BUILD_DIR/link-exe-runner.err" >&2 + fi +else + have_exe_runner=1 +fi + +# jit-runner — for path J. Only on aarch64 host. +if [ $is_aarch64 -eq 1 ]; then + if [ ! -x "$JIT_RUNNER" ]; then + if $CC -I"$ROOT/include" "$LINK_TEST_DIR/harness/jit_runner.c" \ + "$LIB_AR" -o "$JIT_RUNNER" 2>"$BUILD_DIR/jit-runner.err"; then + have_jit_runner=1 + printf ' %s jit-runner\n' "$(color_grn built)" + else + printf ' %s jit-runner (see %s)\n' \ + "$(color_yel warn)" "$BUILD_DIR/jit-runner.err" >&2 + fi + else + have_jit_runner=1 + fi +fi + +printf 'Running cases...\n' + +# ---- per-case loop --------------------------------------------------------- + +CASES=() +for src in "$TEST_DIR"/cases/*.c; do + [ -e "$src" ] || continue + CASES+=("$src") +done + +for src in "${CASES[@]}"; do + name="$(basename "$src" .c)" + [ -n "$FILTER" ] && [[ "$name" != *"$FILTER"* ]] && continue + work="$BUILD_DIR/parse/$name" + mkdir -p "$work" + + # Skip sidecar + if [ -e "$TEST_DIR/cases/$name.skip" ]; then + reason=$(head -n1 "$TEST_DIR/cases/$name.skip") + note_skip "$name" "$reason" + continue + fi + + # Expected exit code (default 0) + expected=0 + if [ -e "$TEST_DIR/cases/$name.expected" ]; then + expected=$(head -n1 "$TEST_DIR/cases/$name.expected") + fi + expected_byte=$(( expected & 0xff )) + + # ---- Path D: in-process JIT (aarch64 only) --------------------------- + if [ $RUN_D -eq 1 ]; then + if [ $is_aarch64 -eq 1 ]; then + t0=$(now_ms) + "$PARSE_RUNNER" --jit "$src" >"$work/d.out" 2>"$work/d.err" + d_rc=$? + dt=$(( $(now_ms) - t0 )); T_D=$(( T_D + dt )) + if [ "$d_rc" -eq "$expected_byte" ]; then + note_pass "$name/D (${dt}ms)" + else + note_fail "$name/D (expected $expected_byte got $d_rc, ${dt}ms)" + fi + else + note_skip "$name/D" "not on aarch64 host" + fi + fi + + # ---- emit (needed by R/E/J) ------------------------------------------ + obj="$work/$name.o" + if [ $RUN_R -eq 1 ] || [ $RUN_E -eq 1 ] || [ $RUN_J -eq 1 ]; then + if ! "$PARSE_RUNNER" --emit "$src" "$obj" 2>"$work/emit.err"; then + note_fail "$name/emit (parse-runner --emit failed; see $work/emit.err)" + continue + fi + fi + + # ---- Path R: ELF roundtrip ------------------------------------------- + if [ $RUN_R -eq 1 ]; then + if [ $have_roundtrip -eq 1 ] && [ $have_readelf -eq 1 ] && [ $have_python3 -eq 1 ]; then + t0=$(now_ms) + rt="$work/$name.rt.o" + r_ok=1; r_msg="" + if ! "$ROUNDTRIP_BIN" "$obj" "$rt" 2>"$work/rt.err"; then + r_ok=0; r_msg=" (roundtrip failed)" + else + "$READELF_BIN" -aW "$obj" | python3 "$NORMALIZE" >"$work/golden.norm" 2>/dev/null + "$READELF_BIN" -aW "$rt" | python3 "$NORMALIZE" >"$work/rt.norm" 2>/dev/null + diff -u "$work/golden.norm" "$work/rt.norm" >"$work/r.diff" 2>&1 || r_ok=0 + fi + dt=$(( $(now_ms) - t0 )); T_R=$(( T_R + dt )) + if [ $r_ok -eq 1 ]; then note_pass "$name/R (${dt}ms)" + else note_fail "$name/R${r_msg} (${dt}ms)"; fi + else + note_skip "$name/R" "missing roundtrip/readelf/python3" + fi + fi + + # ---- Path E: link + qemu/podman -------------------------------------- + if [ $RUN_E -eq 1 ]; then + if [ $have_exe_runner -eq 1 ] && [ $have_clang_cross -eq 1 ]; then + t0=$(now_ms) + start_obj="$work/start.o" + clang $CLANG_TARGET -O1 -ffreestanding -fno-stack-protector \ + -fno-PIC -fno-pie \ + -c "$LINK_TEST_DIR/harness/start.c" -o "$start_obj" 2>/dev/null + + exe="$work/linked.exe" + if ! "$LINK_EXE_RUNNER" -o "$exe" "$obj" "$start_obj" \ + >"$work/exec_link.out" 2>"$work/exec_link.err"; then + dt=$(( $(now_ms) - t0 )); T_E=$(( T_E + dt )) + note_fail "$name/E (link failed, ${dt}ms)" + elif [ $have_runner -eq 1 ]; then + run_aarch64 "$exe" "$work/exec.out" "$work/exec.err" + dt=$(( $(now_ms) - t0 )); T_E=$(( T_E + dt )) + if [ "$RUN_RC" -eq "$expected_byte" ]; then + note_pass "$name/E (${dt}ms)" + else + note_fail "$name/E (expected $expected_byte got $RUN_RC, ${dt}ms)" + fi + else + note_skip "$name/E" "no qemu/podman" + fi + else + note_skip "$name/E" "no link-exe-runner or aarch64 clang" + fi + fi + + # ---- Path J: jit-via-file -------------------------------------------- + if [ $RUN_J -eq 1 ]; then + if [ $have_jit_runner -eq 1 ]; then + t0=$(now_ms) + "$JIT_RUNNER" "$obj" >"$work/jit.out" 2>"$work/jit.err" + j_rc=$? + dt=$(( $(now_ms) - t0 )); T_J=$(( T_J + dt )) + if [ "$j_rc" -eq "$expected_byte" ]; then + note_pass "$name/J (${dt}ms)" + else + note_fail "$name/J (expected $expected_byte got $j_rc, ${dt}ms)" + fi + else + note_skip "$name/J" "no jit-runner (not aarch64 host)" + fi + fi +done + +# ---- summary --------------------------------------------------------------- + +printf '\nResults: %s pass, %s fail, %s skip\n' "$PASS" "$FAIL" "$SKIP" +printf 'Time: D=%dms R=%dms E=%dms J=%dms\n' \ + "$T_D" "$T_R" "$T_E" "$T_J" + +if [ ${#FAIL_NAMES[@]} -gt 0 ]; then + printf 'Failed:\n' + for n in "${FAIL_NAMES[@]}"; do printf ' %s\n' "$n"; done +fi + +if [ ${#SKIP_NAMES[@]} -gt 0 ] && [ "$ALLOW_SKIP" != "1" ]; then + printf 'Skipped (treat as failure; set CFREE_TEST_ALLOW_SKIP=1 to allow):\n' + for n in "${SKIP_NAMES[@]}"; do printf ' %s\n' "$n"; done +fi + +if [ $FAIL -gt 0 ]; then exit 1; fi +if [ $SKIP -gt 0 ] && [ "$ALLOW_SKIP" != "1" ]; then exit 1; fi +exit 0 diff --git a/test/parse/run_errors.sh b/test/parse/run_errors.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# test/parse/run_errors.sh — file-driven negative test runner. +# +# For each test/parse/cases_err/*.c, runs `parse-runner --emit FILE.c +# /dev/null` and expects a nonzero exit (the parser must diagnose the +# constraint violation / syntax error). Mirrors test/pp/run_errors.sh. +# +# Optional sidecar: +# <name>.errpat — substring of stderr that must be present. If +# missing, only exit status is checked. + +set -u + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +TEST_DIR="$ROOT/test/parse" +BUILD_DIR="$ROOT/build/test" + +PARSE_RUNNER="$BUILD_DIR/parse-runner" + +if [ ! -x "$PARSE_RUNNER" ]; then + echo "parse-err: parse-runner not built at $PARSE_RUNNER (run test-parse once first)" >&2 + exit 2 +fi + +cd "$TEST_DIR/cases_err" 2>/dev/null || { + echo "parse-err: no cases_err directory; nothing to test" + exit 0 +} + +pass=0 +fail=0 +failures= + +for src in *.c; do + [ -e "$src" ] || continue + name="${src%.c}" + pat_file="$name.errpat" + err_log="$BUILD_DIR/parse/$name.err.log" + mkdir -p "$BUILD_DIR/parse" + + if "$PARSE_RUNNER" --emit "$src" /dev/null >/dev/null 2>"$err_log"; then + printf 'FAIL %s (expected nonzero exit, got success)\n' "$name" + fail=$((fail + 1)) + failures="$failures $name" + continue + fi + + if [ -e "$pat_file" ]; then + pat=$(head -n1 "$pat_file") + if ! grep -qF -- "$pat" "$err_log"; then + printf 'FAIL %s (stderr missing pattern: %s)\n' "$name" "$pat" + cat "$err_log" + fail=$((fail + 1)) + failures="$failures $name" + continue + fi + fi + + printf 'PASS %s\n' "$name" + pass=$((pass + 1)) +done + +total=$((pass + fail)) +printf '\nparse-err: %d/%d passed\n' "$pass" "$total" +if [ "$fail" -gt 0 ]; then + printf 'parse-err: failures:%s\n' "$failures" + exit 1 +fi diff --git a/test/test.mk b/test/test.mk @@ -18,10 +18,15 @@ # - test-cg: cg / CGTarget / MCEmitter behavioral harness in test/cg/; # four paths per case (D direct-JIT, R roundtrip, E exec, J jit-via-file). # Depends only on libcfree.a; reuses test/link harness binaries. +# - test-parse / test-parse-err: file-driven C parser harness in +# test/parse/; same path matrix as test-cg, but each case is a .c +# source file rather than a hand-built ObjBuilder fixture. Built +# against the public cfree.h surface; reuses cfree-roundtrip, +# link-exe-runner, and jit-runner. -.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-musl test-lib-deps +.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-parse test-parse-err test-musl test-lib-deps -test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-lib-deps +test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-parse test-parse-err test-lib-deps test-lex: bin @CFREE=$(abspath $(BIN)) test/lex/run.sh @@ -63,6 +68,7 @@ HARNESS_CFLAGS = -std=c11 -Wall -Wextra -Werror -isysroot $(SYSROOT) -Iinclude ROUNDTRIP_BIN = build/test/cfree-roundtrip LINK_EXE_RUNNER = build/test/link-exe-runner JIT_RUNNER = build/test/jit-runner +PARSE_RUNNER = build/test/parse-runner # cfree-roundtrip needs `-Isrc` for the internal obj.h surface it inspects. $(ROUNDTRIP_BIN): test/elf/cfree-roundtrip.c $(LIB_AR) @@ -77,6 +83,10 @@ $(JIT_RUNNER): test/link/harness/jit_runner.c $(LIB_AR) @mkdir -p $(dir $@) $(CC) $(HARNESS_CFLAGS) test/link/harness/jit_runner.c $(LIB_AR) -o $@ +$(PARSE_RUNNER): test/parse/harness/parse_runner.c $(LIB_AR) + @mkdir -p $(dir $@) + $(CC) $(HARNESS_CFLAGS) test/parse/harness/parse_runner.c $(LIB_AR) -o $@ + test-elf: lib bin-soft $(ROUNDTRIP_BIN) bash test/elf/run.sh @@ -86,6 +96,12 @@ test-link: lib $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) test-cg: lib $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/cg/run.sh +test-parse: lib $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) + bash test/parse/run.sh + +test-parse-err: lib $(PARSE_RUNNER) + sh test/parse/run_errors.sh + # test-musl: end-to-end static + dynamic musl link/run on aarch64. # Pulls a pinned musl sysroot (test/musl/extract.sh — podman against # Alpine 3.20), builds rt/build/aarch64-linux/libcfree_rt.a for the