kit

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

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:
Mtest/cg/harness/cases.c | 3469+++----------------------------------------------------------------------------
Atest/cg/harness/cases_a.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_b.c | 332+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_c.c | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_d.c | 244+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_e.c | 274+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_f.c | 342+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_g.c | 686+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_h.c | 669+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_i.c | 447+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_mc.c | 26++++++++++++++++++++++++++
Atest/cg/harness/cases_shared.c | 15+++++++++++++++
Atest/cg/harness/cases_shared.h | 17+++++++++++++++++
Mtest/cg/run.sh | 11+++++++++++
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