commit 81614f4229f8b1ba480149409457610863a5c113
parent 9f4d8f11f638031838c7d85e6eb7bec53b495ff8
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 9 May 2026 15:01:44 -0700
test/cg: split cases.c into per-group files
cases.c was past 3500 lines and growing — at the threshold where one
more deferred group (J–R) tips it. Move each group's builders into its
own file (cases_a.c .. cases_i.c, cases_mc.c) and shrink cases.c to
forward declarations + the flat cg_cases[] registry the runner walks.
Per-case helpers and per-group struct types stay file-static. The one
genuine cross-group helper — the struct Pt used by Group B's sret/byval
cases and Group F's copy_bytes case — moves to cases_shared.{c,h} so
its TagId stays interned to a single id.
run.sh updated to include the new files in the cg-runner build line.
Diffstat:
14 files changed, 3535 insertions(+), 3336 deletions(-)
diff --git a/test/cg/harness/cases.c b/test/cg/harness/cases.c
@@ -1,3348 +1,145 @@
/* Test case registry.
*
- * Each case is a static builder function plus an entry in cg_cases[]. The
- * runner finds cases by name, sets up the per-case Compiler + ObjBuilder +
- * MCEmitter (+ CGTarget for non-MC-only cases), invokes the builder, and
- * exits with the result of test_main() (for --jit) or writes the .o (for
- * --emit).
+ * Each case is a builder function plus an entry in cg_cases[]. Builders
+ * live in per-group files (cases_a.c .. cases_i.c, cases_mc.c); helpers
+ * shared across groups live in cases_shared.c. This file is just
+ * forward-declarations + the flat registry the runner walks.
*
- * Adding a case: write a static `build_<name>(CgTestCtx*)`, add a row to
- * the array, and update CORPUS.md.
- *
- * Cases drive the same building blocks the parser will use: pool-interned
- * Types via type_*, ABI classification via abi_func_info, and CGTarget for
- * lowering. Cases that exercise features the lib does not yet implement
- * (calls, params, aggregates, FP) compile and link against the same
- * symbols the parser depends on; they fail at runtime until those land.
+ * Adding a case: add a `static`/non-static `build_<name>` to the
+ * appropriate cases_<x>.c, list its prototype below, append a row to
+ * cg_cases[], and update CORPUS.md.
*
* Expected exit codes are stored modulo 256 — POSIX exit() truncates to
* one byte, and the harness compares against (expected & 0xff). */
#include "cg_test.h"
-#include <string.h>
-
-/* ============================================================
- * Group: MC-only (lowest layer)
- * ============================================================ */
-
-/* mc_smoke — emit `mov w0, #42; ret` as raw AArch64 bytes through
- * MCEmitter. No CGTarget involved. Validates the byte path end-to-end. */
-static void build_mc_smoke(CgTestCtx* ctx)
-{
- static const u8 BYTES[8] = {
- /* mov w0, #42 */ 0x40, 0x05, 0x80, 0x52,
- /* ret */ 0xc0, 0x03, 0x5f, 0xd6,
- };
-
- ObjSymId sym = cgtest_mc_begin_main(ctx);
- ctx->mc->set_section(ctx->mc, ctx->text_sec);
- ctx->mc->emit_align(ctx->mc, 4, 0);
- u32 start = ctx->mc->pos(ctx->mc);
- ctx->mc->emit_bytes(ctx->mc, BYTES, sizeof BYTES);
- cgtest_mc_end_main(ctx, sym, start);
-}
-
-/* ============================================================
- * Group A: function lifecycle and return
- * ============================================================ */
-
-/* a01_return_const_42 — alloc reg, load_imm(42), ret reg. */
-static void build_a01_return_const_42(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- ctx->target->load_imm(ctx->target, REG_op(r, I32), 42);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* a02_return_zero — load_imm(0). */
-static void build_a02_return_zero(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- ctx->target->load_imm(ctx->target, REG_op(r, I32), 0);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* a03_ret_imm — backend materializes the imm directly inside ret(). */
-static void build_a03_ret_imm(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- cgtest_ret_imm(tf, 17, I32);
- cgtest_end(tf);
-}
-
-/* a04_copy_reg — load_imm(7) into r1, copy r1->r2, ret r2. */
-static void build_a04_copy_reg(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg r1 = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- Reg r2 = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- ctx->target->load_imm(ctx->target, REG_op(r1, I32), 7);
- ctx->target->copy(ctx->target, REG_op(r2, I32), REG_op(r1, I32));
- cgtest_ret_reg(tf, r2, I32);
- cgtest_end(tf);
-}
-
-/* a05_return_neg_small — load_imm(-7), ret. Backend should select MOVN.
- * Exit code = -7 & 0xff = 249. */
-static void build_a05_return_neg_small(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- ctx->target->load_imm(ctx->target, REG_op(r, I32), -7);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* a06_return_i64 — i64 load_imm with high bits set, return as i64.
- * test_main is cast to int(*)(void) by the runner, which reads w0
- * (low 32 of x0). Value 0x100000002A → low 32 = 42. */
-static void build_a06_return_i64(CgTestCtx* ctx)
-{
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I64);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I64);
- ctx->target->load_imm(ctx->target, REG_op(r, I64), 0x100000002Aull);
- cgtest_ret_reg(tf, r, I64);
- cgtest_end(tf);
-}
-
-/* a07_void_return — function returns void. The harness wrapper (start.c
- * for path E; the C compiler for path D/J) zeroes x0 before the call, so
- * the observed exit code is 0. */
-static void build_a07_void_return(CgTestCtx* ctx)
-{
- CgTestFn* tf = cgtest_begin_main(ctx, T_void(ctx));
- cgtest_ret_void(tf);
- cgtest_end(tf);
-}
-
-/* a08_multiple_returns — two consecutive ret() calls in straight-line
- * code. The first is taken; the second is dead. Exercises that the
- * backend can emit more than one return-shaped sequence per function. */
-static void build_a08_multiple_returns(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- cgtest_ret_imm(tf, 1, I32);
- cgtest_ret_imm(tf, 2, I32); /* unreachable */
- cgtest_end(tf);
-}
-
-/* a09_load_imm_movz_movk — value that requires multi-step materialization
- * on AArch64 (low 16 != 0, high 16 != 0). 0xABCD → exit 0xCD = 205. */
-static void build_a09_load_imm_movz_movk(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- ctx->target->load_imm(ctx->target, REG_op(r, I32), 0xABCD);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* a10_return_u8 — narrow integer return. Value 200 fits in u8 → 200. */
-static void build_a10_return_u8(CgTestCtx* ctx)
-{
- const Type* U8 = T_u8(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U8);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, U8);
- ctx->target->load_imm(ctx->target, REG_op(r, U8), 200);
- cgtest_ret_reg(tf, r, U8);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group B: frame slots, parameters, locals
- *
- * Several cases here define a helper function and have test_main call
- * it. The helper exercises param wiring; test_main exercises the call
- * sequence. Both share one CGTarget instance — the backend must support
- * multiple func_begin/func_end pairs per TU.
- * ============================================================ */
-
-/* helper used by b01: int echo(int x) { return x; } */
-static ObjSymId build_b01_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "b01_echo", I32, params, 1);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), cgtest_param_slot(tf, 0), I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* b01_param_int — echo(201) → 201. */
-static void build_b01_param_int(CgTestCtx* ctx)
-{
- ObjSymId echo = build_b01_helper(ctx);
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 201 } };
- cgtest_call(tf, echo, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by b02: int sum2(int a, int b) { return a + b; } */
-static ObjSymId build_b02_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32, I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "b02_sum2", I32, params, 2);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg s = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(a, I32), cgtest_param_slot(tf, 0), I32);
- cgtest_load_local(tf, REG_op(b, I32), cgtest_param_slot(tf, 1), I32);
- T->binop(T, BO_IADD, REG_op(s, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, s, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* b02_param_sum — sum2(40, 2) → 42. */
-static void build_b02_param_sum(CgTestCtx* ctx)
-{
- ObjSymId sum2 = build_b02_helper(ctx);
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32, I32 };
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- CgTestArg args[] = {
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 40 },
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 2 },
- };
- cgtest_call(tf, sum2, I32, params, args, 2, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by b03: int sum9(a..i) { return a+b+c+d+e+f+g+h+i; }
- * Nine int parameters force at least one to spill onto the stack on
- * AArch64 SysV (8 GP arg registers). */
-static ObjSymId build_b03_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* p[9] = { I32,I32,I32,I32,I32,I32,I32,I32,I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "b03_sum9", I32, p, 9);
- CGTarget* T = ctx->target;
- Reg accum = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(accum, I32), 0);
- for (u32 i = 0; i < 9; ++i) {
- Reg t = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(t, I32), cgtest_param_slot(tf, i), I32);
- T->binop(T, BO_IADD, REG_op(accum, I32),
- REG_op(accum, I32), REG_op(t, I32));
- }
- cgtest_ret_reg(tf, accum, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* b03_param_spill — sum9(1..9) = 45. */
-static void build_b03_param_spill(CgTestCtx* ctx)
-{
- ObjSymId sum9 = build_b03_helper(ctx);
- const Type* I32 = T_i32(ctx);
- const Type* params[9] = { I32,I32,I32,I32,I32,I32,I32,I32,I32 };
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- CgTestArg args[9];
- for (int i = 0; i < 9; ++i) {
- args[i] = (CgTestArg){ .kind = CGT_ARG_IMM, .type = I32, .v.imm = i+1 };
- }
- cgtest_call(tf, sum9, I32, params, args, 9, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* b04_local_int — alloc local int, store 42, load it back, return. */
-static void build_b04_local_int(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, s, IMM_op(42, I32), I32);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), s, I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* b05_addr_taken_local — addr_of forces frame residency. Take address,
- * store via INDIRECT, load via INDIRECT, return.
- * int x; store-to-slot 17
- * int* p = &x;
- * *p = *p + 1; → 18
- * return *p; */
-static void build_b05_addr_taken_local(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- cgtest_store_local(tf, x, IMM_op(17, I32), I32);
-
- Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
- T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), LOCAL_op(x, I32));
-
- /* val = *p; val += 1; *p = val; return val; */
- Reg val = T->alloc_reg(T, RC_INT, I32);
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->load (T, REG_op(val, I32), IND_op(p, 0, I32), ma);
- T->binop(T, BO_IADD, REG_op(val, I32), REG_op(val, I32), IMM_op(1, I32));
- T->store(T, IND_op(p, 0, I32), REG_op(val, I32), ma);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(out, I32), IND_op(p, 0, I32), ma);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* helper used by b06:
- * struct Pt { int a; int b; };
- * struct Pt mk(void) { return (struct Pt){10, 32}; }
- * On AArch64 SysV a 8-byte struct returns in x0 (or split across regs);
- * the harness leaves register placement to abi_func_info. The body
- * stores .a=10 and .b=32 into a local struct and returns it. */
-static const Type* build_b06_pt_type(CgTestCtx* ctx)
-{
- Sym tag = pool_intern_cstr(ctx->pool, "Pt");
- TagId tid = type_tag_new(ctx->pool, TAG_STRUCT, tag, (SrcLoc){0,0,0});
- TypeRecordBuilder* b = type_record_begin(ctx->pool, TY_STRUCT, tid, tag);
- type_record_field(b, (Field){
- .name = pool_intern_cstr(ctx->pool, "a"), .type = T_i32(ctx) });
- type_record_field(b, (Field){
- .name = pool_intern_cstr(ctx->pool, "b"), .type = T_i32(ctx) });
- return type_record_end(ctx->pool, b);
-}
-
-static ObjSymId build_b06_helper(CgTestCtx* ctx)
-{
- const Type* PT = build_b06_pt_type(ctx);
- CgTestFn* tf = cgtest_begin_func(ctx, "b06_mk", PT, NULL, 0);
- CGTarget* T = ctx->target;
-
- /* Build the struct in a local then return its address; the backend
- * uses fn->abi_info->ret to decide whether to copy into the sret
- * pointer (large struct) or load into the return regs (small). */
- FrameSlot s = cgtest_local(tf, PT, FSF_NONE);
- /* &s + 0 = .a, &s + 4 = .b */
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
- T->addr_of(T, REG_op(base, T_ptr(ctx, PT)), LOCAL_op(s, PT));
- MemAccess ma_i32 = { .type = T_i32(ctx), .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, T_i32(ctx)), IMM_op(10, T_i32(ctx)), ma_i32);
- T->store(T, IND_op(base, 4, T_i32(ctx)), IMM_op(32, T_i32(ctx)), ma_i32);
-
- cgtest_ret_indirect(tf, s);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* b06_sret — pt = mk(); return pt.a + pt.b → 42. */
-static void build_b06_sret(CgTestCtx* ctx)
-{
- const Type* PT = build_b06_pt_type(ctx);
- ObjSymId mk = build_b06_helper(ctx);
- const Type* I32 = T_i32(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* Caller provides a local for the sret destination. The harness
- * passes its address as the ret_storage operand; the backend will
- * either materialize an sret pointer (large) or unpack regs into
- * the local (small) per the ABI. */
- FrameSlot dst = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
- cgtest_call(tf, mk, PT, NULL, NULL, 0, LOCAL_op(dst, PT));
-
- /* Load .a, .b, sum, return. */
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
- T->addr_of(T, REG_op(base, T_ptr(ctx, PT)), LOCAL_op(dst, PT));
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- Reg ra = T->alloc_reg(T, RC_INT, I32);
- Reg rb = T->alloc_reg(T, RC_INT, I32);
- Reg sum = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(ra, I32), IND_op(base, 0, I32), ma);
- T->load(T, REG_op(rb, I32), IND_op(base, 4, I32), ma);
- T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(ra, I32), REG_op(rb, I32));
- cgtest_ret_reg(tf, sum, I32);
- cgtest_end(tf);
-}
-
-/* helper used by b07:
- * struct Pt { int a; int b; };
- * int take(struct Pt p) { return p.a + p.b; }
- * Aggregate-by-value parameter. Caller builds a local Pt and passes its
- * address with byval semantics; the callee receives a local copy. */
-static ObjSymId build_b07_helper(CgTestCtx* ctx)
-{
- const Type* PT = build_b06_pt_type(ctx);
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { PT };
- CgTestFn* tf = cgtest_begin_func(ctx, "b07_take", I32, params, 1);
- CGTarget* T = ctx->target;
-
- /* The param's home slot holds the byval copy. Compute its address
- * and load .a, .b. */
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
- T->addr_of(T, REG_op(base, T_ptr(ctx, PT)),
- LOCAL_op(cgtest_param_slot(tf, 0), PT));
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_PARAM };
- Reg ra = T->alloc_reg(T, RC_INT, I32);
- Reg rb = T->alloc_reg(T, RC_INT, I32);
- Reg sum = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(ra, I32), IND_op(base, 0, I32), ma);
- T->load(T, REG_op(rb, I32), IND_op(base, 4, I32), ma);
- T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(ra, I32), REG_op(rb, I32));
- cgtest_ret_reg(tf, sum, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* b07_byval_param — take({.a=15, .b=27}) → 42. */
-static void build_b07_byval_param(CgTestCtx* ctx)
-{
- const Type* PT = build_b06_pt_type(ctx);
- const Type* I32 = T_i32(ctx);
- ObjSymId take = build_b07_helper(ctx);
- const Type* params[] = { PT };
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot src = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
- T->addr_of(T, REG_op(base, T_ptr(ctx, PT)), LOCAL_op(src, PT));
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, I32), IMM_op(15, I32), ma);
- T->store(T, IND_op(base, 4, I32), IMM_op(27, I32), ma);
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = {
- { .kind = CGT_ARG_BYVAL_LOCAL, .type = PT, .v.slot = src }
- };
- cgtest_call(tf, take, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by b08: int trunc(float f) { return (int)f; } */
-static ObjSymId build_b08_helper(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { F32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "b08_trunc", I32, params, 1);
- CGTarget* T = ctx->target;
-
- Reg f = T->alloc_reg(T, RC_FP, F32);
- cgtest_load_local(tf, REG_op(f, F32), cgtest_param_slot(tf, 0), F32);
- Reg i = T->alloc_reg(T, RC_INT, I32);
- T->convert(T, CV_FTOI_S, REG_op(i, I32), REG_op(f, F32));
- cgtest_ret_reg(tf, i, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* b08_fp_param — trunc(7.5f) → 7. The IMM operand encodes the float
- * bit-pattern; backend uses cls=RC_FP + type=float to materialize via
- * load_const or fmov literal. */
-static void build_b08_fp_param(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* I32 = T_i32(ctx);
- ObjSymId fn = build_b08_helper(ctx);
- const Type* params[] = { F32 };
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* Materialize 7.5f via load_const (immediate float). */
- static const u8 BYTES_75F[4] = { 0x00, 0x00, 0xF0, 0x40 }; /* IEEE 7.5f LE */
- Reg f = T->alloc_reg(T, RC_FP, F32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_75F, .size = 4, .align = 4 };
- T->load_const(T, REG_op(f, F32), cb);
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = F32, .v.reg = f } };
- cgtest_call(tf, fn, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group C: integer arithmetic
- * ============================================================ */
-
-/* c01_add — 1 + 2 = 3 */
-static void build_c01_add(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), 1);
- T->load_imm(T, REG_op(b, I32), 2);
- T->binop(T, BO_IADD, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* c02_sub_mul — 7 * 3 - 4 = 17 */
-static void build_c02_sub_mul(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r7 = T->alloc_reg(T, RC_INT, I32);
- Reg r3 = T->alloc_reg(T, RC_INT, I32);
- Reg r4 = T->alloc_reg(T, RC_INT, I32);
- Reg rmul = T->alloc_reg(T, RC_INT, I32);
- Reg rsub = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r7, I32), 7);
- T->load_imm(T, REG_op(r3, I32), 3);
- T->load_imm(T, REG_op(r4, I32), 4);
- T->binop(T, BO_IMUL, REG_op(rmul, I32), REG_op(r7, I32), REG_op(r3, I32));
- T->binop(T, BO_ISUB, REG_op(rsub, I32), REG_op(rmul, I32), REG_op(r4, I32));
- cgtest_ret_reg(tf, rsub, I32);
- cgtest_end(tf);
-}
-
-/* c03_bitwise — (~3) & 0xff = 252 */
-static void build_c03_bitwise(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r3 = T->alloc_reg(T, RC_INT, I32);
- Reg rinv = T->alloc_reg(T, RC_INT, I32);
- Reg rmask= T->alloc_reg(T, RC_INT, I32);
- Reg rand_ = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r3, I32), 3);
- T->load_imm(T, REG_op(rmask, I32), 0xff);
- T->unop (T, UO_BNOT, REG_op(rinv, I32), REG_op(r3, I32));
- T->binop(T, BO_AND, REG_op(rand_, I32), REG_op(rinv, I32), REG_op(rmask, I32));
- cgtest_ret_reg(tf, rand_, I32);
- cgtest_end(tf);
-}
-
-/* c04_shift — (1<<5) | (16>>1) = 40 */
-static void build_c04_shift(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r1 = T->alloc_reg(T, RC_INT, I32);
- Reg r5 = T->alloc_reg(T, RC_INT, I32);
- Reg r16 = T->alloc_reg(T, RC_INT, I32);
- Reg r1s = T->alloc_reg(T, RC_INT, I32);
- Reg rshl= T->alloc_reg(T, RC_INT, I32);
- Reg rshr= T->alloc_reg(T, RC_INT, I32);
- Reg ror = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r1, I32), 1);
- T->load_imm(T, REG_op(r5, I32), 5);
- T->load_imm(T, REG_op(r16, I32), 16);
- T->load_imm(T, REG_op(r1s, I32), 1);
- T->binop(T, BO_SHL, REG_op(rshl, I32), REG_op(r1, I32), REG_op(r5, I32));
- T->binop(T, BO_SHR_U, REG_op(rshr, I32), REG_op(r16, I32), REG_op(r1s, I32));
- T->binop(T, BO_OR, REG_op(ror, I32), REG_op(rshl, I32),REG_op(rshr, I32));
- cgtest_ret_reg(tf, ror, I32);
- cgtest_end(tf);
-}
-
-/* c05_div_mod — 23/4 + 23%4 = 5 + 3 = 8 (signed) */
-static void build_c05_div_mod(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r23 = T->alloc_reg(T, RC_INT, I32);
- Reg r4 = T->alloc_reg(T, RC_INT, I32);
- Reg rd = T->alloc_reg(T, RC_INT, I32);
- Reg rm = T->alloc_reg(T, RC_INT, I32);
- Reg rs = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r23, I32), 23);
- T->load_imm(T, REG_op(r4, I32), 4);
- T->binop(T, BO_SDIV, REG_op(rd, I32), REG_op(r23, I32), REG_op(r4, I32));
- T->binop(T, BO_SREM, REG_op(rm, I32), REG_op(r23, I32), REG_op(r4, I32));
- T->binop(T, BO_IADD, REG_op(rs, I32), REG_op(rd, I32), REG_op(rm, I32));
- cgtest_ret_reg(tf, rs, I32);
- cgtest_end(tf);
-}
-
-/* c06_xor — 0xa5 ^ 0x5a = 0xff = 255 */
-static void build_c06_xor(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg ra = T->alloc_reg(T, RC_INT, I32);
- Reg rb = T->alloc_reg(T, RC_INT, I32);
- Reg rx = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(ra, I32), 0xa5);
- T->load_imm(T, REG_op(rb, I32), 0x5a);
- T->binop(T, BO_XOR, REG_op(rx, I32), REG_op(ra, I32), REG_op(rb, I32));
- cgtest_ret_reg(tf, rx, I32);
- cgtest_end(tf);
-}
-
-/* c07_iadd_i64 — i64 add. Two i64 values added; low 32 bits returned.
- * (1<<32 | 0x29) + (1<<32 | 0x01) = (2<<32 | 0x2A) → low 32 = 42. */
-static void build_c07_iadd_i64(CgTestCtx* ctx)
-{
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I64);
- CGTarget* T = ctx->target;
- Reg ra = T->alloc_reg(T, RC_INT, I64);
- Reg rb = T->alloc_reg(T, RC_INT, I64);
- Reg rs = T->alloc_reg(T, RC_INT, I64);
- T->load_imm(T, REG_op(ra, I64), 0x100000029ll);
- T->load_imm(T, REG_op(rb, I64), 0x100000001ll);
- T->binop(T, BO_IADD, REG_op(rs, I64), REG_op(ra, I64), REG_op(rb, I64));
- cgtest_ret_reg(tf, rs, I64);
- cgtest_end(tf);
-}
-
-/* c08_unsigned_div — 100u / 7u = 14 */
-static void build_c08_unsigned_div(CgTestCtx* ctx)
-{
- const Type* U32 = T_u32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U32);
- CGTarget* T = ctx->target;
- Reg r100 = T->alloc_reg(T, RC_INT, U32);
- Reg r7 = T->alloc_reg(T, RC_INT, U32);
- Reg rq = T->alloc_reg(T, RC_INT, U32);
- T->load_imm(T, REG_op(r100, U32), 100);
- T->load_imm(T, REG_op(r7, U32), 7);
- T->binop(T, BO_UDIV, REG_op(rq, U32), REG_op(r100, U32), REG_op(r7, U32));
- cgtest_ret_reg(tf, rq, U32);
- cgtest_end(tf);
-}
-
-/* c09_neg — UO_NEG. -42 → exit 256-42 = 214. */
-static void build_c09_neg(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r = T->alloc_reg(T, RC_INT, I32);
- Reg n = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r, I32), 42);
- T->unop(T, UO_NEG, REG_op(n, I32), REG_op(r, I32));
- cgtest_ret_reg(tf, n, I32);
- cgtest_end(tf);
-}
-
-/* c10_logical_not — UO_NOT yields 0/1 from any int. !0 = 1. */
-static void build_c10_logical_not(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg z = T->alloc_reg(T, RC_INT, I32);
- Reg ln = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(z, I32), 0);
- T->unop(T, UO_NOT, REG_op(ln, I32), REG_op(z, I32));
- cgtest_ret_reg(tf, ln, I32);
- cgtest_end(tf);
-}
-
-/* c11_shr_signed — -16 >>(s) 2 = -4 → exit 252. */
-static void build_c11_shr_signed(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg rv = T->alloc_reg(T, RC_INT, I32);
- Reg r2 = T->alloc_reg(T, RC_INT, I32);
- Reg rs = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(rv, I32), -16);
- T->load_imm(T, REG_op(r2, I32), 2);
- T->binop(T, BO_SHR_S, REG_op(rs, I32), REG_op(rv, I32), REG_op(r2, I32));
- cgtest_ret_reg(tf, rs, I32);
- cgtest_end(tf);
-}
-
-/* c12_imul_i64 — i64 mul. 7 * 6 = 42; high bits zero. Exit 42. */
-static void build_c12_imul_i64(CgTestCtx* ctx)
-{
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I64);
- CGTarget* T = ctx->target;
- Reg r7 = T->alloc_reg(T, RC_INT, I64);
- Reg r6 = T->alloc_reg(T, RC_INT, I64);
- Reg rm = T->alloc_reg(T, RC_INT, I64);
- T->load_imm(T, REG_op(r7, I64), 7);
- T->load_imm(T, REG_op(r6, I64), 6);
- T->binop(T, BO_IMUL, REG_op(rm, I64), REG_op(r7, I64), REG_op(r6, I64));
- cgtest_ret_reg(tf, rm, I64);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group D: compare and branch
- * ============================================================ */
-
-/* d01_cmp_eq_true — cmp materializes 0/1; (5 == 5) → 1. */
-static void build_d01_cmp_eq_true(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), 5);
- T->load_imm(T, REG_op(b, I32), 5);
- T->cmp(T, CMP_EQ, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* d02_cmp_eq_false — (5 == 6) → 0. */
-static void build_d02_cmp_eq_false(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), 5);
- T->load_imm(T, REG_op(b, I32), 6);
- T->cmp(T, CMP_EQ, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* d03_cmp_ne — (5 != 6) → 1. */
-static void build_d03_cmp_ne(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), 5);
- T->load_imm(T, REG_op(b, I32), 6);
- T->cmp(T, CMP_NE, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* d04_cmp_lt_signed — (-1 < 1) signed → 1. */
-static void build_d04_cmp_lt_signed(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), -1);
- T->load_imm(T, REG_op(b, I32), 1);
- T->cmp(T, CMP_LT_S, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* d05_cmp_lt_unsigned — same bit patterns as d04 but unsigned: 0xFFFFFFFF
- * is huge, so (0xFFFFFFFF < 1) → 0. Signedness lives in CmpOp, not Type. */
-static void build_d05_cmp_lt_unsigned(CgTestCtx* ctx)
-{
- const Type* U32 = T_u32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, U32);
- Reg b = T->alloc_reg(T, RC_INT, U32);
- Reg d = T->alloc_reg(T, RC_INT, U32);
- T->load_imm(T, REG_op(a, U32), -1);
- T->load_imm(T, REG_op(b, U32), 1);
- T->cmp(T, CMP_LT_U, REG_op(d, U32), REG_op(a, U32), REG_op(b, U32));
- cgtest_ret_reg(tf, d, U32);
- cgtest_end(tf);
-}
-
-/* d06_cmp_ge_signed — boundary: (5 >= 5) → 1 (LE/GE families include eq). */
-static void build_d06_cmp_ge_signed(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), 5);
- T->load_imm(T, REG_op(b, I32), 5);
- T->cmp(T, CMP_GE_S, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* d07_cmp_branch_taken — fused cmp_branch with the branch taken; landing
- * pad past the label returns 42. */
-static void build_d07_cmp_branch_taken(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r, I32), 7);
- Label L = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(r, I32), IMM_op(7, I32), L);
- cgtest_ret_imm(tf, 0, I32); /* dead */
- T->label_place(T, L);
- cgtest_ret_imm(tf, 42, I32);
- cgtest_end(tf);
-}
-
-/* d08_cmp_branch_not_taken — branch not taken; fallthrough returns 33. */
-static void build_d08_cmp_branch_not_taken(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r, I32), 5);
- Label L = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(r, I32), IMM_op(6, I32), L);
- cgtest_ret_imm(tf, 33, I32);
- T->label_place(T, L);
- cgtest_ret_imm(tf, 0, I32); /* dead */
- cgtest_end(tf);
-}
-
-/* d09_cmp_branch_lt_signed — signed compare-and-branch with negative LHS;
- * (-3 < 0) is true. */
-static void build_d09_cmp_branch_lt_signed(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(r, I32), -3);
- Label L = T->label_new(T);
- T->cmp_branch(T, CMP_LT_S, REG_op(r, I32), IMM_op(0, I32), L);
- cgtest_ret_imm(tf, 0, I32); /* dead */
- T->label_place(T, L);
- cgtest_ret_imm(tf, 9, I32);
- cgtest_end(tf);
-}
-
-/* d10_jump — unconditional jump; the early ret is skipped. */
-static void build_d10_jump(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Label L = T->label_new(T);
- T->jump(T, L);
- cgtest_ret_imm(tf, 0, I32); /* dead */
- T->label_place(T, L);
- cgtest_ret_imm(tf, 5, I32);
- cgtest_end(tf);
-}
-
-/* d11_scope_if_true — `int x = 99; if (1) x = 33; return x;`
- * SCOPE_IF consumes the cond at scope_begin; then-branch updates the
- * local; scope_end closes the join. */
-static void build_d11_scope_if_true(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, x, IMM_op(99, I32), I32);
-
- Reg c = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(c, I32), 1);
- CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
- CGScope s = T->scope_begin(T, &desc);
- cgtest_store_local(tf, x, IMM_op(33, I32), I32);
- T->scope_end(T, s);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), x, I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* d12_scope_if_false — `int x = 99; if (0) x = 33; return x;`
- * Then-branch is dead; the local keeps its initial value. */
-static void build_d12_scope_if_false(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, x, IMM_op(99, I32), I32);
-
- Reg c = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(c, I32), 0);
- CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
- CGScope s = T->scope_begin(T, &desc);
- cgtest_store_local(tf, x, IMM_op(33, I32), I32);
- T->scope_end(T, s);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), x, I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* d13_scope_if_else — `int x; if (0) x = 10; else x = 7; return x;`
- * Exercises scope_else: cond is 0, so the else body wins. */
-static void build_d13_scope_if_else(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
-
- Reg c = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(c, I32), 0);
- CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
- CGScope s = T->scope_begin(T, &desc);
- cgtest_store_local(tf, x, IMM_op(10, I32), I32);
- T->scope_else(T, s);
- cgtest_store_local(tf, x, IMM_op(7, I32), I32);
- T->scope_end(T, s);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), x, I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group E: conversions
- *
- * One ConvKind per case, plus the boundary widths the AArch64 backend
- * actually selects between (UXTB/SXTB vs UXTH/SXTH vs UBFX/SBFX, 32→64
- * sign-extend). FP conversions all funnel through ftoi_s so the runner
- * sees an int exit code.
- * ============================================================ */
-
-/* e01_sext_i8_i32 — sext (i8)-1 → i32 = 0xFFFFFFFF; low 8 = 0xFF = 255. */
-static void build_e01_sext_i8_i32(CgTestCtx* ctx)
-{
- const Type* I8 = T_i8(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, I8);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(s, I8), -1);
- T->convert(T, CV_SEXT, REG_op(d, I32), REG_op(s, I8));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e02_zext_u8_i32 — zext (u8)0xFF → i32 = 0xFF; low 8 = 255. The high
- * bits are zeroed, distinguishing this from e01. */
-static void build_e02_zext_u8_i32(CgTestCtx* ctx)
-{
- const Type* U8 = T_u8(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, U8);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(s, U8), 0xFF);
- T->convert(T, CV_ZEXT, REG_op(d, I32), REG_op(s, U8));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e03_sext_i16_i32 — sext (i16)-1000 → 0xFFFFFC18; low 8 = 0x18 = 24. */
-static void build_e03_sext_i16_i32(CgTestCtx* ctx)
-{
- const Type* I16 = T_i16(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, I16);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(s, I16), -1000);
- T->convert(T, CV_SEXT, REG_op(d, I32), REG_op(s, I16));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e04_zext_u16_i32 — zext (u16)0xABCD → 0x0000ABCD; low 8 = 0xCD = 205. */
-static void build_e04_zext_u16_i32(CgTestCtx* ctx)
-{
- const Type* U16 = T_u16(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, U16);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(s, U16), 0xABCD);
- T->convert(T, CV_ZEXT, REG_op(d, I32), REG_op(s, U16));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e05_zext_u32_i64 — zext (u32)0xFFFFFFFF → i64 = 0x00000000FFFFFFFF;
- * runner reads w0 = 0xFFFFFFFF; low 8 = 255. Distinct from e06: high
- * 32 bits are zero. */
-static void build_e05_zext_u32_i64(CgTestCtx* ctx)
-{
- const Type* U32 = T_u32(ctx);
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I64);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, U32);
- Reg d = T->alloc_reg(T, RC_INT, I64);
- T->load_imm(T, REG_op(s, U32), 0xFFFFFFFFll);
- T->convert(T, CV_ZEXT, REG_op(d, I64), REG_op(s, U32));
- cgtest_ret_reg(tf, d, I64);
- cgtest_end(tf);
-}
-
-/* e06_sext_i32_i64 — sext (i32)-1 → i64 = -1; low 8 = 255. Same low-byte
- * exit as e05 but the high bits differ — exercises SXTW vs UXTW. */
-static void build_e06_sext_i32_i64(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I64);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, I64);
- T->load_imm(T, REG_op(s, I32), -1);
- T->convert(T, CV_SEXT, REG_op(d, I64), REG_op(s, I32));
- cgtest_ret_reg(tf, d, I64);
- cgtest_end(tf);
-}
-
-/* e07_trunc_i64_i32 — trunc 0x100000080 → low 32 = 0x80 = 128. */
-static void build_e07_trunc_i64_i32(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, I64);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(s, I64), 0x100000080ll);
- T->convert(T, CV_TRUNC, REG_op(d, I32), REG_op(s, I64));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e08_trunc_i32_i8 — trunc 0x1FF → low 8 = 0xFF; returned as u8 = 255. */
-static void build_e08_trunc_i32_i8(CgTestCtx* ctx)
-{
- const Type* U8 = T_u8(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U8);
- CGTarget* T = ctx->target;
- Reg s = T->alloc_reg(T, RC_INT, I32);
- Reg d = T->alloc_reg(T, RC_INT, U8);
- T->load_imm(T, REG_op(s, I32), 0x1FF);
- T->convert(T, CV_TRUNC, REG_op(d, U8), REG_op(s, I32));
- cgtest_ret_reg(tf, d, U8);
- cgtest_end(tf);
-}
-
-/* e09_itof_s_i32_f32 — i32(7) → f32(7.0) → ftoi_s i32 → 7. Exact
- * round-trip; verifies SCVTF + FCVTZS form a valid pair. */
-static void build_e09_itof_s_i32_f32(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg si = T->alloc_reg(T, RC_INT, I32);
- Reg f = T->alloc_reg(T, RC_FP, F32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(si, I32), 7);
- T->convert(T, CV_ITOF_S, REG_op(f, F32), REG_op(si, I32));
- T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e10_itof_u_u32_f64 — u32(100) → f64(100.0) → ftoi_s i32 → 100.
- * Crosses width on the way up (UCVTF Dn,Wn) and back down. */
-static void build_e10_itof_u_u32_f64(CgTestCtx* ctx)
-{
- const Type* U32 = T_u32(ctx);
- const Type* I32 = T_i32(ctx);
- const Type* F64 = T_f64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg si = T->alloc_reg(T, RC_INT, U32);
- Reg f = T->alloc_reg(T, RC_FP, F64);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(si, U32), 100);
- T->convert(T, CV_ITOF_U, REG_op(f, F64), REG_op(si, U32));
- T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F64));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e11_ftoi_s_neg — ftoi_s(-1.5f) = -1; low 8 = 255. C99 truncation
- * rounds toward zero. */
-static void build_e11_ftoi_s_neg(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- static const u8 BYTES_NEG_1_5[4] = { 0x00, 0x00, 0xC0, 0xBF }; /* -1.5f LE */
- Reg f = T->alloc_reg(T, RC_FP, F32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_NEG_1_5, .size = 4, .align = 4 };
- T->load_const(T, REG_op(f, F32), cb);
- T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e12_ftoi_u_pos — ftoi_u(200.7f) = 200u. Truncation toward zero,
- * matching C's (unsigned)x. */
-static void build_e12_ftoi_u_pos(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* U32 = T_u32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U32);
- CGTarget* T = ctx->target;
- static const u8 BYTES_200_7[4] = { 0x33, 0xB3, 0x48, 0x43 }; /* 200.7f LE */
- Reg f = T->alloc_reg(T, RC_FP, F32);
- Reg d = T->alloc_reg(T, RC_INT, U32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_200_7, .size = 4, .align = 4 };
- T->load_const(T, REG_op(f, F32), cb);
- T->convert(T, CV_FTOI_U, REG_op(d, U32), REG_op(f, F32));
- cgtest_ret_reg(tf, d, U32);
- cgtest_end(tf);
-}
-
-/* e13_fext_f32_f64 — float→double promotion preserves an exactly
- * representable value (3.5f = 3.5). ftoi_s then yields 3. */
-static void build_e13_fext_f32_f64(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* F64 = T_f64(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- static const u8 BYTES_3_5F[4] = { 0x00, 0x00, 0x60, 0x40 }; /* 3.5f LE */
- Reg f32r = T->alloc_reg(T, RC_FP, F32);
- Reg f64r = T->alloc_reg(T, RC_FP, F64);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_3_5F, .size = 4, .align = 4 };
- T->load_const(T, REG_op(f32r, F32), cb);
- T->convert(T, CV_FEXT, REG_op(f64r, F64), REG_op(f32r, F32));
- T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f64r, F64));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e14_ftrunc_f64_f32 — double→float demotion of 7.875 (exact in both);
- * ftoi_s yields 7. */
-static void build_e14_ftrunc_f64_f32(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* F64 = T_f64(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- static const u8 BYTES_7_875[8] = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1F, 0x40, /* 7.875 LE double */
- };
- Reg f64r = T->alloc_reg(T, RC_FP, F64);
- Reg f32r = T->alloc_reg(T, RC_FP, F32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- ConstBytes cb = { .type = F64, .bytes = BYTES_7_875, .size = 8, .align = 8 };
- T->load_const(T, REG_op(f64r, F64), cb);
- T->convert(T, CV_FTRUNC, REG_op(f32r, F32), REG_op(f64r, F64));
- T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f32r, F32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* e15_bitcast_i32_f32 — same-size cross-class reinterpret. 0x40A00000
- * is the IEEE-754 single bit pattern for 5.0f. ftoi_s yields 5,
- * confirming the bits travelled to the FP register intact. */
-static void build_e15_bitcast_i32_f32(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- Reg si = T->alloc_reg(T, RC_INT, I32);
- Reg f = T->alloc_reg(T, RC_FP, F32);
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(si, I32), 0x40A00000); /* 5.0f bit pattern */
- T->convert(T, CV_BITCAST, REG_op(f, F32), REG_op(si, I32));
- T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F32));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group F: memory (loads/stores beyond locals)
- *
- * Group B already exercises the basic load/store-of-local path. Group F
- * pushes the surface: every scalar width, FP load/store, indirect
- * non-zero offsets, store-from-IMM vs store-from-REG, copy_bytes,
- * set_bytes, volatile, and the bitfield methods.
- * ============================================================ */
-
-/* f01_load_store_i8 — local u8; store IMM 200; load; return. */
-static void build_f01_load_store_i8(CgTestCtx* ctx)
-{
- const Type* U8 = T_u8(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U8);
- CGTarget* T = ctx->target;
- FrameSlot s = cgtest_local(tf, U8, FSF_NONE);
- cgtest_store_local(tf, s, IMM_op(200, U8), U8);
- Reg r = T->alloc_reg(T, RC_INT, U8);
- cgtest_load_local(tf, REG_op(r, U8), s, U8);
- cgtest_ret_reg(tf, r, U8);
- cgtest_end(tf);
-}
-
-/* f02_load_store_i16 — local i16; store 0x1234; load; low 8 = 0x34 = 52. */
-static void build_f02_load_store_i16(CgTestCtx* ctx)
-{
- const Type* I16 = T_i16(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I16);
- CGTarget* T = ctx->target;
- FrameSlot s = cgtest_local(tf, I16, FSF_NONE);
- cgtest_store_local(tf, s, IMM_op(0x1234, I16), I16);
- Reg r = T->alloc_reg(T, RC_INT, I16);
- cgtest_load_local(tf, REG_op(r, I16), s, I16);
- cgtest_ret_reg(tf, r, I16);
- cgtest_end(tf);
-}
-
-/* f03_load_store_i64 — local i64; store 0x1_0000_0042; load; runner
- * reads w0 = low 32 = 0x42 = 66. */
-static void build_f03_load_store_i64(CgTestCtx* ctx)
-{
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I64);
- CGTarget* T = ctx->target;
- FrameSlot s = cgtest_local(tf, I64, FSF_NONE);
- cgtest_store_local(tf, s, IMM_op(0x100000042ll, I64), I64);
- Reg r = T->alloc_reg(T, RC_INT, I64);
- cgtest_load_local(tf, REG_op(r, I64), s, I64);
- cgtest_ret_reg(tf, r, I64);
- cgtest_end(tf);
-}
-
-/* f04_load_store_f32 — local f32 home; store FP reg holding 7.5f; load
- * back; ftoi_s → 7. Exercises STR Sn / LDR Sn forms. */
-static void build_f04_load_store_f32(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- static const u8 BYTES_75F[4] = { 0x00, 0x00, 0xF0, 0x40 }; /* 7.5f LE */
-
- FrameSlot s = cgtest_local(tf, F32, FSF_NONE);
- Reg src = T->alloc_reg(T, RC_FP, F32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_75F, .size = 4, .align = 4 };
- T->load_const(T, REG_op(src, F32), cb);
- cgtest_store_local(tf, s, REG_op(src, F32), F32);
-
- Reg dst = T->alloc_reg(T, RC_FP, F32);
- cgtest_load_local(tf, REG_op(dst, F32), s, F32);
- Reg ri = T->alloc_reg(T, RC_INT, I32);
- T->convert(T, CV_FTOI_S, REG_op(ri, I32), REG_op(dst, F32));
- cgtest_ret_reg(tf, ri, I32);
- cgtest_end(tf);
-}
-
-/* f05_load_store_f64 — local f64 home; store FP reg holding 3.25; load
- * back; ftoi_s → 3. STR Dn / LDR Dn. */
-static void build_f05_load_store_f64(CgTestCtx* ctx)
-{
- const Type* F64 = T_f64(ctx);
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
- static const u8 BYTES_3_25[8] = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x40, /* 3.25 LE double */
- };
-
- FrameSlot s = cgtest_local(tf, F64, FSF_NONE);
- Reg src = T->alloc_reg(T, RC_FP, F64);
- ConstBytes cb = { .type = F64, .bytes = BYTES_3_25, .size = 8, .align = 8 };
- T->load_const(T, REG_op(src, F64), cb);
- cgtest_store_local(tf, s, REG_op(src, F64), F64);
-
- Reg dst = T->alloc_reg(T, RC_FP, F64);
- cgtest_load_local(tf, REG_op(dst, F64), s, F64);
- Reg ri = T->alloc_reg(T, RC_INT, I32);
- T->convert(T, CV_FTOI_S, REG_op(ri, I32), REG_op(dst, F64));
- cgtest_ret_reg(tf, ri, I32);
- cgtest_end(tf);
-}
-
-/* f06_indirect_nonzero_offset — addr_of an i64 local, then store/load
- * an i32 at +4. Exercises [base + #imm] addressing past byte 0; also
- * verifies writes to one offset don't clobber a sentinel at another. */
-static void build_f06_indirect_nonzero_offset(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I64, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I64));
- T->addr_of(T, REG_op(base, T_ptr(ctx, I64)), LOCAL_op(s, I64));
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, I32), IMM_op(99, I32), ma);
- T->store(T, IND_op(base, 4, I32), IMM_op(42, I32), ma);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(base, 4, I32), ma);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* f07_store_reg — store from REG (not IMM) into a local slot. b04 stored
- * an immediate; this distinguishes the REG-source store path. */
-static void build_f07_store_reg(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_NONE);
- Reg src = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(src, I32), 17);
- cgtest_store_local(tf, s, REG_op(src, I32), I32);
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(dst, I32), s, I32);
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* f08_copy_bytes — copy_bytes(dst, src, Pt {10,32}); read back dst.a +
- * dst.b → 42. The aggregate move is the operation under test; the per-
- * field load/store after it just reads the result. */
-static void build_f08_copy_bytes(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* PT = build_b06_pt_type(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot src = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
- FrameSlot dst = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
-
- /* Initialize src to {10, 32}. */
- Reg src_addr = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
- T->addr_of(T, REG_op(src_addr, T_ptr(ctx, PT)), LOCAL_op(src, PT));
- MemAccess ma_i32 = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(src_addr, 0, I32), IMM_op(10, I32), ma_i32);
- T->store(T, IND_op(src_addr, 4, I32), IMM_op(32, I32), ma_i32);
-
- Reg dst_addr = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
- T->addr_of(T, REG_op(dst_addr, T_ptr(ctx, PT)), LOCAL_op(dst, PT));
-
- AggregateAccess agg = {
- .type = PT, .size = 8, .align = 4,
- .mem = { .type = PT, .size = 8, .align = 4,
- .alias.kind = ALIAS_LOCAL },
- };
- T->copy_bytes(T,
- REG_op(dst_addr, T_ptr(ctx, PT)),
- REG_op(src_addr, T_ptr(ctx, PT)),
- agg);
-
- Reg ra = T->alloc_reg(T, RC_INT, I32);
- Reg rb = T->alloc_reg(T, RC_INT, I32);
- Reg sum = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(ra, I32), IND_op(dst_addr, 0, I32), ma_i32);
- T->load(T, REG_op(rb, I32), IND_op(dst_addr, 4, I32), ma_i32);
- T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(ra, I32), REG_op(rb, I32));
- cgtest_ret_reg(tf, sum, I32);
- cgtest_end(tf);
-}
-
-/* f09_set_bytes_zero — set_bytes(0) on an i32-sized buffer; load the
- * word back → 0. Exercises the "memset to zero" path which backends
- * often special-case (STR XZR). */
-static void build_f09_set_bytes_zero(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* U8 = T_u8(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
- T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
-
- AggregateAccess agg = {
- .type = I32, .size = 4, .align = 4,
- .mem = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL },
- };
- T->set_bytes(T, REG_op(base, T_ptr(ctx, I32)), IMM_op(0, U8), agg);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(base, 0, I32), agg.mem);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* f10_set_bytes_ff — set_bytes(0xFF) on an i32-sized buffer; load the
- * word → 0xFFFFFFFF; low 8 = 255. Exercises the byte-broadcast path. */
-static void build_f10_set_bytes_ff(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* U8 = T_u8(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
- T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
-
- AggregateAccess agg = {
- .type = I32, .size = 4, .align = 4,
- .mem = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL },
- };
- T->set_bytes(T, REG_op(base, T_ptr(ctx, I32)), IMM_op(0xFF, U8), agg);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(base, 0, I32), agg.mem);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* f11_volatile_rw — same body as b04 but with MF_VOLATILE on both the
- * store and the load. The expected exit value is identical; the
- * difference is in the emitted code (no DSE/DCE, no fold-through-store). */
-static void build_f11_volatile_rw(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_NONE);
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .flags = MF_VOLATILE,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, LOCAL_op(s, I32), IMM_op(42, I32), ma);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), LOCAL_op(s, I32), ma);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* f12_bitfield_unsigned — { unsigned x : 5; } at bit_offset=3 inside a
- * zeroed i32 storage word; store 21; load → 21 (zero-extended). The
- * non-zero bit_offset forces the backend's mask+shift logic. */
-static void build_f12_bitfield_unsigned(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* U32 = T_u32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, U32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
- T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
-
- /* Zero the storage word so neighboring bits don't perturb the read. */
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, I32), IMM_op(0, I32), ma);
-
- BitFieldAccess bf = {
- .field_type = U32,
- .storage = ma,
- .storage_offset = 0,
- .bit_offset = 3,
- .bit_width = 5,
- .signed_ = 0,
- };
- T->bitfield_store(T, REG_op(base, T_ptr(ctx, I32)),
- IMM_op(21, U32), bf);
-
- Reg r = T->alloc_reg(T, RC_INT, U32);
- T->bitfield_load(T, REG_op(r, U32),
- REG_op(base, T_ptr(ctx, I32)), bf);
- cgtest_ret_reg(tf, r, U32);
- cgtest_end(tf);
-}
-
-/* f13_bitfield_signed — { signed x : 5; } at bit_offset=0; store -1
- * (5-bit all-ones); load sign-extends to -1; low 8 = 255. */
-static void build_f13_bitfield_signed(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
- T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, I32), IMM_op(0, I32), ma);
-
- BitFieldAccess bf = {
- .field_type = I32,
- .storage = ma,
- .storage_offset = 0,
- .bit_offset = 0,
- .bit_width = 5,
- .signed_ = 1,
- };
- T->bitfield_store(T, REG_op(base, T_ptr(ctx, I32)),
- IMM_op(-1, I32), bf);
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->bitfield_load(T, REG_op(r, I32),
- REG_op(base, T_ptr(ctx, I32)), bf);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group G: calls (beyond direct-call path)
- *
- * Group B established direct-call mechanics. Group G stresses what falls
- * out once calls compose: indirect calls, recursion, mutual recursion,
- * register-preservation across calls, HFAs, oversized struct byval.
- * ============================================================ */
-
-/* helper used by g01 and g11/g12: int echo(int x) { return x; } */
-static ObjSymId build_g_echo_helper(CgTestCtx* ctx, const char* name)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, name, I32, params, 1);
- Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), cgtest_param_slot(tf, 0), I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g01_indirect_call — int (*fp)(int) = echo; return fp(42); */
-static void build_g01_indirect_call(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId echo = build_g_echo_helper(ctx, "g01_echo");
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* Materialize the function pointer from the GLOBAL symbol. */
- const Type* fn_ty = type_func(ctx->pool, I32, params, 1, 0);
- const Type* fnp_ty = T_ptr(ctx, fn_ty);
- Reg fp = T->alloc_reg(T, RC_INT, fnp_ty);
- T->addr_of(T, REG_op(fp, fnp_ty), GLOBAL_op(echo, 0));
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 42 } };
- cgtest_call_indirect(tf, fp, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by g02: int fact(int n) { return n<2 ? 1 : n*fact(n-1); }
- * Forward-decl the symbol so the body can reference it for recursion. */
-static ObjSymId build_g02_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId sym = cgtest_decl_func(ctx, "g02_fact");
- CgTestFn* tf = cgtest_begin_func_at(ctx, sym, I32, params, 1);
- CGTarget* T = ctx->target;
-
- Reg n = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
-
- /* if (n < 2) goto base; */
- Label base = T->label_new(T);
- T->cmp_branch(T, CMP_LT_S, REG_op(n, I32), IMM_op(2, I32), base);
-
- /* recursive: tmp = fact(n - 1); return n * tmp; */
- Reg n1 = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
-
- Reg rec = T->alloc_reg(T, RC_INT, I32);
- CgTestArg rec_args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
- cgtest_call(tf, sym, I32, params, rec_args, 1, REG_op(rec, I32));
-
- Reg prod = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_IMUL, REG_op(prod, I32), REG_op(n, I32), REG_op(rec, I32));
- cgtest_ret_reg(tf, prod, I32);
-
- /* base: return 1; */
- T->label_place(T, base);
- cgtest_ret_imm(tf, 1, I32);
- cgtest_end(tf);
- return sym;
-}
-
-/* g02_recursion_factorial — fact(5) = 120. */
-static void build_g02_recursion_factorial(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId fact = build_g02_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 5 } };
- cgtest_call(tf, fact, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by g03: int fib(int n) { return n<2?n:fib(n-1)+fib(n-2); } */
-static ObjSymId build_g03_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId sym = cgtest_decl_func(ctx, "g03_fib");
- CgTestFn* tf = cgtest_begin_func_at(ctx, sym, I32, params, 1);
- CGTarget* T = ctx->target;
-
- Reg n = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
-
- /* if (n < 2) return n; */
- Label base = T->label_new(T);
- T->cmp_branch(T, CMP_LT_S, REG_op(n, I32), IMM_op(2, I32), base);
-
- /* a = fib(n-1); b = fib(n-2); return a+b; */
- Reg n1 = T->alloc_reg(T, RC_INT, I32);
- Reg n2 = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
- T->binop(T, BO_ISUB, REG_op(n2, I32), REG_op(n, I32), IMM_op(2, I32));
-
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- CgTestArg a1[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
- CgTestArg a2[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n2 } };
- cgtest_call(tf, sym, I32, params, a1, 1, REG_op(a, I32));
- cgtest_call(tf, sym, I32, params, a2, 1, REG_op(b, I32));
-
- Reg sum = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(a, I32), REG_op(b, I32));
- cgtest_ret_reg(tf, sum, I32);
-
- T->label_place(T, base);
- cgtest_ret_reg(tf, n, I32);
- cgtest_end(tf);
- return sym;
-}
-
-/* g03_recursion_fib — fib(10) = 55. */
-static void build_g03_recursion_fib(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId fib = build_g03_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 10 } };
- cgtest_call(tf, fib, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* g04_mutual_recursion — is_even(8) = 1.
- * int is_even(int n) { return n==0 ? 1 : is_odd(n-1); }
- * int is_odd (int n) { return n==0 ? 0 : is_even(n-1); }
- * Forward-declare both symbols up front so each body can reference the
- * other before it has been emitted. */
-static void build_g04_mutual_recursion(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- CGTarget* T = ctx->target;
-
- ObjSymId sym_e = cgtest_decl_func(ctx, "g04_is_even");
- ObjSymId sym_o = cgtest_decl_func(ctx, "g04_is_odd");
-
- /* is_even body. */
- {
- CgTestFn* tf = cgtest_begin_func_at(ctx, sym_e, I32, params, 1);
- Reg n = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
- Label base = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(n, I32), IMM_op(0, I32), base);
- Reg n1 = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
- Reg r = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
- cgtest_call(tf, sym_o, I32, params, args, 1, REG_op(r, I32));
- cgtest_ret_reg(tf, r, I32);
- T->label_place(T, base);
- cgtest_ret_imm(tf, 1, I32);
- cgtest_end(tf);
- }
-
- /* is_odd body. */
- {
- CgTestFn* tf = cgtest_begin_func_at(ctx, sym_o, I32, params, 1);
- Reg n = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
- Label base = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(n, I32), IMM_op(0, I32), base);
- Reg n1 = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
- Reg r = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
- cgtest_call(tf, sym_e, I32, params, args, 1, REG_op(r, I32));
- cgtest_ret_reg(tf, r, I32);
- T->label_place(T, base);
- cgtest_ret_imm(tf, 0, I32);
- cgtest_end(tf);
- }
-
- /* test_main: return is_even(8) → 1. */
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 8 } };
- cgtest_call(tf, sym_e, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by g05: int inc(int x) { return x+1; } */
-static ObjSymId build_g05_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "g05_inc", I32, params, 1);
- CGTarget* T = ctx->target;
- Reg x = T->alloc_reg(T, RC_INT, I32);
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(x, I32), cgtest_param_slot(tf, 0), I32);
- T->binop(T, BO_IADD, REG_op(r, I32), REG_op(x, I32), IMM_op(1, I32));
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g05_chained_calls — inc(inc(inc(39))) = 42. */
-static void build_g05_chained_calls(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId inc = build_g05_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Reg r1 = T->alloc_reg(T, RC_INT, I32);
- Reg r2 = T->alloc_reg(T, RC_INT, I32);
- Reg r3 = T->alloc_reg(T, RC_INT, I32);
- CgTestArg a1[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 39 } };
- cgtest_call(tf, inc, I32, params, a1, 1, REG_op(r1, I32));
- CgTestArg a2[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = r1 } };
- cgtest_call(tf, inc, I32, params, a2, 1, REG_op(r2, I32));
- CgTestArg a3[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = r2 } };
- cgtest_call(tf, inc, I32, params, a3, 1, REG_op(r3, I32));
- cgtest_ret_reg(tf, r3, I32);
- cgtest_end(tf);
-}
-
-/* helper used by g06:
- * int f(int a, float b, int c, double d, int e)
- * { return a + (int)b + c + (int)d + e; }
- * Mixes int and FP params — abi_func_info routes int→GPR and FP→FP. */
-static ObjSymId build_g06_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- const Type* F64 = T_f64(ctx);
- const Type* params[] = { I32, F32, I32, F64, I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "g06_f", I32, params, 5);
- CGTarget* T = ctx->target;
-
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg c = T->alloc_reg(T, RC_INT, I32);
- Reg e = T->alloc_reg(T, RC_INT, I32);
- Reg fb = T->alloc_reg(T, RC_FP, F32);
- Reg fd = T->alloc_reg(T, RC_FP, F64);
- cgtest_load_local(tf, REG_op(a, I32), cgtest_param_slot(tf, 0), I32);
- cgtest_load_local(tf, REG_op(fb, F32), cgtest_param_slot(tf, 1), F32);
- cgtest_load_local(tf, REG_op(c, I32), cgtest_param_slot(tf, 2), I32);
- cgtest_load_local(tf, REG_op(fd, F64), cgtest_param_slot(tf, 3), F64);
- cgtest_load_local(tf, REG_op(e, I32), cgtest_param_slot(tf, 4), I32);
-
- Reg ib = T->alloc_reg(T, RC_INT, I32);
- Reg id = T->alloc_reg(T, RC_INT, I32);
- T->convert(T, CV_FTOI_S, REG_op(ib, I32), REG_op(fb, F32));
- T->convert(T, CV_FTOI_S, REG_op(id, I32), REG_op(fd, F64));
-
- Reg s = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_IADD, REG_op(s, I32), REG_op(a, I32), REG_op(ib, I32));
- T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(c, I32));
- T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(id, I32));
- T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(e, I32));
- cgtest_ret_reg(tf, s, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g06_mixed_int_fp_params — f(2, 3.0f, 5, 7.0, 25) → 42. */
-static void build_g06_mixed_int_fp_params(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- const Type* F64 = T_f64(ctx);
- const Type* params[] = { I32, F32, I32, F64, I32 };
- ObjSymId f = build_g06_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* Materialize 3.0f and 7.0 in FP regs via load_const. */
- static const u8 BYTES_3F[4] = { 0x00, 0x00, 0x40, 0x40 }; /* 3.0f LE */
- static const u8 BYTES_7D[8] = { 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x1C, 0x40 }; /* 7.0 LE double */
- Reg fb = T->alloc_reg(T, RC_FP, F32);
- Reg fd = T->alloc_reg(T, RC_FP, F64);
- ConstBytes cbf = { .type = F32, .bytes = BYTES_3F, .size = 4, .align = 4 };
- ConstBytes cbd = { .type = F64, .bytes = BYTES_7D, .size = 8, .align = 8 };
- T->load_const(T, REG_op(fb, F32), cbf);
- T->load_const(T, REG_op(fd, F64), cbd);
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = {
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 2 },
- { .kind = CGT_ARG_REG, .type = F32, .v.reg = fb },
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 5 },
- { .kind = CGT_ARG_REG, .type = F64, .v.reg = fd },
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 25 },
- };
- cgtest_call(tf, f, I32, params, args, 5, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by g07: void fill(int *p, int v) { *p = v; } */
-static ObjSymId build_g07_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* VOID = T_void(ctx);
- const Type* params[] = { PI32, I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "g07_fill", VOID, params, 2);
- CGTarget* T = ctx->target;
-
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- Reg v = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 0), PI32);
- cgtest_load_local(tf, REG_op(v, I32), cgtest_param_slot(tf, 1), I32);
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(p, 0, I32), REG_op(v, I32), ma);
- cgtest_ret_void(tf);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g07_void_call_outparam — int x; fill(&x, 42); return x → 42. */
-static void build_g07_void_call_outparam(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* VOID = T_void(ctx);
- const Type* params[] = { PI32, I32 };
- ObjSymId fill = build_g07_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->addr_of(T, REG_op(p, PI32), LOCAL_op(x, I32));
-
- CgTestArg args[] = {
- { .kind = CGT_ARG_REG, .type = PI32, .v.reg = p },
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 42 },
- };
- cgtest_call(tf, fill, VOID, params, args, 2, IMM_op(0, VOID));
-
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), x, I32);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* struct S { int a[8]; }; — 32 bytes, exceeds the 16-byte threshold and is
- * passed by reference (caller-allocated copy) on AArch64 SysV. */
-static const Type* build_g08_struct_type(CgTestCtx* ctx)
-{
- Sym tag = pool_intern_cstr(ctx->pool, "S32");
- TagId tid = type_tag_new(ctx->pool, TAG_STRUCT, tag, (SrcLoc){0,0,0});
- TypeRecordBuilder* b = type_record_begin(ctx->pool, TY_STRUCT, tid, tag);
- /* Eight i32 fields named a0..a7. */
- for (int i = 0; i < 8; ++i) {
- char name[8];
- name[0] = 'a';
- name[1] = (char)('0' + i);
- name[2] = 0;
- type_record_field(b, (Field){
- .name = pool_intern_cstr(ctx->pool, name),
- .type = T_i32(ctx) });
- }
- return type_record_end(ctx->pool, b);
-}
-
-/* helper used by g08: int take(struct S s) { return s.a7; } */
-static ObjSymId build_g08_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* S = build_g08_struct_type(ctx);
- const Type* params[] = { S };
- CgTestFn* tf = cgtest_begin_func(ctx, "g08_take", I32, params, 1);
- CGTarget* T = ctx->target;
-
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, S));
- T->addr_of(T, REG_op(base, T_ptr(ctx, S)),
- LOCAL_op(cgtest_param_slot(tf, 0), S));
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_PARAM };
- Reg r = T->alloc_reg(T, RC_INT, I32);
- /* a7 lives at offset 28. */
- T->load(T, REG_op(r, I32), IND_op(base, 28, I32), ma);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g08_large_struct_byval — 32-byte struct passed by value. */
-static void build_g08_large_struct_byval(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* S = build_g08_struct_type(ctx);
- const Type* params[] = { S };
- ObjSymId take = build_g08_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot src = cgtest_local(tf, S, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, S));
- T->addr_of(T, REG_op(base, T_ptr(ctx, S)), LOCAL_op(src, S));
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- /* Zero out a0..a6 so the helper observation depends only on a7. */
- for (int i = 0; i < 7; ++i) {
- T->store(T, IND_op(base, i*4, I32), IMM_op(0, I32), ma);
- }
- T->store(T, IND_op(base, 28, I32), IMM_op(42, I32), ma);
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_BYVAL_LOCAL, .type = S, .v.slot = src } };
- cgtest_call(tf, take, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* struct V { float x, y; }; — HFA of two f32. AArch64 SysV passes in v0,v1
- * and returns in {v0, v1}. */
-static const Type* build_g_hfa_type(CgTestCtx* ctx)
-{
- Sym tag = pool_intern_cstr(ctx->pool, "V");
- TagId tid = type_tag_new(ctx->pool, TAG_STRUCT, tag, (SrcLoc){0,0,0});
- TypeRecordBuilder* b = type_record_begin(ctx->pool, TY_STRUCT, tid, tag);
- type_record_field(b, (Field){
- .name = pool_intern_cstr(ctx->pool, "x"), .type = T_f32(ctx) });
- type_record_field(b, (Field){
- .name = pool_intern_cstr(ctx->pool, "y"), .type = T_f32(ctx) });
- return type_record_end(ctx->pool, b);
-}
-
-/* helper used by g09: int f(struct V v) { return (int)(v.x + v.y); } */
-static ObjSymId build_g09_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- const Type* V = build_g_hfa_type(ctx);
- const Type* params[] = { V };
- CgTestFn* tf = cgtest_begin_func(ctx, "g09_f", I32, params, 1);
- CGTarget* T = ctx->target;
-
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
- T->addr_of(T, REG_op(base, T_ptr(ctx, V)),
- LOCAL_op(cgtest_param_slot(tf, 0), V));
- MemAccess ma = { .type = F32, .size = 4, .align = 4,
- .alias.kind = ALIAS_PARAM };
- Reg fx = T->alloc_reg(T, RC_FP, F32);
- Reg fy = T->alloc_reg(T, RC_FP, F32);
- Reg fs = T->alloc_reg(T, RC_FP, F32);
- T->load(T, REG_op(fx, F32), IND_op(base, 0, F32), ma);
- T->load(T, REG_op(fy, F32), IND_op(base, 4, F32), ma);
- T->binop(T, BO_FADD, REG_op(fs, F32), REG_op(fx, F32), REG_op(fy, F32));
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->convert(T, CV_FTOI_S, REG_op(r, I32), REG_op(fs, F32));
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g09_hfa_param_f32x2 — f({1.5f, 1.5f}) → 3. */
-static void build_g09_hfa_param_f32x2(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- const Type* V = build_g_hfa_type(ctx);
- const Type* params[] = { V };
- ObjSymId f = build_g09_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* Build local {1.5f, 1.5f} via FP load_const + store_local. */
- static const u8 BYTES_15F[4] = { 0x00, 0x00, 0xC0, 0x3F }; /* 1.5f LE */
- FrameSlot src = cgtest_local(tf, V, FSF_ADDR_TAKEN);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
- T->addr_of(T, REG_op(base, T_ptr(ctx, V)), LOCAL_op(src, V));
- Reg fc = T->alloc_reg(T, RC_FP, F32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_15F, .size = 4, .align = 4 };
- T->load_const(T, REG_op(fc, F32), cb);
- MemAccess ma = { .type = F32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, F32), REG_op(fc, F32), ma);
- T->store(T, IND_op(base, 4, F32), REG_op(fc, F32), ma);
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_BYVAL_LOCAL, .type = V, .v.slot = src } };
- cgtest_call(tf, f, I32, params, args, 1, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* helper used by g10: struct V g10_mk(void) { return (struct V){1.5f, 1.5f}; }
- * Returned via the HFA path — abi_func_info classifies the struct as
- * homogeneous-FP, so the backend places fields into v0/v1 instead of
- * memcpying through an sret pointer. cgtest_ret_indirect drives both. */
-static ObjSymId build_g10_helper(CgTestCtx* ctx)
-{
- const Type* F32 = T_f32(ctx);
- const Type* V = build_g_hfa_type(ctx);
- CgTestFn* tf = cgtest_begin_func(ctx, "g10_mk", V, NULL, 0);
- CGTarget* T = ctx->target;
-
- static const u8 BYTES_15F[4] = { 0x00, 0x00, 0xC0, 0x3F };
- FrameSlot s = cgtest_local(tf, V, FSF_NONE);
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
- T->addr_of(T, REG_op(base, T_ptr(ctx, V)), LOCAL_op(s, V));
- Reg fc = T->alloc_reg(T, RC_FP, F32);
- ConstBytes cb = { .type = F32, .bytes = BYTES_15F, .size = 4, .align = 4 };
- T->load_const(T, REG_op(fc, F32), cb);
- MemAccess ma = { .type = F32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(base, 0, F32), REG_op(fc, F32), ma);
- T->store(T, IND_op(base, 4, F32), REG_op(fc, F32), ma);
-
- cgtest_ret_indirect(tf, s);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* g10_hfa_return_f32x2 — sum fields of returned HFA, ftoi_s → 3. */
-static void build_g10_hfa_return_f32x2(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* F32 = T_f32(ctx);
- const Type* V = build_g_hfa_type(ctx);
- ObjSymId mk = build_g10_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot dst = cgtest_local(tf, V, FSF_ADDR_TAKEN);
- cgtest_call(tf, mk, V, NULL, NULL, 0, LOCAL_op(dst, V));
-
- Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
- T->addr_of(T, REG_op(base, T_ptr(ctx, V)), LOCAL_op(dst, V));
- MemAccess ma = { .type = F32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- Reg fx = T->alloc_reg(T, RC_FP, F32);
- Reg fy = T->alloc_reg(T, RC_FP, F32);
- Reg fs = T->alloc_reg(T, RC_FP, F32);
- T->load(T, REG_op(fx, F32), IND_op(base, 0, F32), ma);
- T->load(T, REG_op(fy, F32), IND_op(base, 4, F32), ma);
- T->binop(T, BO_FADD, REG_op(fs, F32), REG_op(fx, F32), REG_op(fy, F32));
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->convert(T, CV_FTOI_S, REG_op(r, I32), REG_op(fs, F32));
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* g11_caller_saved_live_across_call — x=42 must survive a call that
- * clobbers caller-saved regs. */
-static void build_g11_caller_saved_live_across_call(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId echo = build_g_echo_helper(ctx, "g11_echo");
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Reg x = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(x, I32), 42);
-
- Reg ignored = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 99 } };
- cgtest_call(tf, echo, I32, params, args, 1, REG_op(ignored, I32));
-
- cgtest_ret_reg(tf, x, I32);
- cgtest_end(tf);
-}
-
-/* g12_addr_taken_local_across_call — addr-taken local survives an
- * intervening call. b05 body with a side call between increment and
- * read-back. */
-static void build_g12_addr_taken_local_across_call(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId echo = build_g_echo_helper(ctx, "g12_echo");
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- cgtest_store_local(tf, x, IMM_op(17, I32), I32);
-
- Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
- T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), LOCAL_op(x, I32));
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- Reg val = T->alloc_reg(T, RC_INT, I32);
- T->load (T, REG_op(val, I32), IND_op(p, 0, I32), ma);
- T->binop(T, BO_IADD, REG_op(val, I32), REG_op(val, I32), IMM_op(1, I32));
- T->store(T, IND_op(p, 0, I32), REG_op(val, I32), ma);
-
- /* intervening call — must not corrupt the local or its address. */
- Reg ignored = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 99 } };
- cgtest_call(tf, echo, I32, params, args, 1, REG_op(ignored, I32));
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(out, I32), IND_op(p, 0, I32), ma);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* g13_call_in_loop_induction — for(i=0;i<10;i++) s += id(i); → 45.
- * Built on flat cmp_branch + jump, no SCOPE_LOOP — the induction var
- * lives in an addr-taken slot to force frame-residency across the call. */
-static void build_g13_call_in_loop_induction(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* params[] = { I32 };
- ObjSymId id = build_g_echo_helper(ctx, "g13_id");
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot islot = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot sslot = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, islot, IMM_op(0, I32), I32);
- cgtest_store_local(tf, sslot, IMM_op(0, I32), I32);
-
- Label top = T->label_new(T);
- Label done = T->label_new(T);
- T->label_place(T, top);
- Reg ireg = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ireg, I32), islot, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ireg, I32), IMM_op(10, I32), done);
-
- /* res = id(i); */
- Reg res = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = ireg } };
- cgtest_call(tf, id, I32, params, args, 1, REG_op(res, I32));
-
- /* s += res; */
- Reg sreg = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sreg, I32), sslot, I32);
- T->binop(T, BO_IADD, REG_op(sreg, I32), REG_op(sreg, I32), REG_op(res, I32));
- cgtest_store_local(tf, sslot, REG_op(sreg, I32), I32);
-
- /* i++; jump top. */
- Reg inew = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(inew, I32), islot, I32);
- T->binop(T, BO_IADD, REG_op(inew, I32), REG_op(inew, I32), IMM_op(1, I32));
- cgtest_store_local(tf, islot, REG_op(inew, I32), I32);
- T->jump(T, top);
-
- T->label_place(T, done);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), sslot, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group H: control flow
- *
- * Loops and multi-way branch beyond Group D's scope_if/scope_else.
- * Loops use SCOPE_LOOP with explicit break/continue labels — the
- * caller places continue at the appropriate point (top for while,
- * after-body-before-incr for for-loops). Switches lower to chained
- * cmp_branch + jump (no dedicated switch op). Short-circuit && / ||
- * are exercised by observing that the RHS side effect did not run.
- * ============================================================ */
-
-/* h01_while_sum_0_to_9 — int s=0,i=0; while(i<10){s+=i;i++;} return s; → 45. */
-static void build_h01_while_sum_0_to_9(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T);
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
- T->label_place(T, cnt);
-
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(10, I32), brk);
-
- Reg sr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
- T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(ir, I32));
- cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
-
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, cnt);
-
- T->label_place(T, brk);
- T->scope_end(T, sc);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h02_do_while_once — int i=0; do { i=42; } while(0); return i; → 42. */
-static void build_h02_do_while_once(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T);
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
- T->label_place(T, cnt);
-
- /* body: i = 42; */
- cgtest_store_local(tf, is, IMM_op(42, I32), I32);
-
- /* condition: while (0) — never taken. */
- Reg c = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(c, I32), 0);
- T->cmp_branch(T, CMP_NE, REG_op(c, I32), IMM_op(0, I32), cnt);
-
- T->label_place(T, brk);
- T->scope_end(T, sc);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), is, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h03_for_count_to_10 — for(i=1;i<=10;i++) s+=i; → 55. */
-static void build_h03_for_count_to_10(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
- cgtest_store_local(tf, is, IMM_op(1, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T); /* increment site */
- Label top = T->label_new(T); /* condition test */
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
-
- T->label_place(T, top);
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GT_S, REG_op(ir, I32), IMM_op(10, I32), brk);
-
- /* body: s += i; */
- Reg sr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
- T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(ir, I32));
- cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
-
- /* increment: i++; */
- T->label_place(T, cnt);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, top);
-
- T->label_place(T, brk);
- T->scope_end(T, sc);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h04_loop_break — for(i=0;;i++) if(i==42) break; return i; → 42. */
-static void build_h04_loop_break(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T);
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
- T->label_place(T, cnt);
-
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(42, I32), brk);
-
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, cnt);
-
- T->label_place(T, brk);
- T->scope_end(T, sc);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), is, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h05_loop_continue — sum of even i in [0,20) using continue → 90. */
-static void build_h05_loop_continue(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T); /* the increment site */
- Label top = T->label_new(T);
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
-
- T->label_place(T, top);
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(20, I32), brk);
-
- /* if (i & 1) continue; — odd → skip add. */
- Reg parity = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_AND, REG_op(parity, I32), REG_op(ir, I32), IMM_op(1, I32));
- T->cmp_branch(T, CMP_NE, REG_op(parity, I32), IMM_op(0, I32), cnt);
-
- /* s += i; */
- Reg sr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
- T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(ir, I32));
- cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
-
- T->label_place(T, cnt);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, top);
-
- T->label_place(T, brk);
- T->scope_end(T, sc);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h06_nested_loops — for(i=0;i<3;i++) for(j=0;j<2;j++) s++; → 6. */
-static void build_h06_nested_loops(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot js = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label outer_brk = T->label_new(T);
- Label outer_cnt = T->label_new(T);
- CGScopeDesc d_o = { .kind = SCOPE_LOOP,
- .break_label = outer_brk, .continue_label = outer_cnt };
- CGScope outer = T->scope_begin(T, &d_o);
- T->label_place(T, outer_cnt);
-
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(3, I32), outer_brk);
-
- cgtest_store_local(tf, js, IMM_op(0, I32), I32);
- Label inner_brk = T->label_new(T);
- Label inner_cnt = T->label_new(T);
- CGScopeDesc d_i = { .kind = SCOPE_LOOP,
- .break_label = inner_brk, .continue_label = inner_cnt };
- CGScope inner = T->scope_begin(T, &d_i);
- T->label_place(T, inner_cnt);
-
- Reg jr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(jr, I32), js, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(jr, I32), IMM_op(2, I32), inner_brk);
-
- /* s++ */
- Reg sr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
- T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), IMM_op(1, I32));
- cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
-
- /* j++ */
- T->binop(T, BO_IADD, REG_op(jr, I32), REG_op(jr, I32), IMM_op(1, I32));
- cgtest_store_local(tf, js, REG_op(jr, I32), I32);
- T->jump(T, inner_cnt);
- T->label_place(T, inner_brk);
- T->scope_end(T, inner);
-
- /* i++ */
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, outer_cnt);
- T->label_place(T, outer_brk);
- T->scope_end(T, outer);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h07_break_inner_only — outer counts 3 iterations, inner breaks after
- * incrementing s by 3 each time → s = 9. */
-static void build_h07_break_inner_only(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label outer_brk = T->label_new(T);
- Label outer_cnt = T->label_new(T);
- CGScopeDesc d_o = { .kind = SCOPE_LOOP,
- .break_label = outer_brk, .continue_label = outer_cnt };
- CGScope outer = T->scope_begin(T, &d_o);
- T->label_place(T, outer_cnt);
-
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(3, I32), outer_brk);
-
- /* inner loop: counts 0..2, but inner-break exits after counter reaches 3
- * (so adds 3 to s each outer iteration). */
- FrameSlot js = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, js, IMM_op(0, I32), I32);
- Label inner_brk = T->label_new(T);
- Label inner_cnt = T->label_new(T);
- CGScopeDesc d_i = { .kind = SCOPE_LOOP,
- .break_label = inner_brk, .continue_label = inner_cnt };
- CGScope inner = T->scope_begin(T, &d_i);
- T->label_place(T, inner_cnt);
-
- Reg jr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(jr, I32), js, I32);
- /* if (j >= 3) inner-break */
- T->cmp_branch(T, CMP_GE_S, REG_op(jr, I32), IMM_op(3, I32), inner_brk);
-
- /* s++ */
- Reg sr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
- T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), IMM_op(1, I32));
- cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
-
- /* j++ */
- T->binop(T, BO_IADD, REG_op(jr, I32), REG_op(jr, I32), IMM_op(1, I32));
- cgtest_store_local(tf, js, REG_op(jr, I32), I32);
- T->jump(T, inner_cnt);
- T->label_place(T, inner_brk);
- T->scope_end(T, inner);
-
- /* outer must continue past the inner break — i++ */
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, outer_cnt);
- T->label_place(T, outer_brk);
- T->scope_end(T, outer);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h08_early_return_in_loop — for(i=0;;i++) if(i==17) return i; → 17. */
-static void build_h08_early_return_in_loop(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T);
- Label hit = T->label_new(T);
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
- T->label_place(T, cnt);
-
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(17, I32), hit);
-
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, cnt);
-
- T->label_place(T, hit);
- cgtest_ret_reg(tf, ir, I32);
- /* the rest is dead. */
- T->label_place(T, brk);
- T->scope_end(T, sc);
- cgtest_ret_imm(tf, 0, I32);
- cgtest_end(tf);
-}
-
-/* h09_switch_three_cases — switch(2) {case 1:r=10;break; case 2:r=42;break;
- * case 3:r=99;break;} → 42. */
-static void build_h09_switch_three_cases(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot rs = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, rs, IMM_op(0, I32), I32);
-
- Reg val = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(val, I32), 2);
-
- Label l1 = T->label_new(T), l2 = T->label_new(T), l3 = T->label_new(T);
- Label end = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(1, I32), l1);
- T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(2, I32), l2);
- T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(3, I32), l3);
- T->jump(T, end);
-
- T->label_place(T, l1);
- cgtest_store_local(tf, rs, IMM_op(10, I32), I32);
- T->jump(T, end);
- T->label_place(T, l2);
- cgtest_store_local(tf, rs, IMM_op(42, I32), I32);
- T->jump(T, end);
- T->label_place(T, l3);
- cgtest_store_local(tf, rs, IMM_op(99, I32), I32);
- T->jump(T, end);
-
- T->label_place(T, end);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), rs, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h10_switch_fallthrough — switch(1){case 1: r+=10; case 2: r+=20;} → 30. */
-static void build_h10_switch_fallthrough(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot rs = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, rs, IMM_op(0, I32), I32);
-
- Reg val = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(val, I32), 1);
-
- Label l1 = T->label_new(T), l2 = T->label_new(T);
- Label end = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(1, I32), l1);
- T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(2, I32), l2);
- T->jump(T, end);
-
- T->label_place(T, l1);
- {
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), rs, I32);
- T->binop(T, BO_IADD, REG_op(r, I32), REG_op(r, I32), IMM_op(10, I32));
- cgtest_store_local(tf, rs, REG_op(r, I32), I32);
- }
- /* no break — fall through. */
- T->label_place(T, l2);
- {
- Reg r = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(r, I32), rs, I32);
- T->binop(T, BO_IADD, REG_op(r, I32), REG_op(r, I32), IMM_op(20, I32));
- cgtest_store_local(tf, rs, REG_op(r, I32), I32);
- }
- T->jump(T, end);
-
- T->label_place(T, end);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), rs, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h11_switch_default — switch(99){case 1:r=10;break; default:r=7;} → 7. */
-static void build_h11_switch_default(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot rs = cgtest_local(tf, I32, FSF_NONE);
-
- Reg val = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(val, I32), 99);
-
- Label l1 = T->label_new(T), ldef = T->label_new(T), end = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(1, I32), l1);
- T->jump(T, ldef);
-
- T->label_place(T, l1);
- cgtest_store_local(tf, rs, IMM_op(10, I32), I32);
- T->jump(T, end);
- T->label_place(T, ldef);
- cgtest_store_local(tf, rs, IMM_op(7, I32), I32);
- T->jump(T, end);
-
- T->label_place(T, end);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), rs, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h12_jump_forward — jump L; ret 99 (dead); L: ret 42; → 42. */
-static void build_h12_jump_forward(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Label L = T->label_new(T);
- T->jump(T, L);
- cgtest_ret_imm(tf, 99, I32); /* dead */
- T->label_place(T, L);
- cgtest_ret_imm(tf, 42, I32);
- cgtest_end(tf);
-}
-
-/* h13_jump_backward — counter loop entirely from cmp_branch + backward
- * jump (no SCOPE_LOOP). Loops until i == 10. */
-static void build_h13_jump_backward(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label top = T->label_new(T);
- Label end = T->label_new(T);
- T->label_place(T, top);
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(10, I32), end);
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, top);
-
- T->label_place(T, end);
- cgtest_ret_reg(tf, ir, I32);
- cgtest_end(tf);
-}
-
-/* h14_short_circuit_and_skip — `int s=0; (0) && (s=99,1); return s;` → 0.
- * The RHS side effect must NOT execute when the LHS is 0. */
-static void build_h14_short_circuit_and_skip(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
-
- Reg lhs = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(lhs, I32), 0);
-
- Label rhs = T->label_new(T);
- Label after = T->label_new(T);
- /* if (lhs != 0) goto rhs; else fall through to "after" with skipped RHS. */
- T->cmp_branch(T, CMP_NE, REG_op(lhs, I32), IMM_op(0, I32), rhs);
- T->jump(T, after);
-
- T->label_place(T, rhs);
- /* RHS side effect: s = 99 (must not run). */
- cgtest_store_local(tf, ss, IMM_op(99, I32), I32);
-
- T->label_place(T, after);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h15_short_circuit_or_skip — `int s=0; (1) || (s=99,1); return s;` → 0. */
-static void build_h15_short_circuit_or_skip(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
-
- Reg lhs = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(lhs, I32), 1);
-
- Label after = T->label_new(T);
- /* if (lhs != 0) skip RHS. */
- T->cmp_branch(T, CMP_NE, REG_op(lhs, I32), IMM_op(0, I32), after);
-
- /* RHS side effect (must not run). */
- cgtest_store_local(tf, ss, IMM_op(99, I32), I32);
-
- T->label_place(T, after);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h16_ternary — int x = (5 > 3) ? 42 : 7; return x; → 42. Uses scope_if. */
-static void build_h16_ternary(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot xs = cgtest_local(tf, I32, FSF_NONE);
-
- Reg c = T->alloc_reg(T, RC_INT, I32);
- Reg a = T->alloc_reg(T, RC_INT, I32);
- Reg b = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(a, I32), 5);
- T->load_imm(T, REG_op(b, I32), 3);
- T->cmp(T, CMP_GT_S, REG_op(c, I32), REG_op(a, I32), REG_op(b, I32));
- CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
- CGScope s = T->scope_begin(T, &desc);
- cgtest_store_local(tf, xs, IMM_op(42, I32), I32);
- T->scope_else(T, s);
- cgtest_store_local(tf, xs, IMM_op(7, I32), I32);
- T->scope_end(T, s);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), xs, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h17_ternary_side_effect_one_arm — int s=0; (1)?(s=42):(s=99); return s; → 42.
- * Only the taken arm runs. Uses cmp_branch + flat labels (no scope). */
-static void build_h17_ternary_side_effect_one_arm(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
-
- Reg c = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(c, I32), 1);
-
- Label then_l = T->label_new(T);
- Label end = T->label_new(T);
- T->cmp_branch(T, CMP_NE, REG_op(c, I32), IMM_op(0, I32), then_l);
- /* else arm */
- cgtest_store_local(tf, ss, IMM_op(99, I32), I32);
- T->jump(T, end);
- T->label_place(T, then_l);
- cgtest_store_local(tf, ss, IMM_op(42, I32), I32);
- T->label_place(T, end);
-
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
-}
-
-/* h18_unreachable_after_ret — emit a scalar ret followed by additional
- * (unreachable) ops; backend must tolerate the dead tail. */
-static void build_h18_unreachable_after_ret(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- cgtest_ret_imm(tf, 42, I32);
-
- /* Dead instructions — should not execute, but the emitter must accept them. */
- Reg dead = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(dead, I32), 99);
- cgtest_ret_reg(tf, dead, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Group I: alloca / VLA
- *
- * Stack-allocated runtime-sized memory: alloca with const and runtime
- * sizes, alignment, in-loop distinctness, and crossing a call boundary
- * with the alloca'd pointer. The alloca op signature is
- * alloca_(target, dst REG, size Operand, align).
- * ============================================================ */
-
-/* i01_alloca_const_int — int *p = alloca(4); *p = 42; return *p. */
-static void build_i01_alloca_const_int(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(p, 0, I32), IMM_op(42, I32), ma);
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* i02_alloca_runtime_size — int n=5; int *p = alloca(n*4); fill 1..5; sum=15. */
-static void build_i02_alloca_runtime_size(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* size_bytes = 5 * 4. Use I64 to match alloca's size operand. */
- Reg sz = T->alloc_reg(T, RC_INT, I64);
- T->load_imm(T, REG_op(sz, I64), 20);
-
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), REG_op(sz, I64), 4);
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- /* p[0..4] = 1..5 */
- for (int i = 0; i < 5; ++i) {
- T->store(T, IND_op(p, (i32)(i*4), I32), IMM_op(i+1, I32), ma);
- }
- /* sum */
- Reg acc = T->alloc_reg(T, RC_INT, I32);
- T->load_imm(T, REG_op(acc, I32), 0);
- for (int i = 0; i < 5; ++i) {
- Reg v = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(v, I32), IND_op(p, (i32)(i*4), I32), ma);
- T->binop(T, BO_IADD, REG_op(acc, I32), REG_op(acc, I32), REG_op(v, I32));
- }
- cgtest_ret_reg(tf, acc, I32);
- cgtest_end(tf);
-}
-
-/* i03_alloca_align_16 — alloca(16, align=16); return ((p & 0xF) == 0). */
-static void build_i03_alloca_align_16(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PV = T_ptr_void(ctx);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Reg p = T->alloc_reg(T, RC_INT, PV);
- T->alloca_(T, REG_op(p, PV), IMM_op(16, I64), 16);
-
- /* low_bits = p & 0xF */
- Reg lb = T->alloc_reg(T, RC_INT, I64);
- T->binop(T, BO_AND, REG_op(lb, I64), REG_op(p, I64), IMM_op(0xF, I64));
-
- /* result = (low_bits == 0) */
- Reg d = T->alloc_reg(T, RC_INT, I32);
- T->cmp(T, CMP_EQ, REG_op(d, I32), REG_op(lb, I64), IMM_op(0, I64));
- cgtest_ret_reg(tf, d, I32);
- cgtest_end(tf);
-}
-
-/* i04_alloca_in_loop_distinct — three alloca(4)s in a loop; return
- * (a != b && b != c). Addresses must differ across iterations. */
-static void build_i04_alloca_in_loop_distinct(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* Three slots to record the alloca'd addresses. */
- FrameSlot a = cgtest_local(tf, PI32, FSF_NONE);
- FrameSlot b = cgtest_local(tf, PI32, FSF_NONE);
- FrameSlot c = cgtest_local(tf, PI32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label brk = T->label_new(T);
- Label cnt = T->label_new(T);
- CGScopeDesc d = { .kind = SCOPE_LOOP,
- .break_label = brk, .continue_label = cnt };
- CGScope sc = T->scope_begin(T, &d);
- T->label_place(T, cnt);
-
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(3, I32), brk);
-
- /* p = alloca(4) */
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
-
- /* select destination slot by i. */
- Label sa = T->label_new(T), sb = T->label_new(T), sc_l = T->label_new(T);
- Label after_store = T->label_new(T);
- T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(0, I32), sa);
- T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(1, I32), sb);
- T->jump(T, sc_l);
-
- T->label_place(T, sa);
- cgtest_store_local(tf, a, REG_op(p, PI32), PI32);
- T->jump(T, after_store);
- T->label_place(T, sb);
- cgtest_store_local(tf, b, REG_op(p, PI32), PI32);
- T->jump(T, after_store);
- T->label_place(T, sc_l);
- cgtest_store_local(tf, c, REG_op(p, PI32), PI32);
- T->label_place(T, after_store);
-
- /* i++ */
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, cnt);
- T->label_place(T, brk);
- T->scope_end(T, sc);
-
- /* return (a != b) & (b != c) */
- Reg ra = T->alloc_reg(T, RC_INT, PI32);
- Reg rb = T->alloc_reg(T, RC_INT, PI32);
- Reg rc = T->alloc_reg(T, RC_INT, PI32);
- cgtest_load_local(tf, REG_op(ra, PI32), a, PI32);
- cgtest_load_local(tf, REG_op(rb, PI32), b, PI32);
- cgtest_load_local(tf, REG_op(rc, PI32), c, PI32);
-
- Reg ne1 = T->alloc_reg(T, RC_INT, I32);
- Reg ne2 = T->alloc_reg(T, RC_INT, I32);
- Reg both = T->alloc_reg(T, RC_INT, I32);
- T->cmp(T, CMP_NE, REG_op(ne1, I32), REG_op(ra, PI32), REG_op(rb, PI32));
- T->cmp(T, CMP_NE, REG_op(ne2, I32), REG_op(rb, PI32), REG_op(rc, PI32));
- T->binop(T, BO_AND, REG_op(both, I32), REG_op(ne1, I32), REG_op(ne2, I32));
- cgtest_ret_reg(tf, both, I32);
- cgtest_end(tf);
-}
-
-/* helper used by i05: void fill(int *p, int v) { *p = v; } — same shape as
- * g07 but a separate symbol so the cases don't share state. */
-static ObjSymId build_i05_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* VOID = T_void(ctx);
- const Type* params[] = { PI32, I32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "i05_fill", VOID, params, 2);
- CGTarget* T = ctx->target;
-
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- Reg v = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 0), PI32);
- cgtest_load_local(tf, REG_op(v, I32), cgtest_param_slot(tf, 1), I32);
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(p, 0, I32), REG_op(v, I32), ma);
- cgtest_ret_void(tf);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* i05_alloca_then_call — alloca buf; helper writes 42; load and return. */
-static void build_i05_alloca_then_call(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* VOID = T_void(ctx);
- const Type* params[] = { PI32, I32 };
- ObjSymId fill = build_i05_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
-
- CgTestArg args[] = {
- { .kind = CGT_ARG_REG, .type = PI32, .v.reg = p },
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 42 },
- };
- cgtest_call(tf, fill, VOID, params, args, 2, IMM_op(0, VOID));
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* i06_two_allocas_disjoint — *p=1; *q=2; return *p + *q → 3. */
-static void build_i06_two_allocas_disjoint(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- Reg q = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
- T->alloca_(T, REG_op(q, PI32), IMM_op(4, I64), 4);
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(p, 0, I32), IMM_op(1, I32), ma);
- T->store(T, IND_op(q, 0, I32), IMM_op(2, I32), ma);
-
- Reg vp = T->alloc_reg(T, RC_INT, I32);
- Reg vq = T->alloc_reg(T, RC_INT, I32);
- Reg s = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(vp, I32), IND_op(p, 0, I32), ma);
- T->load(T, REG_op(vq, I32), IND_op(q, 0, I32), ma);
- T->binop(T, BO_IADD, REG_op(s, I32), REG_op(vp, I32), REG_op(vq, I32));
- cgtest_ret_reg(tf, s, I32);
- cgtest_end(tf);
-}
-
-/* i07_alloca_addr_escapes — alloca'd pointer round-trips through an
- * addr-taken local int**, then is dereferenced to write 42. The escape
- * forces the alloca's pointer to be a real value, not a register-only
- * temporary the optimizer could fold away. */
-static void build_i07_alloca_addr_escapes(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* PPI32 = T_ptr(ctx, PI32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* int *holder; */
- FrameSlot holder = cgtest_local(tf, PI32, FSF_ADDR_TAKEN);
-
- /* p = alloca(4); */
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
-
- /* holder = p; */
- cgtest_store_local(tf, holder, REG_op(p, PI32), PI32);
-
- /* int **pp = &holder; */
- Reg pp = T->alloc_reg(T, RC_INT, PPI32);
- T->addr_of(T, REG_op(pp, PPI32), LOCAL_op(holder, PI32));
-
- /* int *back = *pp; *back = 42; return *back; */
- MemAccess ma_p = { .type = PI32, .size = 8, .align = 8,
- .alias.kind = ALIAS_LOCAL };
- Reg back = T->alloc_reg(T, RC_INT, PI32);
- T->load(T, REG_op(back, PI32), IND_op(pp, 0, PI32), ma_p);
-
- MemAccess ma_i = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(back, 0, I32), IMM_op(42, I32), ma_i);
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(back, 0, I32), ma_i);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* helper used by i08: int sum(int n, int *p) — n must be > 0. */
-static ObjSymId build_i08_helper(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* params[] = { I32, PI32 };
- CgTestFn* tf = cgtest_begin_func(ctx, "i08_sum", I32, params, 2);
- CGTarget* T = ctx->target;
-
- Reg n = T->alloc_reg(T, RC_INT, I32);
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
- cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 1), PI32);
-
- /* int s=0; for (i=0;i<n;i++) s += p[i]; */
- FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
- FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
- cgtest_store_local(tf, is, IMM_op(0, I32), I32);
-
- Label top = T->label_new(T);
- Label end = T->label_new(T);
- T->label_place(T, top);
- Reg ir = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(ir, I32), is, I32);
- T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), REG_op(n, I32), end);
-
- /* offset_bytes = i * 4 */
- Reg ofs = T->alloc_reg(T, RC_INT, I32);
- T->binop(T, BO_SHL, REG_op(ofs, I32), REG_op(ir, I32), IMM_op(2, I32));
- /* p_i = p + offset (use I64 ptr arith) */
- Reg pi = T->alloc_reg(T, RC_INT, PI32);
- T->binop(T, BO_IADD, REG_op(pi, PI32), REG_op(p, PI32), REG_op(ofs, I32));
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- Reg v = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(v, I32), IND_op(pi, 0, I32), ma);
-
- Reg sr = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
- T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(v, I32));
- cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
-
- T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
- cgtest_store_local(tf, is, REG_op(ir, I32), I32);
- T->jump(T, top);
-
- T->label_place(T, end);
- Reg out = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(out, I32), ss, I32);
- cgtest_ret_reg(tf, out, I32);
- cgtest_end(tf);
- return tf->sym;
-}
-
-/* i08_vla_param_sum — alloca 9 ints, fill 1..9, helper sums → 45. */
-static void build_i08_vla_param_sum(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- const Type* params[] = { I32, PI32 };
- ObjSymId sum = build_i08_helper(ctx);
-
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* alloca 9*4 = 36 bytes (round up to 40 for 8B align is fine). */
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(36, I64), 4);
-
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- for (int i = 0; i < 9; ++i) {
- T->store(T, IND_op(p, (i32)(i*4), I32), IMM_op(i+1, I32), ma);
- }
-
- Reg dst = T->alloc_reg(T, RC_INT, I32);
- CgTestArg args[] = {
- { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 9 },
- { .kind = CGT_ARG_REG, .type = PI32, .v.reg = p },
- };
- cgtest_call(tf, sum, I32, params, args, 2, REG_op(dst, I32));
- cgtest_ret_reg(tf, dst, I32);
- cgtest_end(tf);
-}
-
-/* i09_alloca_preserves_locals — named locals declared before *and* after
- * an alloca remain readable; the alloca must not overlap their slots. */
-static void build_i09_alloca_preserves_locals(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- /* int x = 17 (declared before alloca). */
- FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, x, IMM_op(17, I32), I32);
-
- /* alloca 4 bytes. */
- Reg p = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
-
- /* int y = 25 (declared after alloca). */
- FrameSlot y = cgtest_local(tf, I32, FSF_NONE);
- cgtest_store_local(tf, y, IMM_op(25, I32), I32);
-
- /* Touch the alloca'd memory so it isn't dead. */
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- T->store(T, IND_op(p, 0, I32), IMM_op(99, I32), ma);
-
- /* return x + y → 42. */
- Reg rx = T->alloc_reg(T, RC_INT, I32);
- Reg ry = T->alloc_reg(T, RC_INT, I32);
- Reg rs = T->alloc_reg(T, RC_INT, I32);
- cgtest_load_local(tf, REG_op(rx, I32), x, I32);
- cgtest_load_local(tf, REG_op(ry, I32), y, I32);
- T->binop(T, BO_IADD, REG_op(rs, I32), REG_op(rx, I32), REG_op(ry, I32));
- cgtest_ret_reg(tf, rs, I32);
- cgtest_end(tf);
-}
-
-/* i10_alloca_after_named_local — frame layout must keep both addressable
- * even when the named local is addr-taken. Same expected as i09 but the
- * named local has FSF_ADDR_TAKEN so the backend must place it in the
- * fixed-frame region, not in the dynamic alloca region. */
-static void build_i10_alloca_after_named_local(CgTestCtx* ctx)
-{
- const Type* I32 = T_i32(ctx);
- const Type* I64 = T_i64(ctx);
- const Type* PI32 = T_ptr(ctx, I32);
- CgTestFn* tf = cgtest_begin_main(ctx, I32);
- CGTarget* T = ctx->target;
-
- FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
- cgtest_store_local(tf, x, IMM_op(42, I32), I32);
-
- /* Take the address of x BEFORE the alloca. */
- Reg px = T->alloc_reg(T, RC_INT, PI32);
- T->addr_of(T, REG_op(px, PI32), LOCAL_op(x, I32));
-
- /* alloca; must not invalidate &x. */
- Reg dyn = T->alloc_reg(T, RC_INT, PI32);
- T->alloca_(T, REG_op(dyn, PI32), IMM_op(8, I64), 4);
-
- /* Reload via the saved &x. */
- MemAccess ma = { .type = I32, .size = 4, .align = 4,
- .alias.kind = ALIAS_LOCAL };
- Reg r = T->alloc_reg(T, RC_INT, I32);
- T->load(T, REG_op(r, I32), IND_op(px, 0, I32), ma);
- cgtest_ret_reg(tf, r, I32);
- cgtest_end(tf);
-}
-
-/* ============================================================
- * Registry
- * ============================================================ */
+/* ---- builder forward decls ---- */
+
+void build_mc_smoke(CgTestCtx*);
+
+void build_a01_return_const_42(CgTestCtx*);
+void build_a02_return_zero(CgTestCtx*);
+void build_a03_ret_imm(CgTestCtx*);
+void build_a04_copy_reg(CgTestCtx*);
+void build_a05_return_neg_small(CgTestCtx*);
+void build_a06_return_i64(CgTestCtx*);
+void build_a07_void_return(CgTestCtx*);
+void build_a08_multiple_returns(CgTestCtx*);
+void build_a09_load_imm_movz_movk(CgTestCtx*);
+void build_a10_return_u8(CgTestCtx*);
+
+void build_b01_param_int(CgTestCtx*);
+void build_b02_param_sum(CgTestCtx*);
+void build_b03_param_spill(CgTestCtx*);
+void build_b04_local_int(CgTestCtx*);
+void build_b05_addr_taken_local(CgTestCtx*);
+void build_b06_sret(CgTestCtx*);
+void build_b07_byval_param(CgTestCtx*);
+void build_b08_fp_param(CgTestCtx*);
+
+void build_c01_add(CgTestCtx*);
+void build_c02_sub_mul(CgTestCtx*);
+void build_c03_bitwise(CgTestCtx*);
+void build_c04_shift(CgTestCtx*);
+void build_c05_div_mod(CgTestCtx*);
+void build_c06_xor(CgTestCtx*);
+void build_c07_iadd_i64(CgTestCtx*);
+void build_c08_unsigned_div(CgTestCtx*);
+void build_c09_neg(CgTestCtx*);
+void build_c10_logical_not(CgTestCtx*);
+void build_c11_shr_signed(CgTestCtx*);
+void build_c12_imul_i64(CgTestCtx*);
+
+void build_d01_cmp_eq_true(CgTestCtx*);
+void build_d02_cmp_eq_false(CgTestCtx*);
+void build_d03_cmp_ne(CgTestCtx*);
+void build_d04_cmp_lt_signed(CgTestCtx*);
+void build_d05_cmp_lt_unsigned(CgTestCtx*);
+void build_d06_cmp_ge_signed(CgTestCtx*);
+void build_d07_cmp_branch_taken(CgTestCtx*);
+void build_d08_cmp_branch_not_taken(CgTestCtx*);
+void build_d09_cmp_branch_lt_signed(CgTestCtx*);
+void build_d10_jump(CgTestCtx*);
+void build_d11_scope_if_true(CgTestCtx*);
+void build_d12_scope_if_false(CgTestCtx*);
+void build_d13_scope_if_else(CgTestCtx*);
+
+void build_e01_sext_i8_i32(CgTestCtx*);
+void build_e02_zext_u8_i32(CgTestCtx*);
+void build_e03_sext_i16_i32(CgTestCtx*);
+void build_e04_zext_u16_i32(CgTestCtx*);
+void build_e05_zext_u32_i64(CgTestCtx*);
+void build_e06_sext_i32_i64(CgTestCtx*);
+void build_e07_trunc_i64_i32(CgTestCtx*);
+void build_e08_trunc_i32_i8(CgTestCtx*);
+void build_e09_itof_s_i32_f32(CgTestCtx*);
+void build_e10_itof_u_u32_f64(CgTestCtx*);
+void build_e11_ftoi_s_neg(CgTestCtx*);
+void build_e12_ftoi_u_pos(CgTestCtx*);
+void build_e13_fext_f32_f64(CgTestCtx*);
+void build_e14_ftrunc_f64_f32(CgTestCtx*);
+void build_e15_bitcast_i32_f32(CgTestCtx*);
+
+void build_f01_load_store_i8(CgTestCtx*);
+void build_f02_load_store_i16(CgTestCtx*);
+void build_f03_load_store_i64(CgTestCtx*);
+void build_f04_load_store_f32(CgTestCtx*);
+void build_f05_load_store_f64(CgTestCtx*);
+void build_f06_indirect_nonzero_offset(CgTestCtx*);
+void build_f07_store_reg(CgTestCtx*);
+void build_f08_copy_bytes(CgTestCtx*);
+void build_f09_set_bytes_zero(CgTestCtx*);
+void build_f10_set_bytes_ff(CgTestCtx*);
+void build_f11_volatile_rw(CgTestCtx*);
+void build_f12_bitfield_unsigned(CgTestCtx*);
+void build_f13_bitfield_signed(CgTestCtx*);
+
+void build_g01_indirect_call(CgTestCtx*);
+void build_g02_recursion_factorial(CgTestCtx*);
+void build_g03_recursion_fib(CgTestCtx*);
+void build_g04_mutual_recursion(CgTestCtx*);
+void build_g05_chained_calls(CgTestCtx*);
+void build_g06_mixed_int_fp_params(CgTestCtx*);
+void build_g07_void_call_outparam(CgTestCtx*);
+void build_g08_large_struct_byval(CgTestCtx*);
+void build_g09_hfa_param_f32x2(CgTestCtx*);
+void build_g10_hfa_return_f32x2(CgTestCtx*);
+void build_g11_caller_saved_live_across_call(CgTestCtx*);
+void build_g12_addr_taken_local_across_call(CgTestCtx*);
+void build_g13_call_in_loop_induction(CgTestCtx*);
+
+void build_h01_while_sum_0_to_9(CgTestCtx*);
+void build_h02_do_while_once(CgTestCtx*);
+void build_h03_for_count_to_10(CgTestCtx*);
+void build_h04_loop_break(CgTestCtx*);
+void build_h05_loop_continue(CgTestCtx*);
+void build_h06_nested_loops(CgTestCtx*);
+void build_h07_break_inner_only(CgTestCtx*);
+void build_h08_early_return_in_loop(CgTestCtx*);
+void build_h09_switch_three_cases(CgTestCtx*);
+void build_h10_switch_fallthrough(CgTestCtx*);
+void build_h11_switch_default(CgTestCtx*);
+void build_h12_jump_forward(CgTestCtx*);
+void build_h13_jump_backward(CgTestCtx*);
+void build_h14_short_circuit_and_skip(CgTestCtx*);
+void build_h15_short_circuit_or_skip(CgTestCtx*);
+void build_h16_ternary(CgTestCtx*);
+void build_h17_ternary_side_effect_one_arm(CgTestCtx*);
+void build_h18_unreachable_after_ret(CgTestCtx*);
+
+void build_i01_alloca_const_int(CgTestCtx*);
+void build_i02_alloca_runtime_size(CgTestCtx*);
+void build_i03_alloca_align_16(CgTestCtx*);
+void build_i04_alloca_in_loop_distinct(CgTestCtx*);
+void build_i05_alloca_then_call(CgTestCtx*);
+void build_i06_two_allocas_disjoint(CgTestCtx*);
+void build_i07_alloca_addr_escapes(CgTestCtx*);
+void build_i08_vla_param_sum(CgTestCtx*);
+void build_i09_alloca_preserves_locals(CgTestCtx*);
+void build_i10_alloca_after_named_local(CgTestCtx*);
+
+/* ---- registry ---- */
const CgCase cg_cases[] = {
/* MC-only */
diff --git a/test/cg/harness/cases_a.c b/test/cg/harness/cases_a.c
@@ -0,0 +1,123 @@
+/* Group A — function lifecycle and return.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group A: function lifecycle and return
+ * ============================================================ */
+
+/* a01_return_const_42 — alloc reg, load_imm(42), ret reg. */
+void build_a01_return_const_42(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ ctx->target->load_imm(ctx->target, REG_op(r, I32), 42);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* a02_return_zero — load_imm(0). */
+void build_a02_return_zero(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ ctx->target->load_imm(ctx->target, REG_op(r, I32), 0);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* a03_ret_imm — backend materializes the imm directly inside ret(). */
+void build_a03_ret_imm(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ cgtest_ret_imm(tf, 17, I32);
+ cgtest_end(tf);
+}
+
+/* a04_copy_reg — load_imm(7) into r1, copy r1->r2, ret r2. */
+void build_a04_copy_reg(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg r1 = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ Reg r2 = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ ctx->target->load_imm(ctx->target, REG_op(r1, I32), 7);
+ ctx->target->copy(ctx->target, REG_op(r2, I32), REG_op(r1, I32));
+ cgtest_ret_reg(tf, r2, I32);
+ cgtest_end(tf);
+}
+
+/* a05_return_neg_small — load_imm(-7), ret. Backend should select MOVN.
+ * Exit code = -7 & 0xff = 249. */
+void build_a05_return_neg_small(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ ctx->target->load_imm(ctx->target, REG_op(r, I32), -7);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* a06_return_i64 — i64 load_imm with high bits set, return as i64.
+ * test_main is cast to int(*)(void) by the runner, which reads w0
+ * (low 32 of x0). Value 0x100000002A → low 32 = 42. */
+void build_a06_return_i64(CgTestCtx* ctx)
+{
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I64);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I64);
+ ctx->target->load_imm(ctx->target, REG_op(r, I64), 0x100000002Aull);
+ cgtest_ret_reg(tf, r, I64);
+ cgtest_end(tf);
+}
+
+/* a07_void_return — function returns void. The harness wrapper (start.c
+ * for path E; the C compiler for path D/J) zeroes x0 before the call, so
+ * the observed exit code is 0. */
+void build_a07_void_return(CgTestCtx* ctx)
+{
+ CgTestFn* tf = cgtest_begin_main(ctx, T_void(ctx));
+ cgtest_ret_void(tf);
+ cgtest_end(tf);
+}
+
+/* a08_multiple_returns — two consecutive ret() calls in straight-line
+ * code. The first is taken; the second is dead. Exercises that the
+ * backend can emit more than one return-shaped sequence per function. */
+void build_a08_multiple_returns(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ cgtest_ret_imm(tf, 1, I32);
+ cgtest_ret_imm(tf, 2, I32); /* unreachable */
+ cgtest_end(tf);
+}
+
+/* a09_load_imm_movz_movk — value that requires multi-step materialization
+ * on AArch64 (low 16 != 0, high 16 != 0). 0xABCD → exit 0xCD = 205. */
+void build_a09_load_imm_movz_movk(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ ctx->target->load_imm(ctx->target, REG_op(r, I32), 0xABCD);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* a10_return_u8 — narrow integer return. Value 200 fits in u8 → 200. */
+void build_a10_return_u8(CgTestCtx* ctx)
+{
+ const Type* U8 = T_u8(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U8);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, U8);
+ ctx->target->load_imm(ctx->target, REG_op(r, U8), 200);
+ cgtest_ret_reg(tf, r, U8);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_b.c b/test/cg/harness/cases_b.c
@@ -0,0 +1,332 @@
+/* Group B — frame slots, parameters, locals.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+#include "cases_shared.h"
+
+/* ============================================================
+ * Group B: frame slots, parameters, locals
+ *
+ * Several cases here define a helper function and have test_main call
+ * it. The helper exercises param wiring; test_main exercises the call
+ * sequence. Both share one CGTarget instance — the backend must support
+ * multiple func_begin/func_end pairs per TU.
+ * ============================================================ */
+
+/* helper used by b01: int echo(int x) { return x; } */
+static ObjSymId build_b01_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "b01_echo", I32, params, 1);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), cgtest_param_slot(tf, 0), I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* b01_param_int — echo(201) → 201. */
+void build_b01_param_int(CgTestCtx* ctx)
+{
+ ObjSymId echo = build_b01_helper(ctx);
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 201 } };
+ cgtest_call(tf, echo, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by b02: int sum2(int a, int b) { return a + b; } */
+static ObjSymId build_b02_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32, I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "b02_sum2", I32, params, 2);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg s = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(a, I32), cgtest_param_slot(tf, 0), I32);
+ cgtest_load_local(tf, REG_op(b, I32), cgtest_param_slot(tf, 1), I32);
+ T->binop(T, BO_IADD, REG_op(s, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, s, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* b02_param_sum — sum2(40, 2) → 42. */
+void build_b02_param_sum(CgTestCtx* ctx)
+{
+ ObjSymId sum2 = build_b02_helper(ctx);
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32, I32 };
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ CgTestArg args[] = {
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 40 },
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 2 },
+ };
+ cgtest_call(tf, sum2, I32, params, args, 2, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by b03: int sum9(a..i) { return a+b+c+d+e+f+g+h+i; }
+ * Nine int parameters force at least one to spill onto the stack on
+ * AArch64 SysV (8 GP arg registers). */
+static ObjSymId build_b03_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* p[9] = { I32,I32,I32,I32,I32,I32,I32,I32,I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "b03_sum9", I32, p, 9);
+ CGTarget* T = ctx->target;
+ Reg accum = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(accum, I32), 0);
+ for (u32 i = 0; i < 9; ++i) {
+ Reg t = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(t, I32), cgtest_param_slot(tf, i), I32);
+ T->binop(T, BO_IADD, REG_op(accum, I32),
+ REG_op(accum, I32), REG_op(t, I32));
+ }
+ cgtest_ret_reg(tf, accum, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* b03_param_spill — sum9(1..9) = 45. */
+void build_b03_param_spill(CgTestCtx* ctx)
+{
+ ObjSymId sum9 = build_b03_helper(ctx);
+ const Type* I32 = T_i32(ctx);
+ const Type* params[9] = { I32,I32,I32,I32,I32,I32,I32,I32,I32 };
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ CgTestArg args[9];
+ for (int i = 0; i < 9; ++i) {
+ args[i] = (CgTestArg){ .kind = CGT_ARG_IMM, .type = I32, .v.imm = i+1 };
+ }
+ cgtest_call(tf, sum9, I32, params, args, 9, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* b04_local_int — alloc local int, store 42, load it back, return. */
+void build_b04_local_int(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, s, IMM_op(42, I32), I32);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), s, I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* b05_addr_taken_local — addr_of forces frame residency. Take address,
+ * store via INDIRECT, load via INDIRECT, return.
+ * int x; store-to-slot 17
+ * int* p = &x;
+ * *p = *p + 1; → 18
+ * return *p; */
+void build_b05_addr_taken_local(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ cgtest_store_local(tf, x, IMM_op(17, I32), I32);
+
+ Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
+ T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), LOCAL_op(x, I32));
+
+ /* val = *p; val += 1; *p = val; return val; */
+ Reg val = T->alloc_reg(T, RC_INT, I32);
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->load (T, REG_op(val, I32), IND_op(p, 0, I32), ma);
+ T->binop(T, BO_IADD, REG_op(val, I32), REG_op(val, I32), IMM_op(1, I32));
+ T->store(T, IND_op(p, 0, I32), REG_op(val, I32), ma);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(out, I32), IND_op(p, 0, I32), ma);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by b06:
+ * struct Pt { int a; int b; };
+ * struct Pt mk(void) { return (struct Pt){10, 32}; }
+ * On AArch64 SysV a 8-byte struct returns in x0 (or split across regs);
+ * the harness leaves register placement to abi_func_info. The body
+ * stores .a=10 and .b=32 into a local struct and returns it. */
+
+static ObjSymId build_b06_helper(CgTestCtx* ctx)
+{
+ const Type* PT = cases_pt_type(ctx);
+ CgTestFn* tf = cgtest_begin_func(ctx, "b06_mk", PT, NULL, 0);
+ CGTarget* T = ctx->target;
+
+ /* Build the struct in a local then return its address; the backend
+ * uses fn->abi_info->ret to decide whether to copy into the sret
+ * pointer (large struct) or load into the return regs (small). */
+ FrameSlot s = cgtest_local(tf, PT, FSF_NONE);
+ /* &s + 0 = .a, &s + 4 = .b */
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, PT)), LOCAL_op(s, PT));
+ MemAccess ma_i32 = { .type = T_i32(ctx), .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, T_i32(ctx)), IMM_op(10, T_i32(ctx)), ma_i32);
+ T->store(T, IND_op(base, 4, T_i32(ctx)), IMM_op(32, T_i32(ctx)), ma_i32);
+
+ cgtest_ret_indirect(tf, s);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* b06_sret — pt = mk(); return pt.a + pt.b → 42. */
+void build_b06_sret(CgTestCtx* ctx)
+{
+ const Type* PT = cases_pt_type(ctx);
+ ObjSymId mk = build_b06_helper(ctx);
+ const Type* I32 = T_i32(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* Caller provides a local for the sret destination. The harness
+ * passes its address as the ret_storage operand; the backend will
+ * either materialize an sret pointer (large) or unpack regs into
+ * the local (small) per the ABI. */
+ FrameSlot dst = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
+ cgtest_call(tf, mk, PT, NULL, NULL, 0, LOCAL_op(dst, PT));
+
+ /* Load .a, .b, sum, return. */
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, PT)), LOCAL_op(dst, PT));
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ Reg ra = T->alloc_reg(T, RC_INT, I32);
+ Reg rb = T->alloc_reg(T, RC_INT, I32);
+ Reg sum = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(ra, I32), IND_op(base, 0, I32), ma);
+ T->load(T, REG_op(rb, I32), IND_op(base, 4, I32), ma);
+ T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(ra, I32), REG_op(rb, I32));
+ cgtest_ret_reg(tf, sum, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by b07:
+ * struct Pt { int a; int b; };
+ * int take(struct Pt p) { return p.a + p.b; }
+ * Aggregate-by-value parameter. Caller builds a local Pt and passes its
+ * address with byval semantics; the callee receives a local copy. */
+static ObjSymId build_b07_helper(CgTestCtx* ctx)
+{
+ const Type* PT = cases_pt_type(ctx);
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { PT };
+ CgTestFn* tf = cgtest_begin_func(ctx, "b07_take", I32, params, 1);
+ CGTarget* T = ctx->target;
+
+ /* The param's home slot holds the byval copy. Compute its address
+ * and load .a, .b. */
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, PT)),
+ LOCAL_op(cgtest_param_slot(tf, 0), PT));
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_PARAM };
+ Reg ra = T->alloc_reg(T, RC_INT, I32);
+ Reg rb = T->alloc_reg(T, RC_INT, I32);
+ Reg sum = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(ra, I32), IND_op(base, 0, I32), ma);
+ T->load(T, REG_op(rb, I32), IND_op(base, 4, I32), ma);
+ T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(ra, I32), REG_op(rb, I32));
+ cgtest_ret_reg(tf, sum, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* b07_byval_param — take({.a=15, .b=27}) → 42. */
+void build_b07_byval_param(CgTestCtx* ctx)
+{
+ const Type* PT = cases_pt_type(ctx);
+ const Type* I32 = T_i32(ctx);
+ ObjSymId take = build_b07_helper(ctx);
+ const Type* params[] = { PT };
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot src = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, PT)), LOCAL_op(src, PT));
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, I32), IMM_op(15, I32), ma);
+ T->store(T, IND_op(base, 4, I32), IMM_op(27, I32), ma);
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = {
+ { .kind = CGT_ARG_BYVAL_LOCAL, .type = PT, .v.slot = src }
+ };
+ cgtest_call(tf, take, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by b08: int trunc(float f) { return (int)f; } */
+static ObjSymId build_b08_helper(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { F32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "b08_trunc", I32, params, 1);
+ CGTarget* T = ctx->target;
+
+ Reg f = T->alloc_reg(T, RC_FP, F32);
+ cgtest_load_local(tf, REG_op(f, F32), cgtest_param_slot(tf, 0), F32);
+ Reg i = T->alloc_reg(T, RC_INT, I32);
+ T->convert(T, CV_FTOI_S, REG_op(i, I32), REG_op(f, F32));
+ cgtest_ret_reg(tf, i, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* b08_fp_param — trunc(7.5f) → 7. The IMM operand encodes the float
+ * bit-pattern; backend uses cls=RC_FP + type=float to materialize via
+ * load_const or fmov literal. */
+void build_b08_fp_param(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* I32 = T_i32(ctx);
+ ObjSymId fn = build_b08_helper(ctx);
+ const Type* params[] = { F32 };
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* Materialize 7.5f via load_const (immediate float). */
+ static const u8 BYTES_75F[4] = { 0x00, 0x00, 0xF0, 0x40 }; /* IEEE 7.5f LE */
+ Reg f = T->alloc_reg(T, RC_FP, F32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_75F, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(f, F32), cb);
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = F32, .v.reg = f } };
+ cgtest_call(tf, fn, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_c.c b/test/cg/harness/cases_c.c
@@ -0,0 +1,216 @@
+/* Group C — integer arithmetic.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group C: integer arithmetic
+ * ============================================================ */
+
+/* c01_add — 1 + 2 = 3 */
+void build_c01_add(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), 1);
+ T->load_imm(T, REG_op(b, I32), 2);
+ T->binop(T, BO_IADD, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* c02_sub_mul — 7 * 3 - 4 = 17 */
+void build_c02_sub_mul(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r7 = T->alloc_reg(T, RC_INT, I32);
+ Reg r3 = T->alloc_reg(T, RC_INT, I32);
+ Reg r4 = T->alloc_reg(T, RC_INT, I32);
+ Reg rmul = T->alloc_reg(T, RC_INT, I32);
+ Reg rsub = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r7, I32), 7);
+ T->load_imm(T, REG_op(r3, I32), 3);
+ T->load_imm(T, REG_op(r4, I32), 4);
+ T->binop(T, BO_IMUL, REG_op(rmul, I32), REG_op(r7, I32), REG_op(r3, I32));
+ T->binop(T, BO_ISUB, REG_op(rsub, I32), REG_op(rmul, I32), REG_op(r4, I32));
+ cgtest_ret_reg(tf, rsub, I32);
+ cgtest_end(tf);
+}
+
+/* c03_bitwise — (~3) & 0xff = 252 */
+void build_c03_bitwise(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r3 = T->alloc_reg(T, RC_INT, I32);
+ Reg rinv = T->alloc_reg(T, RC_INT, I32);
+ Reg rmask= T->alloc_reg(T, RC_INT, I32);
+ Reg rand_ = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r3, I32), 3);
+ T->load_imm(T, REG_op(rmask, I32), 0xff);
+ T->unop (T, UO_BNOT, REG_op(rinv, I32), REG_op(r3, I32));
+ T->binop(T, BO_AND, REG_op(rand_, I32), REG_op(rinv, I32), REG_op(rmask, I32));
+ cgtest_ret_reg(tf, rand_, I32);
+ cgtest_end(tf);
+}
+
+/* c04_shift — (1<<5) | (16>>1) = 40 */
+void build_c04_shift(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r1 = T->alloc_reg(T, RC_INT, I32);
+ Reg r5 = T->alloc_reg(T, RC_INT, I32);
+ Reg r16 = T->alloc_reg(T, RC_INT, I32);
+ Reg r1s = T->alloc_reg(T, RC_INT, I32);
+ Reg rshl= T->alloc_reg(T, RC_INT, I32);
+ Reg rshr= T->alloc_reg(T, RC_INT, I32);
+ Reg ror = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r1, I32), 1);
+ T->load_imm(T, REG_op(r5, I32), 5);
+ T->load_imm(T, REG_op(r16, I32), 16);
+ T->load_imm(T, REG_op(r1s, I32), 1);
+ T->binop(T, BO_SHL, REG_op(rshl, I32), REG_op(r1, I32), REG_op(r5, I32));
+ T->binop(T, BO_SHR_U, REG_op(rshr, I32), REG_op(r16, I32), REG_op(r1s, I32));
+ T->binop(T, BO_OR, REG_op(ror, I32), REG_op(rshl, I32),REG_op(rshr, I32));
+ cgtest_ret_reg(tf, ror, I32);
+ cgtest_end(tf);
+}
+
+/* c05_div_mod — 23/4 + 23%4 = 5 + 3 = 8 (signed) */
+void build_c05_div_mod(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r23 = T->alloc_reg(T, RC_INT, I32);
+ Reg r4 = T->alloc_reg(T, RC_INT, I32);
+ Reg rd = T->alloc_reg(T, RC_INT, I32);
+ Reg rm = T->alloc_reg(T, RC_INT, I32);
+ Reg rs = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r23, I32), 23);
+ T->load_imm(T, REG_op(r4, I32), 4);
+ T->binop(T, BO_SDIV, REG_op(rd, I32), REG_op(r23, I32), REG_op(r4, I32));
+ T->binop(T, BO_SREM, REG_op(rm, I32), REG_op(r23, I32), REG_op(r4, I32));
+ T->binop(T, BO_IADD, REG_op(rs, I32), REG_op(rd, I32), REG_op(rm, I32));
+ cgtest_ret_reg(tf, rs, I32);
+ cgtest_end(tf);
+}
+
+/* c06_xor — 0xa5 ^ 0x5a = 0xff = 255 */
+void build_c06_xor(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg ra = T->alloc_reg(T, RC_INT, I32);
+ Reg rb = T->alloc_reg(T, RC_INT, I32);
+ Reg rx = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(ra, I32), 0xa5);
+ T->load_imm(T, REG_op(rb, I32), 0x5a);
+ T->binop(T, BO_XOR, REG_op(rx, I32), REG_op(ra, I32), REG_op(rb, I32));
+ cgtest_ret_reg(tf, rx, I32);
+ cgtest_end(tf);
+}
+
+/* c07_iadd_i64 — i64 add. Two i64 values added; low 32 bits returned.
+ * (1<<32 | 0x29) + (1<<32 | 0x01) = (2<<32 | 0x2A) → low 32 = 42. */
+void build_c07_iadd_i64(CgTestCtx* ctx)
+{
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I64);
+ CGTarget* T = ctx->target;
+ Reg ra = T->alloc_reg(T, RC_INT, I64);
+ Reg rb = T->alloc_reg(T, RC_INT, I64);
+ Reg rs = T->alloc_reg(T, RC_INT, I64);
+ T->load_imm(T, REG_op(ra, I64), 0x100000029ll);
+ T->load_imm(T, REG_op(rb, I64), 0x100000001ll);
+ T->binop(T, BO_IADD, REG_op(rs, I64), REG_op(ra, I64), REG_op(rb, I64));
+ cgtest_ret_reg(tf, rs, I64);
+ cgtest_end(tf);
+}
+
+/* c08_unsigned_div — 100u / 7u = 14 */
+void build_c08_unsigned_div(CgTestCtx* ctx)
+{
+ const Type* U32 = T_u32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U32);
+ CGTarget* T = ctx->target;
+ Reg r100 = T->alloc_reg(T, RC_INT, U32);
+ Reg r7 = T->alloc_reg(T, RC_INT, U32);
+ Reg rq = T->alloc_reg(T, RC_INT, U32);
+ T->load_imm(T, REG_op(r100, U32), 100);
+ T->load_imm(T, REG_op(r7, U32), 7);
+ T->binop(T, BO_UDIV, REG_op(rq, U32), REG_op(r100, U32), REG_op(r7, U32));
+ cgtest_ret_reg(tf, rq, U32);
+ cgtest_end(tf);
+}
+
+/* c09_neg — UO_NEG. -42 → exit 256-42 = 214. */
+void build_c09_neg(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ Reg n = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r, I32), 42);
+ T->unop(T, UO_NEG, REG_op(n, I32), REG_op(r, I32));
+ cgtest_ret_reg(tf, n, I32);
+ cgtest_end(tf);
+}
+
+/* c10_logical_not — UO_NOT yields 0/1 from any int. !0 = 1. */
+void build_c10_logical_not(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg z = T->alloc_reg(T, RC_INT, I32);
+ Reg ln = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(z, I32), 0);
+ T->unop(T, UO_NOT, REG_op(ln, I32), REG_op(z, I32));
+ cgtest_ret_reg(tf, ln, I32);
+ cgtest_end(tf);
+}
+
+/* c11_shr_signed — -16 >>(s) 2 = -4 → exit 252. */
+void build_c11_shr_signed(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg rv = T->alloc_reg(T, RC_INT, I32);
+ Reg r2 = T->alloc_reg(T, RC_INT, I32);
+ Reg rs = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(rv, I32), -16);
+ T->load_imm(T, REG_op(r2, I32), 2);
+ T->binop(T, BO_SHR_S, REG_op(rs, I32), REG_op(rv, I32), REG_op(r2, I32));
+ cgtest_ret_reg(tf, rs, I32);
+ cgtest_end(tf);
+}
+
+/* c12_imul_i64 — i64 mul. 7 * 6 = 42; high bits zero. Exit 42. */
+void build_c12_imul_i64(CgTestCtx* ctx)
+{
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I64);
+ CGTarget* T = ctx->target;
+ Reg r7 = T->alloc_reg(T, RC_INT, I64);
+ Reg r6 = T->alloc_reg(T, RC_INT, I64);
+ Reg rm = T->alloc_reg(T, RC_INT, I64);
+ T->load_imm(T, REG_op(r7, I64), 7);
+ T->load_imm(T, REG_op(r6, I64), 6);
+ T->binop(T, BO_IMUL, REG_op(rm, I64), REG_op(r7, I64), REG_op(r6, I64));
+ cgtest_ret_reg(tf, rm, I64);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_d.c b/test/cg/harness/cases_d.c
@@ -0,0 +1,244 @@
+/* Group D — compare and branch.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group D: compare and branch
+ * ============================================================ */
+
+/* d01_cmp_eq_true — cmp materializes 0/1; (5 == 5) → 1. */
+void build_d01_cmp_eq_true(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), 5);
+ T->load_imm(T, REG_op(b, I32), 5);
+ T->cmp(T, CMP_EQ, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* d02_cmp_eq_false — (5 == 6) → 0. */
+void build_d02_cmp_eq_false(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), 5);
+ T->load_imm(T, REG_op(b, I32), 6);
+ T->cmp(T, CMP_EQ, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* d03_cmp_ne — (5 != 6) → 1. */
+void build_d03_cmp_ne(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), 5);
+ T->load_imm(T, REG_op(b, I32), 6);
+ T->cmp(T, CMP_NE, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* d04_cmp_lt_signed — (-1 < 1) signed → 1. */
+void build_d04_cmp_lt_signed(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), -1);
+ T->load_imm(T, REG_op(b, I32), 1);
+ T->cmp(T, CMP_LT_S, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* d05_cmp_lt_unsigned — same bit patterns as d04 but unsigned: 0xFFFFFFFF
+ * is huge, so (0xFFFFFFFF < 1) → 0. Signedness lives in CmpOp, not Type. */
+void build_d05_cmp_lt_unsigned(CgTestCtx* ctx)
+{
+ const Type* U32 = T_u32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, U32);
+ Reg b = T->alloc_reg(T, RC_INT, U32);
+ Reg d = T->alloc_reg(T, RC_INT, U32);
+ T->load_imm(T, REG_op(a, U32), -1);
+ T->load_imm(T, REG_op(b, U32), 1);
+ T->cmp(T, CMP_LT_U, REG_op(d, U32), REG_op(a, U32), REG_op(b, U32));
+ cgtest_ret_reg(tf, d, U32);
+ cgtest_end(tf);
+}
+
+/* d06_cmp_ge_signed — boundary: (5 >= 5) → 1 (LE/GE families include eq). */
+void build_d06_cmp_ge_signed(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), 5);
+ T->load_imm(T, REG_op(b, I32), 5);
+ T->cmp(T, CMP_GE_S, REG_op(d, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* d07_cmp_branch_taken — fused cmp_branch with the branch taken; landing
+ * pad past the label returns 42. */
+void build_d07_cmp_branch_taken(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r, I32), 7);
+ Label L = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(r, I32), IMM_op(7, I32), L);
+ cgtest_ret_imm(tf, 0, I32); /* dead */
+ T->label_place(T, L);
+ cgtest_ret_imm(tf, 42, I32);
+ cgtest_end(tf);
+}
+
+/* d08_cmp_branch_not_taken — branch not taken; fallthrough returns 33. */
+void build_d08_cmp_branch_not_taken(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r, I32), 5);
+ Label L = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(r, I32), IMM_op(6, I32), L);
+ cgtest_ret_imm(tf, 33, I32);
+ T->label_place(T, L);
+ cgtest_ret_imm(tf, 0, I32); /* dead */
+ cgtest_end(tf);
+}
+
+/* d09_cmp_branch_lt_signed — signed compare-and-branch with negative LHS;
+ * (-3 < 0) is true. */
+void build_d09_cmp_branch_lt_signed(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(r, I32), -3);
+ Label L = T->label_new(T);
+ T->cmp_branch(T, CMP_LT_S, REG_op(r, I32), IMM_op(0, I32), L);
+ cgtest_ret_imm(tf, 0, I32); /* dead */
+ T->label_place(T, L);
+ cgtest_ret_imm(tf, 9, I32);
+ cgtest_end(tf);
+}
+
+/* d10_jump — unconditional jump; the early ret is skipped. */
+void build_d10_jump(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Label L = T->label_new(T);
+ T->jump(T, L);
+ cgtest_ret_imm(tf, 0, I32); /* dead */
+ T->label_place(T, L);
+ cgtest_ret_imm(tf, 5, I32);
+ cgtest_end(tf);
+}
+
+/* d11_scope_if_true — `int x = 99; if (1) x = 33; return x;`
+ * SCOPE_IF consumes the cond at scope_begin; then-branch updates the
+ * local; scope_end closes the join. */
+void build_d11_scope_if_true(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, x, IMM_op(99, I32), I32);
+
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(c, I32), 1);
+ CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
+ CGScope s = T->scope_begin(T, &desc);
+ cgtest_store_local(tf, x, IMM_op(33, I32), I32);
+ T->scope_end(T, s);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), x, I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* d12_scope_if_false — `int x = 99; if (0) x = 33; return x;`
+ * Then-branch is dead; the local keeps its initial value. */
+void build_d12_scope_if_false(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, x, IMM_op(99, I32), I32);
+
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(c, I32), 0);
+ CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
+ CGScope s = T->scope_begin(T, &desc);
+ cgtest_store_local(tf, x, IMM_op(33, I32), I32);
+ T->scope_end(T, s);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), x, I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* d13_scope_if_else — `int x; if (0) x = 10; else x = 7; return x;`
+ * Exercises scope_else: cond is 0, so the else body wins. */
+void build_d13_scope_if_else(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
+
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(c, I32), 0);
+ CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
+ CGScope s = T->scope_begin(T, &desc);
+ cgtest_store_local(tf, x, IMM_op(10, I32), I32);
+ T->scope_else(T, s);
+ cgtest_store_local(tf, x, IMM_op(7, I32), I32);
+ T->scope_end(T, s);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), x, I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_e.c b/test/cg/harness/cases_e.c
@@ -0,0 +1,274 @@
+/* Group E — conversions.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group E: conversions
+ *
+ * One ConvKind per case, plus the boundary widths the AArch64 backend
+ * actually selects between (UXTB/SXTB vs UXTH/SXTH vs UBFX/SBFX, 32→64
+ * sign-extend). FP conversions all funnel through ftoi_s so the runner
+ * sees an int exit code.
+ * ============================================================ */
+
+/* e01_sext_i8_i32 — sext (i8)-1 → i32 = 0xFFFFFFFF; low 8 = 0xFF = 255. */
+void build_e01_sext_i8_i32(CgTestCtx* ctx)
+{
+ const Type* I8 = T_i8(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, I8);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(s, I8), -1);
+ T->convert(T, CV_SEXT, REG_op(d, I32), REG_op(s, I8));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e02_zext_u8_i32 — zext (u8)0xFF → i32 = 0xFF; low 8 = 255. The high
+ * bits are zeroed, distinguishing this from e01. */
+void build_e02_zext_u8_i32(CgTestCtx* ctx)
+{
+ const Type* U8 = T_u8(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, U8);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(s, U8), 0xFF);
+ T->convert(T, CV_ZEXT, REG_op(d, I32), REG_op(s, U8));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e03_sext_i16_i32 — sext (i16)-1000 → 0xFFFFFC18; low 8 = 0x18 = 24. */
+void build_e03_sext_i16_i32(CgTestCtx* ctx)
+{
+ const Type* I16 = T_i16(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, I16);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(s, I16), -1000);
+ T->convert(T, CV_SEXT, REG_op(d, I32), REG_op(s, I16));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e04_zext_u16_i32 — zext (u16)0xABCD → 0x0000ABCD; low 8 = 0xCD = 205. */
+void build_e04_zext_u16_i32(CgTestCtx* ctx)
+{
+ const Type* U16 = T_u16(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, U16);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(s, U16), 0xABCD);
+ T->convert(T, CV_ZEXT, REG_op(d, I32), REG_op(s, U16));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e05_zext_u32_i64 — zext (u32)0xFFFFFFFF → i64 = 0x00000000FFFFFFFF;
+ * runner reads w0 = 0xFFFFFFFF; low 8 = 255. Distinct from e06: high
+ * 32 bits are zero. */
+void build_e05_zext_u32_i64(CgTestCtx* ctx)
+{
+ const Type* U32 = T_u32(ctx);
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I64);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, U32);
+ Reg d = T->alloc_reg(T, RC_INT, I64);
+ T->load_imm(T, REG_op(s, U32), 0xFFFFFFFFll);
+ T->convert(T, CV_ZEXT, REG_op(d, I64), REG_op(s, U32));
+ cgtest_ret_reg(tf, d, I64);
+ cgtest_end(tf);
+}
+
+/* e06_sext_i32_i64 — sext (i32)-1 → i64 = -1; low 8 = 255. Same low-byte
+ * exit as e05 but the high bits differ — exercises SXTW vs UXTW. */
+void build_e06_sext_i32_i64(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I64);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, I64);
+ T->load_imm(T, REG_op(s, I32), -1);
+ T->convert(T, CV_SEXT, REG_op(d, I64), REG_op(s, I32));
+ cgtest_ret_reg(tf, d, I64);
+ cgtest_end(tf);
+}
+
+/* e07_trunc_i64_i32 — trunc 0x100000080 → low 32 = 0x80 = 128. */
+void build_e07_trunc_i64_i32(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, I64);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(s, I64), 0x100000080ll);
+ T->convert(T, CV_TRUNC, REG_op(d, I32), REG_op(s, I64));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e08_trunc_i32_i8 — trunc 0x1FF → low 8 = 0xFF; returned as u8 = 255. */
+void build_e08_trunc_i32_i8(CgTestCtx* ctx)
+{
+ const Type* U8 = T_u8(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U8);
+ CGTarget* T = ctx->target;
+ Reg s = T->alloc_reg(T, RC_INT, I32);
+ Reg d = T->alloc_reg(T, RC_INT, U8);
+ T->load_imm(T, REG_op(s, I32), 0x1FF);
+ T->convert(T, CV_TRUNC, REG_op(d, U8), REG_op(s, I32));
+ cgtest_ret_reg(tf, d, U8);
+ cgtest_end(tf);
+}
+
+/* e09_itof_s_i32_f32 — i32(7) → f32(7.0) → ftoi_s i32 → 7. Exact
+ * round-trip; verifies SCVTF + FCVTZS form a valid pair. */
+void build_e09_itof_s_i32_f32(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg si = T->alloc_reg(T, RC_INT, I32);
+ Reg f = T->alloc_reg(T, RC_FP, F32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(si, I32), 7);
+ T->convert(T, CV_ITOF_S, REG_op(f, F32), REG_op(si, I32));
+ T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e10_itof_u_u32_f64 — u32(100) → f64(100.0) → ftoi_s i32 → 100.
+ * Crosses width on the way up (UCVTF Dn,Wn) and back down. */
+void build_e10_itof_u_u32_f64(CgTestCtx* ctx)
+{
+ const Type* U32 = T_u32(ctx);
+ const Type* I32 = T_i32(ctx);
+ const Type* F64 = T_f64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg si = T->alloc_reg(T, RC_INT, U32);
+ Reg f = T->alloc_reg(T, RC_FP, F64);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(si, U32), 100);
+ T->convert(T, CV_ITOF_U, REG_op(f, F64), REG_op(si, U32));
+ T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F64));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e11_ftoi_s_neg — ftoi_s(-1.5f) = -1; low 8 = 255. C99 truncation
+ * rounds toward zero. */
+void build_e11_ftoi_s_neg(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ static const u8 BYTES_NEG_1_5[4] = { 0x00, 0x00, 0xC0, 0xBF }; /* -1.5f LE */
+ Reg f = T->alloc_reg(T, RC_FP, F32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_NEG_1_5, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(f, F32), cb);
+ T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e12_ftoi_u_pos — ftoi_u(200.7f) = 200u. Truncation toward zero,
+ * matching C's (unsigned)x. */
+void build_e12_ftoi_u_pos(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* U32 = T_u32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U32);
+ CGTarget* T = ctx->target;
+ static const u8 BYTES_200_7[4] = { 0x33, 0xB3, 0x48, 0x43 }; /* 200.7f LE */
+ Reg f = T->alloc_reg(T, RC_FP, F32);
+ Reg d = T->alloc_reg(T, RC_INT, U32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_200_7, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(f, F32), cb);
+ T->convert(T, CV_FTOI_U, REG_op(d, U32), REG_op(f, F32));
+ cgtest_ret_reg(tf, d, U32);
+ cgtest_end(tf);
+}
+
+/* e13_fext_f32_f64 — float→double promotion preserves an exactly
+ * representable value (3.5f = 3.5). ftoi_s then yields 3. */
+void build_e13_fext_f32_f64(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* F64 = T_f64(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ static const u8 BYTES_3_5F[4] = { 0x00, 0x00, 0x60, 0x40 }; /* 3.5f LE */
+ Reg f32r = T->alloc_reg(T, RC_FP, F32);
+ Reg f64r = T->alloc_reg(T, RC_FP, F64);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_3_5F, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(f32r, F32), cb);
+ T->convert(T, CV_FEXT, REG_op(f64r, F64), REG_op(f32r, F32));
+ T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f64r, F64));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e14_ftrunc_f64_f32 — double→float demotion of 7.875 (exact in both);
+ * ftoi_s yields 7. */
+void build_e14_ftrunc_f64_f32(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* F64 = T_f64(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ static const u8 BYTES_7_875[8] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1F, 0x40, /* 7.875 LE double */
+ };
+ Reg f64r = T->alloc_reg(T, RC_FP, F64);
+ Reg f32r = T->alloc_reg(T, RC_FP, F32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ ConstBytes cb = { .type = F64, .bytes = BYTES_7_875, .size = 8, .align = 8 };
+ T->load_const(T, REG_op(f64r, F64), cb);
+ T->convert(T, CV_FTRUNC, REG_op(f32r, F32), REG_op(f64r, F64));
+ T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f32r, F32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* e15_bitcast_i32_f32 — same-size cross-class reinterpret. 0x40A00000
+ * is the IEEE-754 single bit pattern for 5.0f. ftoi_s yields 5,
+ * confirming the bits travelled to the FP register intact. */
+void build_e15_bitcast_i32_f32(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ Reg si = T->alloc_reg(T, RC_INT, I32);
+ Reg f = T->alloc_reg(T, RC_FP, F32);
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(si, I32), 0x40A00000); /* 5.0f bit pattern */
+ T->convert(T, CV_BITCAST, REG_op(f, F32), REG_op(si, I32));
+ T->convert(T, CV_FTOI_S, REG_op(d, I32), REG_op(f, F32));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_f.c b/test/cg/harness/cases_f.c
@@ -0,0 +1,342 @@
+/* Group F — memory (loads/stores beyond locals).
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+#include "cases_shared.h"
+
+/* ============================================================
+ * Group F: memory (loads/stores beyond locals)
+ *
+ * Group B already exercises the basic load/store-of-local path. Group F
+ * pushes the surface: every scalar width, FP load/store, indirect
+ * non-zero offsets, store-from-IMM vs store-from-REG, copy_bytes,
+ * set_bytes, volatile, and the bitfield methods.
+ * ============================================================ */
+
+/* f01_load_store_i8 — local u8; store IMM 200; load; return. */
+void build_f01_load_store_i8(CgTestCtx* ctx)
+{
+ const Type* U8 = T_u8(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U8);
+ CGTarget* T = ctx->target;
+ FrameSlot s = cgtest_local(tf, U8, FSF_NONE);
+ cgtest_store_local(tf, s, IMM_op(200, U8), U8);
+ Reg r = T->alloc_reg(T, RC_INT, U8);
+ cgtest_load_local(tf, REG_op(r, U8), s, U8);
+ cgtest_ret_reg(tf, r, U8);
+ cgtest_end(tf);
+}
+
+/* f02_load_store_i16 — local i16; store 0x1234; load; low 8 = 0x34 = 52. */
+void build_f02_load_store_i16(CgTestCtx* ctx)
+{
+ const Type* I16 = T_i16(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I16);
+ CGTarget* T = ctx->target;
+ FrameSlot s = cgtest_local(tf, I16, FSF_NONE);
+ cgtest_store_local(tf, s, IMM_op(0x1234, I16), I16);
+ Reg r = T->alloc_reg(T, RC_INT, I16);
+ cgtest_load_local(tf, REG_op(r, I16), s, I16);
+ cgtest_ret_reg(tf, r, I16);
+ cgtest_end(tf);
+}
+
+/* f03_load_store_i64 — local i64; store 0x1_0000_0042; load; runner
+ * reads w0 = low 32 = 0x42 = 66. */
+void build_f03_load_store_i64(CgTestCtx* ctx)
+{
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I64);
+ CGTarget* T = ctx->target;
+ FrameSlot s = cgtest_local(tf, I64, FSF_NONE);
+ cgtest_store_local(tf, s, IMM_op(0x100000042ll, I64), I64);
+ Reg r = T->alloc_reg(T, RC_INT, I64);
+ cgtest_load_local(tf, REG_op(r, I64), s, I64);
+ cgtest_ret_reg(tf, r, I64);
+ cgtest_end(tf);
+}
+
+/* f04_load_store_f32 — local f32 home; store FP reg holding 7.5f; load
+ * back; ftoi_s → 7. Exercises STR Sn / LDR Sn forms. */
+void build_f04_load_store_f32(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ static const u8 BYTES_75F[4] = { 0x00, 0x00, 0xF0, 0x40 }; /* 7.5f LE */
+
+ FrameSlot s = cgtest_local(tf, F32, FSF_NONE);
+ Reg src = T->alloc_reg(T, RC_FP, F32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_75F, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(src, F32), cb);
+ cgtest_store_local(tf, s, REG_op(src, F32), F32);
+
+ Reg dst = T->alloc_reg(T, RC_FP, F32);
+ cgtest_load_local(tf, REG_op(dst, F32), s, F32);
+ Reg ri = T->alloc_reg(T, RC_INT, I32);
+ T->convert(T, CV_FTOI_S, REG_op(ri, I32), REG_op(dst, F32));
+ cgtest_ret_reg(tf, ri, I32);
+ cgtest_end(tf);
+}
+
+/* f05_load_store_f64 — local f64 home; store FP reg holding 3.25; load
+ * back; ftoi_s → 3. STR Dn / LDR Dn. */
+void build_f05_load_store_f64(CgTestCtx* ctx)
+{
+ const Type* F64 = T_f64(ctx);
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+ static const u8 BYTES_3_25[8] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x40, /* 3.25 LE double */
+ };
+
+ FrameSlot s = cgtest_local(tf, F64, FSF_NONE);
+ Reg src = T->alloc_reg(T, RC_FP, F64);
+ ConstBytes cb = { .type = F64, .bytes = BYTES_3_25, .size = 8, .align = 8 };
+ T->load_const(T, REG_op(src, F64), cb);
+ cgtest_store_local(tf, s, REG_op(src, F64), F64);
+
+ Reg dst = T->alloc_reg(T, RC_FP, F64);
+ cgtest_load_local(tf, REG_op(dst, F64), s, F64);
+ Reg ri = T->alloc_reg(T, RC_INT, I32);
+ T->convert(T, CV_FTOI_S, REG_op(ri, I32), REG_op(dst, F64));
+ cgtest_ret_reg(tf, ri, I32);
+ cgtest_end(tf);
+}
+
+/* f06_indirect_nonzero_offset — addr_of an i64 local, then store/load
+ * an i32 at +4. Exercises [base + #imm] addressing past byte 0; also
+ * verifies writes to one offset don't clobber a sentinel at another. */
+void build_f06_indirect_nonzero_offset(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I64, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I64));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, I64)), LOCAL_op(s, I64));
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, I32), IMM_op(99, I32), ma);
+ T->store(T, IND_op(base, 4, I32), IMM_op(42, I32), ma);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(base, 4, I32), ma);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* f07_store_reg — store from REG (not IMM) into a local slot. b04 stored
+ * an immediate; this distinguishes the REG-source store path. */
+void build_f07_store_reg(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_NONE);
+ Reg src = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(src, I32), 17);
+ cgtest_store_local(tf, s, REG_op(src, I32), I32);
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(dst, I32), s, I32);
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* f08_copy_bytes — copy_bytes(dst, src, Pt {10,32}); read back dst.a +
+ * dst.b → 42. The aggregate move is the operation under test; the per-
+ * field load/store after it just reads the result. */
+void build_f08_copy_bytes(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* PT = cases_pt_type(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot src = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
+ FrameSlot dst = cgtest_local(tf, PT, FSF_ADDR_TAKEN);
+
+ /* Initialize src to {10, 32}. */
+ Reg src_addr = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
+ T->addr_of(T, REG_op(src_addr, T_ptr(ctx, PT)), LOCAL_op(src, PT));
+ MemAccess ma_i32 = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(src_addr, 0, I32), IMM_op(10, I32), ma_i32);
+ T->store(T, IND_op(src_addr, 4, I32), IMM_op(32, I32), ma_i32);
+
+ Reg dst_addr = T->alloc_reg(T, RC_INT, T_ptr(ctx, PT));
+ T->addr_of(T, REG_op(dst_addr, T_ptr(ctx, PT)), LOCAL_op(dst, PT));
+
+ AggregateAccess agg = {
+ .type = PT, .size = 8, .align = 4,
+ .mem = { .type = PT, .size = 8, .align = 4,
+ .alias.kind = ALIAS_LOCAL },
+ };
+ T->copy_bytes(T,
+ REG_op(dst_addr, T_ptr(ctx, PT)),
+ REG_op(src_addr, T_ptr(ctx, PT)),
+ agg);
+
+ Reg ra = T->alloc_reg(T, RC_INT, I32);
+ Reg rb = T->alloc_reg(T, RC_INT, I32);
+ Reg sum = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(ra, I32), IND_op(dst_addr, 0, I32), ma_i32);
+ T->load(T, REG_op(rb, I32), IND_op(dst_addr, 4, I32), ma_i32);
+ T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(ra, I32), REG_op(rb, I32));
+ cgtest_ret_reg(tf, sum, I32);
+ cgtest_end(tf);
+}
+
+/* f09_set_bytes_zero — set_bytes(0) on an i32-sized buffer; load the
+ * word back → 0. Exercises the "memset to zero" path which backends
+ * often special-case (STR XZR). */
+void build_f09_set_bytes_zero(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* U8 = T_u8(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
+
+ AggregateAccess agg = {
+ .type = I32, .size = 4, .align = 4,
+ .mem = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL },
+ };
+ T->set_bytes(T, REG_op(base, T_ptr(ctx, I32)), IMM_op(0, U8), agg);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(base, 0, I32), agg.mem);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* f10_set_bytes_ff — set_bytes(0xFF) on an i32-sized buffer; load the
+ * word → 0xFFFFFFFF; low 8 = 255. Exercises the byte-broadcast path. */
+void build_f10_set_bytes_ff(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* U8 = T_u8(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
+
+ AggregateAccess agg = {
+ .type = I32, .size = 4, .align = 4,
+ .mem = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL },
+ };
+ T->set_bytes(T, REG_op(base, T_ptr(ctx, I32)), IMM_op(0xFF, U8), agg);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(base, 0, I32), agg.mem);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* f11_volatile_rw — same body as b04 but with MF_VOLATILE on both the
+ * store and the load. The expected exit value is identical; the
+ * difference is in the emitted code (no DSE/DCE, no fold-through-store). */
+void build_f11_volatile_rw(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_NONE);
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .flags = MF_VOLATILE,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, LOCAL_op(s, I32), IMM_op(42, I32), ma);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), LOCAL_op(s, I32), ma);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* f12_bitfield_unsigned — { unsigned x : 5; } at bit_offset=3 inside a
+ * zeroed i32 storage word; store 21; load → 21 (zero-extended). The
+ * non-zero bit_offset forces the backend's mask+shift logic. */
+void build_f12_bitfield_unsigned(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* U32 = T_u32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, U32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
+
+ /* Zero the storage word so neighboring bits don't perturb the read. */
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, I32), IMM_op(0, I32), ma);
+
+ BitFieldAccess bf = {
+ .field_type = U32,
+ .storage = ma,
+ .storage_offset = 0,
+ .bit_offset = 3,
+ .bit_width = 5,
+ .signed_ = 0,
+ };
+ T->bitfield_store(T, REG_op(base, T_ptr(ctx, I32)),
+ IMM_op(21, U32), bf);
+
+ Reg r = T->alloc_reg(T, RC_INT, U32);
+ T->bitfield_load(T, REG_op(r, U32),
+ REG_op(base, T_ptr(ctx, I32)), bf);
+ cgtest_ret_reg(tf, r, U32);
+ cgtest_end(tf);
+}
+
+/* f13_bitfield_signed — { signed x : 5; } at bit_offset=0; store -1
+ * (5-bit all-ones); load sign-extends to -1; low 8 = 255. */
+void build_f13_bitfield_signed(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot s = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), LOCAL_op(s, I32));
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, I32), IMM_op(0, I32), ma);
+
+ BitFieldAccess bf = {
+ .field_type = I32,
+ .storage = ma,
+ .storage_offset = 0,
+ .bit_offset = 0,
+ .bit_width = 5,
+ .signed_ = 1,
+ };
+ T->bitfield_store(T, REG_op(base, T_ptr(ctx, I32)),
+ IMM_op(-1, I32), bf);
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->bitfield_load(T, REG_op(r, I32),
+ REG_op(base, T_ptr(ctx, I32)), bf);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_g.c b/test/cg/harness/cases_g.c
@@ -0,0 +1,686 @@
+/* Group G — calls (beyond direct-call path).
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group G: calls (beyond direct-call path)
+ *
+ * Group B established direct-call mechanics. Group G stresses what falls
+ * out once calls compose: indirect calls, recursion, mutual recursion,
+ * register-preservation across calls, HFAs, oversized struct byval.
+ * ============================================================ */
+
+/* helper used by g01 and g11/g12: int echo(int x) { return x; } */
+static ObjSymId build_g_echo_helper(CgTestCtx* ctx, const char* name)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, name, I32, params, 1);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), cgtest_param_slot(tf, 0), I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g01_indirect_call — int (*fp)(int) = echo; return fp(42); */
+void build_g01_indirect_call(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId echo = build_g_echo_helper(ctx, "g01_echo");
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* Materialize the function pointer from the GLOBAL symbol. */
+ const Type* fn_ty = type_func(ctx->pool, I32, params, 1, 0);
+ const Type* fnp_ty = T_ptr(ctx, fn_ty);
+ Reg fp = T->alloc_reg(T, RC_INT, fnp_ty);
+ T->addr_of(T, REG_op(fp, fnp_ty), GLOBAL_op(echo, 0));
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 42 } };
+ cgtest_call_indirect(tf, fp, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by g02: int fact(int n) { return n<2 ? 1 : n*fact(n-1); }
+ * Forward-decl the symbol so the body can reference it for recursion. */
+static ObjSymId build_g02_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId sym = cgtest_decl_func(ctx, "g02_fact");
+ CgTestFn* tf = cgtest_begin_func_at(ctx, sym, I32, params, 1);
+ CGTarget* T = ctx->target;
+
+ Reg n = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
+
+ /* if (n < 2) goto base; */
+ Label base = T->label_new(T);
+ T->cmp_branch(T, CMP_LT_S, REG_op(n, I32), IMM_op(2, I32), base);
+
+ /* recursive: tmp = fact(n - 1); return n * tmp; */
+ Reg n1 = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
+
+ Reg rec = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg rec_args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
+ cgtest_call(tf, sym, I32, params, rec_args, 1, REG_op(rec, I32));
+
+ Reg prod = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_IMUL, REG_op(prod, I32), REG_op(n, I32), REG_op(rec, I32));
+ cgtest_ret_reg(tf, prod, I32);
+
+ /* base: return 1; */
+ T->label_place(T, base);
+ cgtest_ret_imm(tf, 1, I32);
+ cgtest_end(tf);
+ return sym;
+}
+
+/* g02_recursion_factorial — fact(5) = 120. */
+void build_g02_recursion_factorial(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId fact = build_g02_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 5 } };
+ cgtest_call(tf, fact, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by g03: int fib(int n) { return n<2?n:fib(n-1)+fib(n-2); } */
+static ObjSymId build_g03_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId sym = cgtest_decl_func(ctx, "g03_fib");
+ CgTestFn* tf = cgtest_begin_func_at(ctx, sym, I32, params, 1);
+ CGTarget* T = ctx->target;
+
+ Reg n = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
+
+ /* if (n < 2) return n; */
+ Label base = T->label_new(T);
+ T->cmp_branch(T, CMP_LT_S, REG_op(n, I32), IMM_op(2, I32), base);
+
+ /* a = fib(n-1); b = fib(n-2); return a+b; */
+ Reg n1 = T->alloc_reg(T, RC_INT, I32);
+ Reg n2 = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
+ T->binop(T, BO_ISUB, REG_op(n2, I32), REG_op(n, I32), IMM_op(2, I32));
+
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg a1[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
+ CgTestArg a2[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n2 } };
+ cgtest_call(tf, sym, I32, params, a1, 1, REG_op(a, I32));
+ cgtest_call(tf, sym, I32, params, a2, 1, REG_op(b, I32));
+
+ Reg sum = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_IADD, REG_op(sum, I32), REG_op(a, I32), REG_op(b, I32));
+ cgtest_ret_reg(tf, sum, I32);
+
+ T->label_place(T, base);
+ cgtest_ret_reg(tf, n, I32);
+ cgtest_end(tf);
+ return sym;
+}
+
+/* g03_recursion_fib — fib(10) = 55. */
+void build_g03_recursion_fib(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId fib = build_g03_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 10 } };
+ cgtest_call(tf, fib, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* g04_mutual_recursion — is_even(8) = 1.
+ * int is_even(int n) { return n==0 ? 1 : is_odd(n-1); }
+ * int is_odd (int n) { return n==0 ? 0 : is_even(n-1); }
+ * Forward-declare both symbols up front so each body can reference the
+ * other before it has been emitted. */
+void build_g04_mutual_recursion(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ CGTarget* T = ctx->target;
+
+ ObjSymId sym_e = cgtest_decl_func(ctx, "g04_is_even");
+ ObjSymId sym_o = cgtest_decl_func(ctx, "g04_is_odd");
+
+ /* is_even body. */
+ {
+ CgTestFn* tf = cgtest_begin_func_at(ctx, sym_e, I32, params, 1);
+ Reg n = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
+ Label base = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(n, I32), IMM_op(0, I32), base);
+ Reg n1 = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
+ cgtest_call(tf, sym_o, I32, params, args, 1, REG_op(r, I32));
+ cgtest_ret_reg(tf, r, I32);
+ T->label_place(T, base);
+ cgtest_ret_imm(tf, 1, I32);
+ cgtest_end(tf);
+ }
+
+ /* is_odd body. */
+ {
+ CgTestFn* tf = cgtest_begin_func_at(ctx, sym_o, I32, params, 1);
+ Reg n = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
+ Label base = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(n, I32), IMM_op(0, I32), base);
+ Reg n1 = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_ISUB, REG_op(n1, I32), REG_op(n, I32), IMM_op(1, I32));
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = n1 } };
+ cgtest_call(tf, sym_e, I32, params, args, 1, REG_op(r, I32));
+ cgtest_ret_reg(tf, r, I32);
+ T->label_place(T, base);
+ cgtest_ret_imm(tf, 0, I32);
+ cgtest_end(tf);
+ }
+
+ /* test_main: return is_even(8) → 1. */
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 8 } };
+ cgtest_call(tf, sym_e, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by g05: int inc(int x) { return x+1; } */
+static ObjSymId build_g05_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "g05_inc", I32, params, 1);
+ CGTarget* T = ctx->target;
+ Reg x = T->alloc_reg(T, RC_INT, I32);
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(x, I32), cgtest_param_slot(tf, 0), I32);
+ T->binop(T, BO_IADD, REG_op(r, I32), REG_op(x, I32), IMM_op(1, I32));
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g05_chained_calls — inc(inc(inc(39))) = 42. */
+void build_g05_chained_calls(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId inc = build_g05_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Reg r1 = T->alloc_reg(T, RC_INT, I32);
+ Reg r2 = T->alloc_reg(T, RC_INT, I32);
+ Reg r3 = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg a1[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 39 } };
+ cgtest_call(tf, inc, I32, params, a1, 1, REG_op(r1, I32));
+ CgTestArg a2[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = r1 } };
+ cgtest_call(tf, inc, I32, params, a2, 1, REG_op(r2, I32));
+ CgTestArg a3[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = r2 } };
+ cgtest_call(tf, inc, I32, params, a3, 1, REG_op(r3, I32));
+ cgtest_ret_reg(tf, r3, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by g06:
+ * int f(int a, float b, int c, double d, int e)
+ * { return a + (int)b + c + (int)d + e; }
+ * Mixes int and FP params — abi_func_info routes int→GPR and FP→FP. */
+static ObjSymId build_g06_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ const Type* F64 = T_f64(ctx);
+ const Type* params[] = { I32, F32, I32, F64, I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "g06_f", I32, params, 5);
+ CGTarget* T = ctx->target;
+
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ Reg e = T->alloc_reg(T, RC_INT, I32);
+ Reg fb = T->alloc_reg(T, RC_FP, F32);
+ Reg fd = T->alloc_reg(T, RC_FP, F64);
+ cgtest_load_local(tf, REG_op(a, I32), cgtest_param_slot(tf, 0), I32);
+ cgtest_load_local(tf, REG_op(fb, F32), cgtest_param_slot(tf, 1), F32);
+ cgtest_load_local(tf, REG_op(c, I32), cgtest_param_slot(tf, 2), I32);
+ cgtest_load_local(tf, REG_op(fd, F64), cgtest_param_slot(tf, 3), F64);
+ cgtest_load_local(tf, REG_op(e, I32), cgtest_param_slot(tf, 4), I32);
+
+ Reg ib = T->alloc_reg(T, RC_INT, I32);
+ Reg id = T->alloc_reg(T, RC_INT, I32);
+ T->convert(T, CV_FTOI_S, REG_op(ib, I32), REG_op(fb, F32));
+ T->convert(T, CV_FTOI_S, REG_op(id, I32), REG_op(fd, F64));
+
+ Reg s = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_IADD, REG_op(s, I32), REG_op(a, I32), REG_op(ib, I32));
+ T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(c, I32));
+ T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(id, I32));
+ T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(e, I32));
+ cgtest_ret_reg(tf, s, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g06_mixed_int_fp_params — f(2, 3.0f, 5, 7.0, 25) → 42. */
+void build_g06_mixed_int_fp_params(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ const Type* F64 = T_f64(ctx);
+ const Type* params[] = { I32, F32, I32, F64, I32 };
+ ObjSymId f = build_g06_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* Materialize 3.0f and 7.0 in FP regs via load_const. */
+ static const u8 BYTES_3F[4] = { 0x00, 0x00, 0x40, 0x40 }; /* 3.0f LE */
+ static const u8 BYTES_7D[8] = { 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x1C, 0x40 }; /* 7.0 LE double */
+ Reg fb = T->alloc_reg(T, RC_FP, F32);
+ Reg fd = T->alloc_reg(T, RC_FP, F64);
+ ConstBytes cbf = { .type = F32, .bytes = BYTES_3F, .size = 4, .align = 4 };
+ ConstBytes cbd = { .type = F64, .bytes = BYTES_7D, .size = 8, .align = 8 };
+ T->load_const(T, REG_op(fb, F32), cbf);
+ T->load_const(T, REG_op(fd, F64), cbd);
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = {
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 2 },
+ { .kind = CGT_ARG_REG, .type = F32, .v.reg = fb },
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 5 },
+ { .kind = CGT_ARG_REG, .type = F64, .v.reg = fd },
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 25 },
+ };
+ cgtest_call(tf, f, I32, params, args, 5, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by g07: void fill(int *p, int v) { *p = v; } */
+static ObjSymId build_g07_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* VOID = T_void(ctx);
+ const Type* params[] = { PI32, I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "g07_fill", VOID, params, 2);
+ CGTarget* T = ctx->target;
+
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ Reg v = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 0), PI32);
+ cgtest_load_local(tf, REG_op(v, I32), cgtest_param_slot(tf, 1), I32);
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(p, 0, I32), REG_op(v, I32), ma);
+ cgtest_ret_void(tf);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g07_void_call_outparam — int x; fill(&x, 42); return x → 42. */
+void build_g07_void_call_outparam(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* VOID = T_void(ctx);
+ const Type* params[] = { PI32, I32 };
+ ObjSymId fill = build_g07_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->addr_of(T, REG_op(p, PI32), LOCAL_op(x, I32));
+
+ CgTestArg args[] = {
+ { .kind = CGT_ARG_REG, .type = PI32, .v.reg = p },
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 42 },
+ };
+ cgtest_call(tf, fill, VOID, params, args, 2, IMM_op(0, VOID));
+
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), x, I32);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* struct S { int a[8]; }; — 32 bytes, exceeds the 16-byte threshold and is
+ * passed by reference (caller-allocated copy) on AArch64 SysV. */
+static const Type* build_g08_struct_type(CgTestCtx* ctx)
+{
+ Sym tag = pool_intern_cstr(ctx->pool, "S32");
+ TagId tid = type_tag_new(ctx->pool, TAG_STRUCT, tag, (SrcLoc){0,0,0});
+ TypeRecordBuilder* b = type_record_begin(ctx->pool, TY_STRUCT, tid, tag);
+ /* Eight i32 fields named a0..a7. */
+ for (int i = 0; i < 8; ++i) {
+ char name[8];
+ name[0] = 'a';
+ name[1] = (char)('0' + i);
+ name[2] = 0;
+ type_record_field(b, (Field){
+ .name = pool_intern_cstr(ctx->pool, name),
+ .type = T_i32(ctx) });
+ }
+ return type_record_end(ctx->pool, b);
+}
+
+/* helper used by g08: int take(struct S s) { return s.a7; } */
+static ObjSymId build_g08_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* S = build_g08_struct_type(ctx);
+ const Type* params[] = { S };
+ CgTestFn* tf = cgtest_begin_func(ctx, "g08_take", I32, params, 1);
+ CGTarget* T = ctx->target;
+
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, S));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, S)),
+ LOCAL_op(cgtest_param_slot(tf, 0), S));
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_PARAM };
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ /* a7 lives at offset 28. */
+ T->load(T, REG_op(r, I32), IND_op(base, 28, I32), ma);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g08_large_struct_byval — 32-byte struct passed by value. */
+void build_g08_large_struct_byval(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* S = build_g08_struct_type(ctx);
+ const Type* params[] = { S };
+ ObjSymId take = build_g08_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot src = cgtest_local(tf, S, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, S));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, S)), LOCAL_op(src, S));
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ /* Zero out a0..a6 so the helper observation depends only on a7. */
+ for (int i = 0; i < 7; ++i) {
+ T->store(T, IND_op(base, i*4, I32), IMM_op(0, I32), ma);
+ }
+ T->store(T, IND_op(base, 28, I32), IMM_op(42, I32), ma);
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_BYVAL_LOCAL, .type = S, .v.slot = src } };
+ cgtest_call(tf, take, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* struct V { float x, y; }; — HFA of two f32. AArch64 SysV passes in v0,v1
+ * and returns in {v0, v1}. */
+static const Type* build_g_hfa_type(CgTestCtx* ctx)
+{
+ Sym tag = pool_intern_cstr(ctx->pool, "V");
+ TagId tid = type_tag_new(ctx->pool, TAG_STRUCT, tag, (SrcLoc){0,0,0});
+ TypeRecordBuilder* b = type_record_begin(ctx->pool, TY_STRUCT, tid, tag);
+ type_record_field(b, (Field){
+ .name = pool_intern_cstr(ctx->pool, "x"), .type = T_f32(ctx) });
+ type_record_field(b, (Field){
+ .name = pool_intern_cstr(ctx->pool, "y"), .type = T_f32(ctx) });
+ return type_record_end(ctx->pool, b);
+}
+
+/* helper used by g09: int f(struct V v) { return (int)(v.x + v.y); } */
+static ObjSymId build_g09_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ const Type* V = build_g_hfa_type(ctx);
+ const Type* params[] = { V };
+ CgTestFn* tf = cgtest_begin_func(ctx, "g09_f", I32, params, 1);
+ CGTarget* T = ctx->target;
+
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, V)),
+ LOCAL_op(cgtest_param_slot(tf, 0), V));
+ MemAccess ma = { .type = F32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_PARAM };
+ Reg fx = T->alloc_reg(T, RC_FP, F32);
+ Reg fy = T->alloc_reg(T, RC_FP, F32);
+ Reg fs = T->alloc_reg(T, RC_FP, F32);
+ T->load(T, REG_op(fx, F32), IND_op(base, 0, F32), ma);
+ T->load(T, REG_op(fy, F32), IND_op(base, 4, F32), ma);
+ T->binop(T, BO_FADD, REG_op(fs, F32), REG_op(fx, F32), REG_op(fy, F32));
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->convert(T, CV_FTOI_S, REG_op(r, I32), REG_op(fs, F32));
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g09_hfa_param_f32x2 — f({1.5f, 1.5f}) → 3. */
+void build_g09_hfa_param_f32x2(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ const Type* V = build_g_hfa_type(ctx);
+ const Type* params[] = { V };
+ ObjSymId f = build_g09_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* Build local {1.5f, 1.5f} via FP load_const + store_local. */
+ static const u8 BYTES_15F[4] = { 0x00, 0x00, 0xC0, 0x3F }; /* 1.5f LE */
+ FrameSlot src = cgtest_local(tf, V, FSF_ADDR_TAKEN);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, V)), LOCAL_op(src, V));
+ Reg fc = T->alloc_reg(T, RC_FP, F32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_15F, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(fc, F32), cb);
+ MemAccess ma = { .type = F32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, F32), REG_op(fc, F32), ma);
+ T->store(T, IND_op(base, 4, F32), REG_op(fc, F32), ma);
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_BYVAL_LOCAL, .type = V, .v.slot = src } };
+ cgtest_call(tf, f, I32, params, args, 1, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by g10: struct V g10_mk(void) { return (struct V){1.5f, 1.5f}; }
+ * Returned via the HFA path — abi_func_info classifies the struct as
+ * homogeneous-FP, so the backend places fields into v0/v1 instead of
+ * memcpying through an sret pointer. cgtest_ret_indirect drives both. */
+static ObjSymId build_g10_helper(CgTestCtx* ctx)
+{
+ const Type* F32 = T_f32(ctx);
+ const Type* V = build_g_hfa_type(ctx);
+ CgTestFn* tf = cgtest_begin_func(ctx, "g10_mk", V, NULL, 0);
+ CGTarget* T = ctx->target;
+
+ static const u8 BYTES_15F[4] = { 0x00, 0x00, 0xC0, 0x3F };
+ FrameSlot s = cgtest_local(tf, V, FSF_NONE);
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, V)), LOCAL_op(s, V));
+ Reg fc = T->alloc_reg(T, RC_FP, F32);
+ ConstBytes cb = { .type = F32, .bytes = BYTES_15F, .size = 4, .align = 4 };
+ T->load_const(T, REG_op(fc, F32), cb);
+ MemAccess ma = { .type = F32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(base, 0, F32), REG_op(fc, F32), ma);
+ T->store(T, IND_op(base, 4, F32), REG_op(fc, F32), ma);
+
+ cgtest_ret_indirect(tf, s);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* g10_hfa_return_f32x2 — sum fields of returned HFA, ftoi_s → 3. */
+void build_g10_hfa_return_f32x2(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* F32 = T_f32(ctx);
+ const Type* V = build_g_hfa_type(ctx);
+ ObjSymId mk = build_g10_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot dst = cgtest_local(tf, V, FSF_ADDR_TAKEN);
+ cgtest_call(tf, mk, V, NULL, NULL, 0, LOCAL_op(dst, V));
+
+ Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, V));
+ T->addr_of(T, REG_op(base, T_ptr(ctx, V)), LOCAL_op(dst, V));
+ MemAccess ma = { .type = F32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ Reg fx = T->alloc_reg(T, RC_FP, F32);
+ Reg fy = T->alloc_reg(T, RC_FP, F32);
+ Reg fs = T->alloc_reg(T, RC_FP, F32);
+ T->load(T, REG_op(fx, F32), IND_op(base, 0, F32), ma);
+ T->load(T, REG_op(fy, F32), IND_op(base, 4, F32), ma);
+ T->binop(T, BO_FADD, REG_op(fs, F32), REG_op(fx, F32), REG_op(fy, F32));
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->convert(T, CV_FTOI_S, REG_op(r, I32), REG_op(fs, F32));
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* g11_caller_saved_live_across_call — x=42 must survive a call that
+ * clobbers caller-saved regs. */
+void build_g11_caller_saved_live_across_call(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId echo = build_g_echo_helper(ctx, "g11_echo");
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Reg x = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(x, I32), 42);
+
+ Reg ignored = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 99 } };
+ cgtest_call(tf, echo, I32, params, args, 1, REG_op(ignored, I32));
+
+ cgtest_ret_reg(tf, x, I32);
+ cgtest_end(tf);
+}
+
+/* g12_addr_taken_local_across_call — addr-taken local survives an
+ * intervening call. b05 body with a side call between increment and
+ * read-back. */
+void build_g12_addr_taken_local_across_call(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId echo = build_g_echo_helper(ctx, "g12_echo");
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ cgtest_store_local(tf, x, IMM_op(17, I32), I32);
+
+ Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32));
+ T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), LOCAL_op(x, I32));
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ Reg val = T->alloc_reg(T, RC_INT, I32);
+ T->load (T, REG_op(val, I32), IND_op(p, 0, I32), ma);
+ T->binop(T, BO_IADD, REG_op(val, I32), REG_op(val, I32), IMM_op(1, I32));
+ T->store(T, IND_op(p, 0, I32), REG_op(val, I32), ma);
+
+ /* intervening call — must not corrupt the local or its address. */
+ Reg ignored = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 99 } };
+ cgtest_call(tf, echo, I32, params, args, 1, REG_op(ignored, I32));
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(out, I32), IND_op(p, 0, I32), ma);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* g13_call_in_loop_induction — for(i=0;i<10;i++) s += id(i); → 45.
+ * Built on flat cmp_branch + jump, no SCOPE_LOOP — the induction var
+ * lives in an addr-taken slot to force frame-residency across the call. */
+void build_g13_call_in_loop_induction(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* params[] = { I32 };
+ ObjSymId id = build_g_echo_helper(ctx, "g13_id");
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot islot = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot sslot = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, islot, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, sslot, IMM_op(0, I32), I32);
+
+ Label top = T->label_new(T);
+ Label done = T->label_new(T);
+ T->label_place(T, top);
+ Reg ireg = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ireg, I32), islot, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ireg, I32), IMM_op(10, I32), done);
+
+ /* res = id(i); */
+ Reg res = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = ireg } };
+ cgtest_call(tf, id, I32, params, args, 1, REG_op(res, I32));
+
+ /* s += res; */
+ Reg sreg = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sreg, I32), sslot, I32);
+ T->binop(T, BO_IADD, REG_op(sreg, I32), REG_op(sreg, I32), REG_op(res, I32));
+ cgtest_store_local(tf, sslot, REG_op(sreg, I32), I32);
+
+ /* i++; jump top. */
+ Reg inew = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(inew, I32), islot, I32);
+ T->binop(T, BO_IADD, REG_op(inew, I32), REG_op(inew, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, islot, REG_op(inew, I32), I32);
+ T->jump(T, top);
+
+ T->label_place(T, done);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), sslot, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_h.c b/test/cg/harness/cases_h.c
@@ -0,0 +1,669 @@
+/* Group H — control flow.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group H: control flow
+ *
+ * Loops and multi-way branch beyond Group D's scope_if/scope_else.
+ * Loops use SCOPE_LOOP with explicit break/continue labels — the
+ * caller places continue at the appropriate point (top for while,
+ * after-body-before-incr for for-loops). Switches lower to chained
+ * cmp_branch + jump (no dedicated switch op). Short-circuit && / ||
+ * are exercised by observing that the RHS side effect did not run.
+ * ============================================================ */
+
+/* h01_while_sum_0_to_9 — int s=0,i=0; while(i<10){s+=i;i++;} return s; → 45. */
+void build_h01_while_sum_0_to_9(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T);
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+ T->label_place(T, cnt);
+
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(10, I32), brk);
+
+ Reg sr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
+ T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(ir, I32));
+ cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
+
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, cnt);
+
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h02_do_while_once — int i=0; do { i=42; } while(0); return i; → 42. */
+void build_h02_do_while_once(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T);
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+ T->label_place(T, cnt);
+
+ /* body: i = 42; */
+ cgtest_store_local(tf, is, IMM_op(42, I32), I32);
+
+ /* condition: while (0) — never taken. */
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(c, I32), 0);
+ T->cmp_branch(T, CMP_NE, REG_op(c, I32), IMM_op(0, I32), cnt);
+
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), is, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h03_for_count_to_10 — for(i=1;i<=10;i++) s+=i; → 55. */
+void build_h03_for_count_to_10(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, is, IMM_op(1, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T); /* increment site */
+ Label top = T->label_new(T); /* condition test */
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+
+ T->label_place(T, top);
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GT_S, REG_op(ir, I32), IMM_op(10, I32), brk);
+
+ /* body: s += i; */
+ Reg sr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
+ T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(ir, I32));
+ cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
+
+ /* increment: i++; */
+ T->label_place(T, cnt);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, top);
+
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h04_loop_break — for(i=0;;i++) if(i==42) break; return i; → 42. */
+void build_h04_loop_break(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T);
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+ T->label_place(T, cnt);
+
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(42, I32), brk);
+
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, cnt);
+
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), is, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h05_loop_continue — sum of even i in [0,20) using continue → 90. */
+void build_h05_loop_continue(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T); /* the increment site */
+ Label top = T->label_new(T);
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+
+ T->label_place(T, top);
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(20, I32), brk);
+
+ /* if (i & 1) continue; — odd → skip add. */
+ Reg parity = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_AND, REG_op(parity, I32), REG_op(ir, I32), IMM_op(1, I32));
+ T->cmp_branch(T, CMP_NE, REG_op(parity, I32), IMM_op(0, I32), cnt);
+
+ /* s += i; */
+ Reg sr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
+ T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(ir, I32));
+ cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
+
+ T->label_place(T, cnt);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, top);
+
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h06_nested_loops — for(i=0;i<3;i++) for(j=0;j<2;j++) s++; → 6. */
+void build_h06_nested_loops(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot js = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label outer_brk = T->label_new(T);
+ Label outer_cnt = T->label_new(T);
+ CGScopeDesc d_o = { .kind = SCOPE_LOOP,
+ .break_label = outer_brk, .continue_label = outer_cnt };
+ CGScope outer = T->scope_begin(T, &d_o);
+ T->label_place(T, outer_cnt);
+
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(3, I32), outer_brk);
+
+ cgtest_store_local(tf, js, IMM_op(0, I32), I32);
+ Label inner_brk = T->label_new(T);
+ Label inner_cnt = T->label_new(T);
+ CGScopeDesc d_i = { .kind = SCOPE_LOOP,
+ .break_label = inner_brk, .continue_label = inner_cnt };
+ CGScope inner = T->scope_begin(T, &d_i);
+ T->label_place(T, inner_cnt);
+
+ Reg jr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(jr, I32), js, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(jr, I32), IMM_op(2, I32), inner_brk);
+
+ /* s++ */
+ Reg sr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
+ T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
+
+ /* j++ */
+ T->binop(T, BO_IADD, REG_op(jr, I32), REG_op(jr, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, js, REG_op(jr, I32), I32);
+ T->jump(T, inner_cnt);
+ T->label_place(T, inner_brk);
+ T->scope_end(T, inner);
+
+ /* i++ */
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, outer_cnt);
+ T->label_place(T, outer_brk);
+ T->scope_end(T, outer);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h07_break_inner_only — outer counts 3 iterations, inner breaks after
+ * incrementing s by 3 each time → s = 9. */
+void build_h07_break_inner_only(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label outer_brk = T->label_new(T);
+ Label outer_cnt = T->label_new(T);
+ CGScopeDesc d_o = { .kind = SCOPE_LOOP,
+ .break_label = outer_brk, .continue_label = outer_cnt };
+ CGScope outer = T->scope_begin(T, &d_o);
+ T->label_place(T, outer_cnt);
+
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(3, I32), outer_brk);
+
+ /* inner loop: counts 0..2, but inner-break exits after counter reaches 3
+ * (so adds 3 to s each outer iteration). */
+ FrameSlot js = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, js, IMM_op(0, I32), I32);
+ Label inner_brk = T->label_new(T);
+ Label inner_cnt = T->label_new(T);
+ CGScopeDesc d_i = { .kind = SCOPE_LOOP,
+ .break_label = inner_brk, .continue_label = inner_cnt };
+ CGScope inner = T->scope_begin(T, &d_i);
+ T->label_place(T, inner_cnt);
+
+ Reg jr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(jr, I32), js, I32);
+ /* if (j >= 3) inner-break */
+ T->cmp_branch(T, CMP_GE_S, REG_op(jr, I32), IMM_op(3, I32), inner_brk);
+
+ /* s++ */
+ Reg sr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
+ T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
+
+ /* j++ */
+ T->binop(T, BO_IADD, REG_op(jr, I32), REG_op(jr, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, js, REG_op(jr, I32), I32);
+ T->jump(T, inner_cnt);
+ T->label_place(T, inner_brk);
+ T->scope_end(T, inner);
+
+ /* outer must continue past the inner break — i++ */
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, outer_cnt);
+ T->label_place(T, outer_brk);
+ T->scope_end(T, outer);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h08_early_return_in_loop — for(i=0;;i++) if(i==17) return i; → 17. */
+void build_h08_early_return_in_loop(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T);
+ Label hit = T->label_new(T);
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+ T->label_place(T, cnt);
+
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(17, I32), hit);
+
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, cnt);
+
+ T->label_place(T, hit);
+ cgtest_ret_reg(tf, ir, I32);
+ /* the rest is dead. */
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+ cgtest_ret_imm(tf, 0, I32);
+ cgtest_end(tf);
+}
+
+/* h09_switch_three_cases — switch(2) {case 1:r=10;break; case 2:r=42;break;
+ * case 3:r=99;break;} → 42. */
+void build_h09_switch_three_cases(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot rs = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, rs, IMM_op(0, I32), I32);
+
+ Reg val = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(val, I32), 2);
+
+ Label l1 = T->label_new(T), l2 = T->label_new(T), l3 = T->label_new(T);
+ Label end = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(1, I32), l1);
+ T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(2, I32), l2);
+ T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(3, I32), l3);
+ T->jump(T, end);
+
+ T->label_place(T, l1);
+ cgtest_store_local(tf, rs, IMM_op(10, I32), I32);
+ T->jump(T, end);
+ T->label_place(T, l2);
+ cgtest_store_local(tf, rs, IMM_op(42, I32), I32);
+ T->jump(T, end);
+ T->label_place(T, l3);
+ cgtest_store_local(tf, rs, IMM_op(99, I32), I32);
+ T->jump(T, end);
+
+ T->label_place(T, end);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), rs, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h10_switch_fallthrough — switch(1){case 1: r+=10; case 2: r+=20;} → 30. */
+void build_h10_switch_fallthrough(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot rs = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, rs, IMM_op(0, I32), I32);
+
+ Reg val = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(val, I32), 1);
+
+ Label l1 = T->label_new(T), l2 = T->label_new(T);
+ Label end = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(1, I32), l1);
+ T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(2, I32), l2);
+ T->jump(T, end);
+
+ T->label_place(T, l1);
+ {
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), rs, I32);
+ T->binop(T, BO_IADD, REG_op(r, I32), REG_op(r, I32), IMM_op(10, I32));
+ cgtest_store_local(tf, rs, REG_op(r, I32), I32);
+ }
+ /* no break — fall through. */
+ T->label_place(T, l2);
+ {
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(r, I32), rs, I32);
+ T->binop(T, BO_IADD, REG_op(r, I32), REG_op(r, I32), IMM_op(20, I32));
+ cgtest_store_local(tf, rs, REG_op(r, I32), I32);
+ }
+ T->jump(T, end);
+
+ T->label_place(T, end);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), rs, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h11_switch_default — switch(99){case 1:r=10;break; default:r=7;} → 7. */
+void build_h11_switch_default(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot rs = cgtest_local(tf, I32, FSF_NONE);
+
+ Reg val = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(val, I32), 99);
+
+ Label l1 = T->label_new(T), ldef = T->label_new(T), end = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(val, I32), IMM_op(1, I32), l1);
+ T->jump(T, ldef);
+
+ T->label_place(T, l1);
+ cgtest_store_local(tf, rs, IMM_op(10, I32), I32);
+ T->jump(T, end);
+ T->label_place(T, ldef);
+ cgtest_store_local(tf, rs, IMM_op(7, I32), I32);
+ T->jump(T, end);
+
+ T->label_place(T, end);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), rs, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h12_jump_forward — jump L; ret 99 (dead); L: ret 42; → 42. */
+void build_h12_jump_forward(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Label L = T->label_new(T);
+ T->jump(T, L);
+ cgtest_ret_imm(tf, 99, I32); /* dead */
+ T->label_place(T, L);
+ cgtest_ret_imm(tf, 42, I32);
+ cgtest_end(tf);
+}
+
+/* h13_jump_backward — counter loop entirely from cmp_branch + backward
+ * jump (no SCOPE_LOOP). Loops until i == 10. */
+void build_h13_jump_backward(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label top = T->label_new(T);
+ Label end = T->label_new(T);
+ T->label_place(T, top);
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(10, I32), end);
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, top);
+
+ T->label_place(T, end);
+ cgtest_ret_reg(tf, ir, I32);
+ cgtest_end(tf);
+}
+
+/* h14_short_circuit_and_skip — `int s=0; (0) && (s=99,1); return s;` → 0.
+ * The RHS side effect must NOT execute when the LHS is 0. */
+void build_h14_short_circuit_and_skip(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+
+ Reg lhs = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(lhs, I32), 0);
+
+ Label rhs = T->label_new(T);
+ Label after = T->label_new(T);
+ /* if (lhs != 0) goto rhs; else fall through to "after" with skipped RHS. */
+ T->cmp_branch(T, CMP_NE, REG_op(lhs, I32), IMM_op(0, I32), rhs);
+ T->jump(T, after);
+
+ T->label_place(T, rhs);
+ /* RHS side effect: s = 99 (must not run). */
+ cgtest_store_local(tf, ss, IMM_op(99, I32), I32);
+
+ T->label_place(T, after);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h15_short_circuit_or_skip — `int s=0; (1) || (s=99,1); return s;` → 0. */
+void build_h15_short_circuit_or_skip(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+
+ Reg lhs = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(lhs, I32), 1);
+
+ Label after = T->label_new(T);
+ /* if (lhs != 0) skip RHS. */
+ T->cmp_branch(T, CMP_NE, REG_op(lhs, I32), IMM_op(0, I32), after);
+
+ /* RHS side effect (must not run). */
+ cgtest_store_local(tf, ss, IMM_op(99, I32), I32);
+
+ T->label_place(T, after);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h16_ternary — int x = (5 > 3) ? 42 : 7; return x; → 42. Uses scope_if. */
+void build_h16_ternary(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot xs = cgtest_local(tf, I32, FSF_NONE);
+
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ Reg a = T->alloc_reg(T, RC_INT, I32);
+ Reg b = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(a, I32), 5);
+ T->load_imm(T, REG_op(b, I32), 3);
+ T->cmp(T, CMP_GT_S, REG_op(c, I32), REG_op(a, I32), REG_op(b, I32));
+ CGScopeDesc desc = { .kind = SCOPE_IF, .cond = REG_op(c, I32) };
+ CGScope s = T->scope_begin(T, &desc);
+ cgtest_store_local(tf, xs, IMM_op(42, I32), I32);
+ T->scope_else(T, s);
+ cgtest_store_local(tf, xs, IMM_op(7, I32), I32);
+ T->scope_end(T, s);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), xs, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h17_ternary_side_effect_one_arm — int s=0; (1)?(s=42):(s=99); return s; → 42.
+ * Only the taken arm runs. Uses cmp_branch + flat labels (no scope). */
+void build_h17_ternary_side_effect_one_arm(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+
+ Reg c = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(c, I32), 1);
+
+ Label then_l = T->label_new(T);
+ Label end = T->label_new(T);
+ T->cmp_branch(T, CMP_NE, REG_op(c, I32), IMM_op(0, I32), then_l);
+ /* else arm */
+ cgtest_store_local(tf, ss, IMM_op(99, I32), I32);
+ T->jump(T, end);
+ T->label_place(T, then_l);
+ cgtest_store_local(tf, ss, IMM_op(42, I32), I32);
+ T->label_place(T, end);
+
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+}
+
+/* h18_unreachable_after_ret — emit a scalar ret followed by additional
+ * (unreachable) ops; backend must tolerate the dead tail. */
+void build_h18_unreachable_after_ret(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ cgtest_ret_imm(tf, 42, I32);
+
+ /* Dead instructions — should not execute, but the emitter must accept them. */
+ Reg dead = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(dead, I32), 99);
+ cgtest_ret_reg(tf, dead, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_i.c b/test/cg/harness/cases_i.c
@@ -0,0 +1,447 @@
+/* Group I — alloca / VLA.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group I: alloca / VLA
+ *
+ * Stack-allocated runtime-sized memory: alloca with const and runtime
+ * sizes, alignment, in-loop distinctness, and crossing a call boundary
+ * with the alloca'd pointer. The alloca op signature is
+ * alloca_(target, dst REG, size Operand, align).
+ * ============================================================ */
+
+/* i01_alloca_const_int — int *p = alloca(4); *p = 42; return *p. */
+void build_i01_alloca_const_int(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(p, 0, I32), IMM_op(42, I32), ma);
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* i02_alloca_runtime_size — int n=5; int *p = alloca(n*4); fill 1..5; sum=15. */
+void build_i02_alloca_runtime_size(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* size_bytes = 5 * 4. Use I64 to match alloca's size operand. */
+ Reg sz = T->alloc_reg(T, RC_INT, I64);
+ T->load_imm(T, REG_op(sz, I64), 20);
+
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), REG_op(sz, I64), 4);
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ /* p[0..4] = 1..5 */
+ for (int i = 0; i < 5; ++i) {
+ T->store(T, IND_op(p, (i32)(i*4), I32), IMM_op(i+1, I32), ma);
+ }
+ /* sum */
+ Reg acc = T->alloc_reg(T, RC_INT, I32);
+ T->load_imm(T, REG_op(acc, I32), 0);
+ for (int i = 0; i < 5; ++i) {
+ Reg v = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(v, I32), IND_op(p, (i32)(i*4), I32), ma);
+ T->binop(T, BO_IADD, REG_op(acc, I32), REG_op(acc, I32), REG_op(v, I32));
+ }
+ cgtest_ret_reg(tf, acc, I32);
+ cgtest_end(tf);
+}
+
+/* i03_alloca_align_16 — alloca(16, align=16); return ((p & 0xF) == 0). */
+void build_i03_alloca_align_16(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PV = T_ptr_void(ctx);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Reg p = T->alloc_reg(T, RC_INT, PV);
+ T->alloca_(T, REG_op(p, PV), IMM_op(16, I64), 16);
+
+ /* low_bits = p & 0xF */
+ Reg lb = T->alloc_reg(T, RC_INT, I64);
+ T->binop(T, BO_AND, REG_op(lb, I64), REG_op(p, I64), IMM_op(0xF, I64));
+
+ /* result = (low_bits == 0) */
+ Reg d = T->alloc_reg(T, RC_INT, I32);
+ T->cmp(T, CMP_EQ, REG_op(d, I32), REG_op(lb, I64), IMM_op(0, I64));
+ cgtest_ret_reg(tf, d, I32);
+ cgtest_end(tf);
+}
+
+/* i04_alloca_in_loop_distinct — three alloca(4)s in a loop; return
+ * (a != b && b != c). Addresses must differ across iterations. */
+void build_i04_alloca_in_loop_distinct(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* Three slots to record the alloca'd addresses. */
+ FrameSlot a = cgtest_local(tf, PI32, FSF_NONE);
+ FrameSlot b = cgtest_local(tf, PI32, FSF_NONE);
+ FrameSlot c = cgtest_local(tf, PI32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label brk = T->label_new(T);
+ Label cnt = T->label_new(T);
+ CGScopeDesc d = { .kind = SCOPE_LOOP,
+ .break_label = brk, .continue_label = cnt };
+ CGScope sc = T->scope_begin(T, &d);
+ T->label_place(T, cnt);
+
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), IMM_op(3, I32), brk);
+
+ /* p = alloca(4) */
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
+
+ /* select destination slot by i. */
+ Label sa = T->label_new(T), sb = T->label_new(T), sc_l = T->label_new(T);
+ Label after_store = T->label_new(T);
+ T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(0, I32), sa);
+ T->cmp_branch(T, CMP_EQ, REG_op(ir, I32), IMM_op(1, I32), sb);
+ T->jump(T, sc_l);
+
+ T->label_place(T, sa);
+ cgtest_store_local(tf, a, REG_op(p, PI32), PI32);
+ T->jump(T, after_store);
+ T->label_place(T, sb);
+ cgtest_store_local(tf, b, REG_op(p, PI32), PI32);
+ T->jump(T, after_store);
+ T->label_place(T, sc_l);
+ cgtest_store_local(tf, c, REG_op(p, PI32), PI32);
+ T->label_place(T, after_store);
+
+ /* i++ */
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, cnt);
+ T->label_place(T, brk);
+ T->scope_end(T, sc);
+
+ /* return (a != b) & (b != c) */
+ Reg ra = T->alloc_reg(T, RC_INT, PI32);
+ Reg rb = T->alloc_reg(T, RC_INT, PI32);
+ Reg rc = T->alloc_reg(T, RC_INT, PI32);
+ cgtest_load_local(tf, REG_op(ra, PI32), a, PI32);
+ cgtest_load_local(tf, REG_op(rb, PI32), b, PI32);
+ cgtest_load_local(tf, REG_op(rc, PI32), c, PI32);
+
+ Reg ne1 = T->alloc_reg(T, RC_INT, I32);
+ Reg ne2 = T->alloc_reg(T, RC_INT, I32);
+ Reg both = T->alloc_reg(T, RC_INT, I32);
+ T->cmp(T, CMP_NE, REG_op(ne1, I32), REG_op(ra, PI32), REG_op(rb, PI32));
+ T->cmp(T, CMP_NE, REG_op(ne2, I32), REG_op(rb, PI32), REG_op(rc, PI32));
+ T->binop(T, BO_AND, REG_op(both, I32), REG_op(ne1, I32), REG_op(ne2, I32));
+ cgtest_ret_reg(tf, both, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by i05: void fill(int *p, int v) { *p = v; } — same shape as
+ * g07 but a separate symbol so the cases don't share state. */
+static ObjSymId build_i05_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* VOID = T_void(ctx);
+ const Type* params[] = { PI32, I32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "i05_fill", VOID, params, 2);
+ CGTarget* T = ctx->target;
+
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ Reg v = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 0), PI32);
+ cgtest_load_local(tf, REG_op(v, I32), cgtest_param_slot(tf, 1), I32);
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(p, 0, I32), REG_op(v, I32), ma);
+ cgtest_ret_void(tf);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* i05_alloca_then_call — alloca buf; helper writes 42; load and return. */
+void build_i05_alloca_then_call(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* VOID = T_void(ctx);
+ const Type* params[] = { PI32, I32 };
+ ObjSymId fill = build_i05_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
+
+ CgTestArg args[] = {
+ { .kind = CGT_ARG_REG, .type = PI32, .v.reg = p },
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 42 },
+ };
+ cgtest_call(tf, fill, VOID, params, args, 2, IMM_op(0, VOID));
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* i06_two_allocas_disjoint — *p=1; *q=2; return *p + *q → 3. */
+void build_i06_two_allocas_disjoint(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ Reg q = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
+ T->alloca_(T, REG_op(q, PI32), IMM_op(4, I64), 4);
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(p, 0, I32), IMM_op(1, I32), ma);
+ T->store(T, IND_op(q, 0, I32), IMM_op(2, I32), ma);
+
+ Reg vp = T->alloc_reg(T, RC_INT, I32);
+ Reg vq = T->alloc_reg(T, RC_INT, I32);
+ Reg s = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(vp, I32), IND_op(p, 0, I32), ma);
+ T->load(T, REG_op(vq, I32), IND_op(q, 0, I32), ma);
+ T->binop(T, BO_IADD, REG_op(s, I32), REG_op(vp, I32), REG_op(vq, I32));
+ cgtest_ret_reg(tf, s, I32);
+ cgtest_end(tf);
+}
+
+/* i07_alloca_addr_escapes — alloca'd pointer round-trips through an
+ * addr-taken local int**, then is dereferenced to write 42. The escape
+ * forces the alloca's pointer to be a real value, not a register-only
+ * temporary the optimizer could fold away. */
+void build_i07_alloca_addr_escapes(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* PPI32 = T_ptr(ctx, PI32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* int *holder; */
+ FrameSlot holder = cgtest_local(tf, PI32, FSF_ADDR_TAKEN);
+
+ /* p = alloca(4); */
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
+
+ /* holder = p; */
+ cgtest_store_local(tf, holder, REG_op(p, PI32), PI32);
+
+ /* int **pp = &holder; */
+ Reg pp = T->alloc_reg(T, RC_INT, PPI32);
+ T->addr_of(T, REG_op(pp, PPI32), LOCAL_op(holder, PI32));
+
+ /* int *back = *pp; *back = 42; return *back; */
+ MemAccess ma_p = { .type = PI32, .size = 8, .align = 8,
+ .alias.kind = ALIAS_LOCAL };
+ Reg back = T->alloc_reg(T, RC_INT, PI32);
+ T->load(T, REG_op(back, PI32), IND_op(pp, 0, PI32), ma_p);
+
+ MemAccess ma_i = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(back, 0, I32), IMM_op(42, I32), ma_i);
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(back, 0, I32), ma_i);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
+/* helper used by i08: int sum(int n, int *p) — n must be > 0. */
+static ObjSymId build_i08_helper(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* params[] = { I32, PI32 };
+ CgTestFn* tf = cgtest_begin_func(ctx, "i08_sum", I32, params, 2);
+ CGTarget* T = ctx->target;
+
+ Reg n = T->alloc_reg(T, RC_INT, I32);
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ cgtest_load_local(tf, REG_op(n, I32), cgtest_param_slot(tf, 0), I32);
+ cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 1), PI32);
+
+ /* int s=0; for (i=0;i<n;i++) s += p[i]; */
+ FrameSlot ss = cgtest_local(tf, I32, FSF_NONE);
+ FrameSlot is = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, ss, IMM_op(0, I32), I32);
+ cgtest_store_local(tf, is, IMM_op(0, I32), I32);
+
+ Label top = T->label_new(T);
+ Label end = T->label_new(T);
+ T->label_place(T, top);
+ Reg ir = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(ir, I32), is, I32);
+ T->cmp_branch(T, CMP_GE_S, REG_op(ir, I32), REG_op(n, I32), end);
+
+ /* offset_bytes = i * 4 */
+ Reg ofs = T->alloc_reg(T, RC_INT, I32);
+ T->binop(T, BO_SHL, REG_op(ofs, I32), REG_op(ir, I32), IMM_op(2, I32));
+ /* p_i = p + offset (use I64 ptr arith) */
+ Reg pi = T->alloc_reg(T, RC_INT, PI32);
+ T->binop(T, BO_IADD, REG_op(pi, PI32), REG_op(p, PI32), REG_op(ofs, I32));
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ Reg v = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(v, I32), IND_op(pi, 0, I32), ma);
+
+ Reg sr = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(sr, I32), ss, I32);
+ T->binop(T, BO_IADD, REG_op(sr, I32), REG_op(sr, I32), REG_op(v, I32));
+ cgtest_store_local(tf, ss, REG_op(sr, I32), I32);
+
+ T->binop(T, BO_IADD, REG_op(ir, I32), REG_op(ir, I32), IMM_op(1, I32));
+ cgtest_store_local(tf, is, REG_op(ir, I32), I32);
+ T->jump(T, top);
+
+ T->label_place(T, end);
+ Reg out = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(out, I32), ss, I32);
+ cgtest_ret_reg(tf, out, I32);
+ cgtest_end(tf);
+ return tf->sym;
+}
+
+/* i08_vla_param_sum — alloca 9 ints, fill 1..9, helper sums → 45. */
+void build_i08_vla_param_sum(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ const Type* params[] = { I32, PI32 };
+ ObjSymId sum = build_i08_helper(ctx);
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* alloca 9*4 = 36 bytes (round up to 40 for 8B align is fine). */
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(36, I64), 4);
+
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ for (int i = 0; i < 9; ++i) {
+ T->store(T, IND_op(p, (i32)(i*4), I32), IMM_op(i+1, I32), ma);
+ }
+
+ Reg dst = T->alloc_reg(T, RC_INT, I32);
+ CgTestArg args[] = {
+ { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 9 },
+ { .kind = CGT_ARG_REG, .type = PI32, .v.reg = p },
+ };
+ cgtest_call(tf, sum, I32, params, args, 2, REG_op(dst, I32));
+ cgtest_ret_reg(tf, dst, I32);
+ cgtest_end(tf);
+}
+
+/* i09_alloca_preserves_locals — named locals declared before *and* after
+ * an alloca remain readable; the alloca must not overlap their slots. */
+void build_i09_alloca_preserves_locals(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ /* int x = 17 (declared before alloca). */
+ FrameSlot x = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, x, IMM_op(17, I32), I32);
+
+ /* alloca 4 bytes. */
+ Reg p = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(p, PI32), IMM_op(4, I64), 4);
+
+ /* int y = 25 (declared after alloca). */
+ FrameSlot y = cgtest_local(tf, I32, FSF_NONE);
+ cgtest_store_local(tf, y, IMM_op(25, I32), I32);
+
+ /* Touch the alloca'd memory so it isn't dead. */
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ T->store(T, IND_op(p, 0, I32), IMM_op(99, I32), ma);
+
+ /* return x + y → 42. */
+ Reg rx = T->alloc_reg(T, RC_INT, I32);
+ Reg ry = T->alloc_reg(T, RC_INT, I32);
+ Reg rs = T->alloc_reg(T, RC_INT, I32);
+ cgtest_load_local(tf, REG_op(rx, I32), x, I32);
+ cgtest_load_local(tf, REG_op(ry, I32), y, I32);
+ T->binop(T, BO_IADD, REG_op(rs, I32), REG_op(rx, I32), REG_op(ry, I32));
+ cgtest_ret_reg(tf, rs, I32);
+ cgtest_end(tf);
+}
+
+/* i10_alloca_after_named_local — frame layout must keep both addressable
+ * even when the named local is addr-taken. Same expected as i09 but the
+ * named local has FSF_ADDR_TAKEN so the backend must place it in the
+ * fixed-frame region, not in the dynamic alloca region. */
+void build_i10_alloca_after_named_local(CgTestCtx* ctx)
+{
+ const Type* I32 = T_i32(ctx);
+ const Type* I64 = T_i64(ctx);
+ const Type* PI32 = T_ptr(ctx, I32);
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ CGTarget* T = ctx->target;
+
+ FrameSlot x = cgtest_local(tf, I32, FSF_ADDR_TAKEN);
+ cgtest_store_local(tf, x, IMM_op(42, I32), I32);
+
+ /* Take the address of x BEFORE the alloca. */
+ Reg px = T->alloc_reg(T, RC_INT, PI32);
+ T->addr_of(T, REG_op(px, PI32), LOCAL_op(x, I32));
+
+ /* alloca; must not invalidate &x. */
+ Reg dyn = T->alloc_reg(T, RC_INT, PI32);
+ T->alloca_(T, REG_op(dyn, PI32), IMM_op(8, I64), 4);
+
+ /* Reload via the saved &x. */
+ MemAccess ma = { .type = I32, .size = 4, .align = 4,
+ .alias.kind = ALIAS_LOCAL };
+ Reg r = T->alloc_reg(T, RC_INT, I32);
+ T->load(T, REG_op(r, I32), IND_op(px, 0, I32), ma);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
+
diff --git a/test/cg/harness/cases_mc.c b/test/cg/harness/cases_mc.c
@@ -0,0 +1,26 @@
+/* MC-only cases — direct MCEmitter byte path; no CGTarget.
+ * See CORPUS.md for the case list and expected values. */
+
+#include "cg_test.h"
+
+/* ============================================================
+ * Group: MC-only (lowest layer)
+ * ============================================================ */
+
+/* mc_smoke — emit `mov w0, #42; ret` as raw AArch64 bytes through
+ * MCEmitter. No CGTarget involved. Validates the byte path end-to-end. */
+void build_mc_smoke(CgTestCtx* ctx)
+{
+ static const u8 BYTES[8] = {
+ /* mov w0, #42 */ 0x40, 0x05, 0x80, 0x52,
+ /* ret */ 0xc0, 0x03, 0x5f, 0xd6,
+ };
+
+ ObjSymId sym = cgtest_mc_begin_main(ctx);
+ ctx->mc->set_section(ctx->mc, ctx->text_sec);
+ ctx->mc->emit_align(ctx->mc, 4, 0);
+ u32 start = ctx->mc->pos(ctx->mc);
+ ctx->mc->emit_bytes(ctx->mc, BYTES, sizeof BYTES);
+ cgtest_mc_end_main(ctx, sym, start);
+}
+
diff --git a/test/cg/harness/cases_shared.c b/test/cg/harness/cases_shared.c
@@ -0,0 +1,15 @@
+/* Shared helpers across Group case files. See cases_shared.h. */
+
+#include "cases_shared.h"
+
+const Type* cases_pt_type(CgTestCtx* ctx)
+{
+ Sym tag = pool_intern_cstr(ctx->pool, "Pt");
+ TagId tid = type_tag_new(ctx->pool, TAG_STRUCT, tag, (SrcLoc){0,0,0});
+ TypeRecordBuilder* b = type_record_begin(ctx->pool, TY_STRUCT, tid, tag);
+ type_record_field(b, (Field){
+ .name = pool_intern_cstr(ctx->pool, "a"), .type = T_i32(ctx) });
+ type_record_field(b, (Field){
+ .name = pool_intern_cstr(ctx->pool, "b"), .type = T_i32(ctx) });
+ return type_record_end(ctx->pool, b);
+}
diff --git a/test/cg/harness/cases_shared.h b/test/cg/harness/cases_shared.h
@@ -0,0 +1,17 @@
+/* Helpers shared across more than one Group's case file.
+ *
+ * Per-case helpers and per-group struct types stay file-static in their
+ * cases_<x>.c. This header is for the rare item that genuinely crosses a
+ * group boundary — e.g., the 8-byte Pt struct used by both Group B's
+ * sret/byval cases and Group F's copy_bytes case. Routing through one
+ * shared definition keeps the type's TagId interned to a single id. */
+
+#ifndef CFREE_TEST_CG_CASES_SHARED_H
+#define CFREE_TEST_CG_CASES_SHARED_H
+
+#include "cg_test.h"
+
+/* struct Pt { int a; int b; }; — 8-byte two-i32 record. */
+const Type* cases_pt_type(CgTestCtx*);
+
+#endif
diff --git a/test/cg/run.sh b/test/cg/run.sh
@@ -131,6 +131,17 @@ if $CC $CFREE_CFLAGS \
"$TEST_DIR/harness/cg_runner.c" \
"$TEST_DIR/harness/cg_test.c" \
"$TEST_DIR/harness/cases.c" \
+ "$TEST_DIR/harness/cases_shared.c" \
+ "$TEST_DIR/harness/cases_mc.c" \
+ "$TEST_DIR/harness/cases_a.c" \
+ "$TEST_DIR/harness/cases_b.c" \
+ "$TEST_DIR/harness/cases_c.c" \
+ "$TEST_DIR/harness/cases_d.c" \
+ "$TEST_DIR/harness/cases_e.c" \
+ "$TEST_DIR/harness/cases_f.c" \
+ "$TEST_DIR/harness/cases_g.c" \
+ "$TEST_DIR/harness/cases_h.c" \
+ "$TEST_DIR/harness/cases_i.c" \
"$LIB_AR" -o "$CG_RUNNER" 2>"$BUILD_DIR/cg-runner.err"; then
printf ' %s cg-runner\n' "$(color_grn built)"
else