kit

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

commit 626bb143300e32d23b3b80d4e92a4ba5bc80dd34
parent 5ae1703a745e575657fee65c946f9e1f241d4a3a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat,  9 May 2026 15:21:40 -0700

test/cg: register Groups N, O, Q (TLS, globals, multi-function)

Adds 31 cases across three previously-deferred groups, each in its own
cases_<x>.c. Expectations target the eventual contract (tls_addr_of,
GLOBAL load/store, per-function text_section_id, multi-function TUs);
many will fail until the backend grows them.

Diffstat:
Mtest/cg/CORPUS.md | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtest/cg/harness/cases.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_n.c | 299+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_o.c | 394+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/cg/harness/cases_q.c | 476+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/cg/run.sh | 3+++
6 files changed, 1316 insertions(+), 3 deletions(-)

diff --git a/test/cg/CORPUS.md b/test/cg/CORPUS.md @@ -322,15 +322,85 @@ in two REG dsts; cases observe each independently. | `l19_sub_overflow_yes` | · | `sub_overflow(INT_MIN,1,&r) → ovf=1`; return `ovf` | 1 | | `l20_mul_overflow_no` | · | `mul_overflow(6,7,&r) → ovf=0`; return `r` | 42 | +## Group N — TLS (thread-local storage) + +Drives `CGTarget.tls_addr_of` plus the `SK_TLS` / `SF_TLS` section/symbol +machinery on `ObjBuilder`. Each case allocates a `.tdata` (initialized) +or `.tbss` (zero-init) section, defines a `SK_TLS` symbol in it, and +accesses storage via `tls_addr_of` → INDIRECT load/store. The backend +chooses the TLS model (LE/IE/LD/GD) from `c->target` and the symbol's +visibility; the expectations here don't presume one. + +The aarch64 backend currently implements TLS Local-Exec only (commit +c1cf117). Path E requires `test/link/harness/start.c`'s TCB+TLS image +setup; paths D/J have no per-thread TLS context yet and are expected to +fail until the JIT runners grow it. + +| Case | Status | Body | Expected | +|---|---|---|---| +| `n01_tls_load_le` | · | `_Thread_local int x=42; return x;` (`.tdata`) | 42 | +| `n02_tls_store_le` | · | `_Thread_local int x; x=42; return x;` (`.tbss`) | 42 | +| `n03_tls_addr_taken` | · | `_Thread_local int x=17; int*p=&x; *p+=1; return *p;` | 18 | +| `n04_tls_i64` | · | `_Thread_local long long x=0x1_0000_002A; return (int)x;` | 42 | +| `n05_tls_in_loop` | · | TLS access inside loop — addr may be hoisted but correct | 10 | +| `n06_tls_two_vars` | · | two distinct TLS vars; `a+b = 10+32` | 42 | +| `n07_tls_bss_zero_init` | · | `_Thread_local int x;` — `.tbss` reads as zero | 0 | +| `n08_tls_addend_offset` | · | `_Thread_local int a[8]={...,42}; return a[7];` | 42 | + +## Group O — sections and globals (non-TLS) + +Drives `addr_of` on `OPK_GLOBAL` operands plus direct `load`/`store` +through `GLOBAL_op` (the spec accepts `LOCAL|GLOBAL|INDIRECT` addr +operands). Exercises the `SecKind` × `SymKind` × `SymBind` matrix on +`ObjBuilder`: `SEC_DATA`, `SEC_BSS`, `SEC_RODATA` × `SK_OBJ` × +`SB_GLOBAL` / `SB_LOCAL`, plus a named non-default text section for a +function. Aggregate-global cases reuse the `Pt` type from +`cases_shared.c` so its `TagId` interns once across groups. + +| Case | Status | Body | Expected | +|---|---|---|---| +| `o01_global_load_data` | · | `int g=42; return g;` — direct GLOBAL load | 42 | +| `o02_global_store_data` | · | `int g; g=42; return g;` — store via GLOBAL operand | 42 | +| `o03_global_bss_zero` | · | uninitialized `.bss` reads as zero | 0 | +| `o04_global_addr_taken` | · | b05 over a global: `&g`, +=1, reload | 18 | +| `o05_global_i64` | · | `long long g=0x1_0000_002A; return (int)g;` | 42 | +| `o06_rodata_load` | · | `static const int rd[4]={1,2,42,4}; return rd[2];` | 42 | +| `o07_global_struct_field` | · | `struct Pt g={10,32}; return g.a + g.b;` | 42 | +| `o08_global_array_runtime_idx` | · | `int g[5]={1..5}; int i=2; return g[i];` | 3 | +| `o09_static_local_linkage` | · | `static int g=42;` — SB_LOCAL data sym | 42 | +| `o10_global_addend` | · | `int g[8]={...,42};` access via OPK_GLOBAL addend = 28 | 42 | +| `o11_text_section_named` | · | helper placed in `.text.o11_helper`; main calls it | 42 | +| `o12_global_across_call` | · | `&g` materialized; intervening call must not corrupt it | 42 | + +## Group Q — multi-function (extends Group B's two-function pattern) + +Group B established that two `func_begin`/`func_end` pairs work in one +TU. Group Q stresses what falls out as the function count grows and +linkage/section attributes vary: many small helpers, mixed +`SB_GLOBAL`/`SB_LOCAL`, distinct signatures sharing one `CGTarget`, +per-function text sections (`-ffunction-sections` analogue), and +forward-declared helpers defined later in the TU. + +| Case | Status | Body | Expected | +|---|---|---|---| +| `q01_three_helpers` | · | `a()+b()+c() = 10+15+17` | 42 | +| `q02_static_internal_linkage` | · | `static int helper(void){return 42;}` — SB_LOCAL | 42 | +| `q03_intra_tu_call_chain` | · | `a→b→c→d`; d returns 42 | 42 | +| `q04_eight_helpers` | · | start at 6, chain through 8 helpers each adding `i+1` | 42 | +| `q05_distinct_signatures` | · | int(int), long(long,long), void(int*), int(void) all called | 42 | +| `q06_function_section_distinct` | · | helper in `.text.q06_helper`, main in default `.text` | 42 | +| `q07_cross_section_calls` | · | a in `.text.q07_a` calls b in `.text.q07_b`; main calls a | 42 | +| `q08_forward_decl_define_late` | · | main calls helper before the helper body is emitted | 42 | +| `q09_helper_calls_helper` | · | `a()` calls `b()`; main calls `a()` | 42 | +| `q10_global_and_static_mix` | · | one SB_GLOBAL + two SB_LOCAL helpers; sum = 12+15+15 | 42 | +| `q11_addr_of_helper_through_global` | · | function ptr stored in `.data` (R_ABS64); indirect call | 42 | + ## Deferred groups | Group | Theme | |---|---| | M | inline asm | -| N | TLS | -| O | sections + globals | | P | set_loc / debug | -| Q | multi-function (extends Group B's two-function pattern) | | R | opt-wrapped equivalence | See `doc/cg_testing.md` for the strategy and group definitions. diff --git a/test/cg/harness/cases.c b/test/cg/harness/cases.c @@ -186,6 +186,40 @@ void build_l18_add_overflow_yes(CgTestCtx*); void build_l19_sub_overflow_yes(CgTestCtx*); void build_l20_mul_overflow_no(CgTestCtx*); +void build_n01_tls_load_le(CgTestCtx*); +void build_n02_tls_store_le(CgTestCtx*); +void build_n03_tls_addr_taken(CgTestCtx*); +void build_n04_tls_i64(CgTestCtx*); +void build_n05_tls_in_loop(CgTestCtx*); +void build_n06_tls_two_vars(CgTestCtx*); +void build_n07_tls_bss_zero_init(CgTestCtx*); +void build_n08_tls_addend_offset(CgTestCtx*); + +void build_o01_global_load_data(CgTestCtx*); +void build_o02_global_store_data(CgTestCtx*); +void build_o03_global_bss_zero(CgTestCtx*); +void build_o04_global_addr_taken(CgTestCtx*); +void build_o05_global_i64(CgTestCtx*); +void build_o06_rodata_load(CgTestCtx*); +void build_o07_global_struct_field(CgTestCtx*); +void build_o08_global_array_runtime_idx(CgTestCtx*); +void build_o09_static_local_linkage(CgTestCtx*); +void build_o10_global_addend(CgTestCtx*); +void build_o11_text_section_named(CgTestCtx*); +void build_o12_global_across_call(CgTestCtx*); + +void build_q01_three_helpers(CgTestCtx*); +void build_q02_static_internal_linkage(CgTestCtx*); +void build_q03_intra_tu_call_chain(CgTestCtx*); +void build_q04_eight_helpers(CgTestCtx*); +void build_q05_distinct_signatures(CgTestCtx*); +void build_q06_function_section_distinct(CgTestCtx*); +void build_q07_cross_section_calls(CgTestCtx*); +void build_q08_forward_decl_define_late(CgTestCtx*); +void build_q09_helper_calls_helper(CgTestCtx*); +void build_q10_global_and_static_mix(CgTestCtx*); +void build_q11_addr_of_helper_through_global(CgTestCtx*); + /* ---- registry ---- */ const CgCase cg_cases[] = { @@ -371,6 +405,43 @@ const CgCase cg_cases[] = { { "l18_add_overflow_yes", build_l18_add_overflow_yes, 1, CG_CASE_DEFAULT }, { "l19_sub_overflow_yes", build_l19_sub_overflow_yes, 1, CG_CASE_DEFAULT }, { "l20_mul_overflow_no", build_l20_mul_overflow_no, 42, CG_CASE_DEFAULT }, + + /* Group N — TLS */ + { "n01_tls_load_le", build_n01_tls_load_le, 42, CG_CASE_DEFAULT }, + { "n02_tls_store_le", build_n02_tls_store_le, 42, CG_CASE_DEFAULT }, + { "n03_tls_addr_taken", build_n03_tls_addr_taken, 18, CG_CASE_DEFAULT }, + { "n04_tls_i64", build_n04_tls_i64, 42, CG_CASE_DEFAULT }, + { "n05_tls_in_loop", build_n05_tls_in_loop, 10, CG_CASE_DEFAULT }, + { "n06_tls_two_vars", build_n06_tls_two_vars, 42, CG_CASE_DEFAULT }, + { "n07_tls_bss_zero_init", build_n07_tls_bss_zero_init, 0, CG_CASE_DEFAULT }, + { "n08_tls_addend_offset", build_n08_tls_addend_offset, 42, CG_CASE_DEFAULT }, + + /* Group O — sections and globals */ + { "o01_global_load_data", build_o01_global_load_data, 42, CG_CASE_DEFAULT }, + { "o02_global_store_data", build_o02_global_store_data, 42, CG_CASE_DEFAULT }, + { "o03_global_bss_zero", build_o03_global_bss_zero, 0, CG_CASE_DEFAULT }, + { "o04_global_addr_taken", build_o04_global_addr_taken, 18, CG_CASE_DEFAULT }, + { "o05_global_i64", build_o05_global_i64, 42, CG_CASE_DEFAULT }, + { "o06_rodata_load", build_o06_rodata_load, 42, CG_CASE_DEFAULT }, + { "o07_global_struct_field", build_o07_global_struct_field, 42, CG_CASE_DEFAULT }, + { "o08_global_array_runtime_idx", build_o08_global_array_runtime_idx, 3, CG_CASE_DEFAULT }, + { "o09_static_local_linkage", build_o09_static_local_linkage, 42, CG_CASE_DEFAULT }, + { "o10_global_addend", build_o10_global_addend, 42, CG_CASE_DEFAULT }, + { "o11_text_section_named", build_o11_text_section_named, 42, CG_CASE_DEFAULT }, + { "o12_global_across_call", build_o12_global_across_call, 42, CG_CASE_DEFAULT }, + + /* Group Q — multi-function */ + { "q01_three_helpers", build_q01_three_helpers, 42, CG_CASE_DEFAULT }, + { "q02_static_internal_linkage", build_q02_static_internal_linkage, 42, CG_CASE_DEFAULT }, + { "q03_intra_tu_call_chain", build_q03_intra_tu_call_chain, 42, CG_CASE_DEFAULT }, + { "q04_eight_helpers", build_q04_eight_helpers, 42, CG_CASE_DEFAULT }, + { "q05_distinct_signatures", build_q05_distinct_signatures, 42, CG_CASE_DEFAULT }, + { "q06_function_section_distinct", build_q06_function_section_distinct, 42, CG_CASE_DEFAULT }, + { "q07_cross_section_calls", build_q07_cross_section_calls, 42, CG_CASE_DEFAULT }, + { "q08_forward_decl_define_late", build_q08_forward_decl_define_late, 42, CG_CASE_DEFAULT }, + { "q09_helper_calls_helper", build_q09_helper_calls_helper, 42, CG_CASE_DEFAULT }, + { "q10_global_and_static_mix", build_q10_global_and_static_mix, 42, CG_CASE_DEFAULT }, + { "q11_addr_of_helper_through_global", build_q11_addr_of_helper_through_global, 42, CG_CASE_DEFAULT }, }; const unsigned cg_cases_count = sizeof(cg_cases) / sizeof(cg_cases[0]); diff --git a/test/cg/harness/cases_n.c b/test/cg/harness/cases_n.c @@ -0,0 +1,299 @@ +/* Group N — TLS (thread-local storage). + * See CORPUS.md for the case list and expected values. + * + * Drives CGTarget.tls_addr_of and the SK_TLS / SF_TLS section/symbol + * machinery on ObjBuilder. Each case allocates a `.tdata` (initialized) + * or `.tbss` (zero-init) section, defines an SK_TLS symbol in it, and + * accesses the storage via tls_addr_of → INDIRECT load/store. + * + * The aarch64 backend currently implements TLS Local-Exec only (see + * c1cf117); GD/IE/LD models are not wired up. Path E (link+run) requires + * test/link/harness/start.c's TCB+TLS setup; paths D/J have no TLS host + * thread context, so they are expected to fail until the JIT runner + * grows TLS support. */ + +#include "cg_test.h" + +/* ============================================================ + * Group N: TLS — _Thread_local globals via tls_addr_of + * ============================================================ */ + +/* Helper: define a `.tdata` section once, return its id. */ +static ObjSecId tls_get_tdata(CgTestCtx* ctx) +{ + Sym name = pool_intern_cstr(ctx->pool, ".tdata"); + return obj_section(ctx->ob, name, SEC_DATA, + SF_ALLOC | SF_WRITE | SF_TLS, 4); +} + +/* Helper: define a `.tbss` section once, return its id. */ +static ObjSecId tls_get_tbss(CgTestCtx* ctx) +{ + Sym name = pool_intern_cstr(ctx->pool, ".tbss"); + return obj_section_ex(ctx->ob, name, SEC_BSS, SSEM_NOBITS, + SF_ALLOC | SF_WRITE | SF_TLS, 4, 0, 0, 0); +} + +/* Helper: define an initialized TLS symbol. Writes `bytes` at the + * current `.tdata` position and emits a SK_TLS symbol pointing to it. */ +static ObjSymId tls_define_init(CgTestCtx* ctx, const char* name, + const u8* bytes, u32 size, u32 align) +{ + ObjSecId sec = tls_get_tdata(ctx); + obj_section_set_align(ctx->ob, sec, align); + u32 ofs = obj_pos(ctx->ob, sec); + /* Pad up to alignment. */ + while (ofs & (align - 1)) { + u8 zero = 0; + obj_write(ctx->ob, sec, &zero, 1); + ofs++; + } + obj_write(ctx->ob, sec, bytes, size); + Sym sname = pool_intern_cstr(ctx->pool, name); + return obj_symbol(ctx->ob, sname, SB_GLOBAL, SK_TLS, sec, ofs, size); +} + +/* Helper: define a zero-initialized TLS symbol in `.tbss`. */ +static ObjSymId tls_define_bss(CgTestCtx* ctx, const char* name, + u32 size, u32 align) +{ + ObjSecId sec = tls_get_tbss(ctx); + obj_section_set_align(ctx->ob, sec, align); + /* obj_reserve_bss tracks bss_size; the symbol value is the offset + * within .tbss, which equals the section's bss_size before reserve. */ + const Section* s = obj_section_get(ctx->ob, sec); + u32 ofs = s->bss_size; + while (ofs & (align - 1)) ofs++; + obj_reserve_bss(ctx->ob, sec, ofs - s->bss_size + size, align); + Sym sname = pool_intern_cstr(ctx->pool, name); + return obj_symbol(ctx->ob, sname, SB_GLOBAL, SK_TLS, sec, ofs, size); +} + +/* n01_tls_load_le — _Thread_local int x = 42; return x; */ +void build_n01_tls_load_le(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 42, 0, 0, 0 }; + ObjSymId x = tls_define_init(ctx, "n01_x", INIT, 4, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I32)), x, 0); + + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* n02_tls_store_le — _Thread_local int x; x = 42; return x; */ +void build_n02_tls_store_le(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId x = tls_define_bss(ctx, "n02_x", 4, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I32)), x, 0); + + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + 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); +} + +/* n03_tls_addr_taken — _Thread_local int x = 17; int *p = &x; *p += 1; + * return *p; — addr-taken TLS local; one materialization of the + * thread pointer is reused for the load/store/load sequence. */ +void build_n03_tls_addr_taken(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 17, 0, 0, 0 }; + ObjSymId x = tls_define_init(ctx, "n03_x", INIT, 4, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I32)), x, 0); + + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + 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); + + 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); +} + +/* n04_tls_i64 — _Thread_local long long x = 0x1_0000_002A; + * return (int)x; — exercises 8-byte TLS access with TLSLE_LDST64 + * relocation kinds (vs the 32-bit family in n01/n02). */ +void build_n04_tls_i64(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + const Type* I64 = T_i64(ctx); + static const u8 INIT[8] = { 0x2A, 0, 0, 0, 0x01, 0, 0, 0 }; + ObjSymId x = tls_define_init(ctx, "n04_x", INIT, 8, 8); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I64)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I64)), x, 0); + + Reg r64 = T->alloc_reg(T, RC_INT, I64); + MemAccess ma = { .type = I64, .size = 8, .align = 8, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r64, I64), IND_op(p, 0, I64), ma); + + Reg r32 = T->alloc_reg(T, RC_INT, I32); + T->convert(T, CV_TRUNC, REG_op(r32, I32), REG_op(r64, I64)); + cgtest_ret_reg(tf, r32, I32); + cgtest_end(tf); +} + +/* n05_tls_in_loop — TLS access inside a loop; the address materialization + * may be hoisted by opt_cgtarget but must remain correct. Body: + * _Thread_local int x = 0; + * for (i = 0; i < 10; i++) x += 1; + * return x; → 10 */ +void build_n05_tls_in_loop(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 0, 0, 0, 0 }; + ObjSymId x = tls_define_init(ctx, "n05_x", INIT, 4, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + FrameSlot islot = cgtest_local(tf, I32, FSF_NONE); + cgtest_store_local(tf, islot, 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); + + /* x += 1; */ + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I32)), x, 0); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + Reg cur = T->alloc_reg(T, RC_INT, I32); + T->load (T, REG_op(cur, I32), IND_op(p, 0, I32), ma); + T->binop(T, BO_IADD, REG_op(cur, I32), REG_op(cur, I32), IMM_op(1, I32)); + T->store(T, IND_op(p, 0, I32), REG_op(cur, I32), ma); + + 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 p2 = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p2, T_ptr(ctx, I32)), x, 0); + Reg out = T->alloc_reg(T, RC_INT, I32); + T->load(T, REG_op(out, I32), IND_op(p2, 0, I32), ma); + cgtest_ret_reg(tf, out, I32); + cgtest_end(tf); +} + +/* n06_tls_two_vars — two distinct TLS variables; sum = 42. + * _Thread_local int a = 10; + * _Thread_local int b = 32; + * return a + b; */ +void build_n06_tls_two_vars(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT_A[4] = { 10, 0, 0, 0 }; + static const u8 INIT_B[4] = { 32, 0, 0, 0 }; + ObjSymId a = tls_define_init(ctx, "n06_a", INIT_A, 4, 4); + ObjSymId b = tls_define_init(ctx, "n06_b", INIT_B, 4, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + + Reg pa = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + Reg pb = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(pa, T_ptr(ctx, I32)), a, 0); + T->tls_addr_of(T, REG_op(pb, T_ptr(ctx, I32)), b, 0); + + Reg ra = T->alloc_reg(T, RC_INT, I32); + Reg rb = T->alloc_reg(T, RC_INT, I32); + T->load(T, REG_op(ra, I32), IND_op(pa, 0, I32), ma); + T->load(T, REG_op(rb, I32), IND_op(pb, 0, I32), ma); + + Reg sum = T->alloc_reg(T, RC_INT, 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); +} + +/* n07_tls_bss_zero_init — _Thread_local int x; (no initializer → .tbss); + * return x; → 0. The TLS image must zero-fill .tbss in the per-thread + * area; the harness's start.c is responsible for that on path E. */ +void build_n07_tls_bss_zero_init(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId x = tls_define_bss(ctx, "n07_x", 4, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I32)), x, 0); + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* n08_tls_addend_offset — _Thread_local int a[8] = {0,..,0,42}; + * return a[7]; — exercises the addend on tls_addr_of (or an indirect + * +offset load). 32 bytes, 4-byte align. Offset of a[7] = 28. */ +void build_n08_tls_addend_offset(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[32] = { + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 42,0,0,0, + }; + ObjSymId arr = tls_define_init(ctx, "n08_arr", INIT, 32, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + /* Address of arr (no addend); read from base+28. The backend may + * fold the addend into the TLSLE relocation sequence. */ + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->tls_addr_of(T, REG_op(p, T_ptr(ctx, I32)), arr, 0); + + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r, I32), IND_op(p, 28, I32), ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} diff --git a/test/cg/harness/cases_o.c b/test/cg/harness/cases_o.c @@ -0,0 +1,394 @@ +/* Group O — sections and globals (non-TLS). + * See CORPUS.md for the case list and expected values. + * + * Drives addr_of on OPK_GLOBAL operands plus direct GLOBAL load/store + * (the load/store methods accept LOCAL|GLOBAL|INDIRECT addr operands). + * Also exercises the SecKind / SymKind / SymBind matrix on ObjBuilder: + * SEC_DATA, SEC_BSS, SEC_RODATA × SK_OBJ × SB_GLOBAL/SB_LOCAL, plus a + * named non-default text section for a function. The aggregate-global + * cases reuse cases_shared's Pt to keep one TagId interned across + * groups. */ + +#include "cg_test.h" +#include "cases_shared.h" + +/* ============================================================ + * Group O: sections and globals + * ============================================================ */ + +/* Helper: define a `.data` symbol initialized to `bytes`. */ +static ObjSymId data_define(CgTestCtx* ctx, const char* name, + const u8* bytes, u32 size, u32 align, + SymBind bind) +{ + Sym sec_name = pool_intern_cstr(ctx->pool, ".data"); + ObjSecId sec = obj_section(ctx->ob, sec_name, SEC_DATA, + SF_ALLOC | SF_WRITE, align); + obj_section_set_align(ctx->ob, sec, align); + u32 ofs = obj_pos(ctx->ob, sec); + while (ofs & (align - 1)) { + u8 z = 0; obj_write(ctx->ob, sec, &z, 1); ofs++; + } + obj_write(ctx->ob, sec, bytes, size); + Sym sname = pool_intern_cstr(ctx->pool, name); + return obj_symbol(ctx->ob, sname, bind, SK_OBJ, sec, ofs, size); +} + +/* Helper: define a zero-initialized `.bss` symbol. */ +static ObjSymId bss_define(CgTestCtx* ctx, const char* name, + u32 size, u32 align, SymBind bind) +{ + Sym sec_name = pool_intern_cstr(ctx->pool, ".bss"); + ObjSecId sec = obj_section_ex(ctx->ob, sec_name, SEC_BSS, SSEM_NOBITS, + SF_ALLOC | SF_WRITE, align, 0, 0, 0); + const Section* s = obj_section_get(ctx->ob, sec); + u32 ofs = s->bss_size; + while (ofs & (align - 1)) ofs++; + obj_reserve_bss(ctx->ob, sec, (ofs - s->bss_size) + size, align); + Sym sname = pool_intern_cstr(ctx->pool, name); + return obj_symbol(ctx->ob, sname, bind, SK_OBJ, sec, ofs, size); +} + +/* Helper: define a `.rodata` symbol initialized to `bytes`. */ +static ObjSymId rodata_define(CgTestCtx* ctx, const char* name, + const u8* bytes, u32 size, u32 align) +{ + Sym sec_name = pool_intern_cstr(ctx->pool, ".rodata"); + ObjSecId sec = obj_section(ctx->ob, sec_name, SEC_RODATA, + SF_ALLOC, align); + u32 ofs = obj_pos(ctx->ob, sec); + while (ofs & (align - 1)) { + u8 z = 0; obj_write(ctx->ob, sec, &z, 1); ofs++; + } + obj_write(ctx->ob, sec, bytes, size); + Sym sname = pool_intern_cstr(ctx->pool, name); + return obj_symbol(ctx->ob, sname, SB_LOCAL, SK_OBJ, sec, ofs, size); +} + +/* o01_global_load_data — int g = 42; return g; — direct GLOBAL load. */ +void build_o01_global_load_data(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 42, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o01_g", INIT, 4, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + /* load directly from a GLOBAL operand — the backend lowers the + * page-relative addressing internally. */ + Operand addr = GLOBAL_op(g, 0); + addr.type = I32; + T->load(T, REG_op(r, I32), addr, ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o02_global_store_data — int g = 0; g = 42; return g; — store via + * GLOBAL operand, then read back. */ +void build_o02_global_store_data(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 0, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o02_g", INIT, 4, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + Operand addr = GLOBAL_op(g, 0); + addr.type = I32; + T->store(T, addr, IMM_op(42, I32), ma); + + Reg r = T->alloc_reg(T, RC_INT, I32); + T->load(T, REG_op(r, I32), addr, ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o03_global_bss_zero — int g; return g; — uninitialized .bss reads + * back as zero. The exit code is 0. */ +void build_o03_global_bss_zero(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId g = bss_define(ctx, "o03_g", 4, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + Operand addr = GLOBAL_op(g, 0); + addr.type = I32; + T->load(T, REG_op(r, I32), addr, ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o04_global_addr_taken — int g = 17; int *p = &g; *p += 1; return *p; + * Mirrors b05 over a global storage class. */ +void build_o04_global_addr_taken(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 17, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o04_g", INIT, 4, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), GLOBAL_op(g, 0)); + + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + 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); + + 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); +} + +/* o05_global_i64 — long long g = 0x1_0000_002A; return (int)g; — 8-byte + * global; exercises wider .data alignment + LDR Xt and downcast. */ +void build_o05_global_i64(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + const Type* I64 = T_i64(ctx); + static const u8 INIT[8] = { 0x2A, 0, 0, 0, 0x01, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o05_g", INIT, 8, 8, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + Reg r64 = T->alloc_reg(T, RC_INT, I64); + MemAccess ma = { .type = I64, .size = 8, .align = 8, + .alias.kind = ALIAS_GLOBAL }; + Operand addr = GLOBAL_op(g, 0); + addr.type = I64; + T->load(T, REG_op(r64, I64), addr, ma); + + Reg r32 = T->alloc_reg(T, RC_INT, I32); + T->convert(T, CV_TRUNC, REG_op(r32, I32), REG_op(r64, I64)); + cgtest_ret_reg(tf, r32, I32); + cgtest_end(tf); +} + +/* o06_rodata_load — static const int rd[4] = {1, 2, 42, 4}; return rd[2]; + * SEC_RODATA write fails at runtime if the linker emits the section + * unwritably (which is the point). */ +void build_o06_rodata_load(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[16] = { + 1, 0, 0, 0, 2, 0, 0, 0, 42, 0, 0, 0, 4, 0, 0, 0, + }; + ObjSymId rd = rodata_define(ctx, "o06_rd", INIT, 16, 4); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), GLOBAL_op(rd, 0)); + + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r, I32), IND_op(p, 8, I32), ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o07_global_struct_field — struct Pt g = {10, 32}; return g.a + g.b; */ +void build_o07_global_struct_field(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + const Type* PT = cases_pt_type(ctx); + (void)PT; + static const u8 INIT[8] = { 10, 0, 0, 0, 32, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o07_g", INIT, 8, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), GLOBAL_op(g, 0)); + + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + Reg a = T->alloc_reg(T, RC_INT, I32); + Reg b = T->alloc_reg(T, RC_INT, I32); + T->load(T, REG_op(a, I32), IND_op(p, 0, I32), ma); + T->load(T, REG_op(b, I32), IND_op(p, 4, I32), ma); + Reg s = T->alloc_reg(T, RC_INT, 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); +} + +/* o08_global_array_runtime_idx — int g[5] = {1,2,3,4,5}; int i=2; return g[i]; + * Index is loaded from a local at runtime; the address is &g + i*4. */ +void build_o08_global_array_runtime_idx(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[20] = { + 1,0,0,0, 2,0,0,0, 3,0,0,0, 4,0,0,0, 5,0,0,0, + }; + ObjSymId g = data_define(ctx, "o08_g", INIT, 20, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + /* int i = 2; — keep i in a local so the index is dynamic. */ + FrameSlot islot = cgtest_local(tf, I32, FSF_NONE); + cgtest_store_local(tf, islot, IMM_op(2, I32), I32); + + Reg base = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->addr_of(T, REG_op(base, T_ptr(ctx, I32)), GLOBAL_op(g, 0)); + + Reg ireg = T->alloc_reg(T, RC_INT, I32); + cgtest_load_local(tf, REG_op(ireg, I32), islot, I32); + /* offs = i * 4 = i << 2 */ + Reg offs = T->alloc_reg(T, RC_INT, T_i64(ctx)); + T->convert(T, CV_SEXT, REG_op(offs, T_i64(ctx)), REG_op(ireg, I32)); + T->binop(T, BO_SHL, REG_op(offs, T_i64(ctx)), + REG_op(offs, T_i64(ctx)), IMM_op(2, T_i64(ctx))); + + /* addr = base + offs */ + Reg addr = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->binop(T, BO_IADD, REG_op(addr, T_ptr(ctx, I32)), + REG_op(base, T_ptr(ctx, I32)), REG_op(offs, T_i64(ctx))); + + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r, I32), IND_op(addr, 0, I32), ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o09_static_local_linkage — static int g = 42; return g; — SB_LOCAL + * (file-static) symbol. The relocation must resolve to the local + * definition without going through a GOT. */ +void build_o09_static_local_linkage(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[4] = { 42, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o09_g", INIT, 4, 4, SB_LOCAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + Operand addr = GLOBAL_op(g, 0); + addr.type = I32; + T->load(T, REG_op(r, I32), addr, ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o10_global_addend — int g[8] = {0,...,0,42}; return *(g+7); — addend + * encoded into the OPK_GLOBAL operand rather than a runtime add. The + * backend may fold the addend into ADD_LO12_NC (or equivalent). */ +void build_o10_global_addend(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + static const u8 INIT[32] = { + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 42,0,0,0, + }; + ObjSymId g = data_define(ctx, "o10_g", INIT, 32, 4, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + /* addr_of(GLOBAL{g, 28}); load *addr. */ + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), GLOBAL_op(g, 28)); + + Reg r = T->alloc_reg(T, RC_INT, I32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + T->load(T, REG_op(r, I32), IND_op(p, 0, I32), ma); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* o11_text_section_named — function placed in `.text.helper`, called + * from test_main in the default `.text`. Models -ffunction-sections / + * __attribute__((section("..."))) on a function. */ +void build_o11_text_section_named(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + const Type* params[] = { I32 }; + + /* Create a separate text section and aim the next func_begin at it. */ + Sym sec_name = pool_intern_cstr(ctx->pool, ".text.o11_helper"); + ObjSecId helper_sec = obj_section(ctx->ob, sec_name, SEC_TEXT, + SF_ALLOC | SF_EXEC, 4); + ObjSecId saved = ctx->text_sec; + ctx->text_sec = helper_sec; + ctx->mc->set_section(ctx->mc, helper_sec); + + /* Helper: int echo(int x) { return x; } */ + CgTestFn* h = cgtest_begin_func(ctx, "o11_helper", I32, params, 1); + Reg hr = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_load_local(h, REG_op(hr, I32), cgtest_param_slot(h, 0), I32); + cgtest_ret_reg(h, hr, I32); + cgtest_end(h); + ObjSymId helper_sym = h->sym; + + /* Restore default text section for test_main. */ + ctx->text_sec = saved; + ctx->mc->set_section(ctx->mc, saved); + + 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 = 42 } }; + cgtest_call(tf, helper_sym, I32, params, args, 1, REG_op(dst, I32)); + cgtest_ret_reg(tf, dst, I32); + cgtest_end(tf); +} + +/* o12_global_across_call — int g = 42; helper modifies nothing relevant; + * return g; — verifies global address materialization is not corrupted + * by an intervening call (caller-saved register policy). */ +void build_o12_global_across_call(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + const Type* params[] = { I32 }; + static const u8 INIT[4] = { 42, 0, 0, 0 }; + ObjSymId g = data_define(ctx, "o12_g", INIT, 4, 4, SB_GLOBAL); + + /* Simple int echo helper, isolated to this case. */ + CgTestFn* h = cgtest_begin_func(ctx, "o12_echo", I32, params, 1); + Reg hr = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_load_local(h, REG_op(hr, I32), cgtest_param_slot(h, 0), I32); + cgtest_ret_reg(h, hr, I32); + cgtest_end(h); + ObjSymId echo = h->sym; + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_GLOBAL }; + + /* Materialize &g, do an intervening call that may clobber p, then + * load *p. The backend must either preserve p or remate the addr. */ + Reg p = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); + T->addr_of(T, REG_op(p, T_ptr(ctx, I32)), GLOBAL_op(g, 0)); + + 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 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); +} diff --git a/test/cg/harness/cases_q.c b/test/cg/harness/cases_q.c @@ -0,0 +1,476 @@ +/* Group Q — multi-function (extends Group B's two-function pattern). + * See CORPUS.md for the case list and expected values. + * + * Group B already validates that two func_begin/func_end pairs work in + * one TU. Group Q stresses what falls out as the function count grows: + * - many small helpers (8+ functions per TU) + * - mixed SB_GLOBAL / SB_LOCAL (file-static) linkage + * - distinct param/return signatures sharing a CGTarget + * - per-function text sections (-ffunction-sections analogue) + * - calls between functions placed in different text sections + * - forward-declared helpers defined later in the TU + * + * Each case constructs a flat call graph rooted at test_main; the oracle + * is the final exit code. */ + +#include "cg_test.h" + +/* ============================================================ + * Group Q: multi-function + * ============================================================ */ + +/* Helper: int return, no params, body returns IMM `v`. */ +static ObjSymId qfn_const(CgTestCtx* ctx, const char* name, i64 v, SymBind bind) +{ + const Type* I32 = T_i32(ctx); + Sym sname = pool_intern_cstr(ctx->pool, name); + ObjSymId sym = obj_symbol(ctx->ob, sname, bind, SK_FUNC, + OBJ_SEC_NONE, 0, 0); + CgTestFn* tf = cgtest_begin_func_at(ctx, sym, I32, NULL, 0); + cgtest_ret_imm(tf, v, I32); + cgtest_end(tf); + return sym; +} + +/* Helper: int echo(int x) — distinct symbol per case. */ +static ObjSymId qfn_echo(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; +} + +/* q01_three_helpers — three int(void) helpers a/b/c each returning + * a partial sum; main returns a()+b()+c() = 10+15+17 = 42. */ +void build_q01_three_helpers(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId a = qfn_const(ctx, "q01_a", 10, SB_GLOBAL); + ObjSymId b = qfn_const(ctx, "q01_b", 15, SB_GLOBAL); + ObjSymId c = qfn_const(ctx, "q01_c", 17, SB_GLOBAL); + + 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 rc = T->alloc_reg(T, RC_INT, I32); + cgtest_call(tf, a, I32, NULL, NULL, 0, REG_op(ra, I32)); + cgtest_call(tf, b, I32, NULL, NULL, 0, REG_op(rb, I32)); + cgtest_call(tf, c, I32, NULL, NULL, 0, REG_op(rc, I32)); + + Reg s = T->alloc_reg(T, RC_INT, I32); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(ra, I32), REG_op(rb, I32)); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(rc, I32)); + cgtest_ret_reg(tf, s, I32); + cgtest_end(tf); +} + +/* q02_static_internal_linkage — `static int helper(void) { return 42; }` + * SB_LOCAL symbol; the call lowers to a near branch resolved within + * this TU (no PLT/GOT). */ +void build_q02_static_internal_linkage(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId h = qfn_const(ctx, "q02_helper", 42, SB_LOCAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, h, I32, NULL, NULL, 0, REG_op(dst, I32)); + cgtest_ret_reg(tf, dst, I32); + cgtest_end(tf); +} + +/* q03_intra_tu_call_chain — a→b→c→d, where a/b/c are bodies that just + * tail-forward to the next, and d returns 42. Built without + * CG_CALL_TAIL — exercises a 4-deep linear call stack. */ +void build_q03_intra_tu_call_chain(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + + /* d: returns 42. */ + ObjSymId d = qfn_const(ctx, "q03_d", 42, SB_GLOBAL); + /* c: returns d(); */ + ObjSymId c; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q03_c", I32, NULL, 0); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, d, I32, NULL, NULL, 0, REG_op(r, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + c = tf->sym; + } + /* b: returns c(); */ + ObjSymId b; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q03_b", I32, NULL, 0); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, c, I32, NULL, NULL, 0, REG_op(r, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + b = tf->sym; + } + /* a: returns b(); */ + ObjSymId a; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q03_a", I32, NULL, 0); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, b, I32, NULL, NULL, 0, REG_op(r, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + a = tf->sym; + } + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, a, I32, NULL, NULL, 0, REG_op(dst, I32)); + cgtest_ret_reg(tf, dst, I32); + cgtest_end(tf); +} + +/* q04_eight_helpers — eight int(int) helpers, each adding a constant. + * Composing them in order yields 0 + 1+2+3+4+5+6+7+8 = 36. Plus a 6 + * baseline → 42. Stresses many func_begin/func_end pairs in one TU. */ +void build_q04_eight_helpers(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + const Type* params[] = { I32 }; + + ObjSymId helpers[8]; + for (int i = 0; i < 8; ++i) { + char name[16]; + name[0] = 'q'; name[1] = '0'; name[2] = '4'; name[3] = '_'; + name[4] = 'h'; name[5] = (char)('1' + i); name[6] = 0; + Sym sn = pool_intern_cstr(ctx->pool, name); + ObjSymId sym = obj_symbol(ctx->ob, sn, SB_GLOBAL, SK_FUNC, + OBJ_SEC_NONE, 0, 0); + CgTestFn* tf = cgtest_begin_func_at(ctx, sym, I32, params, 1); + CGTarget* T = ctx->target; + Reg x = T->alloc_reg(T, RC_INT, I32); + cgtest_load_local(tf, REG_op(x, I32), cgtest_param_slot(tf, 0), I32); + Reg r = T->alloc_reg(T, RC_INT, I32); + T->binop(T, BO_IADD, REG_op(r, I32), + REG_op(x, I32), IMM_op(i + 1, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + helpers[i] = sym; + } + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + /* Start at 6, then chain h1..h8. 6 + (1+2+...+8) = 6 + 36 = 42. */ + Reg cur = T->alloc_reg(T, RC_INT, I32); + T->load_imm(T, REG_op(cur, I32), 6); + for (int i = 0; i < 8; ++i) { + Reg next = T->alloc_reg(T, RC_INT, I32); + CgTestArg args[] = { { .kind = CGT_ARG_REG, .type = I32, .v.reg = cur } }; + cgtest_call(tf, helpers[i], I32, params, args, 1, REG_op(next, I32)); + cur = next; + } + cgtest_ret_reg(tf, cur, I32); + cgtest_end(tf); +} + +/* q05_distinct_signatures — four helpers with distinct (ret, params) + * signatures, all called from main; sum truncated to 42. + * int h_int (int); + * long h_long(long, long); + * void h_void(int*); + * int h_zero(void); + * Sum: 10 + 20 + 5 + 7 = 42 (low 32 of long sum). */ +void build_q05_distinct_signatures(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); + + /* h_int(x) = x + 5 */ + const Type* p_int[] = { I32 }; + ObjSymId h_int; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q05_h_int", I32, p_int, 1); + CGTarget* T = ctx->target; + Reg x = T->alloc_reg(T, RC_INT, I32); + cgtest_load_local(tf, REG_op(x, I32), cgtest_param_slot(tf, 0), I32); + Reg r = T->alloc_reg(T, RC_INT, I32); + T->binop(T, BO_IADD, REG_op(r, I32), REG_op(x, I32), IMM_op(5, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + h_int = tf->sym; + } + /* h_long(a, b) = a + b */ + const Type* p_long[] = { I64, I64 }; + ObjSymId h_long; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q05_h_long", I64, p_long, 2); + CGTarget* T = ctx->target; + Reg a = T->alloc_reg(T, RC_INT, I64); + Reg b = T->alloc_reg(T, RC_INT, I64); + cgtest_load_local(tf, REG_op(a, I64), cgtest_param_slot(tf, 0), I64); + cgtest_load_local(tf, REG_op(b, I64), cgtest_param_slot(tf, 1), I64); + Reg s = T->alloc_reg(T, RC_INT, I64); + T->binop(T, BO_IADD, REG_op(s, I64), REG_op(a, I64), REG_op(b, I64)); + cgtest_ret_reg(tf, s, I64); + cgtest_end(tf); + h_long = tf->sym; + } + /* h_void(p) { *p = 5; } */ + const Type* p_void[] = { PI32 }; + ObjSymId h_void; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q05_h_void", VOID, p_void, 1); + CGTarget* T = ctx->target; + Reg p = T->alloc_reg(T, RC_INT, PI32); + cgtest_load_local(tf, REG_op(p, PI32), cgtest_param_slot(tf, 0), PI32); + MemAccess ma = { .type = I32, .size = 4, .align = 4, + .alias.kind = ALIAS_LOCAL }; + T->store(T, IND_op(p, 0, I32), IMM_op(5, I32), ma); + cgtest_ret_void(tf); + cgtest_end(tf); + h_void = tf->sym; + } + /* h_zero(void) = 7 */ + ObjSymId h_zero = qfn_const(ctx, "q05_h_zero", 7, SB_GLOBAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + /* h_int(5) = 10 */ + Reg r_int = T->alloc_reg(T, RC_INT, I32); + CgTestArg a_int[] = { { .kind = CGT_ARG_IMM, .type = I32, .v.imm = 5 } }; + cgtest_call(tf, h_int, I32, p_int, a_int, 1, REG_op(r_int, I32)); + + /* h_long(8, 12) = 20 */ + Reg r_long = T->alloc_reg(T, RC_INT, I64); + CgTestArg a_long[] = { + { .kind = CGT_ARG_IMM, .type = I64, .v.imm = 8 }, + { .kind = CGT_ARG_IMM, .type = I64, .v.imm = 12 }, + }; + cgtest_call(tf, h_long, I64, p_long, a_long, 2, REG_op(r_long, I64)); + + /* int x; h_void(&x); — x is set to 5. */ + FrameSlot xslot = cgtest_local(tf, I32, FSF_ADDR_TAKEN); + cgtest_store_local(tf, xslot, IMM_op(0, I32), I32); + Reg px = T->alloc_reg(T, RC_INT, PI32); + T->addr_of(T, REG_op(px, PI32), LOCAL_op(xslot, I32)); + CgTestArg a_void[] = { { .kind = CGT_ARG_REG, .type = PI32, .v.reg = px } }; + cgtest_call(tf, h_void, VOID, p_void, a_void, 1, IMM_op(0, VOID)); + Reg r_void = T->alloc_reg(T, RC_INT, I32); + cgtest_load_local(tf, REG_op(r_void, I32), xslot, I32); + + /* h_zero() = 7 */ + Reg r_zero = T->alloc_reg(T, RC_INT, I32); + cgtest_call(tf, h_zero, I32, NULL, NULL, 0, REG_op(r_zero, I32)); + + /* sum = r_int + (i32)r_long + r_void + r_zero. */ + Reg r_long_lo = T->alloc_reg(T, RC_INT, I32); + T->convert(T, CV_TRUNC, REG_op(r_long_lo, I32), REG_op(r_long, I64)); + + Reg s = T->alloc_reg(T, RC_INT, I32); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(r_int, I32), REG_op(r_long_lo, I32)); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(r_void, I32)); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(r_zero, I32)); + cgtest_ret_reg(tf, s, I32); + cgtest_end(tf); +} + +/* q06_function_section_distinct — helper placed in `.text.q06_helper`, + * test_main in default `.text`. CGFuncDesc.text_section_id varies per + * function; the backend must honor it. */ +void build_q06_function_section_distinct(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + + Sym sec_name = pool_intern_cstr(ctx->pool, ".text.q06_helper"); + ObjSecId helper_sec = obj_section(ctx->ob, sec_name, SEC_TEXT, + SF_ALLOC | SF_EXEC, 4); + ObjSecId saved = ctx->text_sec; + ctx->text_sec = helper_sec; + ctx->mc->set_section(ctx->mc, helper_sec); + + ObjSymId helper = qfn_const(ctx, "q06_helper", 42, SB_GLOBAL); + + ctx->text_sec = saved; + ctx->mc->set_section(ctx->mc, saved); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, helper, I32, NULL, NULL, 0, REG_op(r, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* q07_cross_section_calls — two helpers, each in its own + * `.text.<name>`, calling each other plus test_main calling one. + * Caller and callee in distinct text sections must produce a CALL26 + * relocation (or veneer) rather than a fixed PC-relative offset. */ +void build_q07_cross_section_calls(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + + /* helper_a in .text.q07_a — returns helper_b() + 10. */ + Sym sa = pool_intern_cstr(ctx->pool, ".text.q07_a"); + Sym sb = pool_intern_cstr(ctx->pool, ".text.q07_b"); + ObjSecId sec_a = obj_section(ctx->ob, sa, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); + ObjSecId sec_b = obj_section(ctx->ob, sb, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); + + /* Forward-decl both syms so each body can reference the other. */ + ObjSymId hb = cgtest_decl_func(ctx, "q07_b"); + + ObjSecId saved = ctx->text_sec; + + /* helper_a body in sec_a. */ + ctx->text_sec = sec_a; + ctx->mc->set_section(ctx->mc, sec_a); + ObjSymId ha; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q07_a", I32, NULL, 0); + CGTarget* T = ctx->target; + Reg r_b = T->alloc_reg(T, RC_INT, I32); + cgtest_call(tf, hb, I32, NULL, NULL, 0, REG_op(r_b, I32)); + Reg r = T->alloc_reg(T, RC_INT, I32); + T->binop(T, BO_IADD, REG_op(r, I32), REG_op(r_b, I32), IMM_op(10, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + ha = tf->sym; + } + + /* helper_b body in sec_b — returns 32. */ + ctx->text_sec = sec_b; + ctx->mc->set_section(ctx->mc, sec_b); + { + CgTestFn* tf = cgtest_begin_func_at(ctx, hb, I32, NULL, 0); + cgtest_ret_imm(tf, 32, I32); + cgtest_end(tf); + } + + /* test_main back in default `.text`, calls helper_a → 42. */ + ctx->text_sec = saved; + ctx->mc->set_section(ctx->mc, saved); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, ha, I32, NULL, NULL, 0, REG_op(r, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); +} + +/* q08_forward_decl_define_late — declare helper at the start, define it + * after test_main. test_main's call site is emitted before the symbol + * has a section/value; obj_finalize is responsible for resolving the + * relocation once cgtest_begin_func_at fills in the symbol body. */ +void build_q08_forward_decl_define_late(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId h = cgtest_decl_func(ctx, "q08_late"); + + /* Emit test_main first — it calls h before h has a body. */ + CgTestFn* tf = cgtest_begin_main(ctx, I32); + Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, h, I32, NULL, NULL, 0, REG_op(dst, I32)); + cgtest_ret_reg(tf, dst, I32); + cgtest_end(tf); + + /* Now define h. */ + { + CgTestFn* tf2 = cgtest_begin_func_at(ctx, h, I32, NULL, 0); + cgtest_ret_imm(tf2, 42, I32); + cgtest_end(tf2); + } +} + +/* q09_helper_calls_helper — a → b, both globals; main calls a. */ +void build_q09_helper_calls_helper(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId b = qfn_const(ctx, "q09_b", 42, SB_GLOBAL); + + /* a returns b(). */ + ObjSymId a; + { + CgTestFn* tf = cgtest_begin_func(ctx, "q09_a", I32, NULL, 0); + Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, b, I32, NULL, NULL, 0, REG_op(r, I32)); + cgtest_ret_reg(tf, r, I32); + cgtest_end(tf); + a = tf->sym; + } + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); + cgtest_call(tf, a, I32, NULL, NULL, 0, REG_op(dst, I32)); + cgtest_ret_reg(tf, dst, I32); + cgtest_end(tf); +} + +/* q10_global_and_static_mix — three helpers in one TU: SB_GLOBAL + + * SB_LOCAL + SB_LOCAL. All three are called; sum = 12+15+15 = 42. */ +void build_q10_global_and_static_mix(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + ObjSymId g = qfn_const(ctx, "q10_global", 12, SB_GLOBAL); + ObjSymId s1 = qfn_const(ctx, "q10_static1", 15, SB_LOCAL); + ObjSymId s2 = qfn_const(ctx, "q10_static2", 15, SB_LOCAL); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + Reg rg = T->alloc_reg(T, RC_INT, I32); + Reg rs1 = T->alloc_reg(T, RC_INT, I32); + Reg rs2 = T->alloc_reg(T, RC_INT, I32); + cgtest_call(tf, g, I32, NULL, NULL, 0, REG_op(rg, I32)); + cgtest_call(tf, s1, I32, NULL, NULL, 0, REG_op(rs1, I32)); + cgtest_call(tf, s2, I32, NULL, NULL, 0, REG_op(rs2, I32)); + + Reg s = T->alloc_reg(T, RC_INT, I32); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(rg, I32), REG_op(rs1, I32)); + T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(rs2, I32)); + cgtest_ret_reg(tf, s, I32); + cgtest_end(tf); +} + +/* q11_addr_of_helper_through_global — store helper's address into a + * data global, load it, indirect-call. Tests function-symbol relocation + * into a non-text section (data ABS64) and indirect call via REG. */ +void build_q11_addr_of_helper_through_global(CgTestCtx* ctx) +{ + const Type* I32 = T_i32(ctx); + /* The helper. */ + ObjSymId h = qfn_const(ctx, "q11_helper", 42, SB_GLOBAL); + + /* Allocate a .data slot of pointer size with an ABS64 reloc to h. */ + Sym dn = pool_intern_cstr(ctx->pool, ".data"); + ObjSecId data_sec = obj_section(ctx->ob, dn, SEC_DATA, + SF_ALLOC | SF_WRITE, 8); + static const u8 ZERO8[8] = { 0 }; + u32 dofs = obj_pos(ctx->ob, data_sec); + obj_write(ctx->ob, data_sec, ZERO8, 8); + obj_reloc(ctx->ob, data_sec, dofs, R_ABS64, h, 0); + Sym fn = pool_intern_cstr(ctx->pool, "q11_fp"); + ObjSymId fp_sym = obj_symbol(ctx->ob, fn, SB_GLOBAL, SK_OBJ, + data_sec, dofs, 8); + + CgTestFn* tf = cgtest_begin_main(ctx, I32); + CGTarget* T = ctx->target; + + /* Load the function pointer from the global slot. */ + const Type* fn_ty = type_func(ctx->pool, I32, NULL, 0, 0); + const Type* fnp_ty = T_ptr(ctx, fn_ty); + Reg fp = T->alloc_reg(T, RC_INT, fnp_ty); + MemAccess ma = { .type = fnp_ty, .size = 8, .align = 8, + .alias.kind = ALIAS_GLOBAL }; + Operand addr = GLOBAL_op(fp_sym, 0); + addr.type = fnp_ty; + T->load(T, REG_op(fp, fnp_ty), addr, ma); + + Reg dst = T->alloc_reg(T, RC_INT, I32); + cgtest_call_indirect(tf, fp, I32, NULL, NULL, 0, REG_op(dst, I32)); + cgtest_ret_reg(tf, dst, I32); + cgtest_end(tf); +} diff --git a/test/cg/run.sh b/test/cg/run.sh @@ -145,6 +145,9 @@ if $CC $CFREE_CFLAGS \ "$TEST_DIR/harness/cases_j.c" \ "$TEST_DIR/harness/cases_k.c" \ "$TEST_DIR/harness/cases_l.c" \ + "$TEST_DIR/harness/cases_n.c" \ + "$TEST_DIR/harness/cases_o.c" \ + "$TEST_DIR/harness/cases_q.c" \ "$LIB_AR" -o "$CG_RUNNER" 2>"$BUILD_DIR/cg-runner.err"; then printf ' %s cg-runner\n' "$(color_grn built)" else