kit

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

commit df89de575e43a34401cd2a8cfbc73ae97c2e6c0e
parent 9ef2979b70072891cc096bffa3b00a6db7a3f68d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 14 May 2026 09:11:33 -0700

Remove legacy cg implementation

Diffstat:
Mdoc/cg-type-migration-plan.md | 545++++++++++++++++++++++---------------------------------------------------------
Minclude/abi/abi.h | 11++++++++++-
Msrc/abi/abi.c | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/abi/abi.h | 11++++++++++-
Msrc/abi/abi_aapcs64.c | 37+++++++++++++++++++++----------------
Msrc/abi/abi_apple_arm64.c | 4++--
Msrc/abi/abi_internal.h | 10+++++-----
Msrc/abi/abi_rv64.c | 37+++++++++++++++++++++----------------
Msrc/abi/abi_sysv_x64.c | 38++++++++++++++++++++++----------------
Msrc/api/cg.c | 112+++++++++++++++++++++----------------------------------------------------------
Msrc/api/cg_api.h | 9+--------
Asrc/api/cg_type.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/stubs.c | 3+--
Msrc/arch/arch.h | 3++-
Dsrc/cg/cg.c | 1995-------------------------------------------------------------------------------
Dsrc/cg/cg.h | 195-------------------------------------------------------------------------------
Dsrc/cg/fold.c | 154-------------------------------------------------------------------------------
Dsrc/cg/fold.h | 47-----------------------------------------------
Msrc/emu/emu.c | 28+++++++++-------------------
Msrc/emu/emu.h | 6+++---
Msrc/emu/lift.c | 6+++---
Dtest/cg/CORPUS.md | 436-------------------------------------------------------------------------------
Dtest/cg/binder_test.c | 538-------------------------------------------------------------------------------
Dtest/cg/dwarf_validate.sh | 81-------------------------------------------------------------------------------
Dtest/cg/harness/cases.c | 555-------------------------------------------------------------------------------
Dtest/cg/harness/cases_a.c | 112-------------------------------------------------------------------------------
Dtest/cg/harness/cases_asm.c | 101-------------------------------------------------------------------------------
Dtest/cg/harness/cases_b.c | 315-------------------------------------------------------------------------------
Dtest/cg/harness/cases_c.c | 204-------------------------------------------------------------------------------
Dtest/cg/harness/cases_d.c | 230-------------------------------------------------------------------------------
Dtest/cg/harness/cases_e.c | 258-------------------------------------------------------------------------------
Dtest/cg/harness/cases_f.c | 327-------------------------------------------------------------------------------
Dtest/cg/harness/cases_g.c | 660-------------------------------------------------------------------------------
Dtest/cg/harness/cases_h.c | 655-------------------------------------------------------------------------------
Dtest/cg/harness/cases_i.c | 435-------------------------------------------------------------------------------
Dtest/cg/harness/cases_j.c | 573-------------------------------------------------------------------------------
Dtest/cg/harness/cases_k.c | 210-------------------------------------------------------------------------------
Dtest/cg/harness/cases_l.c | 396-------------------------------------------------------------------------------
Dtest/cg/harness/cases_mc.c | 24------------------------
Dtest/cg/harness/cases_n.c | 286-------------------------------------------------------------------------------
Dtest/cg/harness/cases_o.c | 381-------------------------------------------------------------------------------
Dtest/cg/harness/cases_p.c | 132-------------------------------------------------------------------------------
Dtest/cg/harness/cases_q.c | 473-------------------------------------------------------------------------------
Dtest/cg/harness/cases_shared.c | 14--------------
Dtest/cg/harness/cases_shared.h | 17-----------------
Dtest/cg/harness/cg_check_dwarf.c | 429-------------------------------------------------------------------------------
Dtest/cg/harness/cg_runner.c | 657-------------------------------------------------------------------------------
Dtest/cg/harness/cg_test.c | 456-------------------------------------------------------------------------------
Dtest/cg/harness/cg_test.h | 300-------------------------------------------------------------------------------
Dtest/cg/run.sh | 652-------------------------------------------------------------------------------
Mtest/test.mk | 33++++++---------------------------
51 files changed, 665 insertions(+), 12937 deletions(-)

diff --git a/doc/cg-type-migration-plan.md b/doc/cg-type-migration-plan.md @@ -1,404 +1,157 @@ -# CG Type Migration Plan +# Remove C `Type` From `src/` ## Goal -Make the C frontend just another language frontend, like `lang/toy`, while -removing the C language `Type` dependency from `src/`. +`src/` must be language-neutral. C semantic types stay in `lang/c`; generic +codegen, ABI, arch lowering, optimizer, debug, object emission, and emu use +`CfreeCgTypeId`, `CgType`, debug type IDs, or explicit storage facts. -The C frontend should keep its rich C semantic type system privately in -`lang/c`. Codegen, ABI, arch lowering, optimizer, and object emission should -use a narrower, language-neutral `CgType` model constructed only through the -public `include/cfree/cg.h` type constructors. +Completion means: -## Status Checklist - -Keep this section current as migration work lands. - -- [x] Phase 1: Add an internal `CgType` payload behind `CfreeCgTypeId`. -- [x] Phase 1: Add internal `cg_type_*` lookup, layout, and classification - helpers. -- [x] Phase 1: Populate `CgType` from public CG constructors and the temporary - legacy C `Type*` import bridge. -- [x] Phase 1: Move public CG type query APIs to `CgType`. -- [ ] Phase 1: Move `CgType` into a dedicated internal header/module if the - next migration slice needs it outside `src/api/cg.c`. -- [ ] Phase 2: Cache lowered `CfreeCgTypeId` values in the C frontend or in a - frontend-owned `Type*` map. -- [ ] Phase 2: Replace recursive-record placeholder lowering with a real - forward/begin/complete public CG record API. -- [ ] Phase 3: Replace stored `const Type*` in `src/api/cg.c` state with - `CfreeCgTypeId` or `CgType` facts. -- [ ] Phase 4: Migrate ABI APIs and record/function caches from C `Type*` to - CG type handles. -- [ ] Phase 5: Migrate `CGTarget` and arch lowering away from C semantic - types. -- [ ] Phase 6: Migrate optimizer and generic debug bridges away from C - semantic types. -- [ ] Phase 7: Remove legacy C type bridges and shim headers. -- [ ] Phase 8: Register C through the frontend mechanism and remove direct - `src` dependencies on `lang/c`. - -## Target Boundary - -- `lang/c/Type`: C semantic type. It carries C-only facts such as qualifiers, - typedef/tag behavior, incomplete types, decay rules, bitfield syntax, and - source-level compatibility. -- `CgType`: language-neutral codegen type. It carries only storage, layout, - calling convention, and lowering information expressible through - `include/cfree/cg.h`. -- `src/`: must not include or depend on `lang/c` headers. -- `ObjBuilder`: should see symbols, sections, relocs, and bytes, not C types. -- `CGTarget`: should see codegen facts such as size, alignment, register class, - pointer/float/int shape, and operation flags, not C semantic facts. - -## Why This Is Needed - -Today `CfreeCgTypeId` is public, but internally `src/api/cg.c` resolves it back -to the C frontend `Type*`. That makes the public CG API a wrapper around the C -type system and keeps `src` coupled to `lang/c` through compatibility headers. - -The main current dependency shape is: - -- public `cfree_cg_type_*` constructors create/import C `Type*` -- CG stack values, operands, slots, symbols, ABI calls, and target descriptors - store `const Type*` -- ABI and arch helpers inspect `Type*` directly -- `src/type/type.h` is currently a shim to `lang/c/type/type.h` - -The migration inverts that relationship. C `Type` lowers to `CfreeCgTypeId`; -CG internals resolve `CfreeCgTypeId` to `CgType`. - -## CgType Shape - -Add an internal `CgType` representation, probably under `src/api/cg_type.h` or -`src/cg/type.h`. - -It should model only concepts from public `include/cfree/cg.h`: - -```c -typedef struct CgType CgType; - -typedef struct CgTypeField { - CfreeSym name; - CfreeCgTypeId type; - uint64_t offset; - uint32_t align_override; -} CgTypeField; - -struct CgType { - CfreeCgTypeKind kind; - uint64_t size; - uint32_t align; - - union { - struct { - uint32_t width; - } integer; - - struct { - uint32_t width; - } fp; - - struct { - CfreeCgTypeId pointee; - uint32_t address_space; - } ptr; - - struct { - CfreeCgTypeId elem; - uint64_t count; - } array; - - struct { - CfreeCgTypeId ret; - CfreeCgParam* params; - uint32_t nparams; - CfreeCgCallConv call_conv; - int abi_variadic; - CfreeCgAbiAttrs ret_attrs; - } func; - - struct { - CfreeSym tag; - CgTypeField* fields; - uint32_t nfields; - int is_union; - uint32_t align_override; - uint32_t flags; - } record; - - struct { - CfreeSym tag; - CfreeCgTypeId base; - CfreeCgEnumValue* values; - uint32_t nvalues; - } enum_; - - struct { - CfreeSym name; - CfreeCgTypeId base; - } alias; - }; -}; -``` - -Exact field names can change, but the important rule is that `CgType` cannot -grow C-only semantics. - -## Public API Gaps To Address - -Before fully migrating internals, confirm the public type constructors can -represent codegen needs without C-specific escape hatches. - -Known gaps: - -- Record construction needs a forward/incomplete or begin/complete story for - self-referential records. -- Records need to distinguish struct and union. -- Packed/aligned record and field layout must be expressible. -- Bitfields need either direct representation or an explicit frontend-lowered - representation. -- Signedness should stay out of storage type identity where possible. Keep it - on integer operations, comparisons, conversions, and ABI extension attrs. -- `va_list` should be expressible as a target-provided CG type without relying - on C `Type`. - -## Migration Phases - -### Phase 1: Add CgType Registry Behind CfreeCgTypeId - -Change the existing public type registry in `src/api/cg.c` so each -`CfreeCgTypeId` resolves to a canonical `CgType`. - -During this phase, keep the legacy C type pointer as a bridge: - -```c -typedef struct CgApiType { - CgType cg; - const Type* legacy_type; /* temporary migration bridge */ -} CgApiType; -``` - -Add internal helpers: - -```c -const CgType* cg_type_get(Compiler*, CfreeCgTypeId); -uint64_t cg_type_size(Compiler*, CfreeCgTypeId); -uint32_t cg_type_align(Compiler*, CfreeCgTypeId); -int cg_type_is_int(Compiler*, CfreeCgTypeId); -int cg_type_is_float(Compiler*, CfreeCgTypeId); -int cg_type_is_ptr(Compiler*, CfreeCgTypeId); -int cg_type_is_record(Compiler*, CfreeCgTypeId); +```sh +rg 'lang/c|type/type\.h|const Type\*|TypeKind|TY_' src include/abi ``` -Public query APIs such as `cfree_cg_type_size` and -`cfree_cg_type_record_field` should use `CgType`, not C `Type`. - -Deliverable: - -- No behavior change. -- Public tests still pass. -- Existing code may still use `legacy_type`, but new code should not add new - direct `Type*` usage. - -### Phase 2: Cache CgTypeId In The C Frontend Type - -Keep the C frontend `Type`, but make its lowered CG representation explicit. - -Add a cache field to `lang/c/type/type.h`: - -```c -CfreeCgTypeId cg_id; +finds no generic `src` dependency on C semantic types. C-specific files under +`lang/c` may still use `Type`. + +## Current Blockers + +These are the remaining dependency clusters to remove. + +1. **C compatibility shims in `src/`** + - `src/type/type.h` + - `src/decl/decl.h` + - `src/decl/decl_attrs.h` + - `src/lex/lex.h` + - `src/pp/pp.h` + - `src/parse/cg_public_compat.h` + - `src/api/pipeline.c -> lang/c/c.h` + +2. **ABI still exposes C `Type*` bridge APIs** + - `include/abi/abi.h` and `src/abi/abi.h` include `type/type.h`. + - `abi_type_info`, `abi_sizeof`, `abi_alignof`, `abi_record_layout`, and + `abi_func_info` still take `const Type*`. + - `abi_size_type`, `abi_ptrdiff_type`, `abi_intptr_type`, + `abi_uintptr_type`, and `abi_va_list_type` still manufacture C types. + - `src/abi/abi.c` still has C bridge classification/layout code. + +3. **Public CG implementation still stores C `Type*` internally** + - `src/api/cg.c` keeps `CgApiType.type`, `resolve_type`, + `cg_api_type_import`, `cg_api_type_resolve`, stack value types, slot type + tables, symbol type tables, function return types, and bridge helpers. + - It builds legacy C `Type*` values when public CG type constructors are + called. + +4. **`CGTarget` and arch lowering still use C type identity** + - `src/arch/arch.h` forward-declares `Type` and uses `const Type*` in + `FrameSlotDesc`, `MemAccess`, `ConstBytes`, `AggregateAccess`, + `BitFieldAccess`, `Operand`, `CGABIValue`, `CGParamDesc`, `CGFuncDesc`, + `CGCallDesc`, `CGScopeDesc`, `AsmConstraint`, `alloc_reg`, and + `va_arg_`. + - Arch internals include `type/type.h` and use helpers such as + `type_is_64`, `type_is_fp_double`, `type_byte_size`, and + `type_is_signed`. + +5. **Optimizer IR stores C `Type*`** + - `src/opt/ir.h`, `src/opt/ir.c`, `src/opt/opt.c`, + `src/opt/pass_lower.c`. + - `Func.val_type`, instruction result types, frame slots, call metadata, + and `IR_VA_ARG` aux data are still `const Type*`. + +6. **Generic debug has the C debug adapter in `src`** + - `src/debug/c_debug.c` and `src/debug/c_debug.h` walk `Type*`. + - Generic debug comments and APIs still refer to C `Type*` caches. + +7. **Emu stubs still synthesize C `Type*`** + - `src/emu/emu.h` exposes `emu_cpu_type` and `emu_block_fn_type` as + `const Type*`. + - `src/emu/cpu.c` constructs CPU/block types through C type constructors. + +8. **Core pool still has a C type hook** + - `src/core/pool.h` forward-declares `Type`. + - `pool_type` exists only for the old C type interning shape and should move + to `lang/c` or disappear. + +## Removal Order + +Do this in order; each step should keep `make lib`, `make bin`, and +`make test-cg-api` green. Run parse/link tests when touching frontend or ABI +behavior. + +1. **Make C lowering own the `Type* -> CfreeCgTypeId` cache** + - Add a cache field or map in `lang/c`. + - Ensure all C parser/codegen adapters call public CG constructors once per + C type. + - Add public CG record forward/begin/complete support before removing the + recursive-record placeholder bridge. + +2. **Finish `src/api/cg.c` migration** + - Replace all stored `const Type*` with `CfreeCgTypeId` or `CgType` facts. + - Remove legacy C type construction from public CG constructors. + - Keep any unavoidable bridge in tiny, named functions until step 8. + +3. **Make ABI purely CG-typed** + - Rename or replace the `abi_cg_*` APIs as the only ABI layout/classification + APIs. + - Delete C `Type*` ABI APIs and C bridge classification/layout code from + `src/abi`. + - Replace target library type helpers with CG type IDs or move C spellings + of `size_t`, `ptrdiff_t`, `intptr_t`, `uintptr_t`, and `va_list` to + `lang/c`. + - Remove `type/type.h` from `include/abi/abi.h` and `src/abi/abi.h`. + +4. **Make `CGTarget` language-neutral** + - Change target-facing descriptors in `src/arch/arch.h` from `Type*` to + `CfreeCgTypeId` or explicit facts: size, align, reg class, integer width, + float width, pointer/address-space, signedness where operation-specific. + - Replace arch helper reads of C types with CG helpers or operation flags. + - Remove `type/type.h` includes from `src/arch/**`. + +5. **Move optimizer IR off C types** + - Replace IR value/frame/instruction type fields with `CfreeCgTypeId` or + compact derived facts. + - Replace `IR_VA_ARG` `Type*` aux with a CG type handle. + - Remove `type/type.h` from `src/opt/**`. + +6. **Move C debug lowering out of generic debug** + - Move `src/debug/c_debug.*` to `lang/c/debug` or another C frontend adapter. + - Generic debug should consume frontend-provided `DebugTypeId` values, not + inspect C `Type`. + - Remove C type cache language from generic `src/debug` docs/comments. + +7. **Update emu stubs** + - Replace `emu_cpu_type` / `emu_block_fn_type` with CG type IDs or explicit + layout records. + - Build CPU state and block signatures through public CG constructors. + - Remove `type/type.h` from `src/emu/**`. + +8. **Move pool/type interning ownership to `lang/c`** + - Delete `pool_type` from `src/core/pool.*` or move the C-specific type + interning helper under `lang/c/type`. + - Remove the `Type` forward declaration from `src/core/pool.h`. + +9. **Delete compatibility shims and register C like Toy** + - Delete `src/type`, `src/decl`, `src/lex`, `src/pp`, and + `src/parse/cg_public_compat.h` once no `src` file includes them. + - Remove `src/api/pipeline.c`'s direct `lang/c/c.h` include and hardcoded C + branch. + - Register C through the frontend mechanism used by Toy. + +## Do Not Regress + +- Do not put C-only facts into `CgType`. +- Signedness should live on operations, comparisons, conversions, ABI attrs, or + explicit lowering metadata, not storage type identity. +- Object emission must remain byte/section/symbol/reloc based. +- Keep frontend-specific debug/type lowering outside generic `src`. + +## Useful Checks + +```sh +make lib +make bin +make test-cg-api +rg 'lang/c|type/type\.h|const Type\*|TypeKind|TY_' src include/abi +rg 'cg_api_type_import|cg_api_type_resolve|cfree_cg_internal_.*type' src ``` - -or, if mutability of interned `Type` is undesirable, add a frontend-side -map from `Type*` to `CfreeCgTypeId`. - -Update `type_cg_id(CfreeCompiler*, const Type*)` so it constructs through the -public CG API once and returns the cached id thereafter. - -Recursive records need special handling. Prefer a real public -begin/complete/forward record API over the current placeholder behavior. - -Deliverable: - -- `lang/c` lowers to public CG type constructors. -- `src` still builds with the legacy bridge. - -### Phase 3: Migrate src/api/cg.c State To CgType - -Replace stored `const Type*` in CG state with `CfreeCgTypeId` or -`const CgType*`. - -High-priority structures: - -- stack values -- operands -- slots -- symbol type table -- function return type -- memory access descriptors -- conversion helpers -- call descriptors -- intrinsic and atomic lowering helpers - -Temporary bridge calls into old ABI/arch code are acceptable, but they should -be centralized. Do not continue storing C `Type*` in CG state. - -Deliverable: - -- `src/api/cg.c` mostly reasons in `CfreeCgTypeId`/`CgType`. -- Remaining `Type*` use is isolated to bridge functions. - -### Phase 4: Migrate ABI To CgType - -Change ABI APIs from C `Type*` to CG type handles. - -Current APIs to migrate: - -```c -ABITypeInfo abi_type_info(TargetABI*, const Type*); -u32 abi_sizeof(TargetABI*, const Type*); -u32 abi_alignof(TargetABI*, const Type*); -const ABIRecordLayout* abi_record_layout(TargetABI*, const Type*); -const ABIFuncInfo* abi_func_info(TargetABI*, const Type* fn_type); -``` - -Target APIs should look more like: - -```c -ABITypeInfo abi_type_info(TargetABI*, CfreeCgTypeId); -u32 abi_sizeof(TargetABI*, CfreeCgTypeId); -u32 abi_alignof(TargetABI*, CfreeCgTypeId); -const ABIRecordLayout* abi_record_layout(TargetABI*, CfreeCgTypeId); -const ABIFuncInfo* abi_func_info(TargetABI*, CfreeCgTypeId fn_type); -``` - -Record layout should be cached by `CfreeCgTypeId`, not `Type*`. - -Deliverable: - -- ABI no longer includes `type/type.h`. -- ABI classification uses only `CgType` and target facts. - -### Phase 5: Migrate CGTarget And Arch Lowering - -Change target-facing structs in `src/arch/arch.h` from `const Type*` to -`CfreeCgTypeId` or derived `CgType` facts. - -Most arch code only needs: - -- byte size -- alignment -- integer width -- float width -- pointer vs integer vs float -- register class -- signedness for specific signed operations - -Replace helpers such as: - -```c -type_is_64(t) -type_is_fp_double(t) -type_byte_size(t) -type_is_signed(t) -``` - -with CG-type helpers. Where signedness is operation-specific, pass it through -operation metadata instead of reading it from type identity. - -Deliverable: - -- arch code does not include `type/type.h` -- `CGTarget` is language-neutral - -### Phase 6: Migrate Optimizer And Debug Bridges - -Optimizer IR currently stores `const Type*` in several places. Move it to -`CfreeCgTypeId` or `CgType` facts. - -Debug should remain a separate concern: - -- Codegen debug emission can consume frontend-provided debug type IDs. -- C-specific debug lowering from C `Type` should live in `lang/c` or an - explicit C debug adapter, not in generic `src` code. - -Deliverable: - -- optimizer no longer includes C type headers -- generic debug producer does not depend on C type - -### Phase 7: Remove Legacy C Type Bridges - -After CG, ABI, arch, opt, and generic debug no longer need C `Type*`, remove: - -- `legacy_type` from `CgApiType` -- `cg_api_type_import` -- `cg_api_type_resolve` -- `cfree_cg_internal_*_type` -- `src/type/type.h` shim -- `src/decl/*` shims if no longer needed - -Deliverable: - -- `src` no longer depends on `lang/c/type`. -- `lang/c` is the only owner of C semantic types. - -### Phase 8: Make C A Registered Frontend - -Once `src` no longer needs C headers, make C follow the same pattern as Toy. - -Current special case: - -- `src/api/pipeline.c` directly includes `../../lang/c/c.h` -- `compile_into` has a hardcoded `CFREE_LANG_C` branch - -Target: - -- `cfree_c_compile` is registered through the same frontend mechanism as Toy. -- `src/api/pipeline.c` only calls `c->frontends[input->lang]` for language - compilation. -- Eventually replace enum-indexed registration with string registration. - -ASM can remain a builtin path temporarily, or be moved to its own registered -frontend in a later cleanup. - -Deliverable: - -- no `src -> lang/c` include -- C frontend is linked/registered the same way as Toy - -## Suggested PR Sequence - -1. Add `CgType` and registry helpers; keep legacy `Type*`. -2. Add C frontend `Type -> CfreeCgTypeId` cache. -3. Convert public type query APIs to read `CgType`. -4. Convert `src/api/cg.c` stack/operand/slot/symbol state to CG types. -5. Convert ABI to CG types. -6. Convert `CGTarget` and arch lowering to CG types. -7. Convert optimizer/debug generic paths. -8. Delete legacy bridges and shim headers. -9. Register C like Toy and remove direct `src/api/pipeline.c -> lang/c/c.h`. - -Each PR should keep `make lib` and `CFREE_TEST_ALLOW_SKIP=1 make test-parse` -green. Broader `test-cg` and arch-specific tests should be run around phases -4 through 6. - -## Non-Goals - -- Do not remove the C frontend `Type` early. It is still needed for C language - semantics. -- Do not move C-only rules into `CgType`. -- Do not make `ObjBuilder` understand type systems. -- Do not make `CGTarget` inspect language-specific types. - -## Completion Criteria - -- No `src` file includes `lang/c` headers directly or indirectly. -- No `src` file includes `type/type.h` as a C semantic type. -- Public CG type constructors create all codegen-visible type facts. -- ABI, arch, CG, and optimizer operate on `CgType`/`CfreeCgTypeId`. -- C and Toy are both registered language frontends. -- C-specific lexer, preprocessor, parser, decl, and type code live under - `lang/c` only. diff --git a/include/abi/abi.h b/include/abi/abi.h @@ -1,6 +1,8 @@ #ifndef CFREE_ABI_H #define CFREE_ABI_H +#include <cfree/cg.h> + #include "core/core.h" #include "type/type.h" @@ -115,7 +117,14 @@ void abi_fini(TargetABI*); TargetABI* abi_new(Compiler*); void abi_free(TargetABI*); -/* Builtin scalar profiles and general type layout. */ +/* Builtin scalar profiles and general type layout. New code should enter + * through CfreeCgTypeId; Type* overloads are the temporary C frontend bridge. */ +ABITypeInfo abi_cg_type_info(TargetABI*, CfreeCgTypeId); +u32 abi_cg_sizeof(TargetABI*, CfreeCgTypeId); +u32 abi_cg_alignof(TargetABI*, CfreeCgTypeId); +const ABIRecordLayout* abi_cg_record_layout(TargetABI*, CfreeCgTypeId); +const ABIFuncInfo* abi_cg_func_info(TargetABI*, CfreeCgTypeId fn_type); + ABITypeInfo abi_type_info(TargetABI*, const Type*); u32 abi_sizeof(TargetABI*, const Type*); u32 abi_alignof(TargetABI*, const Type*); diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -17,6 +17,8 @@ #include <string.h> #include "abi/abi_internal.h" +#include "api/cg_api.h" +#include "api/cg_type.h" #include "core/arena.h" #include "core/core.h" #include "core/pool.h" @@ -133,50 +135,80 @@ static ABITypeInfo prim_info(TargetABI* a, TypeKind k) { } } -ABITypeInfo abi_type_info(TargetABI* a, const Type* t) { +static ABITypeInfo abi_c_type_info_no_bridge(TargetABI* a, const Type* t); + +ABITypeInfo abi_cg_type_info(TargetABI* a, CfreeCgTypeId id) { ABITypeInfo r = {0, 0, ABI_SC_VOID, 0, 0, 0}; + const CgType* t; + if (!id) return r; + t = cg_type_get(a->c, id); if (!t) return r; switch (t->kind) { - case TY_PTR: + case CFREE_CG_TYPE_ALIAS: + return abi_cg_type_info(a, t->alias.base); + case CFREE_CG_TYPE_PTR: r.size = a->c->target.ptr_size ? a->c->target.ptr_size : 8; r.align = a->c->target.ptr_align ? a->c->target.ptr_align : 8; r.scalar_kind = ABI_SC_PTR; return r; - case TY_ARRAY: { - ABITypeInfo e = abi_type_info(a, t->arr.elem); - r.size = e.size * t->arr.count; + case CFREE_CG_TYPE_ARRAY: { + ABITypeInfo e = abi_cg_type_info(a, t->array.elem); + r.size = e.size * t->array.count; r.align = e.align; return r; } - case TY_STRUCT: - case TY_UNION: { - const ABIRecordLayout* L = abi_record_layout(a, t); + case CFREE_CG_TYPE_RECORD: { + const ABIRecordLayout* L = abi_cg_record_layout(a, id); if (L) { r.size = L->size; r.align = L->align; } return r; } - case TY_ENUM: - return abi_type_info( - a, t->enm.base ? t->enm.base : type_prim(a->c->global, TY_INT)); - case TY_FUNC: + case CFREE_CG_TYPE_ENUM: + return abi_cg_type_info(a, t->enum_.base); + case CFREE_CG_TYPE_FUNC: /* sizeof(function) is undefined in C; use 1 for arithmetic. */ r.size = 1; r.align = 1; return r; + case CFREE_CG_TYPE_VOID: + r.align = 1; + r.scalar_kind = ABI_SC_VOID; + return r; + case CFREE_CG_TYPE_BOOL: + r.size = t->size; + r.align = t->align; + r.scalar_kind = ABI_SC_BOOL; + return r; + case CFREE_CG_TYPE_INT: + r.size = t->size; + r.align = t->align; + r.scalar_kind = ABI_SC_INT; + return r; + case CFREE_CG_TYPE_FLOAT: + r.size = t->size; + r.align = t->align; + r.scalar_kind = ABI_SC_FLOAT; + return r; + case CFREE_CG_TYPE_VARARG_STATE: + r.size = t->size; + r.align = t->align; + return r; default: - return prim_info(a, (TypeKind)t->kind); + return r; } } -ABITypeInfo abi_internal_type_info(TargetABI* a, const Type* t) { - return abi_type_info(a, t); +ABITypeInfo abi_internal_type_info(TargetABI* a, CfreeCgTypeId id) { + return abi_cg_type_info(a, id); } -u32 abi_sizeof(TargetABI* a, const Type* t) { return abi_type_info(a, t).size; } -u32 abi_alignof(TargetABI* a, const Type* t) { - return abi_type_info(a, t).align; +u32 abi_cg_sizeof(TargetABI* a, CfreeCgTypeId id) { + return abi_cg_type_info(a, id).size; +} +u32 abi_cg_alignof(TargetABI* a, CfreeCgTypeId id) { + return abi_cg_type_info(a, id).align; } /* ---- record layout (struct/union) ---- @@ -185,23 +217,25 @@ u32 abi_alignof(TargetABI* a, const Type* t) { * natural alignment, no bitfield packing extensions. When a Windows-x64 * (MSVC bitfield rules) ABI lands, promote this into the vtable. */ -static ABIRecordLayout* compute_record_layout(TargetABI* a, const Type* t) { +static ABIRecordLayout* compute_record_layout(TargetABI* a, CfreeCgTypeId id) { ABIRecordLayout* L = arena_new(a->c->tu, ABIRecordLayout); + const CgType* t = cg_type_get(a->c, id); if (!L) return NULL; + if (!t || t->kind != CFREE_CG_TYPE_RECORD) return NULL; memset(L, 0, sizeof *L); ABIFieldLayout* fl = NULL; - if (t->rec.nfields) { - fl = arena_array(a->c->tu, ABIFieldLayout, t->rec.nfields); - memset(fl, 0, sizeof(ABIFieldLayout) * t->rec.nfields); + if (t->record.nfields) { + fl = arena_array(a->c->tu, ABIFieldLayout, t->record.nfields); + memset(fl, 0, sizeof(ABIFieldLayout) * t->record.nfields); } u32 max_align = 1; - if (t->kind == TY_STRUCT) { + if (!t->record.is_union) { u32 off = 0; - for (u16 i = 0; i < t->rec.nfields; ++i) { - const Field* f = &t->rec.fields[i]; - ABITypeInfo fi = abi_type_info(a, f->type); - if (t->rec.packed) fi.align = 1; + for (u32 i = 0; i < t->record.nfields; ++i) { + const CgTypeField* f = &t->record.fields[i]; + ABITypeInfo fi = abi_cg_type_info(a, f->type); + if (f->align_override == 1) fi.align = 1; if (f->align_override > fi.align) fi.align = f->align_override; if (fi.align > max_align) max_align = fi.align; u32 mask = fi.align ? fi.align - 1 : 0; @@ -216,10 +250,10 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, const Type* t) { L->size = (off + mask) & ~mask; } else { /* TY_UNION */ u32 mx = 0; - for (u16 i = 0; i < t->rec.nfields; ++i) { - const Field* f = &t->rec.fields[i]; - ABITypeInfo fi = abi_type_info(a, f->type); - if (t->rec.packed) fi.align = 1; + for (u32 i = 0; i < t->record.nfields; ++i) { + const CgTypeField* f = &t->record.fields[i]; + ABITypeInfo fi = abi_cg_type_info(a, f->type); + if (f->align_override == 1) fi.align = 1; if (f->align_override > fi.align) fi.align = f->align_override; if (fi.align > max_align) max_align = fi.align; if (fi.size > mx) mx = fi.size; @@ -230,25 +264,26 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, const Type* t) { L->size = (mx + mask) & ~mask; } L->align = max_align; - if (t->rec.align_override > L->align) { - L->align = t->rec.align_override; + if (t->record.align_override > L->align) { + L->align = t->record.align_override; u32 mask = L->align - 1; L->size = (L->size + mask) & ~mask; } - L->nfields = t->rec.nfields; + L->nfields = t->record.nfields; L->fields = fl; return L; } -const ABIRecordLayout* abi_record_layout(TargetABI* a, const Type* t) { - if (!t || (t->kind != TY_STRUCT && t->kind != TY_UNION)) return NULL; +const ABIRecordLayout* abi_cg_record_layout(TargetABI* a, CfreeCgTypeId id) { + const CgType* t = cg_type_get(a->c, id); + if (!t || t->kind != CFREE_CG_TYPE_RECORD) return NULL; for (RecordLayoutCacheEntry* e = a->rec_cache; e; e = e->next) { - if (e->ty == t) return e->layout; + if (e->ty == id) return e->layout; } - ABIRecordLayout* L = compute_record_layout(a, t); + ABIRecordLayout* L = compute_record_layout(a, id); if (!L) return NULL; RecordLayoutCacheEntry* e = arena_new(a->c->tu, RecordLayoutCacheEntry); - e->ty = t; + e->ty = id; e->layout = L; e->next = a->rec_cache; a->rec_cache = e; @@ -257,8 +292,9 @@ const ABIRecordLayout* abi_record_layout(TargetABI* a, const Type* t) { /* ---- function classification (vtabled) ---- */ -const ABIFuncInfo* abi_func_info(TargetABI* a, const Type* fn_type) { - if (!fn_type || fn_type->kind != TY_FUNC) return NULL; +const ABIFuncInfo* abi_cg_func_info(TargetABI* a, CfreeCgTypeId fn_type) { + const CgType* fn = cg_type_get(a->c, fn_type); + if (!fn || fn->kind != CFREE_CG_TYPE_FUNC) return NULL; for (FuncInfoCacheEntry* e = a->fn_cache; e; e = e->next) { if (e->fn == fn_type) return e->info; } @@ -272,6 +308,227 @@ const ABIFuncInfo* abi_func_info(TargetABI* a, const Type* fn_type) { return info; } +static ABITypeInfo abi_c_type_info_no_bridge(TargetABI* a, const Type* t) { + ABITypeInfo r = {0, 0, ABI_SC_VOID, 0, 0, 0}; + if (!t) return r; + switch (t->kind) { + case TY_VOID: + case TY_BOOL: + case TY_CHAR: + case TY_SCHAR: + case TY_UCHAR: + case TY_SHORT: + case TY_USHORT: + case TY_INT: + case TY_UINT: + case TY_LONG: + case TY_ULONG: + case TY_LLONG: + case TY_ULLONG: + case TY_INT128: + case TY_UINT128: + case TY_FLOAT: + case TY_DOUBLE: + case TY_LDOUBLE: + return prim_info(a, (TypeKind)t->kind); + case TY_ENUM: + return abi_c_type_info_no_bridge( + a, t->enm.base ? t->enm.base : type_prim(a->c->global, TY_INT)); + default: + r = abi_cg_type_info(a, cg_api_type_import(a->c, t)); + switch (t->kind) { + case TY_CHAR: + case TY_SCHAR: + case TY_SHORT: + case TY_INT: + case TY_LONG: + case TY_LLONG: + case TY_INT128: + r.signed_ = 1; + break; + default: + r.signed_ = 0; + break; + } + return r; + } +} + +ABITypeInfo abi_type_info(TargetABI* a, const Type* t) { + return abi_c_type_info_no_bridge(a, t); +} + +u32 abi_sizeof(TargetABI* a, const Type* t) { return abi_type_info(a, t).size; } +u32 abi_alignof(TargetABI* a, const Type* t) { + return abi_type_info(a, t).align; +} + +const ABIRecordLayout* abi_record_layout(TargetABI* a, const Type* t) { + ABIRecordLayout* L; + ABIFieldLayout* fl = NULL; + u32 max_align = 1; + if (!t || (t->kind != TY_STRUCT && t->kind != TY_UNION)) return NULL; + L = arena_new(a->c->tu, ABIRecordLayout); + if (!L) return NULL; + memset(L, 0, sizeof *L); + if (t->rec.nfields) { + fl = arena_array(a->c->tu, ABIFieldLayout, t->rec.nfields); + memset(fl, 0, sizeof(ABIFieldLayout) * t->rec.nfields); + } + if (t->kind == TY_STRUCT) { + u32 off = 0; + for (u16 i = 0; i < t->rec.nfields; ++i) { + const Field* f = &t->rec.fields[i]; + ABITypeInfo fi = abi_type_info(a, f->type); + if (t->rec.packed) fi.align = 1; + if (f->align_override > fi.align) fi.align = f->align_override; + if (fi.align > max_align) max_align = fi.align; + u32 mask = fi.align ? fi.align - 1 : 0; + off = (off + mask) & ~mask; + fl[i].offset = off; + fl[i].storage_size = fi.size; + off += fi.size; + } + { + u32 mask = max_align - 1; + L->size = (off + mask) & ~mask; + } + } else { + u32 mx = 0; + for (u16 i = 0; i < t->rec.nfields; ++i) { + const Field* f = &t->rec.fields[i]; + ABITypeInfo fi = abi_type_info(a, f->type); + if (t->rec.packed) fi.align = 1; + if (f->align_override > fi.align) fi.align = f->align_override; + if (fi.align > max_align) max_align = fi.align; + if (fi.size > mx) mx = fi.size; + fl[i].offset = 0; + fl[i].storage_size = fi.size; + } + { + u32 mask = max_align - 1; + L->size = (mx + mask) & ~mask; + } + } + L->align = max_align; + if (t->rec.align_override > L->align) { + L->align = t->rec.align_override; + u32 mask = L->align - 1; + L->size = (L->size + mask) & ~mask; + } + L->nfields = t->rec.nfields; + L->fields = fl; + return L; +} + +static void classify_type_void(ABIArgInfo* out) { + memset(out, 0, sizeof *out); + out->kind = ABI_ARG_IGNORE; +} + +static void classify_type_scalar(TargetABI* a, const Type* t, + ABIArgInfo* out) { + ABITypeInfo ti = abi_type_info(a, t); + ABIArgPart* parts = arena_new(a->c->tu, ABIArgPart); + memset(out, 0, sizeof *out); + memset(parts, 0, sizeof *parts); + out->kind = ABI_ARG_DIRECT; + out->parts = parts; + out->nparts = 1; + parts->cls = (ti.scalar_kind == ABI_SC_FLOAT) ? ABI_CLASS_FP : ABI_CLASS_INT; + parts->loc = ABI_LOC_REG; + parts->size = ti.size; + parts->align = ti.align; +} + +static void classify_type_aggregate(TargetABI* a, const Type* t, + ABIArgInfo* out, int is_return) { + ABITypeInfo ti = abi_type_info(a, t); + memset(out, 0, sizeof *out); + if (ti.size == 0) { + classify_type_void(out); + return; + } + if (ti.size <= 16) { + u32 nparts = (ti.size + 7) / 8; + ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, nparts); + memset(parts, 0, sizeof(ABIArgPart) * nparts); + u32 off = 0; + for (u32 i = 0; i < nparts; ++i) { + u32 chunk = (ti.size - off > 8) ? 8 : (ti.size - off); + parts[i].cls = ABI_CLASS_INT; + parts[i].loc = ABI_LOC_REG; + parts[i].size = chunk; + parts[i].align = 8; + parts[i].src_offset = off; + off += chunk; + } + out->kind = ABI_ARG_DIRECT; + out->parts = parts; + out->nparts = (u16)nparts; + } else { + out->kind = ABI_ARG_INDIRECT; + out->flags = is_return ? ABI_AF_SRET : ABI_AF_BYVAL; + out->indirect_align = ti.align ? ti.align : 8; + } +} + +static void classify_type_one(TargetABI* a, const Type* t, ABIArgInfo* out, + int is_return) { + if (!t || t->kind == TY_VOID) { + classify_type_void(out); + return; + } + switch (t->kind) { + case TY_STRUCT: + case TY_UNION: + classify_type_aggregate(a, t, out, is_return); + return; + default: + classify_type_scalar(a, t, out); + return; + } +} + +static ABIFuncInfo* compute_type_func_info(TargetABI* a, const Type* fn_type) { + ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); + memset(info, 0, sizeof *info); + classify_type_one(a, fn_type->fn.ret, &info->ret, 1); + info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; + info->variadic = fn_type->fn.variadic; + info->nparams = fn_type->fn.nparams; + if (a->c->target.arch == CFREE_ARCH_ARM_64 && + a->c->target.os == CFREE_OS_MACOS) { + info->vararg_on_stack = 1; + } + if (fn_type->fn.nparams) { + ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fn_type->fn.nparams); + memset(arr, 0, sizeof(ABIArgInfo) * fn_type->fn.nparams); + for (u16 i = 0; i < fn_type->fn.nparams; ++i) { + classify_type_one(a, fn_type->fn.params[i], &arr[i], 0); + } + info->params = arr; + } + return info; +} + +const ABIFuncInfo* abi_func_info(TargetABI* a, const Type* fn_type) { + CfreeCgTypeId id; + if (!fn_type || fn_type->kind != TY_FUNC) return NULL; + id = cg_api_type_import(a->c, fn_type); + for (FuncInfoCacheEntry* e = a->fn_cache; e; e = e->next) { + if (e->fn == id) return e->info; + } + ABIFuncInfo* info = compute_type_func_info(a, fn_type); + if (!info) return NULL; + FuncInfoCacheEntry* e = arena_new(a->c->tu, FuncInfoCacheEntry); + e->fn = id; + e->info = info; + e->next = a->fn_cache; + a->fn_cache = e; + return info; +} + /* ---- target-defined library types ---- */ static const Type* size_or_uintptr(TargetABI* a, Pool* p) { diff --git a/src/abi/abi.h b/src/abi/abi.h @@ -1,6 +1,8 @@ #ifndef CFREE_ABI_H #define CFREE_ABI_H +#include <cfree/cg.h> + #include "core/core.h" #include "type/type.h" @@ -115,7 +117,14 @@ void abi_fini(TargetABI*); TargetABI* abi_new(Compiler*); void abi_free(TargetABI*); -/* Builtin scalar profiles and general type layout. */ +/* Builtin scalar profiles and general type layout. New code should enter + * through CfreeCgTypeId; Type* overloads are the temporary C frontend bridge. */ +ABITypeInfo abi_cg_type_info(TargetABI*, CfreeCgTypeId); +u32 abi_cg_sizeof(TargetABI*, CfreeCgTypeId); +u32 abi_cg_alignof(TargetABI*, CfreeCgTypeId); +const ABIRecordLayout* abi_cg_record_layout(TargetABI*, CfreeCgTypeId); +const ABIFuncInfo* abi_cg_func_info(TargetABI*, CfreeCgTypeId fn_type); + ABITypeInfo abi_type_info(TargetABI*, const Type*); u32 abi_sizeof(TargetABI*, const Type*); u32 abi_alignof(TargetABI*, const Type*); diff --git a/src/abi/abi_aapcs64.c b/src/abi/abi_aapcs64.c @@ -14,11 +14,12 @@ #include <string.h> #include "abi/abi_internal.h" +#include "api/cg_type.h" #include "core/arena.h" #include "core/core.h" #include "core/pool.h" -static void classify_scalar(TargetABI* a, const Type* t, ABIArgInfo* out) { +static void classify_scalar(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out) { ABITypeInfo ti = abi_internal_type_info(a, t); out->kind = ABI_ARG_DIRECT; out->flags = ABI_AF_NONE; @@ -41,7 +42,7 @@ static void classify_void(ABIArgInfo* out) { out->kind = ABI_ARG_IGNORE; } -static void classify_aggregate(TargetABI* a, const Type* t, ABIArgInfo* out, +static void classify_aggregate(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, int is_return) { ABITypeInfo ti = abi_internal_type_info(a, t); if (ti.size == 0) { @@ -79,17 +80,20 @@ static void classify_aggregate(TargetABI* a, const Type* t, ABIArgInfo* out, } } -static void classify_one(TargetABI* a, const Type* t, ABIArgInfo* out, +static void classify_one(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, int is_return) { - if (!t || t->kind == TY_VOID) { + const CgType* ty = cg_type_get(a->c, t); + if (!ty || ty->kind == CFREE_CG_TYPE_VOID) { classify_void(out); return; } - switch (t->kind) { - case TY_STRUCT: - case TY_UNION: + switch (ty->kind) { + case CFREE_CG_TYPE_RECORD: classify_aggregate(a, t, out, is_return); return; + case CFREE_CG_TYPE_ALIAS: + classify_one(a, ty->alias.base, out, is_return); + return; default: classify_scalar(a, t, out); return; @@ -98,20 +102,21 @@ static void classify_one(TargetABI* a, const Type* t, ABIArgInfo* out, /* Non-static so apple_arm64_compute_func_info can delegate to it during * the Phase 1 alias period — see abi_apple_arm64.c. */ -ABIFuncInfo* aapcs64_compute_func_info(TargetABI* a, const Type* fn) { +ABIFuncInfo* aapcs64_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); + const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fn->fn.ret, &info->ret, /*is_return=*/1); + classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; - info->variadic = fn->fn.variadic; + info->variadic = fnty->func.abi_variadic; - info->nparams = fn->fn.nparams; - if (fn->fn.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fn->fn.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fn->fn.nparams); - for (u16 i = 0; i < fn->fn.nparams; ++i) { - classify_one(a, fn->fn.params[i], &arr[i], /*is_return=*/0); + info->nparams = (u16)fnty->func.nparams; + if (fnty->func.nparams) { + ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); + memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); + for (u32 i = 0; i < fnty->func.nparams; ++i) { + classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); } info->params = arr; } else { diff --git a/src/abi/abi_apple_arm64.c b/src/abi/abi_apple_arm64.c @@ -29,10 +29,10 @@ #include "core/pool.h" #include "type/type.h" -extern ABIFuncInfo* aapcs64_compute_func_info(TargetABI*, const Type*); +extern ABIFuncInfo* aapcs64_compute_func_info(TargetABI*, CfreeCgTypeId); static ABIFuncInfo* apple_arm64_compute_func_info(TargetABI* a, - const Type* fn) { + CfreeCgTypeId fn) { /* Phase 2: spell out the Darwin variadic / stack-arg-promotion * deltas. For now the AAPCS64 classifier produces ABI-correct * output for the fixed-args-only programs in the v1 cg suite, diff --git a/src/abi/abi_internal.h b/src/abi/abi_internal.h @@ -11,8 +11,8 @@ typedef struct ABIVtable { /* Compute the ABIFuncInfo for a function type. The cache wrapper in - * abi.c calls this once per Type and memoizes the result. */ - ABIFuncInfo* (*compute_func_info)(TargetABI*, const Type* fn); + * abi.c calls this once per CgTypeId and memoizes the result. */ + ABIFuncInfo* (*compute_func_info)(TargetABI*, CfreeCgTypeId fn); /* Build the per-ABI __va_list type. The wrapper in abi.c memoizes. */ const Type* (*va_list_type)(TargetABI*, Pool*); } ABIVtable; @@ -33,13 +33,13 @@ typedef struct FuncInfoCacheEntry FuncInfoCacheEntry; typedef struct RecordLayoutCacheEntry RecordLayoutCacheEntry; struct FuncInfoCacheEntry { - const Type* fn; + CfreeCgTypeId fn; ABIFuncInfo* info; FuncInfoCacheEntry* next; }; struct RecordLayoutCacheEntry { - const Type* ty; + CfreeCgTypeId ty; ABIRecordLayout* layout; RecordLayoutCacheEntry* next; }; @@ -53,6 +53,6 @@ struct TargetABI { }; /* Shared helpers exposed to per-ABI TUs. */ -ABITypeInfo abi_internal_type_info(TargetABI*, const Type*); +ABITypeInfo abi_internal_type_info(TargetABI*, CfreeCgTypeId); #endif diff --git a/src/abi/abi_rv64.c b/src/abi/abi_rv64.c @@ -14,11 +14,12 @@ #include <string.h> #include "abi/abi_internal.h" +#include "api/cg_type.h" #include "core/arena.h" #include "core/core.h" #include "core/pool.h" -static void classify_scalar(TargetABI* a, const Type* t, ABIArgInfo* out) { +static void classify_scalar(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out) { ABITypeInfo ti = abi_internal_type_info(a, t); out->kind = ABI_ARG_DIRECT; out->flags = ABI_AF_NONE; @@ -41,7 +42,7 @@ static void classify_void(ABIArgInfo* out) { out->kind = ABI_ARG_IGNORE; } -static void classify_aggregate(TargetABI* a, const Type* t, ABIArgInfo* out, +static void classify_aggregate(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, int is_return) { ABITypeInfo ti = abi_internal_type_info(a, t); if (ti.size == 0) { @@ -76,37 +77,41 @@ static void classify_aggregate(TargetABI* a, const Type* t, ABIArgInfo* out, } } -static void classify_one(TargetABI* a, const Type* t, ABIArgInfo* out, +static void classify_one(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, int is_return) { - if (!t || t->kind == TY_VOID) { + const CgType* ty = cg_type_get(a->c, t); + if (!ty || ty->kind == CFREE_CG_TYPE_VOID) { classify_void(out); return; } - switch (t->kind) { - case TY_STRUCT: - case TY_UNION: + switch (ty->kind) { + case CFREE_CG_TYPE_RECORD: classify_aggregate(a, t, out, is_return); return; + case CFREE_CG_TYPE_ALIAS: + classify_one(a, ty->alias.base, out, is_return); + return; default: classify_scalar(a, t, out); return; } } -static ABIFuncInfo* rv64_compute_func_info(TargetABI* a, const Type* fn) { +static ABIFuncInfo* rv64_compute_func_info(TargetABI* a, CfreeCgTypeId fn) { ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); + const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fn->fn.ret, &info->ret, /*is_return=*/1); + classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; - info->variadic = fn->fn.variadic; + info->variadic = fnty->func.abi_variadic; - info->nparams = fn->fn.nparams; - if (fn->fn.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fn->fn.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fn->fn.nparams); - for (u16 i = 0; i < fn->fn.nparams; ++i) { - classify_one(a, fn->fn.params[i], &arr[i], /*is_return=*/0); + info->nparams = (u16)fnty->func.nparams; + if (fnty->func.nparams) { + ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); + memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); + for (u32 i = 0; i < fnty->func.nparams; ++i) { + classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); } info->params = arr; } else { diff --git a/src/abi/abi_sysv_x64.c b/src/abi/abi_sysv_x64.c @@ -16,6 +16,7 @@ #include <string.h> #include "abi/abi_internal.h" +#include "api/cg_type.h" #include "core/arena.h" #include "core/core.h" #include "core/pool.h" @@ -25,7 +26,7 @@ static void classify_void(ABIArgInfo* out) { out->kind = ABI_ARG_IGNORE; } -static void classify_scalar(TargetABI* a, const Type* t, ABIArgInfo* out) { +static void classify_scalar(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out) { ABITypeInfo ti = abi_internal_type_info(a, t); out->kind = ABI_ARG_DIRECT; out->flags = ABI_AF_NONE; @@ -43,7 +44,7 @@ static void classify_scalar(TargetABI* a, const Type* t, ABIArgInfo* out) { out->nparts = 1; } -static void classify_aggregate(TargetABI* a, const Type* t, ABIArgInfo* out, +static void classify_aggregate(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, int is_return) { ABITypeInfo ti = abi_internal_type_info(a, t); if (ti.size == 0) { @@ -78,37 +79,42 @@ static void classify_aggregate(TargetABI* a, const Type* t, ABIArgInfo* out, } } -static void classify_one(TargetABI* a, const Type* t, ABIArgInfo* out, +static void classify_one(TargetABI* a, CfreeCgTypeId t, ABIArgInfo* out, int is_return) { - if (!t || t->kind == TY_VOID) { + const CgType* ty = cg_type_get(a->c, t); + if (!ty || ty->kind == CFREE_CG_TYPE_VOID) { classify_void(out); return; } - switch (t->kind) { - case TY_STRUCT: - case TY_UNION: + switch (ty->kind) { + case CFREE_CG_TYPE_RECORD: classify_aggregate(a, t, out, is_return); return; + case CFREE_CG_TYPE_ALIAS: + classify_one(a, ty->alias.base, out, is_return); + return; default: classify_scalar(a, t, out); return; } } -static ABIFuncInfo* sysv_x64_compute_func_info(TargetABI* a, const Type* fn) { +static ABIFuncInfo* sysv_x64_compute_func_info(TargetABI* a, + CfreeCgTypeId fn) { ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); + const CgType* fnty = cg_type_get(a->c, fn); memset(info, 0, sizeof *info); - classify_one(a, fn->fn.ret, &info->ret, /*is_return=*/1); + classify_one(a, fnty->func.ret, &info->ret, /*is_return=*/1); info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; - info->variadic = fn->fn.variadic; + info->variadic = fnty->func.abi_variadic; - info->nparams = fn->fn.nparams; - if (fn->fn.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fn->fn.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fn->fn.nparams); - for (u16 i = 0; i < fn->fn.nparams; ++i) { - classify_one(a, fn->fn.params[i], &arr[i], /*is_return=*/0); + info->nparams = (u16)fnty->func.nparams; + if (fnty->func.nparams) { + ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); + memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); + for (u32 i = 0; i < fnty->func.nparams; ++i) { + classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); } info->params = arr; } else { diff --git a/src/api/cg.c b/src/api/cg.c @@ -6,6 +6,7 @@ #include "abi/abi.h" #include "api/cg_api.h" +#include "api/cg_type.h" #include "arch/arch.h" #include "core/arena.h" #include "core/heap.h" @@ -13,62 +14,6 @@ #include "obj/obj.h" #include "type/type.h" -typedef struct CgTypeField { - CfreeSym name; - CfreeCgTypeId type; - u64 offset; - u32 align_override; -} CgTypeField; - -typedef struct CgType { - CfreeCgTypeKind kind; - u64 size; - u32 align; - u32 pad; - union { - struct { - u32 width; - } integer; - struct { - u32 width; - } fp; - struct { - CfreeCgTypeId pointee; - u32 address_space; - } ptr; - struct { - CfreeCgTypeId elem; - u64 count; - } array; - struct { - CfreeCgTypeId ret; - CfreeCgParam* params; - u32 nparams; - CfreeCgCallConv call_conv; - int abi_variadic; - CfreeCgAbiAttrs ret_attrs; - } func; - struct { - CfreeSym tag; - CgTypeField* fields; - u32 nfields; - int is_union; - u32 align_override; - u32 flags; - } record; - struct { - CfreeSym tag; - CfreeCgTypeId base; - CfreeCgEnumValue* values; - u32 nvalues; - } enum_; - struct { - CfreeSym name; - CfreeCgTypeId base; - } alias; - }; -} CgType; - typedef enum CgApiTypeKind { CG_API_TYPE_PTR, CG_API_TYPE_ARRAY, @@ -1179,8 +1124,7 @@ void cg_api_fini(Compiler* c) { /* ============================================================ * CfreeCg: public codegen API implementation * - * Drives CGTarget directly with its own value stack, mirroring - * the internal CG in src/cg/cg.c but without depending on it. + * Drives CGTarget directly with its own value stack. * ============================================================ */ typedef enum SResidency { @@ -2137,8 +2081,8 @@ void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym) { sym = (ObjSymId)cg_sym; fty = api_sym_type(g, cg_sym); if (!fty) return; - abi = abi_func_info(c->abi, fty); attrs = api_sym_attrs(g, cg_sym); + abi = abi_func_info(c->abi, fty); text_sec = obj_section(ob, pool_intern_cstr(c->global, ".text"), SEC_TEXT, SF_EXEC | SF_ALLOC, 4); @@ -2193,8 +2137,8 @@ CfreeCgSlot cfree_cg_local_slot(CfreeCg* g, CfreeCgTypeId type, fsd.type = ty; fsd.name = (Sym)attrs.name; fsd.loc = g->cur_loc; - fsd.size = abi_sizeof(g->c->abi, ty); - fsd.align = attrs.align ? attrs.align : abi_alignof(g->c->abi, ty); + fsd.size = abi_cg_sizeof(g->c->abi, type); + fsd.align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, type); fsd.kind = FS_LOCAL; if (attrs.flags & CFREE_CG_SLOT_ADDRESS_TAKEN) fsd.flags |= FSF_ADDR_TAKEN; slot = g->target->frame_slot(g->target, &fsd); @@ -2216,8 +2160,8 @@ CfreeCgSlot cfree_cg_param_slot(CfreeCg* g, uint32_t index, CfreeCgTypeId type, fsd.type = ty; fsd.name = (Sym)attrs.name; fsd.loc = g->cur_loc; - fsd.size = abi_sizeof(g->c->abi, ty); - fsd.align = attrs.align ? attrs.align : abi_alignof(g->c->abi, ty); + fsd.size = abi_cg_sizeof(g->c->abi, type); + fsd.align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, type); fsd.kind = FS_PARAM; if (attrs.flags & CFREE_CG_SLOT_ADDRESS_TAKEN) fsd.flags |= FSF_ADDR_TAKEN; slot = g->target->frame_slot(g->target, &fsd); @@ -2265,8 +2209,8 @@ void cfree_cg_push_float(CfreeCg* g, double value, CfreeCgTypeId type) { if (!ty) return; T = g->target; cb.type = ty; - cb.size = (u32)abi_sizeof(g->c->abi, ty); - cb.align = (u32)abi_alignof(g->c->abi, ty); + cb.size = (u32)abi_cg_sizeof(g->c->abi, type); + cb.align = (u32)abi_cg_alignof(g->c->abi, type); if (ty->kind == TY_FLOAT) u.f = (float)value; else @@ -2305,8 +2249,9 @@ CfreeCgSym cfree_cg_const_data(CfreeCg* g, const uint8_t* data, size_t len, if (!pty) return CFREE_CG_SYM_NONE; sec_name = pool_intern_cstr(c->global, ".rodata"); sec = obj_section(ob, sec_name, SEC_RODATA, SF_ALLOC, - align ? align : (u32)abi_alignof(c->abi, pty)); - base = obj_align_to(ob, sec, align ? align : (u32)abi_alignof(c->abi, pty)); + align ? align : (u32)abi_cg_alignof(c->abi, pointee_type)); + base = obj_align_to( + ob, sec, align ? align : (u32)abi_cg_alignof(c->abi, pointee_type)); obj_write(ob, sec, data, len); snprintf(name_buf, sizeof(name_buf), ".Lcfree_ro.%u", g->rodata_counter++); anon_name = pool_intern_cstr(c->global, name_buf); @@ -2647,7 +2592,8 @@ static void api_cg_convert_kind(CfreeCg* g, CfreeCgTypeId dst_type, api_push(g, v); return; } - if (ck == CV_BITCAST && abi_sizeof(g->c->abi, sty) == abi_sizeof(g->c->abi, dty) && + if (ck == CV_BITCAST && + abi_sizeof(g->c->abi, sty) == abi_cg_sizeof(g->c->abi, dst_type) && api_type_class(sty) == api_type_class(dty)) { v.type = dty; v.op.type = dty; @@ -2757,8 +2703,8 @@ void cfree_cg_float_to_uint(CfreeCg* g, CfreeCgTypeId dst, * ============================================================ */ static IntrinKind api_map_intrinsic(CfreeCg* g, CfreeCgIntrinsic intrin, - const Type* result_type) { - u32 size = result_type ? abi_sizeof(g->c->abi, result_type) : 0; + CfreeCgTypeId result_type) { + u32 size = result_type ? abi_cg_sizeof(g->c->abi, result_type) : 0; switch (intrin) { case CFREE_CG_INTRIN_TRAP: return INTRIN_TRAP; @@ -2845,7 +2791,7 @@ void cfree_cg_intrinsic(CfreeCg* g, CfreeCgIntrinsic intrin, uint32_t nargs, h = g->c->env->heap; rty = resolve_type(g->c, result_type); int_ty = type_prim(g->c->global, TY_INT); - kind = api_map_intrinsic(g, intrin, rty); + kind = api_map_intrinsic(g, intrin, result_type); if (kind == INTRIN_NONE) { compiler_panic(g->c, g->cur_loc, "CfreeCg: unsupported intrinsic"); return; @@ -2917,8 +2863,10 @@ static MemAccess api_mem_for_atomic(CfreeCg* g, const Type* val_ty) { MemAccess ma; memset(&ma, 0, sizeof ma); ma.type = val_ty; - ma.size = val_ty ? abi_sizeof(g->c->abi, val_ty) : 0; - ma.align = val_ty ? abi_alignof(g->c->abi, val_ty) : 0; + ma.size = val_ty ? abi_cg_sizeof(g->c->abi, cg_api_type_import(g->c, val_ty)) + : 0; + ma.align = + val_ty ? abi_cg_alignof(g->c->abi, cg_api_type_import(g->c, val_ty)) : 0; ma.flags = MF_ATOMIC; ma.alias.kind = (u8)ALIAS_UNKNOWN; return ma; @@ -2929,13 +2877,13 @@ int cfree_cg_atomic_is_legal(CfreeCompiler* c, CfreeCgMemAccess access, const Type* ty = resolve_type(c, access.type); (void)order; if (!ty) return 0; - return abi_sizeof(c->abi, ty) <= 8; + return abi_cg_sizeof(c->abi, access.type) <= 8; } int cfree_cg_atomic_is_lock_free(CfreeCompiler* c, CfreeCgMemAccess access) { const Type* ty = resolve_type(c, access.type); if (!ty) return 0; - return abi_sizeof(c->abi, ty) <= (u32)c->target.ptr_size; + return abi_cg_sizeof(c->abi, access.type) <= (u32)c->target.ptr_size; } void cfree_cg_atomic_load(CfreeCg* g, CfreeCgMemAccess access, @@ -3569,8 +3517,8 @@ CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) { FrameSlotDesc fsd; memset(&fsd, 0, sizeof fsd); fsd.type = s->result_type; - fsd.size = abi_sizeof(g->c->abi, s->result_type); - fsd.align = abi_alignof(g->c->abi, s->result_type); + fsd.size = abi_cg_sizeof(g->c->abi, result_type); + fsd.align = abi_cg_alignof(g->c->abi, result_type); fsd.kind = FS_LOCAL; s->result_slot = g->target->frame_slot(g->target, &fsd); } @@ -3928,7 +3876,7 @@ void cfree_cg_index(CfreeCg* g, uint64_t offset) { "CfreeCg: index base is not a pointer or array lvalue"); return; } - elemsz = (u32)abi_sizeof(g->c->abi, elem_ty); + elemsz = (u32)abi_cg_sizeof(g->c->abi, cg_api_type_import(g->c, elem_ty)); idx_ty = idx.type ? idx.type : idx.op.type; if (!idx_ty) idx_ty = type_prim(g->c->global, TY_INT); if (base_ty && base_ty->kind == TY_ARRAY) { @@ -3982,7 +3930,7 @@ void cfree_cg_field(CfreeCg* g, uint32_t field_index) { compiler_panic(g->c, g->cur_loc, "CfreeCg: field base is not an lvalue"); return; } - layout = abi_record_layout(g->c->abi, rec_ty); + layout = abi_cg_record_layout(g->c->abi, cg_api_type_import(g->c, rec_ty)); if (!layout || field_index >= layout->nfields) { compiler_panic(g->c, g->cur_loc, "CfreeCg: invalid field index"); return; @@ -4361,7 +4309,7 @@ void cfree_cg_data_begin(CfreeCg* g, CfreeCgSym cg_sym, ty = api_sym_type(g, cg_sym); if (!ty) return; decl_attrs = api_sym_attrs(g, cg_sym); - align = attrs.align ? attrs.align : (u32)abi_alignof(c->abi, ty); + align = attrs.align ? attrs.align : (u32)abi_cg_alignof(c->abi, decl_attrs.type); if (!attrs.section && decl_attrs.as.object.section) { attrs.section = decl_attrs.as.object.section; } @@ -4408,7 +4356,7 @@ void cfree_cg_data_begin(CfreeCg* g, CfreeCgSym cg_sym, g->data_size = 0; if (sym != OBJ_SYM_NONE) { obj_symbol_define(ob, sym, sec, (u64)g->data_base, - (u64)abi_sizeof(c->abi, ty)); + (u64)abi_cg_sizeof(c->abi, decl_attrs.type)); } } @@ -4458,7 +4406,7 @@ void cfree_cg_data_int(CfreeCg* g, uint64_t value, CfreeCgTypeId type) { if (!g) return; ty = resolve_type(g->c, type); if (!ty) return; - size = (u32)abi_sizeof(g->c->abi, ty); + size = (u32)abi_cg_sizeof(g->c->abi, type); if (size > sizeof(bytes)) return; for (u32 i = 0; i < size; ++i) { u32 shift = g->c->target.big_endian ? (size - 1u - i) * 8u : i * 8u; diff --git a/src/api/cg_api.h b/src/api/cg_api.h @@ -3,12 +3,12 @@ #include <cfree/cg.h> +#include "api/cg_type.h" #include "core/core.h" #include "type/type.h" typedef struct CGTarget CGTarget; typedef struct MCEmitter MCEmitter; -typedef struct CgType CgType; typedef uint32_t ObjSymId; enum { @@ -21,13 +21,6 @@ enum { const Type* cg_api_type_resolve(Compiler*, CfreeCgTypeId); CfreeCgTypeId cg_api_type_import(Compiler*, const Type*); -const CgType* cg_type_get(Compiler*, CfreeCgTypeId); -uint64_t cg_type_size(Compiler*, CfreeCgTypeId); -uint32_t cg_type_align(Compiler*, CfreeCgTypeId); -int cg_type_is_int(Compiler*, CfreeCgTypeId); -int cg_type_is_float(Compiler*, CfreeCgTypeId); -int cg_type_is_ptr(Compiler*, CfreeCgTypeId); -int cg_type_is_record(Compiler*, CfreeCgTypeId); Compiler* cfree_cg_internal_compiler(CfreeCg*); CGTarget* cfree_cg_internal_target(CfreeCg*); MCEmitter* cfree_cg_internal_mc(CfreeCg*); diff --git a/src/api/cg_type.h b/src/api/cg_type.h @@ -0,0 +1,72 @@ +#ifndef CFREE_API_CG_TYPE_H +#define CFREE_API_CG_TYPE_H + +#include <cfree/cg.h> + +#include "core/core.h" + +typedef struct CgTypeField { + CfreeSym name; + CfreeCgTypeId type; + u64 offset; + u32 align_override; +} CgTypeField; + +typedef struct CgType { + CfreeCgTypeKind kind; + u64 size; + u32 align; + u32 pad; + union { + struct { + u32 width; + } integer; + struct { + u32 width; + } fp; + struct { + CfreeCgTypeId pointee; + u32 address_space; + } ptr; + struct { + CfreeCgTypeId elem; + u64 count; + } array; + struct { + CfreeCgTypeId ret; + CfreeCgParam* params; + u32 nparams; + CfreeCgCallConv call_conv; + int abi_variadic; + CfreeCgAbiAttrs ret_attrs; + } func; + struct { + CfreeSym tag; + CgTypeField* fields; + u32 nfields; + int is_union; + u32 align_override; + u32 flags; + } record; + struct { + CfreeSym tag; + CfreeCgTypeId base; + CfreeCgEnumValue* values; + u32 nvalues; + } enum_; + struct { + CfreeSym name; + CfreeCgTypeId base; + } alias; + }; +} CgType; + +const CgType* cg_type_get(Compiler*, CfreeCgTypeId); +uint64_t cg_type_size(Compiler*, CfreeCgTypeId); +uint32_t cg_type_align(Compiler*, CfreeCgTypeId); +int cg_type_is_int(Compiler*, CfreeCgTypeId); +int cg_type_is_float(Compiler*, CfreeCgTypeId); +int cg_type_is_ptr(Compiler*, CfreeCgTypeId); +int cg_type_is_record(Compiler*, CfreeCgTypeId); + +#endif diff --git a/src/api/stubs.c b/src/api/stubs.c @@ -34,8 +34,7 @@ static _Noreturn void unimplemented(Compiler* c, const char* what) { /* Preprocessor implementation lives in src/pp/pp.c. */ /* parse_c lives in src/parse/parse.c. parse_asm lives in - * src/parse/parse_asm.c. DeclTable lives in src/decl/decl.c. - * CG lives in src/cg/cg.c. */ + * src/parse/parse_asm.c. DeclTable lives in src/decl/decl.c. */ /* mc_new / mc_free live in src/arch/mc.c. * cgtarget_new / cgtarget_finalize / cgtarget_free live in src/arch/<target>.c diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -4,7 +4,8 @@ #include "abi/abi.h" #include "core/core.h" #include "obj/obj.h" -#include "type/type.h" + +typedef struct Type Type; /* Forward-declared so CGTarget can carry an optional Debug* without * pulling debug/debug.h into every translation unit that includes arch.h. diff --git a/src/cg/cg.c b/src/cg/cg.c @@ -1,1995 +0,0 @@ -/* Single-pass code generator with a TCC-style value stack. - * - * The parser pushes values (lvalues, immediates, register rvalues) and - * issues operations; cg materializes operands and dispatches to CGTarget. - * No AST. At -O0 the wrapped target backend is a real CGTarget; at -O1+ - * opt_cgtarget records the same calls into IR for cross-function passes. - * - * Value stack semantics: - * - SValue.op carries an Operand whose `kind` decides what the value is. - * - OPK_IMM / OPK_REG are rvalues (can be consumed by binop/cmp/store). - * - OPK_LOCAL / OPK_GLOBAL / OPK_INDIRECT are lvalues. cg_load promotes - * them to OPK_REG via target->load + a fresh scratch register. - * - * Register pressure & spill: - * - Each SValue carries an SResidency tag (INHERENT / REG / SPILLED). - * REG-residing SValues own a physical scratch register that must be - * released back to the pool when the value is consumed; SPILLED - * SValues own a frame slot instead and must be reloaded before use. - * - alloc_reg_or_spill is the single allocation entry point. On pool - * exhaustion it picks the deepest unpinned RES_REG SValue from the - * value stack as the spill victim, evicts its register through - * T->spill_reg, and retries. ensure_reg is the dual: it reloads a - * SPILLED SValue's register before consumption, possibly evicting - * another value to make room. - * - Pop sites (binop, cmp, store, branch, call, ...) call release() - * to return regs/slots after consumption — there is no statement- - * boundary scratch reset; ownership tracking is the discipline. - * - cg_call additionally exposes its in-flight CGABIValue array via - * CG.avs_in_flight so the spill driver can re-spill an already- - * materialized arg's register (rewriting storage to OPK_LOCAL) when - * the value stack runs out of victims; this lets calls with more - * reg-class args than the pool size can hold lower correctly. - * - * Some aggregate and backend-specific intrinsic cases are still limited by - * their corpus rows. The interface in cg.h is the commitment; this file fills - * in the slice that's exercised today. */ - -#include "cg/cg.h" - -#include <string.h> - -#include "abi/abi.h" -#include "arch/arch.h" -#include "cg/fold.h" -#include "core/arena.h" -#include "core/core.h" -#include "core/heap.h" -#include "core/pool.h" -#include "debug/debug.h" -#include "obj/obj.h" -#include "type/type.h" - -/* ============================================================ - * Value stack - * ============================================================ */ - -/* Residency: where the value's storage actually lives. INHERENT values - * (IMM, LOCAL, GLOBAL) carry no register obligation. REG values own a - * physical scratch register. SPILLED values had their register evicted - * to a frame slot under register pressure and must be reloaded before - * consumption. */ -typedef enum SResidency { - RES_INHERENT, - RES_REG, - RES_SPILLED, -} SResidency; - -typedef struct SValue { - Operand op; /* IMM/REG (rvalue) or LOCAL/GLOBAL/INDIRECT (lvalue) */ - const Type* type; /* C semantic type of the value (post-promotion) */ - u8 res; /* SResidency */ - u8 pinned; /* 1 = ineligible spill victim (cleared per CG op) */ - u8 pad[2]; - FrameSlot spill_slot; /* valid iff res == RES_SPILLED */ -} SValue; - -#define CG_STACK_INITIAL 16u -#define CG_SPILL_FREE_INITIAL 4u - -struct CG { - Compiler* c; - CGTarget* target; - Debug* debug; - TargetABI* abi; - Pool* pool; - - /* Function scope */ - const CGFuncDesc* fn_desc; - ObjSymId fn_sym; - ObjSecId fn_text_sec; - u32 fn_begin_pos; - const Type* fn_ret_type; - const ABIFuncInfo* fn_abi; - - SrcLoc cur_loc; - - /* Value stack — grown via heap; arena would also work but heap is fine - * since it's freed in cg_free. */ - SValue* stack; - u32 sp; - u32 cap; - - /* Per-function spill-slot free-lists, one per RegClass. A spill takes a - * slot from the free-list (allocating fresh from the backend if empty); - * a reload returns the slot. Frame footprint is bounded by the peak - * concurrent spills per class. Reset at func_end. */ - struct { - FrameSlot* free; - u32 n; - u32 cap; - } slot_pools[3]; /* indexed by RegClass; RC_VEC reserved */ - - /* Set during cg_call's pop+materialize loop to point at the call's - * in-flight CGABIValue array. When alloc_reg_or_spill exhausts the - * stack victim list, it falls back to spilling an OPK_REG arg - * storage entry from this array, rewriting it to OPK_LOCAL so the - * backend's call lowering loads it from the spill slot. NULL outside - * cg_call. Without this fallback, calls with more reg-class args - * than the pool can hold (e.g. 10+ INT args on aarch64) would have - * unreclaimable regs sitting in avs[] while the value stack runs - * out of victims. */ - CGABIValue* avs_in_flight; - u32 avs_in_flight_n; -}; - -static void stack_grow(CG* g, u32 want) { - Heap* h = g->c->env->heap; - u32 cap = g->cap; - SValue* nb; - if (cap >= want) return; - while (cap < want) cap = cap ? cap * 2u : CG_STACK_INITIAL; - nb = (SValue*)h->alloc(h, sizeof(SValue) * cap, _Alignof(SValue)); - if (g->stack) { - memcpy(nb, g->stack, sizeof(SValue) * g->sp); - h->free(h, g->stack, sizeof(SValue) * g->cap); - } - g->stack = nb; - g->cap = cap; -} - -static void push(CG* g, SValue v) { - stack_grow(g, g->sp + 1); - g->stack[g->sp++] = v; -} - -/* __int128 / unsigned __int128 are parsed and laid out, but no backend - * implements arithmetic, conversion, or load/store on them yet. Trip a - * clear panic at the first codegen op that would touch the value. */ -static void reject_int128(CG* g, const Type* ty, const char* where) { - if (ty && (ty->kind == TY_INT128 || ty->kind == TY_UINT128)) { - compiler_panic(g->c, g->cur_loc, - "%s: __int128 codegen not implemented", where); - } -} - -static SValue pop(CG* g) { - if (g->sp == 0) { - compiler_panic(g->c, g->cur_loc, "cg: stack underflow"); - } - return g->stack[--g->sp]; -} - - -/* Residency of an Operand as it should land on the stack at construction - * time. REG → owns a register; INDIRECT → owns its base register; the - * rest carry no register obligation. */ -static u8 residency_for(const Operand* o) { - if (o->kind == OPK_REG) return RES_REG; - if (o->kind == OPK_INDIRECT) return RES_REG; - return RES_INHERENT; -} - -static SValue make_sv(Operand op, const Type* ty) { - SValue sv; - memset(&sv, 0, sizeof sv); - sv.op = op; - sv.type = ty; - sv.res = residency_for(&op); - sv.spill_slot = FRAME_SLOT_NONE; - return sv; -} - -/* ============================================================ - * Operand sugar - * ============================================================ */ - -static u8 type_class(const Type* ty) { - if (ty && (ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE || - ty->kind == TY_LDOUBLE)) { - return RC_FP; - } - return RC_INT; -} - -static Operand op_imm(i64 v, const Type* ty) { - Operand o; - memset(&o, 0, sizeof o); - o.kind = OPK_IMM; - o.cls = type_class(ty); - o.type = ty; - o.v.imm = v; - return o; -} - -static Operand op_reg(Reg r, const Type* ty) { - Operand o; - memset(&o, 0, sizeof o); - o.kind = OPK_REG; - o.cls = type_class(ty); - o.type = ty; - o.v.reg = r; - return o; -} - -static Operand op_local(FrameSlot s, const Type* ty) { - Operand o; - memset(&o, 0, sizeof o); - o.kind = OPK_LOCAL; - o.cls = RC_INT; - o.type = ty; - o.v.frame_slot = s; - return o; -} - -static Operand op_global(ObjSymId sym, i64 addend, const Type* ty) { - Operand o; - memset(&o, 0, sizeof o); - o.kind = OPK_GLOBAL; - o.cls = RC_INT; - o.type = ty; - o.v.global.sym = sym; - o.v.global.addend = addend; - return o; -} - -/* ============================================================ - * MemAccess derivation - * ============================================================ */ - -static MemAccess derive_mem(CG* g, const Type* ty, AliasKind alias_kind, - i32 alias_local) { - MemAccess m; - memset(&m, 0, sizeof m); - m.type = ty; - m.size = abi_sizeof(g->abi, ty); - m.align = abi_alignof(g->abi, ty); - m.flags = MF_NONE; - if (ty && (ty->qual & Q_VOLATILE)) m.flags |= MF_VOLATILE; - if (ty && (ty->qual & Q_ATOMIC)) m.flags |= MF_ATOMIC; - m.alias.kind = (u8)alias_kind; - if (alias_kind == ALIAS_LOCAL) { - m.alias.v.local_id = alias_local; - } - return m; -} - -/* Pick an alias root from an lvalue Operand. */ -static AliasKind alias_for_lvalue(const Operand* o) { - switch (o->kind) { - case OPK_LOCAL: - return ALIAS_LOCAL; - case OPK_GLOBAL: - return ALIAS_GLOBAL; - case OPK_INDIRECT: - default: - return ALIAS_UNKNOWN; - } -} - -/* MemAccess for a load/store through an lvalue Operand. Routes through - * derive_mem with the alias root and any local-id taken from the lvalue - * itself. The Operand must be OPK_LOCAL/GLOBAL/INDIRECT. */ -static MemAccess mem_for_lvalue(CG* g, const Operand* lv, const Type* ty) { - AliasKind ak = alias_for_lvalue(lv); - i32 al = (ak == ALIAS_LOCAL) ? (i32)lv->v.frame_slot : 0; - return derive_mem(g, ty, ak, al); -} - -/* C type carried by an SValue, with a fallback to the Operand's own type - * field for SValues whose `type` was never set (notably the bare-slot - * cg_push_local path). The two should agree when both present. */ -static const Type* sv_type(const SValue* sv) { - return sv->type ? sv->type : sv->op.type; -} - -/* Build an OPK_INDIRECT Operand for [base + ofs] of type `ty`. The base - * register is always RC_INT (it holds a pointer). */ -static Operand op_indirect(Reg base, i32 ofs, const Type* ty) { - Operand o; - memset(&o, 0, sizeof o); - o.kind = OPK_INDIRECT; - o.cls = RC_INT; - o.type = ty; - o.v.ind.base = base; - o.v.ind.ofs = ofs; - return o; -} - -/* ============================================================ - * Register-pool & spill driver - * ============================================================ */ - -/* Class an SValue's register lives in: RC_FP for float types, RC_INT for - * everything else (including OPK_INDIRECT base regs which always hold a - * pointer-sized integer). */ -static u8 class_of_sv(const SValue* sv) { - if (sv->op.kind == OPK_INDIRECT) return RC_INT; - return type_class(sv_type(sv)); -} - -/* The C type whose width matches the register an SValue currently owns. - * For OPK_INDIRECT, that's a pointer to the lvalue's type (the base reg - * holds an address); for everything else, the SValue's plain type. Used - * by the spill/reload machinery to size the slot store/load. */ -static const Type* sv_owned_reg_type(CG* g, const SValue* sv) { - if (sv->op.kind == OPK_INDIRECT) { - return type_ptr(g->pool, sv->type ? sv->type : type_void(g->pool)); - } - return sv_type(sv); -} - -static u8 reg_of_sv(const SValue* sv) { - if (sv->op.kind == OPK_REG) return (u8)sv->op.v.reg; - if (sv->op.kind == OPK_INDIRECT) return (u8)sv->op.v.ind.base; - return 0; -} - -/* Write the register an SValue owns. For OPK_INDIRECT the ind.base - * field; for OPK_REG the v.reg field. Used by ensure_reg to restore - * the reloaded reg, and by alloc_reg_or_spill to mark a freshly- - * spilled SValue's reg slot as REG_NONE so any stale read fails fast. */ -static void set_owned_reg(SValue* sv, Reg r) { - if (sv->op.kind == OPK_INDIRECT) { - sv->op.v.ind.base = r; - } else { - sv->op.v.reg = r; - } -} - -/* MemAccess for a spill/reload. Width is driven by the type of the value - * whose register is moving — pointer-sized for OPK_INDIRECT bases, - * otherwise the value's own type. The alias root is ALIAS_LOCAL with - * id 0: spill traffic is internal CG bookkeeping that opt's alias - * analysis never inspects, and the slot itself disambiguates accesses. */ -static MemAccess mem_for_spill(CG* g, const SValue* sv) { - return derive_mem(g, sv_owned_reg_type(g, sv), ALIAS_LOCAL, 0); -} - -/* Per-class spill-slot size in bytes. FP slots are 16 bytes to cover - * `double` and the spilled portion of `long double`; INT slots are - * pointer-width. Both values are also the slot's alignment. */ -static u32 spill_slot_size(u8 cls) { return (cls == RC_FP) ? 16u : 8u; } - -/* Take a spill slot from the per-class free-list, or allocate a fresh one. */ -static FrameSlot take_spill_slot(CG* g, u8 cls) { - if (g->slot_pools[cls].n > 0) { - return g->slot_pools[cls].free[--g->slot_pools[cls].n]; - } - FrameSlotDesc d; - memset(&d, 0, sizeof d); - d.size = spill_slot_size(cls); - d.align = d.size; - d.kind = FS_SPILL; - return g->target->frame_slot(g->target, &d); -} - -static void return_spill_slot(CG* g, FrameSlot s, u8 cls) { - if (s == FRAME_SLOT_NONE) return; - if (g->slot_pools[cls].n == g->slot_pools[cls].cap) { - Heap* h = g->c->env->heap; - u32 cap = g->slot_pools[cls].cap; - u32 nc = cap ? cap * 2u : CG_SPILL_FREE_INITIAL; - FrameSlot* nb = - (FrameSlot*)h->alloc(h, sizeof(FrameSlot) * nc, _Alignof(FrameSlot)); - if (g->slot_pools[cls].free) { - memcpy(nb, g->slot_pools[cls].free, - sizeof(FrameSlot) * g->slot_pools[cls].n); - h->free(h, g->slot_pools[cls].free, sizeof(FrameSlot) * cap); - } - g->slot_pools[cls].free = nb; - g->slot_pools[cls].cap = nc; - } - g->slot_pools[cls].free[g->slot_pools[cls].n++] = s; -} - -/* Walk the value stack from index 0 upward and return the first - * unpinned RES_REG SValue that matches `cls`. FIFO-from-bottom == the - * deepest live value, which matches the intuition that the top of - * stack is about to be consumed. Pinned entries are skipped — they're - * the operands of an in-flight CG op that mustn't be evicted from - * under itself (see cg_dup, which keeps its source on the stack while - * allocating the duplicate's destination register). Returns NULL if - * no eligible victim exists; that's a CG bug at this call site. */ -static SValue* pick_victim(CG* g, u8 cls) { - for (u32 i = 0; i < g->sp; ++i) { - SValue* sv = &g->stack[i]; - if (sv->res != RES_REG) continue; - if (sv->pinned) continue; - if (class_of_sv(sv) != cls) continue; - return sv; - } - return NULL; -} - -/* Release the resources owned by a single in-flight CGABIValue arg - * after the call has returned: REG storage goes back to the reg pool, - * LOCAL storage produced by spill_avs_victim returns its slot to the - * per-class spill-slot pool, INDIRECT storage (an aggregate arg reached - * through a pointer, e.g. `f(p->aggr)`) returns its base reg. IMM and - * other kinds carry no runtime ownership and need nothing. - * - * Aggregate-typed OPK_LOCAL is a borrowed lvalue — the slot belongs to - * a user local or a stable byval/return frame slot — and must NOT - * return to the spill pool (size and class mismatch corrupt it). The - * scalar-vs-aggregate type check is the discriminator since - * spill_avs_victim only ever spills scalar-typed REG storage. */ -static void release_arg_storage(CG* g, const Operand* st) { - if (st->kind == OPK_REG) { - g->target->free_reg(g->target, st->v.reg, st->cls); - } else if (st->kind == OPK_LOCAL) { - const Type* t = st->type; - if (t && (t->kind == TY_STRUCT || t->kind == TY_UNION)) return; - return_spill_slot(g, st->v.frame_slot, st->cls); - } else if (st->kind == OPK_INDIRECT) { - g->target->free_reg(g->target, st->v.ind.base, RC_INT); - } -} - -/* Try to spill a CGABIValue arg from the in-flight set whose `storage` - * is an OPK_REG of `cls`. Mutates the entry: storage becomes OPK_LOCAL - * pointing at a freshly-taken spill slot, and the backend's call - * lowering will load from there when materializing the arg into its - * ABI register. Returns 1 if a victim was found and spilled, 0 if none - * was eligible. Used by alloc_reg_or_spill as a fallback when the - * value stack has no eligible victim — see CG.avs_in_flight. */ -static int spill_avs_victim(CG* g, u8 cls) { - CGTarget* T = g->target; - if (!g->avs_in_flight) return 0; - for (u32 i = 0; i < g->avs_in_flight_n; ++i) { - CGABIValue* av = &g->avs_in_flight[i]; - if (av->storage.kind != OPK_REG) continue; - if (av->storage.cls != cls) continue; - FrameSlot slot = take_spill_slot(g, cls); - SValue tmp = make_sv(av->storage, av->type); - MemAccess ma = mem_for_spill(g, &tmp); - T->spill_reg(T, av->storage, slot, ma); - /* Rewrite storage to OPK_LOCAL so the backend reads from the slot. - * The cls is preserved on the operand so the cleanup loop knows - * which spill-slot pool to return the slot to. */ - Operand local = op_local(slot, av->type); - local.cls = cls; - av->storage = local; - return 1; - } - return 0; -} - -/* Allocate a register; on pool exhaustion, spill the deepest live - * RES_REG value to a frame slot and try again. If the value stack has - * no eligible victim, fall back to spilling an in-flight cg_call arg - * (see avs_in_flight) — without that fallback, a call with more args - * than the pool size can hold becomes unsatisfiable since the popped- - * but-not-yet-emitted arg regs would sit unreclaimable in avs[]. */ -static Reg alloc_reg_or_spill(CG* g, u8 cls, const Type* ty) { - CGTarget* T = g->target; - Reg r = T->alloc_reg(T, cls, ty); - if (r != (Reg)REG_NONE) return r; - - SValue* victim = pick_victim(g, cls); - if (victim) { - FrameSlot slot = take_spill_slot(g, cls); - Operand victim_reg = op_reg((Reg)reg_of_sv(victim), victim->type); - T->spill_reg(T, victim_reg, slot, mem_for_spill(g, victim)); - - victim->spill_slot = slot; - victim->res = RES_SPILLED; - /* Mark the reg slot as REG_NONE so any stale read fails fast; the - * reload via ensure_reg will write the new reg back into the same - * field. */ - set_owned_reg(victim, (Reg)REG_NONE); - } else if (!spill_avs_victim(g, cls)) { - compiler_panic(g->c, g->cur_loc, - "cg: regalloc — no spillable victim (class %u)", - (unsigned)cls); - } - - r = T->alloc_reg(T, cls, ty); - if (r == (Reg)REG_NONE) { - compiler_panic(g->c, g->cur_loc, - "cg: regalloc — class %u still empty after spill", - (unsigned)cls); - } - return r; -} - -/* Reload a spilled SValue back into a register (possibly evicting another - * live value). After return, `sv->res == RES_REG` and the operand reflects - * the reloaded register. INDIRECT lvalues are restored to OPK_INDIRECT - * with a fresh base; the deferred-load identity is preserved. */ -static void ensure_reg(CG* g, SValue* sv) { - if (sv->res != RES_SPILLED) return; - CGTarget* T = g->target; - u8 cls = class_of_sv(sv); - const Type* ty = sv_owned_reg_type(g, sv); - Reg r = alloc_reg_or_spill(g, cls, ty); - T->reload_reg(T, op_reg(r, ty), sv->spill_slot, mem_for_spill(g, sv)); - return_spill_slot(g, sv->spill_slot, cls); - sv->spill_slot = FRAME_SLOT_NONE; - /* For INDIRECT, the lvalue's deferred-load identity is preserved: only - * the base reg changes. For everything else, refresh the whole REG - * operand so its `type` matches the SValue's current type tag. */ - if (sv->op.kind == OPK_INDIRECT) { - sv->op.v.ind.base = r; - } else { - sv->op = op_reg(r, sv_type(sv)); - } - sv->res = RES_REG; -} - -/* Release any register or spill slot owned by an SValue popped off the - * stack and not consumed by a downstream operation. */ -static void release(CG* g, SValue* sv) { - if (sv->res == RES_REG) { - g->target->free_reg(g->target, (Reg)reg_of_sv(sv), class_of_sv(sv)); - } else if (sv->res == RES_SPILLED) { - return_spill_slot(g, sv->spill_slot, class_of_sv(sv)); - sv->spill_slot = FRAME_SLOT_NONE; - } - sv->res = RES_INHERENT; -} - -/* ============================================================ - * Construction - * ============================================================ */ - -CG* cg_new(Compiler* c, CGTarget* t, Debug* d) { - Heap* h = c->env->heap; - CG* g = (CG*)h->alloc(h, sizeof(CG), _Alignof(CG)); - memset(g, 0, sizeof *g); - g->c = c; - g->target = t; - g->debug = d; - g->abi = c->abi; - g->pool = c->global; - /* Wire Debug into the backend so per-instruction emit calls can attribute - * line rows. cg owns this hookup per DESIGN §11. */ - if (t) t->debug = d; - if (t && t->mc) t->mc->debug = d; - return g; -} - -void cg_free(CG* g) { - Heap* h; - if (!g) return; - h = g->c->env->heap; - if (g->stack) h->free(h, g->stack, sizeof(SValue) * g->cap); - for (u32 c = 0; c < 3; ++c) { - if (g->slot_pools[c].free) { - h->free(h, g->slot_pools[c].free, - sizeof(FrameSlot) * g->slot_pools[c].cap); - } - } - h->free(h, g, sizeof *g); -} - -CGTarget* cg_target(CG* g) { - return g ? g->target : NULL; -} - -/* ============================================================ - * Function lifecycle - * ============================================================ */ - -void cg_func_begin(CG* g, const CGFuncDesc* fd) { - CGTarget* T = g->target; - g->fn_desc = fd; - g->fn_sym = fd->sym; - g->fn_text_sec = fd->text_section_id; - g->fn_ret_type = fd->fn_type ? fd->fn_type->fn.ret : NULL; - g->fn_abi = fd->abi; - g->sp = 0; - /* Per-function spill-slot free-lists reset. The backing arrays are - * reused; only the counts go to zero since slot ids belong to the new - * function's frame. */ - for (u32 c = 0; c < 3; ++c) g->slot_pools[c].n = 0; - - /* Class-1 DWARF: a new subprogram opens. doc/DWARF.md §3.1 makes this - * the parser's job; we forward through cg as a convenience hook. */ - if (g->debug) { - debug_func_begin(g->debug, fd->sym, DEBUG_TYPE_NONE, fd->loc); - } - - g->fn_begin_pos = T->mc ? T->mc->pos(T->mc) : 0u; - T->func_begin(T, fd); -} - -void cg_func_end(CG* g) { - CGTarget* T = g->target; - T->func_end(T); - if (g->debug && T->mc) { - u32 end_pos = T->mc->pos(T->mc); - debug_func_pc_range(g->debug, g->fn_text_sec, g->fn_begin_pos, end_pos); - debug_func_end(g->debug); - } - g->fn_desc = NULL; -} - -/* ============================================================ - * Locals / parameters - * ============================================================ */ - -FrameSlot cg_local(CG* g, const FrameSlotDesc* d) { - return g->target->frame_slot(g->target, d); -} - -void cg_param(CG* g, const CGParamDesc* d) { g->target->param(g->target, d); } - -void cg_bind_decl(CG* g, DeclId id) { - /* Decl binding is parser territory at this slice; nothing for cg to do. */ - (void)g; - (void)id; -} - -/* ============================================================ - * Pushes - * ============================================================ */ - -void cg_push_int(CG* g, i64 v, const Type* ty) { - push(g, make_sv(op_imm(v, ty), ty)); -} - -void cg_push_const(CG* g, ConstBytes cb) { - /* Materialize into a fresh register through target->load_const so the - * stack value is plain rvalue REG. The constant pool / immediate-encoding - * choice is the backend's. */ - CGTarget* T = g->target; - Reg r = alloc_reg_or_spill(g, type_class(cb.type), cb.type); - Operand dst = op_reg(r, cb.type); - T->load_const(T, dst, cb); - push(g, make_sv(dst, cb.type)); -} - -void cg_push_float(CG* g, double v, const Type* ty) { - /* Convenience path that sidesteps exact-bit literal materialization. - * Conforming literal parsing should prefer cg_push_const. */ - CGTarget* T = g->target; - union { - double d; - float f; - u8 b[8]; - } u; - ConstBytes cb; - /* `long double` (binary128 on AAPCS64) needs the rt soft-float helpers - * — `__floatsitf`, `__extenddftf2`, `__addtf3`, ... — which cg does - * not yet route through. Refuse to silently lower a TF literal as a - * narrower precision; the caller has miscategorized the type or is - * ahead of the wiring. */ - if (ty && ty->kind == TY_LDOUBLE) { - compiler_panic(g->c, g->cur_loc, - "cg_push_float: long double (binary128) literal needs " - "rt soft-float wiring (rt/lib/fp_tf); not yet routed " - "through cg"); - } - cb.type = ty; - cb.size = abi_sizeof(g->abi, ty); - cb.align = abi_alignof(g->abi, ty); - if (ty && ty->kind == TY_FLOAT) { - u.f = (float)v; - } else { - u.d = v; - } - cb.bytes = u.b; - cg_push_const(g, cb); - (void)T; -} - -void cg_push_str(CG* g, Sym str_id, const Type* ty) { - /* Place the string bytes in .rodata and push a pointer. v1 unused by - * the spine corpus; left as a clean stub. */ - (void)g; - (void)str_id; - (void)ty; - compiler_panic(g->c, g->cur_loc, "cg_push_str: not implemented in v1 slice"); -} - -void cg_push_local(CG* g, FrameSlot s) { - /* The slot's type isn't recorded in cg directly — we trust the parser's - * declared local type. Spine: local types come back through the parser's - * scope record, not through cg, so the push uses NULL type and the - * subsequent cg_load supplies the right type. The parser actually pushes - * via the type-aware variant; this base entry is here for completeness. */ - push(g, make_sv(op_local(s, NULL), NULL)); -} - -/* Type-aware variants used by the parser. Not in the public header; the - * parser calls these directly via a small extension below. */ -void cg_push_local_typed(CG* g, FrameSlot s, const Type* ty); -void cg_push_local_typed(CG* g, FrameSlot s, const Type* ty) { - push(g, make_sv(op_local(s, ty), ty)); -} - -/* Pop a pointer rvalue and push an OPK_INDIRECT lvalue for the pointee. - * The parser uses this to implement unary `*`. The pointer is materialized - * into a register; the resulting lvalue's MemAccess alias root is unknown - * (not LOCAL/GLOBAL), which is the right conservative answer for *ptr. */ -static Operand force_reg(CG* g, SValue* v, const Type* ty); -void cg_deref(CG* g, const Type* pointee_ty); -void cg_deref(CG* g, const Type* pointee_ty) { - SValue v = pop(g); - /* The pointer reg becomes the new lvalue's base — ownership transfers - * from `v` to the new INDIRECT SValue, so no release on `v`. */ - Operand src = force_reg(g, &v, sv_type(&v)); - push(g, make_sv(op_indirect(src.v.reg, 0, pointee_ty), pointee_ty)); -} - -/* Read the type of the value currently on top of the stack without popping. - * The parser uses this for type-driven dispatch (e.g. function-call lowering - * needs the callee's TY_FUNC) without re-deriving from its own state. */ -const Type* cg_top_type(CG* g); -const Type* cg_top_type(CG* g) { - if (g->sp == 0) return NULL; - return g->stack[g->sp - 1].type; -} - -/* Type of the second-from-top SValue. Used by the parser when both operands - * of a binary operator are already on the stack and it needs to pick a - * pointer-arithmetic vs. integer-arithmetic lowering. */ -const Type* cg_top2_type(CG* g); -const Type* cg_top2_type(CG* g) { - if (g->sp < 2) return NULL; - return g->stack[g->sp - 2].type; -} - -/* Replace the type tag on the top SValue without emitting code. Used by - * the parser for casts that are no-ops at the value level (e.g. pointer- - * to-pointer of the same width); the underlying register/operand stays - * the same, only the C type the parser/backend will read changes. */ -void cg_retag_top(CG* g, const Type* ty); -void cg_retag_top(CG* g, const Type* ty) { - if (g->sp == 0) return; - g->stack[g->sp - 1].type = ty; - g->stack[g->sp - 1].op.type = ty; -} - -void cg_push_global(CG* g, ObjSymId sym, const Type* ty) { - /* TLS storage isn't reachable via a single (ADRP+ADD)-style addressing - * mode: the access sequence is multi-instruction (LE: tpidr_el0 + tprel - * relocs; macho: TLV descriptor call). Materialize the per-thread - * address eagerly through target->tls_addr_of and push it as an - * OPK_INDIRECT lvalue so subsequent load/store/addr_of paths emit the - * normal indirect sequence rather than the OPK_GLOBAL path. */ - CGTarget* T = g->target; - const ObjSym* os = obj_symbol_get(T->obj, sym); - if (os && os->kind == SK_TLS) { - const Type* pty = type_ptr(g->pool, ty); - Reg r = alloc_reg_or_spill(g, RC_INT, pty); - Operand dst = op_reg(r, pty); - T->tls_addr_of(T, dst, sym, 0); - push(g, make_sv(op_indirect(r, 0, ty), ty)); - return; - } - push(g, make_sv(op_global(sym, 0, ty), ty)); -} - -/* ============================================================ - * Stack manipulation - * ============================================================ */ - -void cg_dup(CG* g) { - /* Duplicate the top SValue. INHERENT values (IMM/LOCAL/GLOBAL) carry - * no register ownership — copying the SValue is enough. REG-owning - * values must materialize into a second register so each side of the - * dup can be released independently; the contents come over via - * target->copy. - * - * The source stays on the stack while the destination register is - * allocated, which means alloc_reg_or_spill could in principle pick - * the source as the spill victim if pool pressure is high and there - * is no other eligible RES_REG on the stack. Pin the source for the - * duration to keep pick_victim away from it. */ - CGTarget* T = g->target; - if (g->sp == 0) compiler_panic(g->c, g->cur_loc, "cg_dup: stack empty"); - SValue* top_p = &g->stack[g->sp - 1]; - ensure_reg(g, top_p); - SValue v = *top_p; /* snapshot AFTER reload — v.op now reflects fresh reg */ - if (v.res != RES_REG) { - push(g, v); - return; - } - top_p->pinned = 1; - const Type* ty = sv_owned_reg_type(g, &v); - Reg r = alloc_reg_or_spill(g, class_of_sv(&v), ty); - T->copy(T, op_reg(r, ty), op_reg((Reg)reg_of_sv(&v), ty)); - /* Refresh the stack pointer: alloc_reg_or_spill above may have spilled - * a different stack entry (the top was pinned, so it stayed put), but - * the SValue array address could have been reallocated by a future - * grow. Today no path inside copy/spill grows the stack, but reading - * through the original `top_p` after a potential realloc would be UB - * regardless. */ - g->stack[g->sp - 1].pinned = 0; - /* The duplicate is `v` with its owned reg replaced by `r`. set_owned_reg - * writes into ind.base for INDIRECT lvalues or v.reg for REG rvalues — - * either way the duplicate ends up RES_REG with the freshly-copied - * value, independent of the source. */ - SValue dup = v; - set_owned_reg(&dup, r); - dup.res = RES_REG; - dup.pinned = 0; - dup.spill_slot = FRAME_SLOT_NONE; - push(g, dup); -} - -void cg_swap(CG* g) { - SValue a; - SValue b; - if (g->sp < 2) compiler_panic(g->c, g->cur_loc, "cg_swap: need 2 values"); - a = g->stack[g->sp - 1]; - b = g->stack[g->sp - 2]; - g->stack[g->sp - 1] = b; - g->stack[g->sp - 2] = a; -} - -void cg_drop(CG* g) { - SValue v = pop(g); - release(g, &v); -} - -/* ============================================================ - * load / store / addr - * ============================================================ */ - -static int is_lvalue(const Operand* o) { - return o->kind == OPK_LOCAL || o->kind == OPK_GLOBAL || - o->kind == OPK_INDIRECT; -} - -void cg_load(CG* g) { - SValue v = pop(g); - ensure_reg(g, &v); - if (!is_lvalue(&v.op)) { - /* Already an rvalue — passing-through is correct (cg_load is idempotent - * on rvalues so the parser can call it eagerly). */ - push(g, v); - return; - } - /* force_reg's lvalue branch does exactly what cg_load wants: alloc a - * fresh value reg, T->load through the lvalue's MemAccess, free the - * old INDIRECT base if any, retag v as RES_REG. */ - const Type* ty = sv_type(&v); - reject_int128(g, ty, "cg_load"); - Operand dst = force_reg(g, &v, ty); - push(g, make_sv(dst, ty)); -} - -void cg_addr(CG* g) { - SValue v = pop(g); - CGTarget* T = g->target; - ensure_reg(g, &v); - if (!is_lvalue(&v.op)) { - compiler_panic(g->c, g->cur_loc, "cg_addr: operand is not an lvalue"); - } - const Type* pty = type_ptr(g->pool, sv_type(&v)); - Reg r = alloc_reg_or_spill(g, RC_INT, pty); - Operand dst = op_reg(r, pty); - T->addr_of(T, dst, v.op); - release(g, &v); - push(g, make_sv(dst, pty)); -} - -void cg_store(CG* g) { - /* stack: [..., lv, rv] → [..., rv] - * - * C semantics: the value of an assignment expression is the value - * stored. Leaving rv on top of the stack lets the parser fall through - * to the next operator naturally; statement-context callers cg_drop - * the leftover. */ - SValue rv = pop(g); - SValue lv = pop(g); - CGTarget* T = g->target; - ensure_reg(g, &rv); - ensure_reg(g, &lv); - if (!is_lvalue(&lv.op)) { - compiler_panic(g->c, g->cur_loc, "cg_store: destination is not an lvalue"); - } - const Type* ty = sv_type(&lv); - reject_int128(g, ty, "cg_store"); - /* IMM is a legal source for store; otherwise force the rvalue into a - * register. force_reg handles the lvalue → REG transition cleanly. */ - Operand src; - if (rv.op.kind == OPK_IMM || rv.op.kind == OPK_REG) { - src = rv.op; - } else { - src = force_reg(g, &rv, sv_type(&rv)); - } - T->store(T, lv.op, src, mem_for_lvalue(g, &lv.op, ty)); - release(g, &lv); - /* Result of assignment expression: leave the stored rvalue on top. - * Ownership of any reg in `src` transfers to the new SValue. */ - push(g, make_sv(src, ty)); -} - -/* ============================================================ - * Aggregates / bitfields — placeholders - * ============================================================ */ - -void cg_copy_aggregate(CG* g, AggregateAccess a) { - (void)a; - compiler_panic(g->c, g->cur_loc, "cg_copy_aggregate: not in v1 slice"); -} -void cg_set_aggregate(CG* g, AggregateAccess a) { - (void)a; - compiler_panic(g->c, g->cur_loc, "cg_set_aggregate: not in v1 slice"); -} -void cg_bitfield_load(CG* g, BitFieldAccess b) { - (void)b; - compiler_panic(g->c, g->cur_loc, "cg_bitfield_load: not in v1 slice"); -} -void cg_bitfield_store(CG* g, BitFieldAccess b) { - (void)b; - compiler_panic(g->c, g->cur_loc, "cg_bitfield_store: not in v1 slice"); -} - -/* ============================================================ - * Arithmetic / compare / convert - * ============================================================ */ - -/* Like force_reg, but leaves an OPK_IMM SValue alone — the CGTarget - * contract for binop/unop/cmp accepts IMM sources, so we avoid burning - * a value-stack register on `x + 3` style sites. The backend decides - * imm-form vs. materialize per the literal's width. */ -static Operand force_reg_unless_imm(CG* g, SValue* v, const Type* ty); - -/* Force an SValue (already popped, by reference) into a register operand - * of the given type. Mutates `*v` so that v->op is OPK_REG and v->res is - * RES_REG; on lvalue inputs this means the original lvalue's base reg is - * freed and replaced by the freshly-loaded value reg. The caller can - * then release(g, v) to give the register back when the operation is - * done with it. */ -static Operand force_reg(CG* g, SValue* v, const Type* ty) { - CGTarget* T = g->target; - ensure_reg(g, v); - if (v->op.kind == OPK_REG) return v->op; - Reg r = alloc_reg_or_spill(g, type_class(ty), ty); - Operand dst = op_reg(r, ty); - if (v->op.kind == OPK_IMM) { - T->load_imm(T, dst, v->op.v.imm); - } else if (is_lvalue(&v->op)) { - T->load(T, dst, v->op, mem_for_lvalue(g, &v->op, ty)); - /* Old INDIRECT base reg is no longer referenced — release it. - * INDIRECT bases are always pointer-typed (RC_INT). */ - if (v->op.kind == OPK_INDIRECT) { - T->free_reg(T, v->op.v.ind.base, RC_INT); - } - } else { - compiler_panic(g->c, g->cur_loc, "cg: cannot force operand to register"); - } - v->op = dst; - v->res = RES_REG; - return dst; -} - -static Operand force_reg_unless_imm(CG* g, SValue* v, const Type* ty) { - if (v->op.kind == OPK_IMM) return v->op; - return force_reg(g, v, ty); -} - -void cg_binop(CG* g, BinOp op) { - /* stack: [a, b] → [a OP b] */ - SValue b = pop(g); - SValue a = pop(g); - CGTarget* T = g->target; - /* Result type is `a`'s type at this slice (parser already coerced). */ - const Type* ty = a.type ? a.type : b.type; - reject_int128(g, ty, "cg_binop"); - - /* Tier 1+2: constant-fold or apply algebraic identities via the - * pure fold helper. KEEP_A/KEEP_B re-push the non-constant operand - * unchanged after releasing the IMM side (IMM carries no reg/slot - * obligation, but the helper is symmetric and a no-op release is - * cheap). */ - { - Operand folded; - switch (cg_fold_binop(op, a.op, b.op, ty, g->abi, &folded)) { - case CG_FOLD_IMM: - release(g, &a); - release(g, &b); - push(g, make_sv(folded, ty)); - return; - case CG_FOLD_KEEP_A: - release(g, &b); - push(g, a); - return; - case CG_FOLD_KEEP_B: - release(g, &a); - push(g, b); - return; - case CG_FOLD_NONE: break; - } - } - - /* IMM sources are legal per the binop contract (arch.h) — the backend - * picks imm-form vs. materialize. cg_fold_binop has already collapsed - * IMM+IMM, so at most one operand here is IMM. */ - Operand ra = force_reg_unless_imm(g, &a, ty); - Operand rb = force_reg_unless_imm(g, &b, ty); - Reg rr = alloc_reg_or_spill(g, type_class(ty), ty); - Operand dst = op_reg(rr, ty); - T->binop(T, op, dst, ra, rb); - release(g, &a); - release(g, &b); - push(g, make_sv(dst, ty)); -} - -void cg_unop(CG* g, UnOp op) { - SValue a = pop(g); - CGTarget* T = g->target; - const Type* ty = a.type ? a.type : a.op.type; - reject_int128(g, ty, "cg_unop"); - - { - Operand folded; - if (cg_fold_unop(op, a.op, ty, g->abi, &folded) == CG_FOLD_IMM) { - release(g, &a); - push(g, make_sv(folded, ty)); - return; - } - } - - Operand ra = force_reg_unless_imm(g, &a, ty); - Reg rr = alloc_reg_or_spill(g, type_class(ty), ty); - Operand dst = op_reg(rr, ty); - T->unop(T, op, dst, ra); - release(g, &a); - push(g, make_sv(dst, ty)); -} - -void cg_cmp(CG* g, CmpOp op) { - /* stack: [a, b] → [i32 result 0/1] */ - SValue b = pop(g); - SValue a = pop(g); - CGTarget* T = g->target; - const Type* opty = a.type ? a.type : b.type; - const Type* i32 = type_prim(g->pool, TY_INT); - - { - Operand folded; - if (cg_fold_cmp(op, a.op, b.op, i32, g->abi, &folded) == CG_FOLD_IMM) { - release(g, &a); - release(g, &b); - push(g, make_sv(folded, i32)); - return; - } - } - - Operand ra = force_reg_unless_imm(g, &a, opty); - Operand rb = force_reg_unless_imm(g, &b, opty); - Reg rr = alloc_reg_or_spill(g, RC_INT, i32); - Operand dst = op_reg(rr, i32); - T->cmp(T, op, dst, ra, rb); - release(g, &a); - release(g, &b); - push(g, make_sv(dst, i32)); -} - -void cg_inc_dec(CG* g, BinOp op, int post) { - /* stack: [lv] → [resultval]. Materialize the in-place update inside cg - * because juggling lv + old + new through dup/swap from outside requires - * a 3-element rotate the stack API doesn't expose. */ - CGTarget* T = g->target; - SValue lv = pop(g); - ensure_reg(g, &lv); - if (!is_lvalue(&lv.op)) { - compiler_panic(g->c, g->cur_loc, - "cg_inc_dec: target is not an lvalue"); - } - const Type* ty = sv_type(&lv); - MemAccess ma = mem_for_lvalue(g, &lv.op, ty); - - /* Load current value into r_old, compute r_new = r_old +/- 1, store back. */ - Reg r_old = alloc_reg_or_spill(g, type_class(ty), ty); - Operand o_old = op_reg(r_old, ty); - T->load(T, o_old, lv.op, ma); - - Reg r_new = alloc_reg_or_spill(g, type_class(ty), ty); - Operand o_new = op_reg(r_new, ty); - T->binop(T, op, o_new, o_old, op_imm(1, ty)); - - T->store(T, lv.op, o_new, ma); - - /* Free whichever register is NOT being returned, plus any base reg the - * lvalue owned. */ - T->free_reg(T, post ? r_new : r_old, type_class(ty)); - release(g, &lv); - push(g, make_sv(post ? o_old : o_new, ty)); -} - -void cg_convert(CG* g, const Type* dst_ty) { - SValue v = pop(g); - CGTarget* T = g->target; - const Type* sty = v.type ? v.type : v.op.type; - reject_int128(g, sty, "cg_convert"); - reject_int128(g, dst_ty, "cg_convert"); - ConvKind ck; - Operand src; - Reg rr; - Operand dst; - /* Trivial: same type. */ - if (sty == dst_ty) { - push(g, v); - return; - } - /* `long double` (binary128) conversions need the rt soft-float helpers - * — `__floatsitf`, `__fixtfsi`, `__extenddftf2`, `__trunctfdf2` — which - * cg does not yet emit. Refuse rather than silently miscompile through - * the FP convert dispatch below (the aarch64 backend would otherwise - * mis-encode a 16-byte operand as a `d` register). */ - if ((sty && sty->kind == TY_LDOUBLE) || - (dst_ty && dst_ty->kind == TY_LDOUBLE)) { - compiler_panic(g->c, g->cur_loc, - "cg_convert: long double (binary128) conversion needs " - "rt soft-float wiring (rt/lib/fp_tf); not yet routed " - "through cg"); - } - /* Pick a ConvKind from src/dst kinds. Same-size same-class integer - * reinterprets are bit-identity and reduce to a retag (no instruction); - * everything else routes to the backend's convert hook. */ - { - int s_int = type_is_int(sty); - int d_int = type_is_int(dst_ty); - int s_flt = sty && (sty->kind == TY_FLOAT || sty->kind == TY_DOUBLE || - sty->kind == TY_LDOUBLE); - int d_flt = dst_ty && (dst_ty->kind == TY_FLOAT || dst_ty->kind == TY_DOUBLE || - dst_ty->kind == TY_LDOUBLE); - u32 s_sz = sty ? abi_sizeof(g->abi, sty) : 0; - u32 d_sz = dst_ty ? abi_sizeof(g->abi, dst_ty) : 0; - int s_signed = sty ? abi_type_info(g->abi, sty).signed_ : 0; - int s_ptr = type_is_ptr(sty); - int d_ptr = type_is_ptr(dst_ty); - /* Pointers are scalar GPR-class values that convert to/from integers - * the same way an unsigned of equal width would: same-size is a - * retag, narrowing is a TRUNC, widening is a ZEXT. Treat them as int - * for the purposes of selecting a ConvKind. */ - int s_int_or_ptr = s_int || s_ptr; - int d_int_or_ptr = d_int || d_ptr; - if (s_int_or_ptr && d_int_or_ptr) { - if (d_sz < s_sz) { - ck = CV_TRUNC; - } else if (d_sz > s_sz) { - ck = (s_int && s_signed) ? CV_SEXT : CV_ZEXT; - } else { - /* Same-size reinterpret (signed↔unsigned, ptr↔int, ptr↔ptr). The - * bit pattern is unchanged; just retag the C type and push back. */ - v.type = dst_ty; - v.op.type = dst_ty; - push(g, v); - return; - } - } else if (s_int && d_flt) { - ck = s_signed ? CV_ITOF_S : CV_ITOF_U; - } else if (s_flt && d_int) { - int d_signed = abi_type_info(g->abi, dst_ty).signed_; - ck = d_signed ? CV_FTOI_S : CV_FTOI_U; - } else if (s_flt && d_flt) { - ck = (d_sz > s_sz) ? CV_FEXT : CV_FTRUNC; - } else { - ck = CV_BITCAST; - } - } - src = force_reg(g, &v, sty); - rr = alloc_reg_or_spill(g, type_class(dst_ty), dst_ty); - dst = op_reg(rr, dst_ty); - T->convert(T, ck, dst, src); - release(g, &v); - push(g, make_sv(dst, dst_ty)); -} - -/* ============================================================ - * Calls / return - * ============================================================ */ - -void cg_call(CG* g, u32 nargs, const Type* fn_type) { - /* stack: [..., callee, arg0..argN-1] → [result] (or nothing if void) */ - CGTarget* T = g->target; - const ABIFuncInfo* abi = abi_func_info(g->abi, fn_type); - const Type* ret_ty = fn_type->fn.ret; - int has_result = ret_ty && ret_ty->kind != TY_VOID; - - if (g->sp < (u32)nargs + 1u) { - compiler_panic(g->c, g->cur_loc, "cg_call: stack underflow"); - } - CGABIValue* avs = NULL; - if (nargs) { - avs = arena_array(g->c->tu, CGABIValue, nargs); - memset(avs, 0, sizeof(CGABIValue) * nargs); - } - - /* Expose avs to the regalloc fallback. As we pop and materialize args - * one at a time, the popped regs accumulate in avs[] off the value - * stack, where pick_victim can't reach them. If pressure exhausts the - * pool while reloading a spilled arg later in the loop, spill_avs_victim - * picks an already-materialized avs entry, stores it to a frame slot, - * and rewrites avs[i].storage to OPK_LOCAL — the backend's call - * lowering loads from the slot. */ - g->avs_in_flight = avs; - g->avs_in_flight_n = nargs; - - /* Pop args in reverse so we can fill avs[i] in declaration order. - * Scalar lvalues materialize into a register through force_reg (which - * also frees an old INDIRECT base); OPK_IMM and OPK_REG pass through - * so the call sees the same operand. Aggregate args (struct/union) - * stay as lvalues — the backend reads each ABI part from - * &storage + part->src_offset (DIRECT) or passes the address - * itself (INDIRECT/byval). The parser is expected to have left an - * OPK_LOCAL/GLOBAL/INDIRECT on the value stack for them. */ - for (u32 i = 0; i < nargs; ++i) { - u32 idx = nargs - 1u - i; - SValue arg = pop(g); - ensure_reg(g, &arg); - int is_vararg = (idx >= abi->nparams); - const Type* aty; - if (is_vararg) { - aty = arg.type ? arg.type : sv_type(&arg); - } else { - aty = fn_type->fn.params ? fn_type->fn.params[idx] : arg.type; - } - avs[idx].type = aty; - avs[idx].abi = is_vararg ? NULL : &abi->params[idx]; - int is_aggregate = aty && (aty->kind == TY_STRUCT || aty->kind == TY_UNION); - if (is_aggregate) { - if (!is_lvalue(&arg.op)) { - compiler_panic(g->c, g->cur_loc, - "cg_call: aggregate arg requires an lvalue source " - "(got operand kind %d)", - (int)arg.op.kind); - } - /* Stamp the operand's type with the aggregate type so - * release_arg_storage recognizes this as a borrowed lvalue and - * leaves the slot alone. */ - Operand st = arg.op; - st.type = aty; - avs[idx].storage = st; - avs[idx].size = abi_sizeof(g->abi, aty); - } else { - avs[idx].storage = - is_lvalue(&arg.op) ? force_reg(g, &arg, aty) : arg.op; - } - } - - SValue callee = pop(g); - ensure_reg(g, &callee); - /* Direct calls keep the OPK_GLOBAL operand; indirect calls force the - * function pointer into a register. */ - Operand callee_op = (callee.op.kind == OPK_GLOBAL) - ? callee.op - : force_reg(g, &callee, fn_type); - - CGCallDesc desc; - memset(&desc, 0, sizeof desc); - desc.fn_type = fn_type; - desc.abi = abi; - desc.callee = callee_op; - desc.args = avs; - desc.nargs = nargs; - desc.flags = CG_CALL_NONE; - desc.ret.type = ret_ty; - desc.ret.abi = &abi->ret; - int ret_is_aggregate = - has_result && (ret_ty->kind == TY_STRUCT || ret_ty->kind == TY_UNION); - FrameSlot ret_slot = FRAME_SLOT_NONE; - if (has_result) { - if (ret_is_aggregate) { - /* Caller-side home for the return: INDIRECT (sret) writes through - * the hidden destination pointer into this slot; DIRECT multi-part - * has the backend store each return register at part->src_offset - * within it. Either way the parser receives an OPK_LOCAL lvalue. */ - FrameSlotDesc fsd; - memset(&fsd, 0, sizeof fsd); - fsd.type = ret_ty; - fsd.size = abi_sizeof(g->abi, ret_ty); - fsd.align = abi_alignof(g->abi, ret_ty); - fsd.kind = FS_LOCAL; - fsd.flags = FSF_ADDR_TAKEN; - ret_slot = g->target->frame_slot(g->target, &fsd); - desc.ret.storage = op_local(ret_slot, ret_ty); - } else { - Reg r = alloc_reg_or_spill(g, type_class(ret_ty), ret_ty); - desc.ret.storage = op_reg(r, ret_ty); - } - } - - T->call(T, &desc); - - /* Tear down the in-flight arg set: each entry's storage may be a REG - * (return to pool) or OPK_LOCAL (a spill slot, return to per-class - * free-list). IMMs carry no runtime ownership. */ - for (u32 i = 0; i < nargs; ++i) { - release_arg_storage(g, &avs[i].storage); - } - g->avs_in_flight = NULL; - g->avs_in_flight_n = 0; - - if (callee.op.kind != OPK_GLOBAL) { - /* Indirect callees are function pointers; they live in int regs. */ - T->free_reg(T, callee_op.v.reg, RC_INT); - } - if (has_result) { - push(g, make_sv(desc.ret.storage, ret_ty)); - } -} - -void cg_tail_call(CG* g, u32 nargs, const Type* fn_type) { - /* Sibling-call form. v1 routes through cg_call with CG_CALL_TAIL. */ - (void)nargs; - (void)fn_type; - compiler_panic(g->c, g->cur_loc, "cg_tail_call: not in v1 slice"); -} - -void cg_ret(CG* g, int has_value) { - CGTarget* T = g->target; - const ABIFuncInfo* abi = g->fn_abi; - if (!has_value) { - T->ret(T, NULL); - return; - } - { - SValue v = pop(g); - const Type* rty = g->fn_ret_type; - int is_aggregate = rty && (rty->kind == TY_STRUCT || rty->kind == TY_UNION); - CGABIValue av; - memset(&av, 0, sizeof av); - av.type = rty; - av.abi = &abi->ret; - if (is_aggregate) { - /* Aggregate return: backend reads parts from the source lvalue - * (DIRECT) or memcpys it through the sret pointer (INDIRECT). */ - if (!is_lvalue(&v.op)) { - compiler_panic(g->c, g->cur_loc, - "cg_ret: aggregate return requires an lvalue source " - "(got operand kind %d)", - (int)v.op.kind); - } - av.storage = v.op; - av.storage.type = rty; - av.size = abi_sizeof(g->abi, rty); - T->ret(T, &av); - /* No register/spill obligation to release — the source slot is - * borrowed and the underlying lvalue's owner (e.g. the function's - * local) cleans up at func_end. */ - return; - } - Operand ret_op = force_reg(g, &v, rty); - av.storage = ret_op; - T->ret(T, &av); - release(g, &v); - } -} - -/* ============================================================ - * alloca / variadics / setjmp / atomics - * ============================================================ */ - -void cg_alloca(CG* g) { - /* Pop the size (i64 imm or reg), call CGTarget.alloca_, push the resulting - * void* aligned to max_align_t. The 16-byte alignment is the AAPCS64 - * max_align_t; cg trusts the backend to honor it (aa_alloca_ rounds the - * size up to a 16-byte multiple, which is what keeps SP aligned). */ - CGTarget* T = g->target; - SValue sz = pop(g); - const Type* void_ptr = type_ptr(g->pool, type_void(g->pool)); - ensure_reg(g, &sz); - Operand sz_op = - (sz.op.kind == OPK_IMM) ? sz.op : force_reg(g, &sz, sv_type(&sz)); - Reg dst_r = alloc_reg_or_spill(g, RC_INT, void_ptr); - Operand dst = op_reg(dst_r, void_ptr); - T->alloca_(T, dst, sz_op, /*align=*/16); - release(g, &sz); - push(g, make_sv(dst, void_ptr)); -} -/* Variadics. Parser pushes &ap (pointer rvalue) before each call; cg pops - * it as a register operand and forwards to the backend. va_arg additionally - * allocates a destination register typed by the requested arg type. */ -void cg_va_start_(CG* g) { - CGTarget* T = g->target; - SValue ap = pop(g); - Operand ap_op = force_reg(g, &ap, sv_type(&ap)); - T->va_start_(T, ap_op); - release(g, &ap); -} -void cg_va_arg_(CG* g, const Type* t) { - CGTarget* T = g->target; - SValue ap = pop(g); - Operand ap_op = force_reg(g, &ap, sv_type(&ap)); - Reg dst_r = alloc_reg_or_spill(g, type_class(t), t); - Operand dst = op_reg(dst_r, t); - T->va_arg_(T, dst, ap_op, t); - release(g, &ap); - push(g, make_sv(dst, t)); -} -void cg_va_end_(CG* g) { - CGTarget* T = g->target; - SValue ap = pop(g); - Operand ap_op = force_reg(g, &ap, sv_type(&ap)); - T->va_end_(T, ap_op); - release(g, &ap); -} -void cg_va_copy_(CG* g) { - CGTarget* T = g->target; - /* Parser pushes &dst then &src; pop src first. */ - SValue src = pop(g); - SValue dst = pop(g); - Operand src_op = force_reg(g, &src, sv_type(&src)); - Operand dst_op = force_reg(g, &dst, sv_type(&dst)); - T->va_copy_(T, dst_op, src_op); - release(g, &src); - release(g, &dst); -} -void cg_setjmp(CG* g) { - CGTarget* T = g->target; - SValue buf = pop(g); - Operand buf_op = force_reg(g, &buf, sv_type(&buf)); - const Type* int_ty = type_prim(g->pool, TY_INT); - Reg dst_r = alloc_reg_or_spill(g, RC_INT, int_ty); - Operand dst = op_reg(dst_r, int_ty); - T->intrinsic(T, INTRIN_SETJMP, &dst, 1u, &buf_op, 1u); - release(g, &buf); - push(g, make_sv(dst, int_ty)); -} -void cg_longjmp(CG* g) { - CGTarget* T = g->target; - SValue val = pop(g); - SValue buf = pop(g); - Operand args[2]; - args[0] = force_reg(g, &buf, sv_type(&buf)); - args[1] = (val.op.kind == OPK_IMM || val.op.kind == OPK_REG) - ? val.op - : force_reg(g, &val, sv_type(&val)); - T->intrinsic(T, INTRIN_LONGJMP, NULL, 0u, args, 2u); - release(g, &val); - release(g, &buf); -} -/* Atomics. The parser pushes the address as a pointer rvalue (typed `T*`) - * and any value operands as plain rvalues; cg pops them, materializes - * registers, derives a MemAccess from the pointee type, and dispatches to - * the backend. MF_ATOMIC is set on the MemAccess so opt sees the access - * as atomic regardless of any qualifier on the pointee. */ -static const Type* atomic_pointee(CG* g, const Type* pty, const char* who) { - if (!pty || pty->kind != TY_PTR) { - compiler_panic(g->c, g->cur_loc, "%s: operand is not a pointer", who); - } - return pty->ptr.pointee; -} - -static MemAccess mem_for_atomic(CG* g, const Type* val_ty) { - MemAccess ma = derive_mem(g, val_ty, ALIAS_UNKNOWN, 0); - ma.flags |= MF_ATOMIC; - return ma; -} - -void cg_atomic_load(CG* g, MemOrder o) { - CGTarget* T = g->target; - SValue ptr = pop(g); - ensure_reg(g, &ptr); - const Type* pty = sv_type(&ptr); - const Type* val_ty = atomic_pointee(g, pty, "cg_atomic_load"); - Operand addr = force_reg(g, &ptr, pty); - Reg dst_r = alloc_reg_or_spill(g, type_class(val_ty), val_ty); - Operand dst = op_reg(dst_r, val_ty); - T->atomic_load(T, dst, addr, mem_for_atomic(g, val_ty), o); - release(g, &ptr); - push(g, make_sv(dst, val_ty)); -} - -void cg_atomic_store(CG* g, MemOrder o) { - CGTarget* T = g->target; - SValue val = pop(g); - SValue ptr = pop(g); - ensure_reg(g, &val); - ensure_reg(g, &ptr); - const Type* pty = sv_type(&ptr); - const Type* val_ty = atomic_pointee(g, pty, "cg_atomic_store"); - Operand addr = force_reg(g, &ptr, pty); - Operand src = (val.op.kind == OPK_IMM || val.op.kind == OPK_REG) - ? val.op - : force_reg(g, &val, val_ty); - T->atomic_store(T, addr, src, mem_for_atomic(g, val_ty), o); - release(g, &val); - release(g, &ptr); -} - -void cg_atomic_rmw(CG* g, AtomicOp a, MemOrder o) { - CGTarget* T = g->target; - SValue val = pop(g); - SValue ptr = pop(g); - ensure_reg(g, &val); - ensure_reg(g, &ptr); - const Type* pty = sv_type(&ptr); - const Type* val_ty = atomic_pointee(g, pty, "cg_atomic_rmw"); - Operand addr = force_reg(g, &ptr, pty); - Operand vop = (val.op.kind == OPK_IMM || val.op.kind == OPK_REG) - ? val.op - : force_reg(g, &val, val_ty); - Reg dst_r = alloc_reg_or_spill(g, type_class(val_ty), val_ty); - Operand dst = op_reg(dst_r, val_ty); - T->atomic_rmw(T, a, dst, addr, vop, mem_for_atomic(g, val_ty), o); - release(g, &val); - release(g, &ptr); - push(g, make_sv(dst, val_ty)); -} - -void cg_atomic_cas(CG* g, MemOrder succ, MemOrder fail) { - CGTarget* T = g->target; - SValue desired = pop(g); - SValue expected = pop(g); - SValue ptr = pop(g); - ensure_reg(g, &desired); - ensure_reg(g, &expected); - ensure_reg(g, &ptr); - const Type* pty = sv_type(&ptr); - const Type* val_ty = atomic_pointee(g, pty, "cg_atomic_cas"); - Operand addr = force_reg(g, &ptr, pty); - Operand exp_op = (expected.op.kind == OPK_IMM || expected.op.kind == OPK_REG) - ? expected.op - : force_reg(g, &expected, val_ty); - Operand des_op = (desired.op.kind == OPK_IMM || desired.op.kind == OPK_REG) - ? desired.op - : force_reg(g, &desired, val_ty); - Reg prior_r = alloc_reg_or_spill(g, type_class(val_ty), val_ty); - const Type* i32 = type_prim(g->pool, TY_INT); - Reg ok_r = alloc_reg_or_spill(g, RC_INT, i32); - Operand prior = op_reg(prior_r, val_ty); - Operand ok = op_reg(ok_r, i32); - T->atomic_cas(T, prior, ok, addr, exp_op, des_op, mem_for_atomic(g, val_ty), - succ, fail); - release(g, &desired); - release(g, &expected); - release(g, &ptr); - push(g, make_sv(prior, val_ty)); - push(g, make_sv(ok, i32)); -} - -void cg_fence(CG* g, MemOrder o) { g->target->fence(g->target, o); } - -/* One-arg, one-result intrinsic returning C `int`. Used by __builtin_ctz / - * clz / popcount: the operand drives the width (sf bit on aa64, REX.W on - * x64, sf on rv64) while the result is always `int`. */ -void cg_intrinsic_unary_to_int(CG* g, IntrinKind kind) { - CGTarget* T = g->target; - SValue v = pop(g); - const Type* arg_ty = sv_type(&v); - ensure_reg(g, &v); - Operand arg = force_reg(g, &v, arg_ty); - const Type* int_ty = type_prim(g->pool, TY_INT); - Reg dst_r = alloc_reg_or_spill(g, RC_INT, int_ty); - Operand dst = op_reg(dst_r, int_ty); - T->intrinsic(T, kind, &dst, 1u, &arg, 1u); - release(g, &v); - push(g, make_sv(dst, int_ty)); -} - -void cg_intrinsic_void(CG* g, IntrinKind kind) { - CGTarget* T = g->target; - T->intrinsic(T, kind, NULL, 0u, NULL, 0u); -} - -/* ============================================================ - * Control flow — flat labels - * ============================================================ */ - -CGLabel cg_label_new(CG* g) { return (CGLabel)g->target->label_new(g->target); } - -void cg_label_place(CG* g, CGLabel l) { - g->target->label_place(g->target, (Label)l); -} - -void cg_jump(CG* g, CGLabel l) { g->target->jump(g->target, (Label)l); } - -void cg_branch_true(CG* g, CGLabel l) { - /* Pop i1 and branch if nonzero. v1 synthesizes cmp_branch(CMP_NE, val, 0). */ - SValue v = pop(g); - CGTarget* T = g->target; - const Type* ty = v.type ? v.type : type_prim(g->pool, TY_INT); - /* Mirror cg_branch_false: a literal condition resolves at compile time. */ - if (v.op.kind == OPK_IMM) { - if (v.op.v.imm != 0) { - T->jump(T, (Label)l); - } - release(g, &v); - return; - } - Operand a = force_reg(g, &v, ty); - Operand zero = op_imm(0, ty); - T->cmp_branch(T, CMP_NE, a, zero, (Label)l); - release(g, &v); -} - -void cg_branch_false(CG* g, CGLabel l) { - SValue v = pop(g); - CGTarget* T = g->target; - const Type* ty = v.type ? v.type : type_prim(g->pool, TY_INT); - /* Constant-fold: branch on a known-zero immediate becomes unconditional; - * branch on a known-nonzero immediate becomes a no-op. The aarch64 - * cmp_branch handles immediates too, but folding here keeps the emitted - * code clean and lets `if (1) ...` skip the cmp entirely. */ - if (v.op.kind == OPK_IMM) { - if (v.op.v.imm == 0) { - T->jump(T, (Label)l); - } - release(g, &v); - return; - } - { - Operand a = force_reg(g, &v, ty); - Operand zero = op_imm(0, ty); - T->cmp_branch(T, CMP_EQ, a, zero, (Label)l); - release(g, &v); - } -} - -/* ============================================================ - * Structured control flow — passthrough to target - * ============================================================ */ - -CGScope cg_scope_begin(CG* g, CGScopeConfig cfg) { - CGScopeDesc d; - memset(&d, 0, sizeof d); - d.kind = (u8)cfg.kind; - d.break_label = (Label)cfg.break_label; - d.continue_label = (Label)cfg.continue_label; - d.result_type = cfg.result_type; - if (cfg.kind == SCOPE_IF) { - /* Pop the condition. */ - SValue v = pop(g); - const Type* ty = v.type ? v.type : type_prim(g->pool, TY_INT); - d.cond = force_reg(g, &v, ty); - /* The cond reg is consumed by the backend's scope_begin emit; once - * the comparison/branch is in flight there's no live use, so free - * it back to the pool now. */ - release(g, &v); - } - return (CGScope)g->target->scope_begin(g->target, &d); -} - -void cg_scope_else(CG* g, CGScope s) { - g->target->scope_else(g->target, (CGScope)s); -} - -void cg_scope_end(CG* g, CGScope s) { - g->target->scope_end(g->target, (CGScope)s); -} - -void cg_break(CG* g, CGScope s) { - g->target->break_to(g->target, (CGScope)s); -} - -void cg_continue(CG* g, CGScope s) { - g->target->continue_to(g->target, (CGScope)s); -} - -/* ============================================================ - * Source location - * ============================================================ */ - -void cg_set_loc(CG* g, SrcLoc loc) { - g->cur_loc = loc; - if (g->target->set_loc) g->target->set_loc(g->target, loc); - if (g->debug) debug_set_pending_loc(g->debug, loc); -} - -/* ============================================================ - * Inline asm — constraint binder (doc/INLINEASM.md §5). - * - * The parser pushed `nin` input SValues onto the value stack in declaration - * order (the Nth input is at the top). Outputs come back as fresh SValues - * that the parser assigns to its declared lvalues. AsmConstraint.type - * carries the bound expression's C type (parser-populated); the binder - * routes it through alloc_reg + type_class so FP outputs land in RC_FP, - * pointer outputs keep their pointer type, and narrow types get the right - * width. Hand-built test constraints (NULL type) fall back to 64-bit int. - * - * Constraints handled: - * inputs : "r" (force into REG), "i" (must be IMM), - * "m" (materialize an INDIRECT lvalue), - * "0".."9" (matching: bind to out_ops[N].v.reg) - * outputs : "=r" (alloc fresh), "+r" (alloc fresh; expects a parallel - * matching input slot), "=&r" (early-clobber: alloc disjoint - * from any input reg) - * Clobbers: - * "memory" — spill all live RES_REG SValues so subsequent reads reload. - * register names — passed through to target->asm_block (the arch backend - * routes them through its call-clobber set). - * "cc" — silently ignored on aarch64 (NZCV is reserved across blocks). */ - -/* Parse a leading non-negative decimal index from a constraint string. - * Returns -1 if the first character isn't a digit. */ -static int asm_parse_match_index(const char* s) { - if (!s || s[0] < '0' || s[0] > '9') return -1; - int n = 0; - for (const char* p = s; *p >= '0' && *p <= '9'; ++p) { - n = n * 10 + (*p - '0'); - } - return n; -} - -/* Skip leading "=&" / "=" / "+" modifier prefix and return a pointer past - * it. The remainder is the body letter ("r", "m", ...). */ -static const char* asm_constraint_body(const char* s) { - if (!s) return ""; - if (s[0] == '=' && s[1] == '&') return s + 2; - if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; - return s; -} - -static int asm_is_early_clobber(const char* s) { - if (!s) return 0; - if (s[0] == '=' && s[1] == '&') return 1; - if (s[0] == '&') return 1; - return 0; -} - -void cg_inline_asm(CG* g, const char* tmpl, const AsmConstraint* outs, u32 nout, - const AsmConstraint* ins, u32 nin, const Sym* clobbers, - u32 nclob) { - CGTarget* T = g->target; - Heap* h = g->c->env->heap; - /* Fallback for hand-built test constraints that don't carry a type. The - * parser always populates AsmConstraint.type from the bound expression's - * C type; only unit-test constraints leave it NULL. */ - const Type* fallback_ty = type_prim(g->pool, TY_LLONG); - - /* ---- pop inputs in reverse, store in declaration order ---- */ - SValue* in_svs = NULL; - if (nin) { - in_svs = (SValue*)h->alloc(h, sizeof(SValue) * nin, _Alignof(SValue)); - for (u32 i = 0; i < nin; ++i) { - u32 idx = nin - 1u - i; - in_svs[idx] = pop(g); - ensure_reg(g, &in_svs[idx]); - } - } - - Operand* in_ops = NULL; - if (nin) { - in_ops = (Operand*)h->alloc(h, sizeof(Operand) * nin, _Alignof(Operand)); - memset(in_ops, 0, sizeof(Operand) * nin); - } - Operand* out_ops = NULL; - if (nout) { - out_ops = (Operand*)h->alloc(h, sizeof(Operand) * nout, _Alignof(Operand)); - memset(out_ops, 0, sizeof(Operand) * nout); - } - /* Tracks whether each out_ops[i] reg was freshly allocated (and should be - * pushed back as RES_REG owning that reg) vs. shared with an input that - * still owns the reg. */ - u8* out_reg_owned = NULL; - if (nout) { - out_reg_owned = (u8*)h->alloc(h, nout, 1); - memset(out_reg_owned, 0, nout); - } - - /* ---- Pass 1: allocate output regs that are NOT early-clobber. ---- - * Early-clobber (=&r) outputs are allocated in pass 3 once input regs - * are known so the disjoint-set property is checkable. */ - for (u32 i = 0; i < nout; ++i) { - const char* body = asm_constraint_body(outs[i].str); - if (asm_is_early_clobber(outs[i].str)) continue; - if (body[0] == 'r') { - const Type* oty = outs[i].type ? outs[i].type : fallback_ty; - u8 cls = type_class(oty); - Reg r = alloc_reg_or_spill(g, cls, oty); - out_ops[i] = op_reg(r, oty); - out_reg_owned[i] = 1; - } else { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: unsupported output constraint '%s'", - outs[i].str ? outs[i].str : ""); - } - } - - /* ---- Pass 2: materialize inputs per constraint. ---- - * Matching constraints ("0".."9") need their referenced output's reg to - * already exist; non-early outputs satisfy that after pass 1. (An output - * referenced by a matching input must not itself be early-clobber — that - * combination is meaningless; we panic below if the parser produced it.) */ - for (u32 i = 0; i < nin; ++i) { - const char* s = ins[i].str ? ins[i].str : ""; - int matched = asm_parse_match_index(s); - if (matched >= 0) { - if ((u32)matched >= nout) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: matching constraint '%s' references " - "out-of-range output %d", - s, matched); - } - if (asm_is_early_clobber(outs[matched].str)) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: matching input '%s' references " - "early-clobber output =&r", - s); - } - /* Force input into the output's register. If the input is already an - * IMM or in a different reg, materialize via target->copy/load_imm - * into the bound output reg. The input SValue keeps its own reg - * (which we'll release at the end); the binding only needs the - * value to be present in out_ops[matched].v.reg before the asm runs. */ - Operand bound = out_ops[matched]; - ensure_reg(g, &in_svs[i]); - if (in_svs[i].op.kind == OPK_REG && - in_svs[i].op.v.reg == bound.v.reg) { - /* Already in place. */ - } else if (in_svs[i].op.kind == OPK_IMM) { - T->load_imm(T, bound, in_svs[i].op.v.imm); - } else { - Operand src = force_reg(g, &in_svs[i], sv_type(&in_svs[i])); - T->copy(T, bound, src); - } - in_ops[i] = bound; - continue; - } - if (s[0] == 'r') { - in_ops[i] = force_reg(g, &in_svs[i], sv_type(&in_svs[i])); - } else if (s[0] == 'i') { - if (in_svs[i].op.kind != OPK_IMM) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: 'i' constraint requires constant input"); - } - in_ops[i] = in_svs[i].op; - } else if (s[0] == 'm') { - if (in_svs[i].op.kind == OPK_INDIRECT) { - in_ops[i] = in_svs[i].op; - } else if (is_lvalue(&in_svs[i].op)) { - const Type* lt = sv_type(&in_svs[i]); - const Type* pty = type_ptr(g->pool, lt ? lt : type_void(g->pool)); - Reg r = alloc_reg_or_spill(g, RC_INT, pty); - Operand dst = op_reg(r, pty); - T->addr_of(T, dst, in_svs[i].op); - /* Replace the SValue's lvalue with an INDIRECT pointing at the - * freshly-loaded address; the new INDIRECT owns the base reg, so - * release() at the end of the block will free it. */ - if (in_svs[i].op.kind == OPK_INDIRECT) { - T->free_reg(T, in_svs[i].op.v.ind.base, RC_INT); - } - in_svs[i].op = op_indirect(r, 0, lt); - in_svs[i].res = RES_REG; - in_ops[i] = in_svs[i].op; - } else { - compiler_panic( - g->c, g->cur_loc, - "cg_inline_asm: 'm' constraint requires an addressable operand"); - } - } else { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: unsupported input constraint '%s'", s); - } - } - - /* ---- Pass 3: allocate early-clobber outputs (=&r) disjoint from inputs. - * The reg pool only hands out free regs, so any reg returned by alloc_reg - * is by construction not in use by any input materialized above. We loop - * to retry if the pool happens to recycle a reg the spill machinery just - * freed (none of the input materializers above call free_reg on input - * regs while inputs are still live, so a single alloc suffices in - * practice — but the loop documents the intent and gives a clean panic - * point). */ - for (u32 i = 0; i < nout; ++i) { - if (!asm_is_early_clobber(outs[i].str)) continue; - const char* body = asm_constraint_body(outs[i].str); - if (body[0] != 'r') { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: unsupported early-clobber constraint '%s'", - outs[i].str); - } - const Type* oty = outs[i].type ? outs[i].type : fallback_ty; - u8 cls = type_class(oty); - Reg r = alloc_reg_or_spill(g, cls, oty); - /* Validate disjoint: walk inputs, collide-check. The pool guarantees - * uniqueness against currently-allocated regs, so this is belt-and- - * suspenders, but the panic gives a meaningful diagnostic if any - * future binder change breaks the invariant. */ - for (u32 k = 0; k < nin; ++k) { - if (in_ops[k].kind == OPK_REG && in_ops[k].v.reg == r) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: early-clobber output collided with " - "input reg (binder bug)"); - } - if (in_ops[k].kind == OPK_INDIRECT && in_ops[k].v.ind.base == r) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: early-clobber output collided with " - "input INDIRECT base (binder bug)"); - } - } - out_ops[i] = op_reg(r, oty); - out_reg_owned[i] = 1; - } - - /* ---- "memory" clobber: spill all live RES_REG SValues. ---- - * Intern "memory" once per call; Sym equality is pointer-equal after - * interning. The remaining stack values become RES_SPILLED, so a later - * read goes through ensure_reg → reload_reg. */ - Sym sym_memory = pool_intern_cstr(g->pool, "memory"); - int has_memory_clobber = 0; - for (u32 i = 0; i < nclob; ++i) { - if (clobbers[i] == sym_memory) { - has_memory_clobber = 1; - break; - } - } - if (has_memory_clobber) { - for (u32 i = 0; i < g->sp; ++i) { - SValue* sv = &g->stack[i]; - if (sv->res != RES_REG) continue; - u8 cls = class_of_sv(sv); - FrameSlot slot = take_spill_slot(g, cls); - Operand victim_reg = op_reg((Reg)reg_of_sv(sv), sv->type); - T->spill_reg(T, victim_reg, slot, mem_for_spill(g, sv)); - T->free_reg(T, (Reg)reg_of_sv(sv), cls); - sv->spill_slot = slot; - sv->res = RES_SPILLED; - set_owned_reg(sv, (Reg)REG_NONE); - } - } - - /* ---- Named register clobbers: spill any live SValue currently bound - * to a clobbered physical reg. Skipped when "memory" already swept - * the stack above. Backends without resolve_reg_name accept all named - * clobbers as no-ops (matches pre-v1 behavior). */ - if (!has_memory_clobber && T->resolve_reg_name) { - for (u32 i = 0; i < nclob; ++i) { - Reg phys; - RegClass cls; - if (T->resolve_reg_name(T, clobbers[i], &phys, &cls) != 0) continue; - /* Reject overlap with bound in/out operands (GCC contract). */ - for (u32 k = 0; k < nout; ++k) { - if (out_ops[k].kind == OPK_REG && out_ops[k].cls == cls && - (Reg)out_ops[k].v.reg == phys) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: named clobber overlaps output reg"); - } - } - for (u32 k = 0; k < nin; ++k) { - if (in_ops[k].kind == OPK_REG && in_ops[k].cls == cls && - (Reg)in_ops[k].v.reg == phys) { - compiler_panic(g->c, g->cur_loc, - "cg_inline_asm: named clobber overlaps input reg"); - } - } - for (u32 k = 0; k < g->sp; ++k) { - SValue* sv = &g->stack[k]; - if (sv->res != RES_REG) continue; - if (class_of_sv(sv) != cls) continue; - if ((Reg)reg_of_sv(sv) != phys) continue; - FrameSlot slot = take_spill_slot(g, cls); - Operand victim_reg = op_reg(phys, sv->type); - T->spill_reg(T, victim_reg, slot, mem_for_spill(g, sv)); - T->free_reg(T, phys, cls); - sv->spill_slot = slot; - sv->res = RES_SPILLED; - set_owned_reg(sv, (Reg)REG_NONE); - } - } - } - - /* ---- Call the per-arch asm_block. ---- */ - T->asm_block(T, tmpl, outs, nout, out_ops, ins, nin, in_ops, clobbers, nclob); - - /* ---- Release input SValue resources. ---- - * Inputs are consumed by the asm block. Their owned regs/slots return to - * the pool. Note: matching inputs that were copied into an output reg - * still own their original input reg — release frees that one; the - * output reg lives on through the pushed output SValue. */ - for (u32 i = 0; i < nin; ++i) { - release(g, &in_svs[i]); - } - - /* ---- Push outputs back as fresh SValues for the parser to assign. ---- - * Each pushed SValue owns the freshly-allocated reg (RES_REG), so the - * parser's eventual cg_store on it will release the reg after consuming. */ - for (u32 i = 0; i < nout; ++i) { - const Type* oty = outs[i].type ? outs[i].type : fallback_ty; - SValue sv = make_sv(out_ops[i], oty); - /* If the target overwrote out_ops[i] with a different kind (e.g. a - * memory location), make_sv already classified residency correctly. */ - if (!out_reg_owned[i] && sv.res == RES_REG) { - /* Not owned by us — the value is borrowed from elsewhere. Treat as - * inherent to avoid double-free. (No production path produces this - * today, but the bookkeeping is explicit.) */ - sv.res = RES_INHERENT; - } - push(g, sv); - } - - if (in_svs) h->free(h, in_svs, sizeof(SValue) * nin); - if (in_ops) h->free(h, in_ops, sizeof(Operand) * nin); - if (out_ops) h->free(h, out_ops, sizeof(Operand) * nout); - if (out_reg_owned) h->free(h, out_reg_owned, nout); -} diff --git a/src/cg/cg.h b/src/cg/cg.h @@ -1,195 +0,0 @@ -#ifndef CFREE_CG_H -#define CFREE_CG_H - -#include "arch/arch.h" -#include "decl/decl.h" -#include "type/type.h" - -typedef struct CG CG; -typedef struct Debug Debug; - -/* Debug is optional; pass NULL when -g is off. */ -CG* cg_new(Compiler*, CGTarget*, Debug*); -void cg_free(CG*); -CGTarget* cg_target(CG*); - -/* ----- functions ----- */ -void cg_func_begin(CG*, const CGFuncDesc*); -void cg_func_end(CG*); - -/* ----- locals & params ----- */ -FrameSlot cg_local( - CG*, const FrameSlotDesc*); /* returns frame slot; pushes nothing */ -void cg_param(CG*, const CGParamDesc*); - -/* ----- value-stack pushes ----- */ -void cg_push_int(CG*, i64, const Type*); -void cg_push_const(CG*, ConstBytes); /* exact ABI bytes */ -void cg_push_float(CG*, double, - const Type*); /* convenience for simple parser paths */ -void cg_push_str(CG*, Sym str_id, - const Type*); /* into rodata; pushes pointer */ -void cg_push_local(CG*, FrameSlot); /* lvalue */ -void cg_push_global(CG*, ObjSymId, const Type*); /* lvalue */ - -/* ----- value-stack manipulation ----- */ -void cg_load(CG*); /* lvalue → rvalue; derives MemAccess */ -void cg_addr(CG*); /* lvalue → ptr rvalue */ -void cg_store(CG*); /* [..., lv, rv] → []; derives MemAccess */ -void cg_dup(CG*); -void cg_swap(CG*); -void cg_drop(CG*); - -/* Aggregate and bitfield operations keep C object semantics visible to direct - * targets and opt. Addresses are lvalues or pointer rvalues on the value stack; - * sizes, offsets, storage units, and alignments come from TargetABI. */ -void cg_copy_aggregate(CG*, - AggregateAccess); /* [..., dst_addr, src_addr] → [] */ -void cg_set_aggregate(CG*, AggregateAccess); /* [..., dst_addr, byte] → [] */ -void cg_bitfield_load(CG*, BitFieldAccess); /* [..., record_addr] → value */ -void cg_bitfield_store(CG*, - BitFieldAccess); /* [..., record_addr, value] → [] */ - -void cg_binop(CG*, BinOp); -void cg_unop(CG*, UnOp); -void cg_cmp(CG*, CmpOp); -void cg_convert(CG*, const Type* dst); /* picks ConvKind from src/dst */ - -/* Increment/decrement an lvalue in place. Pops the lvalue from the value - * stack, performs `*lv = *lv +/- 1`, and pushes the result rvalue. With - * `post=1` the pushed value is the OLD value (post-inc/dec); with - * `post=0` it is the NEW value (pre-inc/dec). `op` is BO_IADD or BO_ISUB. - * The integer-1 step is the parser's responsibility for non-integer - * types (pointer arithmetic), but the spine slice deals only with - * integer locals. */ -void cg_inc_dec(CG*, BinOp op, int post); - -/* Direct vs indirect: callee on the stack distinguishes itself by - * SValue/operand kind. CG obtains ABIFuncInfo from Compiler.abi, materializes - * CGABIValue argument/return parts, then calls CGTarget.call with a CGCallDesc. - * On WASM, fn_type selects the call_indirect type index (interned Type* - * identity is the index source of truth). */ -void cg_call(CG*, u32 nargs, - const Type* fn_type); /* stack: [..., callee, arg0..argN-1] - → result (if non-void) */ -/* Sibling call: pops [callee, arg0..argN-1], lowers as a tail call. The - * caller's epilogue runs and control transfers to the callee, whose RET - * returns to the caller's caller. cg_tail_call implicitly terminates the - * function — the parser must NOT follow it with cg_ret. v1 has must-tail - * semantics: legality (ABI-compatible return, args fit in registers, no - * caller-frame pointer escapes via byval) is the caller's responsibility; - * the backend panics if it cannot honor the tail request. */ -void cg_tail_call(CG*, u32 nargs, const Type* fn_type); -void cg_ret(CG*, int has_value); - -/* ----- C declarations and global initializers ----- - * Parser records C declaration semantics through DeclTable. CG consumes DeclIds - * only when a declaration becomes executable code or an addressable object. */ -void cg_bind_decl(CG*, DeclId); - -/* ----- alloca ----- - * Dynamic stack allocation. Pops `size_bytes` (i64), pushes `void*` aligned to - * max_align_t. v1 does not parse C99/C11 VLAs (predefines __STDC_NO_VLA__); - * cg_alloca is reachable only via the __builtin_alloca path. */ -void cg_alloca(CG*); - -/* ----- variadics ----- - * va_list type is per-arch (defined in <stdarg.h>). The four ops match the C - * macros after builtin substitution. cg_va_arg pops &ap and pushes the next - * arg of `t`. cg_va_start/end/copy pop the va_list addresses and push nothing. - */ -/* The trailing underscores avoid colliding with <stdarg.h> macros — cfree - * sources include stdarg.h for compiler_panicv (see core.h). */ -void cg_va_start_(CG*); /* pop &ap */ -void cg_va_arg_(CG*, const Type* t); /* pop &ap; push value */ -void cg_va_end_(CG*); /* pop &ap */ -void cg_va_copy_(CG*); /* pop &dst, &src */ - -/* ----- setjmp / longjmp ----- - * Intrinsic lowering for targets that cannot use a plain libc call. Real - * native arches generally parse <setjmp.h>'s setjmp as a normal call. - * cg_setjmp pops &buf and pushes i32 (0 on direct return, nonzero on longjmp). - * cg_longjmp pops &buf and val; does not return. */ -void cg_setjmp(CG*); -void cg_longjmp(CG*); - -/* ----- atomics ----- - * Pointer operands are typed `_Atomic T*`. cg derives MemAccess from the - * pointee type, qualifiers, alignment facts, and alias root; the pointee type - * drives width and tells the backend whether the op fits inline or routes to - * compiler-rt. */ -void cg_atomic_load(CG*, MemOrder); /* pops ptr; pushes value */ -void cg_atomic_store(CG*, MemOrder); /* pops ptr, value */ -void cg_atomic_rmw(CG*, AtomicOp, MemOrder); /* pops ptr, val; pushes prior */ -void cg_atomic_cas(CG*, MemOrder success, MemOrder failure); -/* pops ptr, expected, desired; - * pushes (prior, ok_i1) */ -void cg_fence(CG*, MemOrder); - -/* ----- intrinsics ----- - * Builtin lowering for one-arg one-result intrinsics whose width is taken from - * the operand and whose C result type is `int` (e.g. __builtin_ctz / clz / - * popcount). Pops one rvalue, dispatches to CGTarget.intrinsic with the given - * kind, pushes the result as `int`. */ -void cg_intrinsic_unary_to_int(CG*, IntrinKind); - -/* Zero-operand, zero-result intrinsic (e.g. __builtin_trap, - * __builtin_unreachable). Lowers via CGTarget.intrinsic and pushes no - * value — caller is responsible for pushing a dummy `int 0` if the - * builtin appears in an expression context. */ -void cg_intrinsic_void(CG*, IntrinKind); - -/* ----- control flow (CG-level labels) ----- - * cg_branch_true fuses with a preceding cg_cmp into a single - * CGTarget.cmp_branch when the i1 on top of stack is the unconsumed result of - * that cmp. For a non-cmp i1, it emits cmp_branch(CMP_NE, val, IMM_ZERO, - * label). */ -typedef u32 CGLabel; -CGLabel cg_label_new(CG*); -void cg_label_place(CG*, CGLabel); -void cg_jump(CG*, CGLabel); -void cg_branch_true(CG*, CGLabel); /* pops i1 */ -void cg_branch_false(CG*, CGLabel); - -/* ----- structured control flow ----- - * Used for if / while / for / do — the cases where the parser already knows - * the structure. Nests like a stack: every scope_begin must pair with one - * scope_end at the same nesting depth. Break and continue targets are explicit - * so C `for` continue jumps to the increment expression, not necessarily the - * loop header. - * - * Real backends implement these as a thin shim over label_place/jump (no code - * size cost). The WASM backend consumes them directly to emit block/loop/if - * with structurally-bounded br targets — that's the source of CFI on WASM - * without invoking the relooper. - * - * goto, computed-goto, and switch fallthrough still go through the flat label - * API above. opt's IR is flat-CFG; at -O2 the WASM lowering pass relooper - * reconstructs structure from the flat IR. At -O0/-O1 (no opt wrapper), - * CG drives the WASM CGTarget directly with scope ops and no relooper runs. */ -/* ScopeKind is shared with CGTarget (see arch.h). */ -typedef u32 CGScope; -typedef struct CGScopeConfig { - ScopeKind kind; - CGLabel break_label; - CGLabel continue_label; - const Type* result_type; -} CGScopeConfig; -CGScope cg_scope_begin(CG*, CGScopeConfig); /* IF: pops i1 */ -void cg_scope_else(CG*, CGScope); /* IF only */ -void cg_scope_end(CG*, CGScope); -void cg_break(CG*, CGScope); -void cg_continue(CG*, CGScope); /* LOOP only */ - -/* ----- source location ----- */ -void cg_set_loc(CG*, SrcLoc); /* propagates to CGTarget and Debug */ - -/* ----- inline asm ----- - * Inputs are popped from the CG stack in declaration order before outputs are - * pushed back as fresh SValues. Constraints are GCC-style strings; binding - * is per-arch and happens inside CGTarget.asm_block. */ -void cg_inline_asm(CG*, const char* tmpl, const AsmConstraint* outs, u32 nout, - const AsmConstraint* ins, u32 nin, const Sym* clobbers, - u32 nclob); - -#endif diff --git a/src/cg/fold.c b/src/cg/fold.c @@ -1,154 +0,0 @@ -#include "cg/fold.h" - -/* Truncate (and re-sign-extend, for signed types) a folded i64 down to - * the width of `ty`, so that subsequent folds and compares see the same - * value the backend would have produced after narrowing to the - * destination register. No-op for >= 8-byte types and for NULL ty. */ -static i64 narrow(TargetABI* abi, const Type* ty, i64 v) { - if (!ty || !abi) return v; - u32 sz = abi_sizeof(abi, ty); - if (sz >= 8) return v; - u64 mask = ((u64)1 << (sz * 8)) - 1; - u64 u = (u64)v & mask; - if (abi_type_info(abi, ty).signed_) { - u64 sign_bit = (u64)1 << (sz * 8 - 1); - if (u & sign_bit) u |= ~mask; - } - return (i64)u; -} - -static Operand make_imm(i64 v, const Type* ty) { - Operand o; - o.kind = OPK_IMM; - o.cls = RC_INT; - o.pad = 0; - o.type = ty; - o.v.imm = v; - return o; -} - -/* Literal-literal integer binop. Returns 1 with *out set, or 0 if `op` - * isn't a foldable kind. Excludes SDIV/UDIV/SREM/UREM (must trap on - * divisor 0 and INT_MIN/-1), SHL/SHR_* (count >= width is type-width- - * dependent and not in this tier), and float ops (rounding/NaN belong - * to the backend). */ -static int literal_binop(BinOp op, i64 a, i64 b, i64* out) { - switch (op) { - case BO_IADD: *out = (i64)((u64)a + (u64)b); return 1; - case BO_ISUB: *out = (i64)((u64)a - (u64)b); return 1; - case BO_IMUL: *out = (i64)((u64)a * (u64)b); return 1; - case BO_AND: *out = a & b; return 1; - case BO_OR: *out = a | b; return 1; - case BO_XOR: *out = a ^ b; return 1; - default: return 0; - } -} - -/* Algebraic-identity dispatch for an integer binop with one literal - * operand. `k` is the constant; `k_on_right` distinguishes lhs vs rhs - * for non-commutative ops (ISUB, SHL, SHR_*, SDIV, UDIV). - * FID_NONE — no identity, caller emits normally. - * FID_KEEP — drop the IMM, the non-constant operand is the result. - * FID_ZERO — result is constant 0; drop both operands. */ -typedef enum FoldIdent { FID_NONE, FID_KEEP, FID_ZERO } FoldIdent; - -static FoldIdent identity_for(BinOp op, i64 k, int k_on_right) { - switch (op) { - case BO_IADD: case BO_OR: case BO_XOR: - /* x + 0, 0 + x, x | 0, 0 | x, x ^ 0, 0 ^ x */ - return (k == 0) ? FID_KEEP : FID_NONE; - case BO_ISUB: - /* x - 0 only; 0 - x needs a UO_NEG and isn't an identity. */ - return (k == 0 && k_on_right) ? FID_KEEP : FID_NONE; - case BO_IMUL: - if (k == 1) return FID_KEEP; - if (k == 0) return FID_ZERO; - return FID_NONE; - case BO_AND: - if (k == 0) return FID_ZERO; - /* All-ones mask of any width sign-extends to -1 as i64. */ - if (k == -1) return FID_KEEP; - return FID_NONE; - case BO_SDIV: case BO_UDIV: - /* x / 1 only; 1 / x isn't an identity and divisor-on-lhs gives - * no useful fold. */ - return (k == 1 && k_on_right) ? FID_KEEP : FID_NONE; - case BO_SHL: case BO_SHR_S: case BO_SHR_U: - /* x << 0, x >> 0 only; a zero shift-count on the lhs is the - * value being shifted — folding that would need to release the - * rhs operand, deferred until a use exists. */ - return (k == 0 && k_on_right) ? FID_KEEP : FID_NONE; - default: - return FID_NONE; - } -} - -CGFoldKind cg_fold_binop(BinOp op, Operand a, Operand b, const Type* ty, - TargetABI* abi, Operand* out) { - /* Tier 1: both literal — fold to a single IMM. */ - if (a.kind == OPK_IMM && b.kind == OPK_IMM) { - i64 r; - if (literal_binop(op, a.v.imm, b.v.imm, &r)) { - *out = make_imm(narrow(abi, ty, r), ty); - return CG_FOLD_IMM; - } - } - /* Tier 2: algebraic identities. Side-effect-free: the non-constant - * operand has already been materialized onto the value stack, so any - * computation that produced it has already executed. Dropping the - * IMM side is the caller's responsibility (release reg/slot if any). */ - if (b.kind == OPK_IMM) { - switch (identity_for(op, b.v.imm, /*k_on_right=*/1)) { - case FID_KEEP: return CG_FOLD_KEEP_A; - case FID_ZERO: *out = make_imm(0, ty); return CG_FOLD_IMM; - case FID_NONE: break; - } - } - if (a.kind == OPK_IMM) { - switch (identity_for(op, a.v.imm, /*k_on_right=*/0)) { - case FID_KEEP: return CG_FOLD_KEEP_B; - case FID_ZERO: *out = make_imm(0, ty); return CG_FOLD_IMM; - case FID_NONE: break; - } - } - return CG_FOLD_NONE; -} - -CGFoldKind cg_fold_unop(UnOp op, Operand a, const Type* ty, - TargetABI* abi, Operand* out) { - if (a.kind != OPK_IMM) return CG_FOLD_NONE; - i64 v = a.v.imm; - i64 r; - switch (op) { - case UO_NEG: r = (i64)(-(u64)v); break; - case UO_BNOT: r = ~v; break; - case UO_NOT: r = v ? 0 : 1; break; - default: return CG_FOLD_NONE; - } - *out = make_imm(narrow(abi, ty, r), ty); - return CG_FOLD_IMM; -} - -CGFoldKind cg_fold_cmp(CmpOp op, Operand a, Operand b, const Type* int_ty, - TargetABI* abi, Operand* out) { - if (a.kind != OPK_IMM || b.kind != OPK_IMM) return CG_FOLD_NONE; - (void)abi; /* compare result is `int` 0/1 — no narrowing needed */ - i64 x = a.v.imm; - i64 y = b.v.imm; - i64 r; - switch (op) { - case CMP_EQ: r = (x == y); break; - case CMP_NE: r = (x != y); break; - case CMP_LT_S: r = (x < y); break; - case CMP_LE_S: r = (x <= y); break; - case CMP_GT_S: r = (x > y); break; - case CMP_GE_S: r = (x >= y); break; - case CMP_LT_U: r = ((u64)x < (u64)y); break; - case CMP_LE_U: r = ((u64)x <= (u64)y); break; - case CMP_GT_U: r = ((u64)x > (u64)y); break; - case CMP_GE_U: r = ((u64)x >= (u64)y); break; - default: return CG_FOLD_NONE; - } - *out = make_imm(r, int_ty); - return CG_FOLD_IMM; -} diff --git a/src/cg/fold.h b/src/cg/fold.h @@ -1,47 +0,0 @@ -#ifndef CFREE_CG_FOLD_H -#define CFREE_CG_FOLD_H - -/* Pure constant-folding and algebraic-identity helpers for binop/unop/cmp - * on Operand inputs. No CG or IR state: callers (cg.c today, opt's - * pass_gvn / pass_combine eventually) inspect the result and apply it - * to whichever value representation they hold. All folds are restricted - * to integer domain — float ops never reach the OPK_IMM path (FP - * literals materialize via load_const to OPK_REG before they enter the - * value stack). Division, remainder, shifts, and FP arithmetic are - * deliberately excluded from the literal-fold paths to preserve trap - * semantics and rounding behavior; algebraic identities on those ops - * are limited to cases that don't depend on UB-exploiting transforms - * (see doc/OPT.md §5.5). */ - -#include "abi/abi.h" -#include "arch/arch.h" -#include "type/type.h" - -typedef enum CGFoldKind { - CG_FOLD_NONE, /* no fold; caller emits normally */ - CG_FOLD_IMM, /* result is the OPK_IMM Operand in *out; drop both inputs */ - CG_FOLD_KEEP_A, /* result is `a` unchanged; drop `b` */ - CG_FOLD_KEEP_B, /* result is `b` unchanged; drop `a` */ -} CGFoldKind; - -/* Binop fold + identity. Examines `a` and `b` against `op`: - * - both OPK_IMM → CG_FOLD_IMM, *out = literal narrowed to `ty` - * - one OPK_IMM identity → CG_FOLD_KEEP_A or _B, or CG_FOLD_IMM (zero) - * - otherwise → CG_FOLD_NONE - * `ty` is the result type (used for width-narrowing on the fold path). - * `abi` supplies size/signedness; required when ty is non-NULL. */ -CGFoldKind cg_fold_binop(BinOp op, Operand a, Operand b, const Type* ty, - TargetABI* abi, Operand* out); - -/* Unop fold. Returns CG_FOLD_IMM with *out set on success, CG_FOLD_NONE - * otherwise. Only integer-domain unops are folded. */ -CGFoldKind cg_fold_unop(UnOp op, Operand a, const Type* ty, - TargetABI* abi, Operand* out); - -/* Integer-compare fold. Returns CG_FOLD_IMM with *out set to 0 or 1 of - * type `int_ty` on success. FP compares (CMP_*_F) return CG_FOLD_NONE — - * NaN/ordering belongs to the backend. */ -CGFoldKind cg_fold_cmp(CmpOp op, Operand a, Operand b, const Type* int_ty, - TargetABI* abi, Operand* out); - -#endif diff --git a/src/emu/emu.c b/src/emu/emu.c @@ -11,16 +11,14 @@ #include "emu/emu.h" #include <cfree.h> +#include <cfree/cg.h> #include <setjmp.h> #include <string.h> -#include "arch/arch.h" -#include "cg/cg.h" #include "core/heap.h" #include "core/pool.h" #include "link/link.h" #include "obj/obj.h" -#include "opt/opt.h" /* ---- Lifecycle ---- */ @@ -140,9 +138,7 @@ static void* translate_block(CfreeEmu* e, u64 guest_pc) { EmuInst insts[EMU_MAX_INSTS_PER_BLOCK]; u32 ninsts; ObjBuilder* ob; - MCEmitter* mc; - CGTarget* target; - CG* cg; + CfreeCg* cg; Sym block_name; ObjSymId block_sym; EmuLiftCtx ctx; @@ -172,18 +168,16 @@ static void* translate_block(CfreeEmu* e, u64 guest_pc) { for (j = 0; j < ninsts; ++j) emu_trace_insn(e->c, guest_pc, &insts[j]); } - /* Per-block ObjBuilder + MC + CGTarget pipeline. The block lands - * as a single host function. */ + /* Per-block ObjBuilder + public CG pipeline. The block lands as a single + * host function once per-ISA lifters start emitting real code. */ ob = obj_new(e->c); - mc = mc_new(e->c, ob); - target = cgtarget_new(e->c, ob, mc); - if (e->opt_level > 0) target = opt_cgtarget_new(e->c, target, e->opt_level); - cg = cg_new(e->c, target, /*Debug*/ NULL); + cg = cfree_cg_new(e->c, ob); + if (!cg) compiler_panic(e->c, no_loc(), "emu: cfree_cg_new failed"); block_name = emu_block_sym_name(e->c, guest_pc); /* Forward-declare the block's symbol so the lifter can refer to it - * via cg_func_begin. obj_symbol_define fills in (section, value, size) - * once the function is emitted. */ + * via cfree_cg_func_begin. obj_symbol_define fills in (section, value, + * size) once the function is emitted. */ block_sym = obj_symbol(ob, block_name, SB_GLOBAL, SK_FUNC, OBJ_SEC_NONE, 0, 0); @@ -196,13 +190,9 @@ static void* translate_block(CfreeEmu* e, u64 guest_pc) { emu_lift_block(e->guest_arch, cg, insts, ninsts, &ctx); - cgtarget_finalize(target); + cfree_cg_free(cg); obj_finalize(ob); - cg_free(cg); - cgtarget_free(target); /* opt_cgtarget cascades to wrapped target */ - mc_free(mc); - /* Add the block's object to the session linker and extend the * image. link_resolve_extend places the new section at the next * free offset within the reserved VA region (must not change host diff --git a/src/emu/emu.h b/src/emu/emu.h @@ -12,12 +12,12 @@ * never reaches into ISA-specific code. */ #include <cfree.h> +#include <cfree/cg.h> #include "core/core.h" #include "obj/obj.h" #include "type/type.h" -typedef struct CG CG; typedef struct LinkImage LinkImage; typedef struct Linker Linker; @@ -107,8 +107,8 @@ typedef struct EmuLiftCtx { } EmuLiftCtx; /* Walk `insts` and emit one CG function (signature next_pc_t(CPUState*)) - * for the block. Calls cg_func_begin/end exactly once. */ -void emu_lift_block(CfreeEmuArch, CG*, const EmuInst* insts, u32 n, + * for the block. Calls cfree_cg_func_begin/end exactly once. */ +void emu_lift_block(CfreeEmuArch, CfreeCg*, const EmuInst* insts, u32 n, const EmuLiftCtx*); /* ---- Code cache ------------------------------------------------- */ diff --git a/src/emu/lift.c b/src/emu/lift.c @@ -4,12 +4,12 @@ * pipeline below CG is unchanged from the C front-end. */ #include <cfree.h> +#include <cfree/cg.h> -#include "cg/cg.h" #include "emu/emu.h" -void emu_lift_block(CfreeEmuArch arch, CG* cg, const EmuInst* insts, u32 n, - const EmuLiftCtx* ctx) { +void emu_lift_block(CfreeEmuArch arch, CfreeCg* cg, const EmuInst* insts, + u32 n, const EmuLiftCtx* ctx) { /* Per-ISA lifter tables not yet landed. translate_block panics * before it would finalize an empty block, so this stub never * silently produces an executable host function. */ diff --git a/test/cg/CORPUS.md b/test/cg/CORPUS.md @@ -1,436 +0,0 @@ -# cg / CGTarget / MCEmitter test corpus - -Coverage matrix for `test/cg/`. Each registered case in -`harness/cases.c` is one row; behavioral oracle is `test_main`'s return -value (mod 256, since POSIX exit codes are one byte). Mirrors the CORPUS -shape used by `test/elf/` and `test/link/`. - -Test paths per case (run.sh): - -- **D** in-process JIT (aarch64 host only) — `cg-runner --jit NAME`. -- **R** ELF roundtrip (host-arch agnostic) — `cg-runner --emit` → - `cfree-roundtrip` → `readelf` + `normalize.py` diff. -- **E** exec via qemu/podman — `cg-runner --emit` + `start.o` → - `link-exe-runner` → run. -- **J** jit-via-file (aarch64 host only) — `cg-runner --emit` → - `jit-runner`. -- **W** DWARF check (Group P only) — `cg-runner --emit` + - `cg-runner --dwarf-checks NAME | cg_check_dwarf OBJ`. Opens the obj - via `cfree_dwarf_open` and asserts the line program / subprograms - registered for the case. Cases without registered checks skip - silently. - -`O` (opt-wrapped) lands once `opt_cgtarget` is implemented. - -The harness drives the same building blocks the parser will: pool-interned -Types via `type_*`, ABI classification via `abi_func_info`, and `CGTarget` -for lowering. There are no ABI mocks. Cases that exercise interfaces the -lib does not yet implement (param/call/aggregate/FP methods on the -backend; `type_func`/`abi_func_info`) link against the same symbols the -parser will, and fail at runtime until those land — that is intentional. - -## Status legend - -- ★ landed in the spine -- · planned (case registered, expected value fixed) -- (deferred) — explicit non-goal for the current pass - -## MC-only — direct MCEmitter - -| Case | Status | Expected | Notes | -|---|---|---|---| -| `mc_smoke` | ★ | 42 | hand-built `mov w0, #42; ret`; analogue of `test/elf/unit/smoke.c` | - -## Group A — function lifecycle and return - -| Case | Status | Body | Expected | -|---|---|---|---| -| `a01_return_const_42` | ★ | `alloc_reg; load_imm 42; ret reg` | 42 | -| `a02_return_zero` | ★ | `load_imm 0; ret reg` | 0 | -| `a03_ret_imm` | ★ | `ret IMM 17` (backend materializes) | 17 | -| `a04_copy_reg` | ★ | `load_imm 7; copy r1->r2; ret r2` | 7 | -| `a05_return_neg_small` | ★ | `load_imm -7` via MOVN; ret | 249 (= -7 & 0xff) | -| `a06_return_i64` | ★ | i64 `load_imm 0x1_0000_002A`; ret as i64 | 42 (low 32 of x0) | -| `a07_void_return` | ★ | `ret(NULL)` | 0 (via _start zeroing x0) | -| `a08_multiple_returns` | ★ | `ret_imm 1; ret_imm 2` (second is dead) | 1 | -| `a09_load_imm_movz_movk` | ★ | `load_imm 0xABCD` (multi-step materialize) | 205 (= 0xCD) | -| `a10_return_u8` | ★ | `load_imm 200` into u8 reg; ret | 200 | - -## Group B — frame slots, parameters, locals - -Param/call cases pair a helper function with `test_main`. Both share one -`CGTarget` instance — the backend must support multiple -`func_begin`/`func_end` pairs per TU. `cgtest_begin_func` builds -`CGFuncDesc` from a real `type_func`/`abi_func_info` pair; param -materialization, slot allocation, and call lowering use the live -`TargetABI`. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `b01_param_int` | ★ | `int echo(int x){return x;}; echo(201)` | 201 | -| `b02_param_sum` | ★ | `int sum2(int a,int b){return a+b;}; sum2(40,2)` | 42 | -| `b03_param_spill` | ★ | `int sum9(a..i)`; nine int params (8 GPR, 1 stack); `sum9(1..9)` | 45 | -| `b04_local_int` | ★ | local int slot; `*p = 42; return *p` | 42 | -| `b05_addr_taken_local` | ★ | `int x=17; int*p=&x; *p+=1; return *p` | 18 | -| `b06_sret` | ★ | `struct Pt{int a,b;}; Pt mk(){{10,32}}; pt=mk(); return pt.a+pt.b` | 42 | -| `b07_byval_param` | ★ | `int take(struct Pt p){return p.a+p.b;}; take({15,27})` | 42 | -| `b08_fp_param` | ★ | `int trunc(float f){return (int)f;}; trunc(7.5f)` | 7 | - -## Group C — integer arithmetic - -| Case | Status | Body | Expected | -|---|---|---|---| -| `c01_add` | ★ | `1 + 2` | 3 | -| `c02_sub_mul` | ★ | `7 * 3 - 4` | 17 | -| `c03_bitwise` | ★ | `(~3) & 0xff` | 252 | -| `c04_shift` | ★ | `(1<<5) \| (16>>1)` (logical shr) | 40 | -| `c05_div_mod` | ★ | `23 / 4 + 23 % 4` (signed) | 8 | -| `c06_xor` | ★ | `0xa5 ^ 0x5a` | 255 | -| `c07_iadd_i64` | ★ | i64 `0x1_0000_0029 + 0x1_0000_0001` | 42 (low 32) | -| `c08_unsigned_div` | ★ | `100u / 7u` | 14 | -| `c09_neg` | ★ | `UO_NEG` 42 | 214 (= -42 & 0xff) | -| `c10_logical_not` | ★ | `UO_NOT 0` (zero-test → 0/1) | 1 | -| `c11_shr_signed` | ★ | `-16 >>(s) 2` | 252 (= -4 & 0xff) | -| `c12_imul_i64` | ★ | i64 `7 * 6` | 42 | - -## Group D — compare and branch - -Both arms of `cmp` (materializes 0/1 in a GPR) and `cmp_branch` (fused -test+branch) plus the structured-CFG ops `scope_*`. `cmp` cases return -the materialized 0/1 directly; `cmp_branch` cases return distinct -sentinels from the taken vs. fallthrough paths so the oracle can tell -them apart. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `d01_cmp_eq_true` | · | `cmp(EQ, 5, 5)` materialize → 1 | 1 | -| `d02_cmp_eq_false` | · | `cmp(EQ, 5, 6)` → 0 | 0 | -| `d03_cmp_ne` | · | `cmp(NE, 5, 6)` → 1 | 1 | -| `d04_cmp_lt_signed` | · | `cmp(LT_S, -1, 1)` → 1 | 1 | -| `d05_cmp_lt_unsigned` | · | `cmp(LT_U, 0xFFFFFFFFu, 1u)` → 0 (signedness in op, not Type) | 0 | -| `d06_cmp_ge_signed` | · | `cmp(GE_S, 5, 5)` → 1 (boundary on inclusive ops) | 1 | -| `d07_cmp_branch_taken` | · | `cmp_branch(EQ, 7, 7) → L`; landing pad returns 42 | 42 | -| `d08_cmp_branch_not_taken` | · | `cmp_branch(EQ, 5, 6) → L`; fallthrough returns 33 | 33 | -| `d09_cmp_branch_lt_signed` | · | `cmp_branch(LT_S, -3, 0) → L`; landing pad returns 9 | 9 | -| `d10_jump` | · | unconditional `jump L`; early ret is dead | 5 | -| `d11_scope_if_true` | · | `int x=99; if(1) x=33; return x;` | 33 | -| `d12_scope_if_false` | · | `int x=99; if(0) x=33; return x;` | 99 | -| `d13_scope_if_else` | · | `if(0) x=10; else x=7; return x;` (exercises `scope_else`) | 7 | - -## Group E — conversions - -One `ConvKind` per case, plus the boundary widths the AArch64 backend -selects between (`UXTB`/`SXTB` vs `UXTH`/`SXTH` vs `UBFX`/`SBFX`, -32→64 sign- vs zero-extend). FP cases all funnel back through -`CV_FTOI_S` so the runner sees an int exit code. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `e01_sext_i8_i32` | · | `sext (i8)-1 → i32` = 0xFFFFFFFF; low 8 = 255 | 255 | -| `e02_zext_u8_i32` | · | `zext (u8)0xFF → i32` = 0x000000FF; low 8 = 255 | 255 | -| `e03_sext_i16_i32` | · | `sext (i16)-1000 → i32` = 0xFFFFFC18; low 8 = 0x18 = 24 | 24 | -| `e04_zext_u16_i32` | · | `zext (u16)0xABCD → i32` = 0x0000ABCD; low 8 = 0xCD = 205 | 205 | -| `e05_zext_u32_i64` | · | `zext (u32)0xFFFFFFFF → i64`; low 32 = 0xFFFFFFFF; low 8 = 255 | 255 | -| `e06_sext_i32_i64` | · | `sext (i32)-1 → i64` = -1; low 32 = 0xFFFFFFFF; low 8 = 255 | 255 | -| `e07_trunc_i64_i32` | · | `trunc 0x100000080 → i32` = 0x80 | 128 | -| `e08_trunc_i32_i8` | · | `trunc 0x1FF → i8` = 0xFF; returned as u8 | 255 | -| `e09_itof_s_i32_f32` | · | `(i32)7 → f32 7.0 → ftoi_s` round-trip | 7 | -| `e10_itof_u_u32_f64` | · | `(u32)100 → f64 100.0 → ftoi_s` cross-width | 100 | -| `e11_ftoi_s_neg` | · | `ftoi_s(-1.5f) = -1` (truncate toward zero); low 8 = 255 | 255 | -| `e12_ftoi_u_pos` | · | `ftoi_u(200.7f) = 200u` | 200 | -| `e13_fext_f32_f64` | · | `fext 3.5f → 3.5 → ftoi_s` → 3 | 3 | -| `e14_ftrunc_f64_f32` | · | `ftrunc 7.875 → 7.875f → ftoi_s` → 7 | 7 | -| `e15_bitcast_i32_f32` | · | `bitcast 0x40A00000 → f32 5.0f → ftoi_s` (same-size cross-class) | 5 | - -## Group F — memory (loads/stores beyond locals) - -Group B already exercises basic load/store of an i32 local. 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. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `f01_load_store_i8` | · | local u8; store IMM 200; load; return | 200 | -| `f02_load_store_i16` | · | local i16; store 0x1234; load; low 8 = 0x34 | 52 | -| `f03_load_store_i64` | · | local i64; store 0x1_0000_0042; runner reads w0 = 0x42 | 66 | -| `f04_load_store_f32` | · | local f32; store FP reg = 7.5f; load; ftoi_s | 7 | -| `f05_load_store_f64` | · | local f64; store FP reg = 3.25; load; ftoi_s | 3 | -| `f06_indirect_nonzero_offset` | · | i64 local addr-taken; store i32 at +0 (sentinel) and +4 | 42 | -| `f07_store_reg` | · | store from REG (vs IMM in b04) into a local i32; reload | 17 | -| `f08_copy_bytes` | · | `copy_bytes(dst, src, Pt {10,32})`; sum dst.a+dst.b | 42 | -| `f09_set_bytes_zero` | · | `set_bytes(0)` over an i32 buffer; load → 0 | 0 | -| `f10_set_bytes_ff` | · | `set_bytes(0xFF)` over an i32 buffer; load = 0xFFFFFFFF; low 8 | 255 | -| `f11_volatile_rw` | · | b04 body with `MF_VOLATILE` on both store and load | 42 | -| `f12_bitfield_unsigned` | · | `{u: 5}` at bit_offset=3; store 21; load (zero-extend) | 21 | -| `f13_bitfield_signed` | · | `{s: 5}` at bit_offset=0; store -1; load sign-extends; low 8 | 255 | - -## Group G — calls (beyond direct-call path) - -Group B established the direct-call mechanics (param/return, stack -spill, sret, byval, fp param). Group G stresses what falls out once -calls *compose*: indirect calls through function pointers, recursion, -register-preservation across calls, HFAs, and pass-by-pointer for -oversized aggregates. Each helper is its own `func_begin`/`func_end` -under the same `CGTarget`. `cmp_branch`-driven recursion bases share -the Group D control surface. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `g01_indirect_call` | · | `int (*fp)(int) = echo; return fp(42);` (call via REG, not direct symbol) | 42 | -| `g02_recursion_factorial` | · | `int f(int n){return n<2?1:n*f(n-1);}; f(5)` | 120 | -| `g03_recursion_fib` | · | `int f(int n){return n<2?n:f(n-1)+f(n-2);}; f(10)` | 55 | -| `g04_mutual_recursion` | · | `is_even`/`is_odd` cross-recursion; `is_even(8)` | 1 | -| `g05_chained_calls` | · | `inc(inc(inc(39)))` — return value of one is the arg of the next | 42 | -| `g06_mixed_int_fp_params` | · | `int f(int a, float b, int c, double d, int e)`; integer sum truncated | 42 | -| `g07_void_call_outparam` | · | `void fill(int *p, int v); int x; fill(&x, 42); return x;` | 42 | -| `g08_large_struct_byval` | · | `struct S{int a[8];}` (>16B) passed by value (ABI: indirect) | 42 | -| `g09_hfa_param_f32x2` | · | `struct V{float x,y;}` HFA param `(1.5,1.5)`; ftoi_s of caller-side sum | 3 | -| `g10_hfa_return_f32x2` | · | HFA return `{1.5f,1.5f}`; ftoi_s of caller-side sum | 3 | -| `g11_caller_saved_live_across_call` | · | local `int x=42` live across a clobbering call; backend must preserve | 42 | -| `g12_addr_taken_local_across_call` | · | b05-style addr-taken local survives an intervening call | 18 | -| `g13_call_in_loop_induction` | · | `for(i=0;i<10;i++) s += id(i);` — induction var preserved across call | 45 | - -## Group H — control flow - -Builds out the loop and multi-way branch surface beyond Group D's -`scope_if`/`scope_else`. Includes both structured loop ops -(`scope_loop`, `scope_break`, `scope_continue`) and the unstructured -`jump`/label form so the backend exercises arbitrary CFGs (forward -and backward `jump`). `switch`-style multi-way uses repeated -`cmp_branch`. Short-circuit `&&`/`||` are exercised at the IR level -(lowered to chained `cmp_branch` + materialize) — the test proves -short-circuit by observing that the RHS side effect did *not* run. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `h01_while_sum_0_to_9` | · | `int s=0,i=0; while(i<10){ s+=i; i++; } return s;` | 45 | -| `h02_do_while_once` | · | `int i=0; do { i=42; } while(0); return i;` | 42 | -| `h03_for_count_to_10` | · | `int s=0; for(int i=1;i<=10;i++) s+=i; return s;` | 55 | -| `h04_loop_break` | · | `for(i=0;;i++) if(i==42) break; return i;` | 42 | -| `h05_loop_continue` | · | sum of even i in `[0,20)` using `continue` to skip odds | 90 | -| `h06_nested_loops` | · | `for(i=0;i<3;i++) for(j=0;j<2;j++) s++; return s;` | 6 | -| `h07_break_inner_only` | · | `break` exits inner loop only — outer continues | 9 | -| `h08_early_return_in_loop` | · | `for(i=0;;i++) if(i==17) return i;` | 17 | -| `h09_switch_three_cases` | · | `switch(2){case 1:r=10;break; case 2:r=42;break; case 3:r=99;break;}` | 42 | -| `h10_switch_fallthrough` | · | `case 1: r+=10; case 2: r+=20;` (no break) on input 1 | 30 | -| `h11_switch_default` | · | `switch(99){case 1:..;break; default: r=7;}` returns default | 7 | -| `h12_jump_forward` | · | `jump L; ret 99 (unreachable); L: ret 42;` — backend tolerates dead op | 42 | -| `h13_jump_backward` | · | counter loop built from `cmp_branch` + backward `jump` (no scope ops) | 10 | -| `h14_short_circuit_and_skip` | · | `int s=0; (0) && (s=99,1); return s;` — RHS side effect must be skipped | 0 | -| `h15_short_circuit_or_skip` | · | `int s=0; (1) \|\| (s=99,1); return s;` — RHS side effect must be skipped| 0 | -| `h16_ternary` | · | `int x = (5>3) ? 42 : 7; return x;` | 42 | -| `h17_ternary_side_effect_one_arm` | · | `int s=0; (1) ? (s=42) : (s=99); return s;` — only taken arm runs | 42 | -| `h18_unreachable_after_ret` | · | ops emitted after a `ret` (dead block); backend must not crash | 42 | - -## Group I — alloca / VLA - -Stack-allocated runtime-sized memory: the `alloca` op (constant- and -runtime-size), required-alignment variants, two-allocas-disjoint, and -VLAs as parameters. Oracles exercise both the *address* (alignment, -distinct per allocation) and the *contents* (writes survive, helpers -can deref). - -| Case | Status | Body | Expected | -|---|---|---|---| -| `i01_alloca_const_int` | · | `int *p = alloca(sizeof(int)); *p = 42; return *p;` | 42 | -| `i02_alloca_runtime_size` | · | `int n=5; int *p = alloca(n*sizeof(int));` fill `1..5`; sum | 15 | -| `i03_alloca_align_16` | · | alloca with 16-byte alignment request; return `((uintptr_t)p & 0xF)==0` | 1 | -| `i04_alloca_in_loop_distinct` | · | 3 iters, each `alloca(4)` + record addr; return `(a!=b && b!=c)` | 1 | -| `i05_alloca_then_call` | · | alloca buf; pass to helper that writes 42; reload after call | 42 | -| `i06_two_allocas_disjoint` | · | `int *p=alloca(4); int *q=alloca(4); *p=1; *q=2; return *p+*q;` | 3 | -| `i07_alloca_addr_escapes` | · | alloca buf; helper stores `&buf` then reads it back | 42 | -| `i08_vla_param_sum` | · | helper `int sum(int n, int a[n])`; pass VLA `1..9`; sum | 45 | -| `i09_alloca_preserves_locals` | · | named `int` locals before+after alloca; both readable post-alloca | 42 | -| `i10_alloca_after_named_local`| · | alloca after a fixed local — frame layout must keep both addressable | 42 | - -## Group J — varargs - -Drives `va_start_`, `va_arg_`, `va_end_`, `va_copy_` on `CGTarget` and -the ABI's vararg classification (`abi_va_list_type` + the -`vararg_*_offset` fields on `ABIFuncInfo`). Each case pairs a variadic -helper (`int f(int n, ...)`) with a `test_main` caller; the helper -allocates an `ap` of `abi_va_list_type` size in a local slot and passes -its address to `va_start_`/`va_arg_`. AArch64 PCS routes int and FP var -args through separate save areas, so spill cases exist for each. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `j01_va_int_sum_3` | · | `int sum(int n, ...)`; `sum(3, 1, 2, 3)` (basic va_start/va_arg/va_end) | 6 | -| `j02_va_zero_args` | · | `sum(0)` — va_start/va_end with zero va_arg calls | 0 | -| `j03_va_int_spill` | · | `sum(10, 1..10)` — 10 var ints (>7 in GPR save area; rest spill) | 55 | -| `j04_va_int64` | · | `sum_ll(2, 21LL, 21LL)` — i64 var args; low 32 of sum | 42 | -| `j05_va_double_sum` | · | `int sumd(int n, ...){ftoi_s of fp accumulator}`; `sumd(3, 1.5, 2.0, 3.5)` | 7 | -| `j06_va_double_spill` | · | `sumd(9, 0.5×9)` — exhaust FP save area; last spills | 4 | -| `j07_va_mixed_int_dbl` | · | `int f(int n, int, double, int, double)`; sum truncated to int | 42 | -| `j08_va_copy` | · | `va_copy(b, a)`; consume first arg from each — equal halves of `42` | 42 | -| `j09_va_two_fixed` | · | `int f(int a, int b, ...) { return a+b+va_arg(); }` — second fixed slot | 42 | - -## Group K — atomics - -Exercises `atomic_load`, `atomic_store`, `atomic_rmw` (every `AtomicOp` -kind), `atomic_cas` (success and failure paths), and `fence`. Each case -stores into an `FSF_ADDR_TAKEN` i32/i64 local, performs one atomic op -via the helper's address operand, then reads back via plain load to -verify the post-state. `MemOrder` is varied across cases so a backend -that bakes an ordering bit reset wins consistency. A successful CAS -returns the prior; failure leaves memory unchanged. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `k01_atomic_load_relaxed` | · | `int x=42; r=atomic_load(&x, RELAXED); return r;` | 42 | -| `k02_atomic_store_load_acq` | · | `atomic_store(&x, 42, RELEASE); r=atomic_load(&x, ACQUIRE);` | 42 | -| `k03_atomic_load_seq_cst` | · | `atomic_load(&x, SEQ_CST)` — full barrier ordering | 42 | -| `k04_atomic_rmw_add` | · | `x=40; prior=rmw(ADD,&x,2,SEQ_CST); return atomic_load(&x);` post-state | 42 | -| `k05_atomic_rmw_xchg` | · | `x=99; rmw(XCHG,&x,42); return load(&x);` | 42 | -| `k06_atomic_rmw_and` | · | `x=0xFF; rmw(AND,&x,0x2A); return load(&x);` | 42 | -| `k07_atomic_rmw_or` | · | `x=0x20; rmw(OR,&x,0x0A); return load(&x);` | 42 | -| `k08_atomic_rmw_xor` | · | `x=0xFF; rmw(XOR,&x,0xD5); return load(&x);` (= 0x2A) | 42 | -| `k09_atomic_rmw_sub` | · | `x=44; rmw(SUB,&x,2); return load(&x);` | 42 | -| `k10_atomic_rmw_nand` | · | `x=0xFF; rmw(NAND,&x,0xD5);` post-state low 8 = `~(0xFF&0xD5)&0xFF = 0x2A` | 42 | -| `k11_atomic_cas_success` | · | `x=10; cas(&x,exp=10,des=42)→ok=1;` post-load | 42 | -| `k12_atomic_cas_failure` | · | `x=10; cas(&x,exp=99,des=42)→ok=0;` post-load (unchanged) | 10 | -| `k13_atomic_load_i64` | · | i64 atomic load of `0x1_0000_002A`; low 8 | 42 | -| `k14_atomic_rmw_prior` | · | return `prior` from `rmw(ADD,&x=40,2)` (not post-state) → 40 | 40 | -| `k15_fence_seq_cst` | · | `fence(SEQ_CST)` between two plain stores+loads; no observable race | 42 | - -## Group L — intrinsics - -Drives `CGTarget.intrinsic` across every `IntrinKind` group. Bit ops -return their result in a single REG dst. `MEMCPY`/`MEMMOVE`/`MEMSET` -take three address/byte/n args and write through memory. Hint kinds -(`PREFETCH`, `EXPECT`, `UNREACHABLE`, `TRAP`, `ASSUME_ALIGNED`) are -emitted on a path the test then steps over; the oracle is the post-hint -control flow. Checked-arith intrinsics return `(result, overflow_flag)` -in two REG dsts; cases observe each independently. - -| Case | Status | Body | Expected | -|---|---|---|---| -| `l01_popcount_u32` | · | `popcount(0x000000FF) → 8` | 8 | -| `l02_popcount_u64` | · | `popcount((u64)-1) → 64` | 64 | -| `l03_ctz_u32` | · | `ctz(0x80) → 7` | 7 | -| `l04_clz_u32` | · | `clz(0x000000FF) → 24` (32-bit) | 24 | -| `l05_bswap16` | · | `bswap16(0x1234) → 0x3412`; low 8 | 18 | -| `l06_bswap32` | · | `bswap32(0x11223344) → 0x44332211`; low 8 | 17 | -| `l07_bswap64` | · | `bswap64(0x1122334455667788) → 0x8877665544332211`; low 8 | 17 | -| `l08_memcpy_4` | · | i32 src=42; `memcpy(&dst,&src,4)`; return dst | 42 | -| `l09_memmove_overlap` | · | `int a[5]={1,2,3,4,5}; memmove(a+1,a,16); return a[4];` (overlap-safe) | 4 | -| `l10_memset_zero` | · | `int b[4]; memset(b,0,16); return b[2];` | 0 | -| `l11_memset_ff` | · | `int b; memset(&b,0xFF,4); return b;` low 8 | 255 | -| `l12_expect_taken` | · | `if (__builtin_expect(x==1,1)) return 42;` with `x=1` | 42 | -| `l13_unreachable_live` | · | `if (x) return 42; else __builtin_unreachable();` with `x=1` | 42 | -| `l14_trap_live` | · | `if (x) return 42; else __builtin_trap();` with `x=1` — trap path unreached | 42 | -| `l15_prefetch_noop` | · | `__builtin_prefetch(p); *p = 42; return *p;` — hint must not corrupt p | 42 | -| `l16_assume_aligned` | · | `p = assume_aligned(p,8); *p=42; return *p;` — hint must round-trip p | 42 | -| `l17_add_overflow_no` | · | `add_overflow(20,22,&r) → ovf=0`; return `r` | 42 | -| `l18_add_overflow_yes` | · | `add_overflow(INT_MAX,1,&r) → ovf=1`; return `ovf` | 1 | -| `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 | - -## Group P — set_loc / debug - -Drives the producer-side wiring described in `doc/DWARF.md` §3: -`cgtest_set_loc` fans the SrcLoc to both `CGTarget.set_loc` (→ MCEmitter -→ per-instruction `debug_emit_row`) and `debug_set_pending_loc`. The -runner constructs a `Debug*` for cases that register W directives, -plumbs it onto `MCEmitter.debug` and `CGTarget.debug`, and calls -`debug_emit` between `cgtarget_finalize` and `obj_finalize`. The case -body still returns 42 so D/R/E/J keep passing; the **W** path is the -metadata oracle and reads the emitted obj back through `cfree_dwarf_*`. - -Phase status: -- Phase 0 wiring (this group's prerequisite): `cgtest_set_loc`, - `MCEmitter.debug` line-row fanout in `emit32`, `CGTarget.debug`, and - `cgtest_begin_func` / `cgtest_end` calling `debug_func_begin` / - `debug_func_pc_range` are all in place. -- Phase 1+2 (real `.debug_*` sections + `cfree_dwarf_open`): owned by - Agents A/B; W flips green for p01..p05 once both land. -- Phase 3 (`debug_local`, `cfree_dwarf_var_at`): unblocks p07. - -| Case | Status | Body | Expected (D/E/J / W) | -|---|---|---|---| -| `p01_line_one_inst` | · | `set_loc(p01.c:10)` before single `load_imm 42; ret`; W asserts addr↔line round-trip and `subprogram test_main` | 42 / line p01.c:10 + subprogram test_main | -| `p02_line_monotone` | · | three `set_loc` transitions on (p02.c, 1/2/3), each followed by a `load_imm`; W asserts all three lines round-trip | 42 / lines p02.c:1,2,3 + subprogram test_main | -| `p03_line_repeat` | · | `set_loc(p03.c:7)` → `load_imm`; `set_loc(p03.c:8)` → `load_imm`; `set_loc(p03.c:7)` again before final `load_imm`. W asserts the (p03.c, 7) binding survives the round-trip | 42 / line p03.c:7 + subprogram test_main | -| `p05_func_pc_range` | · | identical to p01 with file `p05.c`; W additionally asserts the subprogram pc range size lies in [16, 256] bytes | 42 / line p05.c:11 + subprogram + pc_range | -| `p07_local_loc` | Phase 3 | one i32 local (`my_local`) stored to and reloaded from a frame slot; W asserts `var_at` returns a frame-relative location for the name | 42 / line p07.c:5 + subprogram + var (Phase 3) | - -## Deferred groups - -| Group | Theme | -|---|---| -| M | inline asm | -| R | opt-wrapped equivalence | diff --git a/test/cg/binder_test.c b/test/cg/binder_test.c @@ -1,538 +0,0 @@ -/* Unit test for cg_inline_asm — the constraint binder (Track B of - * doc/INLINEASM.md). Builds a Compiler with a stand-in CGTarget that - * records every operand the binder hands to asm_block, then asserts the - * binding shape for each constraint kind: - * - * "r" — input forced to OPK_REG. - * "=r" — output gets a fresh REG, pushed back as an SValue. - * "+r" — output reg is the same as the matching input slot's reg. - * "=&r" — output reg is disjoint from any input reg. - * "i" — input must be OPK_IMM; passes through. - * "m" — addressable lvalue → OPK_INDIRECT in the bound input. - * "0"..."N" — matching input bound to out_ops[N].v.reg. - * "memory" — every live RES_REG SValue on the CG stack is spilled - * via target->spill_reg before asm_block fires. - * register-name — passed straight through in the clobbers array. - * "cc" — accepted-and-dropped on the binder side (still appears - * in the clobbers array we forward — the arch backend - * handles the no-op). - * - * The mock target is the smallest thing that compiles: it hands out reg - * ids 1, 2, 3, ... from a tiny pool, refuses to do real codegen, and - * appends every call into a log buffer the test asserts against. - * - * Built standalone (no cg-runner dependency) so the test runs without - * the JIT / link harness. Wired into test/test.mk as a separate target - * (test-cg-binder). */ - -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <cfree.h> - -#include "abi/abi.h" -#include "arch/arch.h" -#include "cg/cg.h" -#include "core/core.h" -#include "core/heap.h" -#include "core/pool.h" -#include "type/type.h" - -/* ---- host glue ------------------------------------------------------- */ - -static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { - (void)h; - (void)a; - return n ? malloc(n) : NULL; -} -static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) { - (void)h; - (void)o; - (void)a; - return realloc(p, n); -} -static void h_free(CfreeHeap* h, void* p, size_t n) { - (void)h; - (void)n; - free(p); -} -static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; - -static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, - const char* fmt, va_list ap) { - static const char* names[] = {"note", "warning", "error", "fatal"}; - (void)s; - (void)loc; - fprintf(stderr, "%s: ", names[k]); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); -} -static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0}; - -/* ---- mock CGTarget --------------------------------------------------- */ - -#define MOCK_LOG_CAP 4096u -#define MOCK_REG_CAP 16u - -typedef struct MockTarget { - CGTarget base; - - /* Tiny reg pool: hand out ids in [1, MOCK_REG_CAP]. 0 is "free", 1 is - * "in use". Class is ignored — the binder asks for RC_INT throughout. */ - u8 in_use[MOCK_REG_CAP + 1u]; - - /* Spill-slot id counter; binder doesn't care about layout, only id - * uniqueness. */ - FrameSlot next_slot; - - /* Recorded asm_block call (last one wins; the binder only fires once - * per cg_inline_asm). */ - int asm_called; - const char* tmpl; - u32 nout, nin, nclob; - Operand out_ops[8]; - Operand in_ops[8]; - Sym clobbers[8]; - - /* Log of side effects: spills, copies, load_imms, addr_ofs, free_regs. - * Each entry is a one-line summary; the test scans for substrings. */ - char log[MOCK_LOG_CAP]; - u32 log_len; -} MockTarget; - -static void mock_logf(MockTarget* m, const char* fmt, ...) { - if (m->log_len >= MOCK_LOG_CAP - 1u) return; - va_list ap; - va_start(ap, fmt); - int n = vsnprintf(m->log + m->log_len, MOCK_LOG_CAP - m->log_len, fmt, ap); - va_end(ap); - if (n > 0) m->log_len += (u32)n; -} - -static Reg m_alloc_reg(CGTarget* t, RegClass cls, const Type* ty) { - (void)cls; - (void)ty; - MockTarget* m = (MockTarget*)t; - for (u32 i = 1; i <= MOCK_REG_CAP; ++i) { - if (!m->in_use[i]) { - m->in_use[i] = 1; - return (Reg)i; - } - } - return (Reg)REG_NONE; -} - -static void m_free_reg(CGTarget* t, Reg r, RegClass cls) { - (void)cls; - MockTarget* m = (MockTarget*)t; - if (r != (Reg)REG_NONE && r <= MOCK_REG_CAP) m->in_use[r] = 0; - mock_logf(m, "free_reg r%u\n", (unsigned)r); -} - -static FrameSlot m_frame_slot(CGTarget* t, const FrameSlotDesc* d) { - (void)d; - MockTarget* m = (MockTarget*)t; - return ++m->next_slot; -} - -static void m_spill_reg(CGTarget* t, Operand src, FrameSlot s, MemAccess ma) { - (void)ma; - MockTarget* m = (MockTarget*)t; - mock_logf(m, "spill_reg r%u -> slot %u\n", (unsigned)src.v.reg, (unsigned)s); -} - -static void m_reload_reg(CGTarget* t, Operand dst, FrameSlot s, MemAccess ma) { - (void)ma; - MockTarget* m = (MockTarget*)t; - mock_logf(m, "reload_reg slot %u -> r%u\n", (unsigned)s, (unsigned)dst.v.reg); -} - -static void m_load_imm(CGTarget* t, Operand dst, i64 imm) { - MockTarget* m = (MockTarget*)t; - mock_logf(m, "load_imm r%u = %lld\n", (unsigned)dst.v.reg, (long long)imm); -} - -static void m_copy(CGTarget* t, Operand dst, Operand src) { - MockTarget* m = (MockTarget*)t; - mock_logf(m, "copy r%u <- r%u\n", (unsigned)dst.v.reg, (unsigned)src.v.reg); -} - -static void m_addr_of(CGTarget* t, Operand dst, Operand lv) { - MockTarget* m = (MockTarget*)t; - unsigned src_id = 0; - switch (lv.kind) { - case OPK_LOCAL: src_id = (unsigned)lv.v.frame_slot; break; - case OPK_GLOBAL: src_id = (unsigned)lv.v.global.sym; break; - case OPK_INDIRECT: src_id = (unsigned)lv.v.ind.base; break; - default: src_id = 0; - } - mock_logf(m, "addr_of r%u <- kind=%u id=%u\n", (unsigned)dst.v.reg, - (unsigned)lv.kind, src_id); -} - -static void m_set_loc(CGTarget* t, SrcLoc loc) { - (void)t; - (void)loc; -} - -static void m_func_begin(CGTarget* t, const CGFuncDesc* fd) { - (void)t; - (void)fd; -} -static void m_func_end(CGTarget* t) { (void)t; } -static void m_param(CGTarget* t, const CGParamDesc* d) { - (void)t; - (void)d; -} - -static void m_asm_block(CGTarget* t, const char* tmpl, - const AsmConstraint* outs, u32 nout, Operand* out_ops, - const AsmConstraint* ins, u32 nin, - const Operand* in_ops, const Sym* clobbers, u32 nclob) { - (void)outs; - (void)ins; - MockTarget* m = (MockTarget*)t; - m->asm_called = 1; - m->tmpl = tmpl; - m->nout = nout; - m->nin = nin; - m->nclob = nclob; - if (nout > 8) nout = 8; - if (nin > 8) nin = 8; - if (nclob > 8) nclob = 8; - for (u32 i = 0; i < nout; ++i) m->out_ops[i] = out_ops[i]; - for (u32 i = 0; i < nin; ++i) m->in_ops[i] = in_ops[i]; - for (u32 i = 0; i < nclob; ++i) m->clobbers[i] = clobbers[i]; - mock_logf(m, "asm_block tmpl=%s nout=%u nin=%u nclob=%u\n", - tmpl ? tmpl : "(null)", (unsigned)m->nout, (unsigned)m->nin, - (unsigned)m->nclob); -} - -static void mock_target_init(MockTarget* m, Compiler* c) { - memset(m, 0, sizeof *m); - m->base.c = c; - m->base.alloc_reg = m_alloc_reg; - m->base.free_reg = m_free_reg; - m->base.frame_slot = m_frame_slot; - m->base.param = m_param; - m->base.spill_reg = m_spill_reg; - m->base.reload_reg = m_reload_reg; - m->base.load_imm = m_load_imm; - m->base.copy = m_copy; - m->base.addr_of = m_addr_of; - m->base.func_begin = m_func_begin; - m->base.func_end = m_func_end; - m->base.set_loc = m_set_loc; - m->base.asm_block = m_asm_block; -} - -/* ---- per-test compiler scaffold ------------------------------------- */ - -typedef struct TestCtx { - Compiler cc; - Compiler* c; - MockTarget mt; - CG* g; - const Type* i64_ty; -} TestCtx; - -static void tc_init(TestCtx* tc) { - CfreeEnv env; - memset(&env, 0, sizeof env); - env.heap = &g_heap; - env.diag = &g_diag; - env.now = -1; - - CfreeTarget tgt; - memset(&tgt, 0, sizeof tgt); - tgt.arch = CFREE_ARCH_ARM_64; - tgt.os = CFREE_OS_LINUX; - tgt.obj = CFREE_OBJ_ELF; - tgt.ptr_size = 8; - tgt.ptr_align = 8; - - /* compiler_init wants the env on the heap-pointer side; stash it. */ - static CfreeEnv s_env_stash; - s_env_stash = env; - compiler_init(&tc->cc, tgt, &s_env_stash); - tc->c = &tc->cc; - - mock_target_init(&tc->mt, tc->c); - tc->g = cg_new(tc->c, &tc->mt.base, NULL); - tc->i64_ty = type_prim(tc->c->global, TY_LLONG); -} - -static void tc_fini(TestCtx* tc) { - cg_free(tc->g); - compiler_fini(&tc->cc); -} - -/* ---- assertion helpers ----------------------------------------------- */ - -static int g_fails = 0; -static int g_cases = 0; - -#define EXPECT(cond, ...) do { \ - ++g_cases; \ - if (!(cond)) { \ - ++g_fails; \ - fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ - fprintf(stderr, " "); \ - fprintf(stderr, __VA_ARGS__); \ - fputc('\n', stderr); \ - } \ -} while (0) - -static int log_contains(const MockTarget* m, const char* needle) { - return strstr(m->log, needle) != NULL; -} - -/* ---- test cases ------------------------------------------------------ */ - -static void test_r_in(void) { - TestCtx tc; - tc_init(&tc); - /* asm("nop" :: "r"(42)) */ - cg_push_int(tc.g, 42, tc.i64_ty); - AsmConstraint ins[1] = {{.str="r", .dir=ASM_IN}}; - cg_inline_asm(tc.g, "nop", NULL, 0, ins, 1, NULL, 0); - EXPECT(tc.mt.asm_called, "asm_block was not invoked"); - EXPECT(tc.mt.nin == 1, "nin=%u", tc.mt.nin); - EXPECT(tc.mt.in_ops[0].kind == OPK_REG, "in_ops[0].kind=%u", - tc.mt.in_ops[0].kind); - /* The IMM was materialized into a freshly-allocated reg; load_imm shows it. */ - EXPECT(log_contains(&tc.mt, "load_imm"), "missing load_imm in log:\n%s", - tc.mt.log); - tc_fini(&tc); -} - -static void test_eq_r_out(void) { - TestCtx tc; - tc_init(&tc); - /* asm("mov %0, #1" : "=r"(x)) — pushes an output SValue back. */ - AsmConstraint outs[1] = {{.str="=r", .dir=ASM_OUT}}; - cg_inline_asm(tc.g, "mov %0, #1", outs, 1, NULL, 0, NULL, 0); - EXPECT(tc.mt.asm_called, "asm_block was not invoked"); - EXPECT(tc.mt.nout == 1, "nout=%u", tc.mt.nout); - EXPECT(tc.mt.out_ops[0].kind == OPK_REG, "out_ops[0].kind=%u", - tc.mt.out_ops[0].kind); - EXPECT(tc.mt.out_ops[0].v.reg != (Reg)REG_NONE, "out reg should be allocated"); - tc_fini(&tc); -} - -static void test_plus_r_inout(void) { - TestCtx tc; - tc_init(&tc); - /* +r is GCC's "use this reg as both input and output". The parser - * convention this binder honors: emit one output with =r-style behavior - * and one matching input "0" with the input value to seed the reg. */ - cg_push_int(tc.g, 7, tc.i64_ty); - AsmConstraint outs[1] = {{.str="+r", .dir=ASM_INOUT}}; - AsmConstraint ins[1] = {{.str="0", .dir=ASM_IN}}; - cg_inline_asm(tc.g, "add %0, %0, #1", outs, 1, ins, 1, NULL, 0); - EXPECT(tc.mt.nout == 1 && tc.mt.nin == 1, "nout/nin"); - EXPECT(tc.mt.out_ops[0].kind == OPK_REG, "out reg"); - EXPECT(tc.mt.in_ops[0].kind == OPK_REG, "in reg"); - EXPECT(tc.mt.out_ops[0].v.reg == tc.mt.in_ops[0].v.reg, - "matching constraint should bind to same reg (out=%u in=%u)", - (unsigned)tc.mt.out_ops[0].v.reg, (unsigned)tc.mt.in_ops[0].v.reg); - tc_fini(&tc); -} - -static void test_eq_amp_r_early_clobber(void) { - TestCtx tc; - tc_init(&tc); - /* asm("..." : "=&r"(x) : "r"(y)) — output reg must differ from input reg. */ - cg_push_int(tc.g, 5, tc.i64_ty); - AsmConstraint outs[1] = {{.str="=&r", .dir=ASM_OUT}}; - AsmConstraint ins[1] = {{.str="r", .dir=ASM_IN}}; - cg_inline_asm(tc.g, "tmpl", outs, 1, ins, 1, NULL, 0); - EXPECT(tc.mt.out_ops[0].kind == OPK_REG && tc.mt.in_ops[0].kind == OPK_REG, - "REGs expected"); - EXPECT(tc.mt.out_ops[0].v.reg != tc.mt.in_ops[0].v.reg, - "early-clobber should be disjoint (out=%u in=%u)", - (unsigned)tc.mt.out_ops[0].v.reg, (unsigned)tc.mt.in_ops[0].v.reg); - tc_fini(&tc); -} - -static void test_i_constant(void) { - TestCtx tc; - tc_init(&tc); - cg_push_int(tc.g, 99, tc.i64_ty); - AsmConstraint ins[1] = {{.str="i", .dir=ASM_IN}}; - cg_inline_asm(tc.g, "tmpl", NULL, 0, ins, 1, NULL, 0); - EXPECT(tc.mt.in_ops[0].kind == OPK_IMM, "in kind=%u", tc.mt.in_ops[0].kind); - EXPECT(tc.mt.in_ops[0].v.imm == 99, "in imm=%lld", - (long long)tc.mt.in_ops[0].v.imm); - /* No load_imm (the binder forwards the IMM unchanged). */ - EXPECT(!log_contains(&tc.mt, "load_imm"), - "'i' should not load_imm, log:\n%s", tc.mt.log); - tc_fini(&tc); -} - -static void test_m_memory_lvalue(void) { - TestCtx tc; - tc_init(&tc); - /* Push a local lvalue; "m" should materialize it into OPK_INDIRECT - * via target->addr_of. */ - FrameSlotDesc fsd; - memset(&fsd, 0, sizeof fsd); - fsd.size = 8; - fsd.align = 8; - fsd.kind = FS_LOCAL; - FrameSlot s = cg_local(tc.g, &fsd); - /* Use the type-aware push path. We declare it via prototype: */ - void cg_push_local_typed(CG*, FrameSlot, const Type*); - cg_push_local_typed(tc.g, s, tc.i64_ty); - AsmConstraint ins[1] = {{.str="m", .dir=ASM_IN}}; - cg_inline_asm(tc.g, "ldr w0, %0", NULL, 0, ins, 1, NULL, 0); - EXPECT(tc.mt.in_ops[0].kind == OPK_INDIRECT, "in kind=%u", - tc.mt.in_ops[0].kind); - EXPECT(log_contains(&tc.mt, "addr_of"), - "expected addr_of in log:\n%s", tc.mt.log); - tc_fini(&tc); -} - -static void test_matching_input(void) { - TestCtx tc; - tc_init(&tc); - /* Output =r at index 0; input "0" should bind to its reg. */ - cg_push_int(tc.g, 11, tc.i64_ty); - AsmConstraint outs[1] = {{.str="=r", .dir=ASM_OUT}}; - AsmConstraint ins[1] = {{.str="0", .dir=ASM_IN}}; - cg_inline_asm(tc.g, "tmpl", outs, 1, ins, 1, NULL, 0); - EXPECT(tc.mt.out_ops[0].v.reg == tc.mt.in_ops[0].v.reg, - "matching '0' input should reuse out reg (out=%u in=%u)", - (unsigned)tc.mt.out_ops[0].v.reg, (unsigned)tc.mt.in_ops[0].v.reg); - tc_fini(&tc); -} - -static void test_memory_clobber_spills_live_regs(void) { - TestCtx tc; - tc_init(&tc); - /* Push a LOCAL lvalue, load it into a reg via cg_load — that leaves a - * live RES_REG SValue at the bottom of the stack. Then call asm with a - * "memory" clobber and verify the live reg got spilled before the - * asm_block fired. */ - FrameSlotDesc fsd; - memset(&fsd, 0, sizeof fsd); - fsd.size = 8; - fsd.align = 8; - fsd.kind = FS_LOCAL; - FrameSlot s = cg_local(tc.g, &fsd); - void cg_push_local_typed(CG*, FrameSlot, const Type*); - cg_push_local_typed(tc.g, s, tc.i64_ty); - /* Need a load implementation on the mock to promote LOCAL → REG. - * The mock doesn't implement target->load — instead push an immediate - * to get a REG-resident SValue without calling cg_load. */ - cg_push_int(tc.g, 0, tc.i64_ty); /* IMM, not REG, won't be a spill victim */ - - /* Force a REG-resident SValue at the bottom by pushing an int and - * promoting via cg_inline_asm itself — easier: skip this complexity and - * directly observe spill via a real reg-resident value built by =r - * output being pushed back. */ - AsmConstraint outs1[1] = {{.str="=r", .dir=ASM_OUT}}; - cg_inline_asm(tc.g, "produce", outs1, 1, NULL, 0, NULL, 0); - /* Now the stack has a REG-resident SValue from the produced output. - * Reset the log before the second call so we can scan for spill_reg - * specifically caused by "memory". */ - tc.mt.log_len = 0; - tc.mt.log[0] = '\0'; - - Sym mem_sym = pool_intern_cstr(tc.c->global, "memory"); - Sym clobs[1] = {mem_sym}; - cg_inline_asm(tc.g, "barrier", NULL, 0, NULL, 0, clobs, 1); - EXPECT(log_contains(&tc.mt, "spill_reg"), - "expected spill_reg from memory clobber, log:\n%s", tc.mt.log); - tc_fini(&tc); -} - -static void test_register_clobber_passthrough(void) { - TestCtx tc; - tc_init(&tc); - Sym x0_sym = pool_intern_cstr(tc.c->global, "x0"); - Sym clobs[1] = {x0_sym}; - cg_inline_asm(tc.g, "tmpl", NULL, 0, NULL, 0, clobs, 1); - EXPECT(tc.mt.nclob == 1, "nclob=%u", tc.mt.nclob); - EXPECT(tc.mt.clobbers[0] == x0_sym, "register clobber not forwarded"); - tc_fini(&tc); -} - -static void test_cc_clobber_silent(void) { - TestCtx tc; - tc_init(&tc); - /* "cc" should not cause spills; the binder forwards it but does no - * special work. (Arch backends drop it on aarch64.) */ - Sym cc_sym = pool_intern_cstr(tc.c->global, "cc"); - Sym clobs[1] = {cc_sym}; - /* Arrange a live REG-resident SValue first; verify it is NOT spilled. */ - AsmConstraint outs1[1] = {{.str="=r", .dir=ASM_OUT}}; - cg_inline_asm(tc.g, "produce", outs1, 1, NULL, 0, NULL, 0); - tc.mt.log_len = 0; - tc.mt.log[0] = '\0'; - cg_inline_asm(tc.g, "tmpl", NULL, 0, NULL, 0, clobs, 1); - EXPECT(!log_contains(&tc.mt, "spill_reg"), - "'cc' must not trigger spills, log:\n%s", tc.mt.log); - EXPECT(tc.mt.nclob == 1, "nclob=%u", tc.mt.nclob); - tc_fini(&tc); -} - -/* AsmConstraint.type drives RegClass for fresh output regs. An FP-typed - * output must land in RC_FP; a pointer-typed output stays in RC_INT. - * Hand-built (NULL-type) constraints fall back to int / 64-bit (covered - * by every other case in this file). */ -static void test_output_type_drives_regclass(void) { - TestCtx tc; - tc_init(&tc); - - /* asm("..." : "=r"(double_var)) → RC_FP allocation. */ - const Type* dbl_ty = type_prim(tc.c->global, TY_DOUBLE); - AsmConstraint outs_fp[1] = {{.str="=r", .type=dbl_ty, .dir=ASM_OUT}}; - cg_inline_asm(tc.g, "fmov %0, #1.0", outs_fp, 1, NULL, 0, NULL, 0); - EXPECT(tc.mt.nout == 1, "nout=%u", tc.mt.nout); - EXPECT(tc.mt.out_ops[0].kind == OPK_REG, "fp out kind=%u", - tc.mt.out_ops[0].kind); - EXPECT(tc.mt.out_ops[0].cls == RC_FP, - "fp out cls=%u (expected RC_FP=%u)", tc.mt.out_ops[0].cls, RC_FP); - EXPECT(tc.mt.out_ops[0].type == dbl_ty, - "fp out type lost through binder"); - - /* Drop the SValue the previous call pushed back so the next call - * starts from a clean stack. */ - cg_drop(tc.g); - tc.mt.asm_called = 0; - tc.mt.nout = 0; - - /* asm("..." : "=r"(int_ptr)) → RC_INT, pointer type preserved. */ - const Type* ptr_ty = type_ptr(tc.c->global, type_prim(tc.c->global, TY_INT)); - AsmConstraint outs_p[1] = {{.str="=r", .type=ptr_ty, .dir=ASM_OUT}}; - cg_inline_asm(tc.g, "mov %0, sp", outs_p, 1, NULL, 0, NULL, 0); - EXPECT(tc.mt.out_ops[0].kind == OPK_REG, "ptr out kind=%u", - tc.mt.out_ops[0].kind); - EXPECT(tc.mt.out_ops[0].cls == RC_INT, - "ptr out cls=%u (expected RC_INT=%u)", tc.mt.out_ops[0].cls, RC_INT); - EXPECT(tc.mt.out_ops[0].type == ptr_ty, - "ptr out type lost through binder"); - - cg_drop(tc.g); - tc_fini(&tc); -} - -int main(void) { - test_r_in(); - test_eq_r_out(); - test_plus_r_inout(); - test_eq_amp_r_early_clobber(); - test_i_constant(); - test_m_memory_lvalue(); - test_matching_input(); - test_memory_clobber_spills_live_regs(); - test_register_clobber_passthrough(); - test_cc_clobber_silent(); - test_output_type_drives_regclass(); - - fprintf(stderr, "binder_test: %d cases, %d failures\n", g_cases, g_fails); - return g_fails ? 1 : 0; -} diff --git a/test/cg/dwarf_validate.sh b/test/cg/dwarf_validate.sh @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -# test/cg/dwarf_validate.sh — optional third-party DWARF validators. -# -# Per doc/DWARF.md §5.3: run `llvm-dwarfdump --verify` and `readelf` over -# the Phase-1 obj files Group P produces. These are NOT the oracle for -# any case; the W path's `cg_check_dwarf` is. They exist to catch wire- -# format errors that our own consumer would miss in the same way the -# producer makes them. -# -# Usage: -# test/cg/dwarf_validate.sh [obj-file ...] -# -# With no arguments, validates every emitted obj under build/test/cg/p*/. -# Tools are gated on `command -v` checks; missing tools are skipped -# silently (exit 0). One non-zero per failed verify; the script returns -# the count of failures. - -set -u - -ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -BUILD_DIR="$ROOT/build/test/cg" - -DWARFDUMP="$(command -v llvm-dwarfdump 2>/dev/null || true)" -READELF_BIN="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)" - -if [ -z "$DWARFDUMP" ] && [ -z "$READELF_BIN" ]; then - printf 'dwarf_validate: neither llvm-dwarfdump nor readelf in PATH; skipping\n' - exit 0 -fi - -# Collect targets. -declare -a OBJS -if [ $# -gt 0 ]; then - OBJS=("$@") -else - if [ ! -d "$BUILD_DIR" ]; then - printf 'dwarf_validate: %s does not exist; run test-cg first\n' "$BUILD_DIR" >&2 - exit 0 - fi - while IFS= read -r f; do OBJS+=("$f"); done \ - < <(find "$BUILD_DIR" -path '*/p*/p*.o' -type f 2>/dev/null) -fi - -if [ ${#OBJS[@]} -eq 0 ]; then - printf 'dwarf_validate: no Group P obj files found; skipping\n' - exit 0 -fi - -fails=0 -for obj in "${OBJS[@]}"; do - [ -f "$obj" ] || continue - printf '== %s ==\n' "$obj" - - if [ -n "$DWARFDUMP" ]; then - if ! "$DWARFDUMP" --verify "$obj" >/tmp/dwarf_verify.out 2>&1; then - printf ' FAIL llvm-dwarfdump --verify\n' - sed -n '1,40p' /tmp/dwarf_verify.out | sed 's/^/ /' - fails=$((fails + 1)) - else - printf ' PASS llvm-dwarfdump --verify\n' - fi - fi - - if [ -n "$READELF_BIN" ]; then - # Reference render. Non-zero return is a structural error; we - # don't diff content, just confirm the reader can walk every - # required section. - if ! "$READELF_BIN" --debug-dump=info,line,abbrev,aranges \ - "$obj" >/tmp/dwarf_readelf.out 2>&1; then - printf ' FAIL readelf --debug-dump=info,line,abbrev,aranges\n' - sed -n '1,20p' /tmp/dwarf_readelf.out | sed 's/^/ /' - fails=$((fails + 1)) - else - printf ' PASS readelf --debug-dump=info,line,abbrev,aranges\n' - fi - fi -done - -total=${#OBJS[@]} -printf '\ndwarf_validate: %d/%d objs failed\n' "$fails" "$total" -exit "$fails" diff --git a/test/cg/harness/cases.c b/test/cg/harness/cases.c @@ -1,555 +0,0 @@ -/* Test case registry. - * - * 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: 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" - -/* ---- 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*); - -void build_j01_va_int_sum_3(CgTestCtx*); -void build_j02_va_zero_args(CgTestCtx*); -void build_j03_va_int_spill(CgTestCtx*); -void build_j04_va_int64(CgTestCtx*); -void build_j05_va_double_sum(CgTestCtx*); -void build_j06_va_double_spill(CgTestCtx*); -void build_j07_va_mixed_int_dbl(CgTestCtx*); -void build_j08_va_copy(CgTestCtx*); -void build_j09_va_two_fixed(CgTestCtx*); - -void build_k01_atomic_load_relaxed(CgTestCtx*); -void build_k02_atomic_store_load_acq(CgTestCtx*); -void build_k03_atomic_load_seq_cst(CgTestCtx*); -void build_k04_atomic_rmw_add(CgTestCtx*); -void build_k05_atomic_rmw_xchg(CgTestCtx*); -void build_k06_atomic_rmw_and(CgTestCtx*); -void build_k07_atomic_rmw_or(CgTestCtx*); -void build_k08_atomic_rmw_xor(CgTestCtx*); -void build_k09_atomic_rmw_sub(CgTestCtx*); -void build_k10_atomic_rmw_nand(CgTestCtx*); -void build_k11_atomic_cas_success(CgTestCtx*); -void build_k12_atomic_cas_failure(CgTestCtx*); -void build_k13_atomic_load_i64(CgTestCtx*); -void build_k14_atomic_rmw_prior(CgTestCtx*); -void build_k15_fence_seq_cst(CgTestCtx*); - -void build_l01_popcount_u32(CgTestCtx*); -void build_l02_popcount_u64(CgTestCtx*); -void build_l03_ctz_u32(CgTestCtx*); -void build_l04_clz_u32(CgTestCtx*); -void build_l05_bswap16(CgTestCtx*); -void build_l06_bswap32(CgTestCtx*); -void build_l07_bswap64(CgTestCtx*); -void build_l08_memcpy_4(CgTestCtx*); -void build_l09_memmove_overlap(CgTestCtx*); -void build_l10_memset_zero(CgTestCtx*); -void build_l11_memset_ff(CgTestCtx*); -void build_l12_expect_taken(CgTestCtx*); -void build_l13_unreachable_live(CgTestCtx*); -void build_l14_trap_live(CgTestCtx*); -void build_l15_prefetch_noop(CgTestCtx*); -void build_l16_assume_aligned(CgTestCtx*); -void build_l17_add_overflow_no(CgTestCtx*); -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_p01_line_one_inst(CgTestCtx*); -void build_p02_line_monotone(CgTestCtx*); -void build_p03_line_repeat(CgTestCtx*); -void build_p05_func_pc_range(CgTestCtx*); -void build_p07_local_loc(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*); -void build_asm01_mov_imm_out(CgTestCtx*); -void build_asm02_copy_input(CgTestCtx*); -void build_asm03_named_clobber(CgTestCtx*); - -/* ---- registry ---- */ - -const CgCase cg_cases[] = { - /* MC-only */ - {"mc_smoke", build_mc_smoke, 42, CG_CASE_MC_ONLY, CG_ARCH_AARCH64}, - - /* Group A — function lifecycle and return */ - {"a01_return_const_42", build_a01_return_const_42, 42, CG_CASE_DEFAULT}, - {"a02_return_zero", build_a02_return_zero, 0, CG_CASE_DEFAULT}, - {"a03_ret_imm", build_a03_ret_imm, 17, CG_CASE_DEFAULT}, - {"a04_copy_reg", build_a04_copy_reg, 7, CG_CASE_DEFAULT}, - {"a05_return_neg_small", build_a05_return_neg_small, 249, CG_CASE_DEFAULT}, - {"a06_return_i64", build_a06_return_i64, 42, CG_CASE_DEFAULT}, - {"a07_void_return", build_a07_void_return, 0, CG_CASE_DEFAULT}, - {"a08_multiple_returns", build_a08_multiple_returns, 1, CG_CASE_DEFAULT}, - {"a09_load_imm_movz_movk", build_a09_load_imm_movz_movk, 205, - CG_CASE_DEFAULT}, - {"a10_return_u8", build_a10_return_u8, 200, CG_CASE_DEFAULT}, - - /* Group B — frame slots, parameters, locals */ - {"b01_param_int", build_b01_param_int, 201, CG_CASE_DEFAULT}, - {"b02_param_sum", build_b02_param_sum, 42, CG_CASE_DEFAULT}, - {"b03_param_spill", build_b03_param_spill, 45, CG_CASE_DEFAULT}, - {"b04_local_int", build_b04_local_int, 42, CG_CASE_DEFAULT}, - {"b05_addr_taken_local", build_b05_addr_taken_local, 18, CG_CASE_DEFAULT}, - {"b06_sret", build_b06_sret, 42, CG_CASE_DEFAULT}, - {"b07_byval_param", build_b07_byval_param, 42, CG_CASE_DEFAULT}, - {"b08_fp_param", build_b08_fp_param, 7, CG_CASE_DEFAULT}, - - /* Group C — integer arithmetic */ - {"c01_add", build_c01_add, 3, CG_CASE_DEFAULT}, - {"c02_sub_mul", build_c02_sub_mul, 17, CG_CASE_DEFAULT}, - {"c03_bitwise", build_c03_bitwise, 252, CG_CASE_DEFAULT}, - {"c04_shift", build_c04_shift, 40, CG_CASE_DEFAULT}, - {"c05_div_mod", build_c05_div_mod, 8, CG_CASE_DEFAULT}, - {"c06_xor", build_c06_xor, 255, CG_CASE_DEFAULT}, - {"c07_iadd_i64", build_c07_iadd_i64, 42, CG_CASE_DEFAULT}, - {"c08_unsigned_div", build_c08_unsigned_div, 14, CG_CASE_DEFAULT}, - {"c09_neg", build_c09_neg, 214, CG_CASE_DEFAULT}, - {"c10_logical_not", build_c10_logical_not, 1, CG_CASE_DEFAULT}, - {"c11_shr_signed", build_c11_shr_signed, 252, CG_CASE_DEFAULT}, - {"c12_imul_i64", build_c12_imul_i64, 42, CG_CASE_DEFAULT}, - - /* Group D — compare and branch */ - {"d01_cmp_eq_true", build_d01_cmp_eq_true, 1, CG_CASE_DEFAULT}, - {"d02_cmp_eq_false", build_d02_cmp_eq_false, 0, CG_CASE_DEFAULT}, - {"d03_cmp_ne", build_d03_cmp_ne, 1, CG_CASE_DEFAULT}, - {"d04_cmp_lt_signed", build_d04_cmp_lt_signed, 1, CG_CASE_DEFAULT}, - {"d05_cmp_lt_unsigned", build_d05_cmp_lt_unsigned, 0, CG_CASE_DEFAULT}, - {"d06_cmp_ge_signed", build_d06_cmp_ge_signed, 1, CG_CASE_DEFAULT}, - {"d07_cmp_branch_taken", build_d07_cmp_branch_taken, 42, CG_CASE_DEFAULT}, - {"d08_cmp_branch_not_taken", build_d08_cmp_branch_not_taken, 33, - CG_CASE_DEFAULT}, - {"d09_cmp_branch_lt_signed", build_d09_cmp_branch_lt_signed, 9, - CG_CASE_DEFAULT}, - {"d10_jump", build_d10_jump, 5, CG_CASE_DEFAULT}, - {"d11_scope_if_true", build_d11_scope_if_true, 33, CG_CASE_DEFAULT}, - {"d12_scope_if_false", build_d12_scope_if_false, 99, CG_CASE_DEFAULT}, - {"d13_scope_if_else", build_d13_scope_if_else, 7, CG_CASE_DEFAULT}, - - /* Group E — conversions */ - {"e01_sext_i8_i32", build_e01_sext_i8_i32, 255, CG_CASE_DEFAULT}, - {"e02_zext_u8_i32", build_e02_zext_u8_i32, 255, CG_CASE_DEFAULT}, - {"e03_sext_i16_i32", build_e03_sext_i16_i32, 24, CG_CASE_DEFAULT}, - {"e04_zext_u16_i32", build_e04_zext_u16_i32, 205, CG_CASE_DEFAULT}, - {"e05_zext_u32_i64", build_e05_zext_u32_i64, 255, CG_CASE_DEFAULT}, - {"e06_sext_i32_i64", build_e06_sext_i32_i64, 255, CG_CASE_DEFAULT}, - {"e07_trunc_i64_i32", build_e07_trunc_i64_i32, 128, CG_CASE_DEFAULT}, - {"e08_trunc_i32_i8", build_e08_trunc_i32_i8, 255, CG_CASE_DEFAULT}, - {"e09_itof_s_i32_f32", build_e09_itof_s_i32_f32, 7, CG_CASE_DEFAULT}, - {"e10_itof_u_u32_f64", build_e10_itof_u_u32_f64, 100, CG_CASE_DEFAULT}, - {"e11_ftoi_s_neg", build_e11_ftoi_s_neg, 255, CG_CASE_DEFAULT}, - {"e12_ftoi_u_pos", build_e12_ftoi_u_pos, 200, CG_CASE_DEFAULT}, - {"e13_fext_f32_f64", build_e13_fext_f32_f64, 3, CG_CASE_DEFAULT}, - {"e14_ftrunc_f64_f32", build_e14_ftrunc_f64_f32, 7, CG_CASE_DEFAULT}, - {"e15_bitcast_i32_f32", build_e15_bitcast_i32_f32, 5, CG_CASE_DEFAULT}, - - /* Group F — memory (loads/stores beyond locals) */ - {"f01_load_store_i8", build_f01_load_store_i8, 200, CG_CASE_DEFAULT}, - {"f02_load_store_i16", build_f02_load_store_i16, 52, CG_CASE_DEFAULT}, - {"f03_load_store_i64", build_f03_load_store_i64, 66, CG_CASE_DEFAULT}, - {"f04_load_store_f32", build_f04_load_store_f32, 7, CG_CASE_DEFAULT}, - {"f05_load_store_f64", build_f05_load_store_f64, 3, CG_CASE_DEFAULT}, - {"f06_indirect_nonzero_offset", build_f06_indirect_nonzero_offset, 42, - CG_CASE_DEFAULT}, - {"f07_store_reg", build_f07_store_reg, 17, CG_CASE_DEFAULT}, - {"f08_copy_bytes", build_f08_copy_bytes, 42, CG_CASE_DEFAULT}, - {"f09_set_bytes_zero", build_f09_set_bytes_zero, 0, CG_CASE_DEFAULT}, - {"f10_set_bytes_ff", build_f10_set_bytes_ff, 255, CG_CASE_DEFAULT}, - {"f11_volatile_rw", build_f11_volatile_rw, 42, CG_CASE_DEFAULT}, - {"f12_bitfield_unsigned", build_f12_bitfield_unsigned, 21, CG_CASE_DEFAULT}, - {"f13_bitfield_signed", build_f13_bitfield_signed, 255, CG_CASE_DEFAULT}, - - /* Group G — calls (beyond direct-call path) */ - {"g01_indirect_call", build_g01_indirect_call, 42, CG_CASE_DEFAULT}, - {"g02_recursion_factorial", build_g02_recursion_factorial, 120, - CG_CASE_DEFAULT}, - {"g03_recursion_fib", build_g03_recursion_fib, 55, CG_CASE_DEFAULT}, - {"g04_mutual_recursion", build_g04_mutual_recursion, 1, CG_CASE_DEFAULT}, - {"g05_chained_calls", build_g05_chained_calls, 42, CG_CASE_DEFAULT}, - {"g06_mixed_int_fp_params", build_g06_mixed_int_fp_params, 42, - CG_CASE_DEFAULT}, - {"g07_void_call_outparam", build_g07_void_call_outparam, 42, - CG_CASE_DEFAULT}, - {"g08_large_struct_byval", build_g08_large_struct_byval, 42, - CG_CASE_DEFAULT}, - {"g09_hfa_param_f32x2", build_g09_hfa_param_f32x2, 3, CG_CASE_DEFAULT}, - {"g10_hfa_return_f32x2", build_g10_hfa_return_f32x2, 3, CG_CASE_DEFAULT}, - {"g11_caller_saved_live_across_call", - build_g11_caller_saved_live_across_call, 42, CG_CASE_DEFAULT}, - {"g12_addr_taken_local_across_call", build_g12_addr_taken_local_across_call, - 18, CG_CASE_DEFAULT}, - {"g13_call_in_loop_induction", build_g13_call_in_loop_induction, 45, - CG_CASE_DEFAULT}, - - /* Group H — control flow */ - {"h01_while_sum_0_to_9", build_h01_while_sum_0_to_9, 45, CG_CASE_DEFAULT}, - {"h02_do_while_once", build_h02_do_while_once, 42, CG_CASE_DEFAULT}, - {"h03_for_count_to_10", build_h03_for_count_to_10, 55, CG_CASE_DEFAULT}, - {"h04_loop_break", build_h04_loop_break, 42, CG_CASE_DEFAULT}, - {"h05_loop_continue", build_h05_loop_continue, 90, CG_CASE_DEFAULT}, - {"h06_nested_loops", build_h06_nested_loops, 6, CG_CASE_DEFAULT}, - {"h07_break_inner_only", build_h07_break_inner_only, 9, CG_CASE_DEFAULT}, - {"h08_early_return_in_loop", build_h08_early_return_in_loop, 17, - CG_CASE_DEFAULT}, - {"h09_switch_three_cases", build_h09_switch_three_cases, 42, - CG_CASE_DEFAULT}, - {"h10_switch_fallthrough", build_h10_switch_fallthrough, 30, - CG_CASE_DEFAULT}, - {"h11_switch_default", build_h11_switch_default, 7, CG_CASE_DEFAULT}, - {"h12_jump_forward", build_h12_jump_forward, 42, CG_CASE_DEFAULT}, - {"h13_jump_backward", build_h13_jump_backward, 10, CG_CASE_DEFAULT}, - {"h14_short_circuit_and_skip", build_h14_short_circuit_and_skip, 0, - CG_CASE_DEFAULT}, - {"h15_short_circuit_or_skip", build_h15_short_circuit_or_skip, 0, - CG_CASE_DEFAULT}, - {"h16_ternary", build_h16_ternary, 42, CG_CASE_DEFAULT}, - {"h17_ternary_side_effect_one_arm", build_h17_ternary_side_effect_one_arm, - 42, CG_CASE_DEFAULT}, - {"h18_unreachable_after_ret", build_h18_unreachable_after_ret, 42, - CG_CASE_DEFAULT}, - - /* Group I — alloca / VLA */ - {"i01_alloca_const_int", build_i01_alloca_const_int, 42, CG_CASE_DEFAULT}, - {"i02_alloca_runtime_size", build_i02_alloca_runtime_size, 15, - CG_CASE_DEFAULT}, - {"i03_alloca_align_16", build_i03_alloca_align_16, 1, CG_CASE_DEFAULT}, - {"i04_alloca_in_loop_distinct", build_i04_alloca_in_loop_distinct, 1, - CG_CASE_DEFAULT}, - {"i05_alloca_then_call", build_i05_alloca_then_call, 42, CG_CASE_DEFAULT}, - {"i06_two_allocas_disjoint", build_i06_two_allocas_disjoint, 3, - CG_CASE_DEFAULT}, - {"i07_alloca_addr_escapes", build_i07_alloca_addr_escapes, 42, - CG_CASE_DEFAULT}, - {"i08_vla_param_sum", build_i08_vla_param_sum, 45, CG_CASE_DEFAULT}, - {"i09_alloca_preserves_locals", build_i09_alloca_preserves_locals, 42, - CG_CASE_DEFAULT}, - {"i10_alloca_after_named_local", build_i10_alloca_after_named_local, 42, - CG_CASE_DEFAULT}, - - /* Group J — varargs */ - {"j01_va_int_sum_3", build_j01_va_int_sum_3, 6, CG_CASE_DEFAULT}, - {"j02_va_zero_args", build_j02_va_zero_args, 0, CG_CASE_DEFAULT}, - {"j03_va_int_spill", build_j03_va_int_spill, 55, CG_CASE_DEFAULT}, - {"j04_va_int64", build_j04_va_int64, 42, CG_CASE_DEFAULT}, - {"j05_va_double_sum", build_j05_va_double_sum, 7, CG_CASE_DEFAULT}, - {"j06_va_double_spill", build_j06_va_double_spill, 4, CG_CASE_DEFAULT}, - {"j07_va_mixed_int_dbl", build_j07_va_mixed_int_dbl, 42, CG_CASE_DEFAULT}, - {"j08_va_copy", build_j08_va_copy, 42, CG_CASE_DEFAULT}, - {"j09_va_two_fixed", build_j09_va_two_fixed, 42, CG_CASE_DEFAULT}, - - /* Group K — atomics */ - {"k01_atomic_load_relaxed", build_k01_atomic_load_relaxed, 42, - CG_CASE_DEFAULT}, - {"k02_atomic_store_load_acq", build_k02_atomic_store_load_acq, 42, - CG_CASE_DEFAULT}, - {"k03_atomic_load_seq_cst", build_k03_atomic_load_seq_cst, 42, - CG_CASE_DEFAULT}, - {"k04_atomic_rmw_add", build_k04_atomic_rmw_add, 42, CG_CASE_DEFAULT}, - {"k05_atomic_rmw_xchg", build_k05_atomic_rmw_xchg, 42, CG_CASE_DEFAULT}, - {"k06_atomic_rmw_and", build_k06_atomic_rmw_and, 42, CG_CASE_DEFAULT}, - {"k07_atomic_rmw_or", build_k07_atomic_rmw_or, 42, CG_CASE_DEFAULT}, - {"k08_atomic_rmw_xor", build_k08_atomic_rmw_xor, 42, CG_CASE_DEFAULT}, - {"k09_atomic_rmw_sub", build_k09_atomic_rmw_sub, 42, CG_CASE_DEFAULT}, - {"k10_atomic_rmw_nand", build_k10_atomic_rmw_nand, 42, CG_CASE_DEFAULT}, - {"k11_atomic_cas_success", build_k11_atomic_cas_success, 42, - CG_CASE_DEFAULT}, - {"k12_atomic_cas_failure", build_k12_atomic_cas_failure, 10, - CG_CASE_DEFAULT}, - {"k13_atomic_load_i64", build_k13_atomic_load_i64, 42, CG_CASE_DEFAULT}, - {"k14_atomic_rmw_prior", build_k14_atomic_rmw_prior, 40, CG_CASE_DEFAULT}, - {"k15_fence_seq_cst", build_k15_fence_seq_cst, 42, CG_CASE_DEFAULT}, - - /* Group L — intrinsics */ - {"l01_popcount_u32", build_l01_popcount_u32, 8, CG_CASE_DEFAULT}, - {"l02_popcount_u64", build_l02_popcount_u64, 64, CG_CASE_DEFAULT}, - {"l03_ctz_u32", build_l03_ctz_u32, 7, CG_CASE_DEFAULT}, - {"l04_clz_u32", build_l04_clz_u32, 24, CG_CASE_DEFAULT}, - {"l05_bswap16", build_l05_bswap16, 18, CG_CASE_DEFAULT}, - {"l06_bswap32", build_l06_bswap32, 17, CG_CASE_DEFAULT}, - {"l07_bswap64", build_l07_bswap64, 17, CG_CASE_DEFAULT}, - {"l08_memcpy_4", build_l08_memcpy_4, 42, CG_CASE_DEFAULT}, - {"l09_memmove_overlap", build_l09_memmove_overlap, 4, CG_CASE_DEFAULT}, - {"l10_memset_zero", build_l10_memset_zero, 0, CG_CASE_DEFAULT}, - {"l11_memset_ff", build_l11_memset_ff, 255, CG_CASE_DEFAULT}, - {"l12_expect_taken", build_l12_expect_taken, 42, CG_CASE_DEFAULT}, - {"l13_unreachable_live", build_l13_unreachable_live, 42, CG_CASE_DEFAULT}, - {"l14_trap_live", build_l14_trap_live, 42, CG_CASE_DEFAULT}, - {"l15_prefetch_noop", build_l15_prefetch_noop, 42, CG_CASE_DEFAULT}, - {"l16_assume_aligned", build_l16_assume_aligned, 42, CG_CASE_DEFAULT}, - {"l17_add_overflow_no", build_l17_add_overflow_no, 42, CG_CASE_DEFAULT}, - {"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 P — set_loc / debug. The exit-code oracle (D/E/J) is 42; the - * W path checks the line program. See cases_p.c for the contract. - * Phase-1 producer + Phase-2 consumer make p01..p05 viable; p07 - * additionally needs Phase-3 (debug_local). */ - {"p01_line_one_inst", build_p01_line_one_inst, 42, CG_CASE_DEFAULT}, - {"p02_line_monotone", build_p02_line_monotone, 42, CG_CASE_DEFAULT}, - {"p03_line_repeat", build_p03_line_repeat, 42, CG_CASE_DEFAULT}, - {"p05_func_pc_range", build_p05_func_pc_range, 42, CG_CASE_DEFAULT}, - {"p07_local_loc", build_p07_local_loc, 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}, - - /* Group ASM — inline asm */ - {"asm01_mov_imm_out", build_asm01_mov_imm_out, 42, CG_CASE_DEFAULT, - CG_ARCH_AARCH64}, - {"asm02_copy_input", build_asm02_copy_input, 7, CG_CASE_DEFAULT, - CG_ARCH_AARCH64}, - {"asm03_named_clobber", build_asm03_named_clobber, 99, CG_CASE_DEFAULT, - CG_ARCH_AARCH64}, -}; - -const unsigned cg_cases_count = sizeof(cg_cases) / sizeof(cg_cases[0]); - -/* ---- DWARF check registry (path W) ---- - * Only Group P has entries today. See cg_test.h for the directive - * vocabulary. */ -const CgDwarfCheck cg_dwarf_checks[] = { - {"p01_line_one_inst", - "subprogram test_main\n" - "line p01.c 10\n"}, - /* p02 — three statements, three line rows (monotone). */ - {"p02_line_monotone", - "subprogram test_main\n" - "line p02.c 1\n" - "line p02.c 2\n" - "line p02.c 3\n"}, - /* p03 — same line repeated on two distinct PCs; one round-trip is - * enough to assert the binding survives. */ - {"p03_line_repeat", - "subprogram test_main\n" - "line p03.c 7\n"}, - /* p05 — function pc range. test_main is a tiny prologue + load_imm + - * ret + epilogue; the AArch64 prologue+epilogue alone are ~7 words - * (28 bytes), so the function size easily exceeds 16 bytes and is - * comfortably under 256 bytes. */ - {"p05_func_pc_range", - "subprogram test_main\n" - "line p05.c 11\n" - "pc_range p05.c 11 16 256\n"}, - /* p07 — local variable location. The decl-info pipeline (debug_local) - * is Phase 3; until that lands the var directive will fail and the - * line/subprogram directives keep us honest about what is wired. */ - {"p07_local_loc", - "subprogram test_main\n" - "line p07.c 5\n" - "var 0x0 my_local frame *\n"}, -}; - -const unsigned cg_dwarf_checks_count = - sizeof(cg_dwarf_checks) / sizeof(cg_dwarf_checks[0]); diff --git a/test/cg/harness/cases_a.c b/test/cg/harness/cases_a.c @@ -1,112 +0,0 @@ -/* 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_asm.c b/test/cg/harness/cases_asm.c @@ -1,101 +0,0 @@ -/* Group ASM — inline-asm smoke cases. - * - * Each builder drives ctx->target->asm_block directly with hand-built - * AsmConstraint / Operand arrays, then returns the asm output through - * cgtest_ret_reg. At opt-level 0 the call lands in aa_asm_block; at - * opt-level >0 it is captured as IR_ASM_BLOCK by w_asm_block and replayed - * to the real backend at lowering time. Both paths must produce the - * same exit code. - * - * See CORPUS.md for the case list and expected values. */ - -#include <string.h> - -#include "cg_test.h" - -/* asm01_mov_imm_out — `__asm__("mov %w0, #42" : "=r"(rc));` then return rc. */ -void build_asm01_mov_imm_out(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); - - AsmConstraint outs[1]; - memset(outs, 0, sizeof outs); - outs[0].str = "=r"; - outs[0].dir = ASM_OUT; - outs[0].type = I32; - - Operand out_ops[1]; - out_ops[0] = REG_op(r, I32); - - ctx->target->asm_block(ctx->target, "mov %w0, #42", - outs, /*nout=*/1, out_ops, - /*ins=*/NULL, /*nin=*/0, /*in_ops=*/NULL, - /*clobbers=*/NULL, /*nclob=*/0); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* asm02_copy_input — `__asm__("mov %w0, %w1" : "=r"(rc) : "r"(7));` */ -void build_asm02_copy_input(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg rin = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - ctx->target->load_imm(ctx->target, REG_op(rin, I32), 7); - - Reg rout = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - - AsmConstraint outs[1]; - memset(outs, 0, sizeof outs); - outs[0].str = "=r"; - outs[0].dir = ASM_OUT; - outs[0].type = I32; - Operand out_ops[1]; - out_ops[0] = REG_op(rout, I32); - - AsmConstraint ins[1]; - memset(ins, 0, sizeof ins); - ins[0].str = "r"; - ins[0].dir = ASM_IN; - ins[0].type = I32; - Operand in_ops[1]; - in_ops[0] = REG_op(rin, I32); - - ctx->target->asm_block(ctx->target, "mov %w0, %w1", - outs, /*nout=*/1, out_ops, - ins, /*nin=*/1, in_ops, - /*clobbers=*/NULL, /*nclob=*/0); - ctx->target->free_reg(ctx->target, rin, RC_INT); - cgtest_ret_reg(tf, rout, I32); - cgtest_end(tf); -} - -/* asm03_named_clobber — clobber x19 (callee-saved) without using it. The - * func prologue must save/restore x19 even though no SValue ever bound - * it; this is the hwm-bump path in aa_asm_block. The asm body trashes - * x19 (mov x19, #0xdead) and returns a constant; correct exit code - * indicates x19 was preserved by the prologue. */ -void build_asm03_named_clobber(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); - - AsmConstraint outs[1]; - memset(outs, 0, sizeof outs); - outs[0].str = "=r"; - outs[0].dir = ASM_OUT; - outs[0].type = I32; - Operand out_ops[1]; - out_ops[0] = REG_op(r, I32); - - Sym clobs[1]; - clobs[0] = pool_intern_cstr(ctx->pool, "x19"); - - ctx->target->asm_block(ctx->target, - "mov x19, #1; mov %w0, #99", - outs, /*nout=*/1, out_ops, - /*ins=*/NULL, /*nin=*/0, /*in_ops=*/NULL, - clobs, /*nclob=*/1); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} diff --git a/test/cg/harness/cases_b.c b/test/cg/harness/cases_b.c @@ -1,315 +0,0 @@ -/* Group B — frame slots, parameters, locals. - * See CORPUS.md for the case list and expected values. */ - -#include "cases_shared.h" -#include "cg_test.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 @@ -1,204 +0,0 @@ -/* 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 @@ -1,230 +0,0 @@ -/* 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 @@ -1,258 +0,0 @@ -/* 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 @@ -1,327 +0,0 @@ -/* Group F — memory (loads/stores beyond locals). - * See CORPUS.md for the case list and expected values. */ - -#include "cases_shared.h" -#include "cg_test.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 @@ -1,660 +0,0 @@ -/* 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 @@ -1,655 +0,0 @@ -/* 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 @@ -1,435 +0,0 @@ -/* 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_j.c b/test/cg/harness/cases_j.c @@ -1,573 +0,0 @@ -/* Group J — varargs. - * See CORPUS.md for the case list and expected values. */ - -#include <string.h> - -#include "cg_test.h" -#include "core/arena.h" -#include "core/pool.h" - -/* ============================================================ - * Group J: varargs - * - * Drives va_start_/va_arg_/va_end_/va_copy_ on CGTarget plus the ABI's - * variadic classification (abi_func_info on a type_func with variadic=1 - * carries vararg_gp_offset/vararg_fp_offset/vararg_overflow_offset). The - * standard cgtest_begin_func/cgtest_call helpers hardcode variadic=0 so - * this file mirrors the variadic-aware paths locally. - * - * Each test_main calls a variadic helper. The helper allocates an `ap` - * local of abi_va_list_type size (FSF_ADDR_TAKEN), invokes va_start_ - * with &ap, runs n va_arg_'s, calls va_end_, and returns the - * accumulator. - * ============================================================ */ - -/* ---- variadic-aware helpers ---- */ - -/* Mirrors cgtest_begin_func_at but builds fn_type with variadic=1. - * The caller passes the count of fixed (named) params; var args are - * appended at the call site. */ -static CgTestFn* j_begin_va_func(CgTestCtx* ctx, const char* name, - const Type* ret_ty, - const Type* const* fixed_param_types, - u32 nfixed) { - CgTestFn* tf = arena_new(ctx->c->tu, CgTestFn); - memset(tf, 0, sizeof *tf); - tf->ctx = ctx; - tf->ret_ty = ret_ty; - - const Type** ptypes = NULL; - if (nfixed) { - ptypes = arena_array(ctx->c->tu, const Type*, nfixed); - for (u32 i = 0; i < nfixed; ++i) ptypes[i] = fixed_param_types[i]; - } - tf->fn_type = type_func(ctx->pool, ret_ty, ptypes, (u16)nfixed, 1); - tf->abi_info = abi_func_info(ctx->c->abi, tf->fn_type); - tf->sym = cgtest_decl_func(ctx, name); - - CGParamDesc* pds = NULL; - if (nfixed) { - tf->params = arena_array(ctx->c->tu, CgTestParam, nfixed); - memset(tf->params, 0, sizeof(CgTestParam) * nfixed); - pds = arena_array(ctx->c->tu, CGParamDesc, nfixed); - memset(pds, 0, sizeof(CGParamDesc) * nfixed); - for (u32 i = 0; i < nfixed; ++i) { - tf->params[i].type = ptypes[i]; - tf->params[i].abi = &tf->abi_info->params[i]; - pds[i].index = i; - pds[i].type = ptypes[i]; - pds[i].slot = FRAME_SLOT_NONE; - pds[i].abi = &tf->abi_info->params[i]; - pds[i].incoming = tf->abi_info->params[i].parts; - pds[i].nincoming = tf->abi_info->params[i].nparts; - } - } - tf->nparams = nfixed; - - tf->fd.sym = tf->sym; - tf->fd.text_section_id = ctx->text_sec; - tf->fd.group_id = OBJ_GROUP_NONE; - tf->fd.fn_type = tf->fn_type; - tf->fd.abi = tf->abi_info; - tf->fd.params = pds; - tf->fd.nparams = nfixed; - - ctx->target->func_begin(ctx->target, &tf->fd); - - for (u32 i = 0; i < nfixed; ++i) { - FrameSlotDesc fsd = { - .type = ptypes[i], - .size = abi_sizeof(ctx->c->abi, ptypes[i]), - .align = abi_alignof(ctx->c->abi, ptypes[i]), - .kind = FS_PARAM, - .flags = FSF_NONE, - }; - FrameSlot s = ctx->target->frame_slot(ctx->target, &fsd); - tf->params[i].slot = s; - pds[i].slot = s; - ctx->target->param(ctx->target, &pds[i]); - } - return tf; -} - -/* Direct call to a variadic callee. fn_type built with variadic=1; the - * abi info reports per-arg classification including the ABI's - * vararg-vs-fixed split. */ -static void j_call_va(CgTestFn* caller, ObjSymId callee_sym, const Type* ret_ty, - const Type* const* arg_types, const CgTestArg* args, - u32 nargs, u32 nfixed, Operand ret_storage) { - CgTestCtx* ctx = caller->ctx; - const Type** ptypes = NULL; - if (nargs) { - ptypes = arena_array(ctx->c->tu, const Type*, nargs); - for (u32 i = 0; i < nargs; ++i) ptypes[i] = arg_types[i]; - } - /* type_func with variadic=1; nparams is the fixed count. nfixed must - * match the helper's named-param count even though we pass nargs - * Type pointers — abi_func_info reads its variadic flag from the - * Type and handles per-arg classification via ABIFuncInfo.params[]. */ - const Type* fn_ty = type_func(ctx->pool, ret_ty, ptypes, (u16)nfixed, 1); - const ABIFuncInfo* info = abi_func_info(ctx->c->abi, fn_ty); - - CGABIValue* avs = NULL; - if (nargs) { - avs = arena_array(ctx->c->tu, CGABIValue, nargs); - memset(avs, 0, sizeof(CGABIValue) * nargs); - for (u32 i = 0; i < nargs; ++i) { - CGABIValue* av = &avs[i]; - av->type = arg_types[i]; - av->abi = (i < info->nparams) ? &info->params[i] : NULL; - switch (args[i].kind) { - case CGT_ARG_IMM: - av->storage = IMM_op(args[i].v.imm, arg_types[i]); - break; - case CGT_ARG_REG: - av->storage = REG_op(args[i].v.reg, arg_types[i]); - break; - default: - av->storage = LOCAL_op(args[i].v.slot, arg_types[i]); - break; - } - } - } - - CGCallDesc desc; - memset(&desc, 0, sizeof desc); - desc.fn_type = fn_ty; - desc.abi = info; - desc.callee = GLOBAL_op(callee_sym, 0); - desc.args = avs; - desc.nargs = nargs; - desc.ret.type = ret_ty; - desc.ret.abi = &info->ret; - desc.ret.storage = ret_storage; - ctx->target->call(ctx->target, &desc); -} - -/* ---- shared helpers ---- */ - -/* Allocate an ap local of abi_va_list_type and addr_of into a register. */ -typedef struct VaApRegs { - FrameSlot slot; - Reg ap_addr; - const Type* ap_ty; -} VaApRegs; - -static VaApRegs j_alloc_ap(CgTestFn* tf) { - CgTestCtx* ctx = tf->ctx; - const Type* ap_ty = abi_va_list_type(ctx->c->abi, ctx->pool); - const Type* ap_pty = T_ptr(ctx, ap_ty); - FrameSlot ap_slot = cgtest_local(tf, ap_ty, FSF_ADDR_TAKEN); - Reg ap_addr = ctx->target->alloc_reg(ctx->target, RC_INT, ap_pty); - ctx->target->addr_of(ctx->target, REG_op(ap_addr, ap_pty), - LOCAL_op(ap_slot, ap_ty)); - return (VaApRegs){ap_slot, ap_addr, ap_ty}; -} - -/* Build helper: int sum(int n, ...) { va_start(ap); int s=0; for(i=0;i<n;i++) - * s += va_arg(ap, T); va_end(ap); return s; } — T is the va_arg type. */ -static ObjSymId j_build_int_sum_helper(CgTestCtx* ctx, const char* name, - const Type* va_ty, const Type* acc_ty) { - const Type* I32 = T_i32(ctx); - const Type* params[] = {I32}; - CgTestFn* tf = j_begin_va_func(ctx, name, acc_ty, 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); - - VaApRegs ap = j_alloc_ap(tf); - T->va_start_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - - /* Accumulator starts at 0. */ - FrameSlot ss = cgtest_local(tf, acc_ty, FSF_NONE); - cgtest_store_local(tf, ss, IMM_op(0, acc_ty), acc_ty); - 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), REG_op(n, I32), end); - - Reg v = T->alloc_reg(T, RC_INT, va_ty); - T->va_arg_(T, REG_op(v, va_ty), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), - va_ty); - - Reg sr = T->alloc_reg(T, RC_INT, acc_ty); - cgtest_load_local(tf, REG_op(sr, acc_ty), ss, acc_ty); - T->binop(T, BO_IADD, REG_op(sr, acc_ty), REG_op(sr, acc_ty), - REG_op(v, va_ty)); - cgtest_store_local(tf, ss, REG_op(sr, acc_ty), acc_ty); - - 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); - - T->va_end_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - Reg out = T->alloc_reg(T, RC_INT, acc_ty); - cgtest_load_local(tf, REG_op(out, acc_ty), ss, acc_ty); - cgtest_ret_reg(tf, out, acc_ty); - cgtest_end(tf); - return tf->sym; -} - -/* Build helper: int sumd(int n, ...) — fp accumulator, ftoi_s before return. */ -static ObjSymId j_build_double_sum_helper(CgTestCtx* ctx, const char* name) { - const Type* I32 = T_i32(ctx); - const Type* F64 = T_f64(ctx); - const Type* params[] = {I32}; - CgTestFn* tf = j_begin_va_func(ctx, name, 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); - - VaApRegs ap = j_alloc_ap(tf); - T->va_start_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - - FrameSlot ss = cgtest_local(tf, F64, FSF_NONE); - Reg zero = T->alloc_reg(T, RC_FP, F64); - /* Materialize 0.0 via a u64 zero bitcast: easier — use convert(0). */ - Reg iz = T->alloc_reg(T, RC_INT, I32); - T->load_imm(T, REG_op(iz, I32), 0); - T->convert(T, CV_ITOF_S, REG_op(zero, F64), REG_op(iz, I32)); - cgtest_store_local(tf, ss, REG_op(zero, F64), F64); - - 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), REG_op(n, I32), end); - - Reg v = T->alloc_reg(T, RC_FP, F64); - T->va_arg_(T, REG_op(v, F64), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), F64); - - Reg sr = T->alloc_reg(T, RC_FP, F64); - cgtest_load_local(tf, REG_op(sr, F64), ss, F64); - T->binop(T, BO_FADD, REG_op(sr, F64), REG_op(sr, F64), REG_op(v, F64)); - cgtest_store_local(tf, ss, REG_op(sr, F64), F64); - - 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); - - T->va_end_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - Reg final = T->alloc_reg(T, RC_FP, F64); - cgtest_load_local(tf, REG_op(final, F64), ss, F64); - Reg ir32 = T->alloc_reg(T, RC_INT, I32); - T->convert(T, CV_FTOI_S, REG_op(ir32, I32), REG_op(final, F64)); - cgtest_ret_reg(tf, ir32, I32); - cgtest_end(tf); - return tf->sym; -} - -/* ---- cases ---- */ - -/* j01_va_int_sum_3 — sum(3, 1, 2, 3) → 6. */ -void build_j01_va_int_sum_3(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - ObjSymId sum = j_build_int_sum_helper(ctx, "j01_sum", I32, I32); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - const Type* atypes[] = {I32, I32, I32, I32}; - CgTestArg args[] = { - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 3}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 1}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 2}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 3}, - }; - j_call_va(tf, sum, I32, atypes, args, 4, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* j02_va_zero_args — sum(0); va_start/va_end with no va_arg → 0. */ -void build_j02_va_zero_args(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - ObjSymId sum = j_build_int_sum_helper(ctx, "j02_sum", I32, I32); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - const Type* atypes[] = {I32}; - CgTestArg args[] = {{.kind = CGT_ARG_IMM, .type = I32, .v.imm = 0}}; - j_call_va(tf, sum, I32, atypes, args, 1, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* j03_va_int_spill — sum(10, 1..10) → 55. Exhausts AArch64 GPR save area. */ -void build_j03_va_int_spill(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - ObjSymId sum = j_build_int_sum_helper(ctx, "j03_sum", I32, I32); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - const Type* atypes[11] = {I32, I32, I32, I32, I32, I32, - I32, I32, I32, I32, I32}; - CgTestArg args[11]; - args[0] = (CgTestArg){.kind = CGT_ARG_IMM, .type = I32, .v.imm = 10}; - for (int i = 0; i < 10; ++i) { - args[i + 1] = (CgTestArg){.kind = CGT_ARG_IMM, .type = I32, .v.imm = i + 1}; - } - j_call_va(tf, sum, I32, atypes, args, 11, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* j04_va_int64 — sum_ll(2, 21LL, 21LL); low 32 of result → 42. */ -void build_j04_va_int64(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* I64 = T_i64(ctx); - ObjSymId sum = j_build_int_sum_helper(ctx, "j04_sum_ll", I64, I64); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - Reg r64 = T->alloc_reg(T, RC_INT, I64); - const Type* atypes[] = {I32, I64, I64}; - CgTestArg args[] = { - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 2}, - {.kind = CGT_ARG_IMM, .type = I64, .v.imm = 21}, - {.kind = CGT_ARG_IMM, .type = I64, .v.imm = 21}, - }; - j_call_va(tf, sum, I64, atypes, args, 3, 1, REG_op(r64, I64)); - /* Truncate to i32. */ - 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); -} - -/* ---- helpers for fp + double-arg passing ---- */ - -/* Emit a call_const for a double-precision FP constant from raw little-endian - * bytes; returns the FP reg. */ -static Reg j_load_f64(CgTestCtx* ctx, const u8* bytes_le8) { - const Type* F64 = T_f64(ctx); - Reg r = ctx->target->alloc_reg(ctx->target, RC_FP, F64); - ConstBytes cb = {.type = F64, .bytes = bytes_le8, .size = 8, .align = 8}; - ctx->target->load_const(ctx->target, REG_op(r, F64), cb); - return r; -} - -/* j05_va_double_sum — sumd(3, 1.5, 2.0, 3.5) → 7. */ -void build_j05_va_double_sum(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* F64 = T_f64(ctx); - ObjSymId sumd = j_build_double_sum_helper(ctx, "j05_sumd"); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - /* 1.5, 2.0, 3.5 as little-endian double bytes. */ - static const u8 D15[8] = {0, 0, 0, 0, 0, 0, 0xF8, 0x3F}; - static const u8 D20[8] = {0, 0, 0, 0, 0, 0, 0x00, 0x40}; - static const u8 D35[8] = {0, 0, 0, 0, 0, 0, 0x0C, 0x40}; - Reg r1 = j_load_f64(ctx, D15); - Reg r2 = j_load_f64(ctx, D20); - Reg r3 = j_load_f64(ctx, D35); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - - const Type* atypes[] = {I32, F64, F64, F64}; - CgTestArg args[] = { - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 3}, - {.kind = CGT_ARG_REG, .type = F64, .v.reg = r1}, - {.kind = CGT_ARG_REG, .type = F64, .v.reg = r2}, - {.kind = CGT_ARG_REG, .type = F64, .v.reg = r3}, - }; - j_call_va(tf, sumd, I32, atypes, args, 4, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* j06_va_double_spill — sumd(9, 0.5×9) → 4 (after ftoi_s of 4.5). */ -void build_j06_va_double_spill(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* F64 = T_f64(ctx); - ObjSymId sumd = j_build_double_sum_helper(ctx, "j06_sumd"); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - static const u8 D05[8] = {0, 0, 0, 0, 0, 0, 0xE0, 0x3F}; /* 0.5 */ - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - - const Type* atypes[10] = {I32, F64, F64, F64, F64, F64, F64, F64, F64, F64}; - CgTestArg args[10]; - args[0] = (CgTestArg){.kind = CGT_ARG_IMM, .type = I32, .v.imm = 9}; - for (int i = 0; i < 9; ++i) { - Reg r = j_load_f64(ctx, D05); - args[i + 1] = (CgTestArg){.kind = CGT_ARG_REG, .type = F64, .v.reg = r}; - } - j_call_va(tf, sumd, I32, atypes, args, 10, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* helper for j07: int f(int n, int a, double b, int c, double d) — fixed n, - * then 4 var args of mixed kind. Body sums int+(int)b+int+(int)d. */ -static ObjSymId j_build_j07_helper(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* F64 = T_f64(ctx); - const Type* params[] = {I32}; - CgTestFn* tf = j_begin_va_func(ctx, "j07_f", I32, params, 1); - CGTarget* T = ctx->target; - - VaApRegs ap = j_alloc_ap(tf); - T->va_start_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - - Reg a = T->alloc_reg(T, RC_INT, I32); - Reg c = T->alloc_reg(T, RC_INT, I32); - Reg b = T->alloc_reg(T, RC_FP, F64); - Reg d = T->alloc_reg(T, RC_FP, F64); - T->va_arg_(T, REG_op(a, I32), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), I32); - T->va_arg_(T, REG_op(b, F64), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), F64); - T->va_arg_(T, REG_op(c, I32), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), I32); - T->va_arg_(T, REG_op(d, F64), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), F64); - - 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(b, F64)); - T->convert(T, CV_FTOI_S, REG_op(id, I32), REG_op(d, 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->va_end_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - cgtest_ret_reg(tf, s, I32); - cgtest_end(tf); - return tf->sym; -} - -/* j07_va_mixed_int_dbl — f(_, 10, 16.5, 7, 8.5) → 10+16+7+8 = 41 truncated. - * Adjust constants so int sum lands at 42: 10 + (int)16.0 + 8 + (int)8.0 = 42. - */ -void build_j07_va_mixed_int_dbl(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* F64 = T_f64(ctx); - ObjSymId f = j_build_j07_helper(ctx); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - static const u8 D16[8] = {0, 0, 0, 0, 0, 0, 0x30, 0x40}; /* 16.0 */ - static const u8 D08[8] = {0, 0, 0, 0, 0, 0, 0x20, 0x40}; /* 8.0 */ - Reg b = j_load_f64(ctx, D16); - Reg d = j_load_f64(ctx, D08); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - - const Type* atypes[] = {I32, I32, F64, I32, F64}; - CgTestArg args[] = { - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 0 /* unused n */}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 10}, - {.kind = CGT_ARG_REG, .type = F64, .v.reg = b}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 8}, - {.kind = CGT_ARG_REG, .type = F64, .v.reg = d}, - }; - j_call_va(tf, f, I32, atypes, args, 5, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* helper for j08: int f(int n, ...) { va_list a, b; va_start(a); va_copy(b,a); - * int x = va_arg(a, int); int y = va_arg(b, int); return x + y; } - * Both ap and bp see the same first var arg, so x == y. */ -static ObjSymId j_build_j08_helper(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* params[] = {I32}; - CgTestFn* tf = j_begin_va_func(ctx, "j08_f", I32, params, 1); - CGTarget* T = ctx->target; - - /* Two va_list locals + their addresses. */ - const Type* ap_ty = abi_va_list_type(ctx->c->abi, ctx->pool); - const Type* ap_pty = T_ptr(ctx, ap_ty); - FrameSlot ap = cgtest_local(tf, ap_ty, FSF_ADDR_TAKEN); - FrameSlot bp = cgtest_local(tf, ap_ty, FSF_ADDR_TAKEN); - Reg a_addr = T->alloc_reg(T, RC_INT, ap_pty); - Reg b_addr = T->alloc_reg(T, RC_INT, ap_pty); - T->addr_of(T, REG_op(a_addr, ap_pty), LOCAL_op(ap, ap_ty)); - T->addr_of(T, REG_op(b_addr, ap_pty), LOCAL_op(bp, ap_ty)); - - T->va_start_(T, REG_op(a_addr, ap_pty)); - T->va_copy_(T, REG_op(b_addr, ap_pty), REG_op(a_addr, ap_pty)); - - Reg x = T->alloc_reg(T, RC_INT, I32); - Reg y = T->alloc_reg(T, RC_INT, I32); - T->va_arg_(T, REG_op(x, I32), REG_op(a_addr, ap_pty), I32); - T->va_arg_(T, REG_op(y, I32), REG_op(b_addr, ap_pty), I32); - - Reg s = T->alloc_reg(T, RC_INT, I32); - T->binop(T, BO_IADD, REG_op(s, I32), REG_op(x, I32), REG_op(y, I32)); - - T->va_end_(T, REG_op(a_addr, ap_pty)); - T->va_end_(T, REG_op(b_addr, ap_pty)); - cgtest_ret_reg(tf, s, I32); - cgtest_end(tf); - return tf->sym; -} - -/* j08_va_copy — f(_, 21) → 21+21 = 42 (both va_lists see arg 0). */ -void build_j08_va_copy(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - ObjSymId f = j_build_j08_helper(ctx); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - const Type* atypes[] = {I32, I32}; - CgTestArg args[] = { - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 0}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 21}, - }; - j_call_va(tf, f, I32, atypes, args, 2, 1, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} - -/* helper for j09: int f(int a, int b, ...) { va_list ap; va_start(ap, b); - * int c = va_arg(ap, int); va_end(ap); return a + b + c; } */ -static ObjSymId j_build_j09_helper(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* params[] = {I32, I32}; - CgTestFn* tf = j_begin_va_func(ctx, "j09_f", 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); - 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); - - VaApRegs ap = j_alloc_ap(tf); - T->va_start_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - Reg c = T->alloc_reg(T, RC_INT, I32); - T->va_arg_(T, REG_op(c, I32), REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty)), I32); - T->va_end_(T, REG_op(ap.ap_addr, T_ptr(ctx, ap.ap_ty))); - - 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)); - T->binop(T, BO_IADD, REG_op(s, I32), REG_op(s, I32), REG_op(c, I32)); - cgtest_ret_reg(tf, s, I32); - cgtest_end(tf); - return tf->sym; -} - -/* j09_va_two_fixed — f(10, 15, 17) → 42. */ -void build_j09_va_two_fixed(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - ObjSymId f = j_build_j09_helper(ctx); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg dst = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - const Type* atypes[] = {I32, I32, I32}; - CgTestArg args[] = { - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 10}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 15}, - {.kind = CGT_ARG_IMM, .type = I32, .v.imm = 17}, - }; - j_call_va(tf, f, I32, atypes, args, 3, 2, REG_op(dst, I32)); - cgtest_ret_reg(tf, dst, I32); - cgtest_end(tf); -} diff --git a/test/cg/harness/cases_k.c b/test/cg/harness/cases_k.c @@ -1,210 +0,0 @@ -/* Group K — atomics. - * See CORPUS.md for the case list and expected values. */ - -#include "cg_test.h" - -/* ============================================================ - * Group K: atomics - * - * Drives atomic_load / atomic_store / atomic_rmw / atomic_cas / fence - * on CGTarget across every AtomicOp and several MemOrders. Every case - * uses an FSF_ADDR_TAKEN i32 (or i64 for k13) local as the atomic - * object: store-into via plain store sets the prior state, the atomic - * op is then dispatched against the address, and a plain load after - * reads the post-state for the oracle. The MF_ATOMIC flag rides along - * the MemAccess so the backend can route to ldar/stlr-class encodings. - * ============================================================ */ - -/* Helper: build the standard prelude — a single addr-taken i32 local x - * pre-initialized to `init`, plus its address in a register. */ -typedef struct KCtx { - CgTestFn* tf; - FrameSlot x; - Reg p_addr; -} KCtx; - -static KCtx k_open_i32(CgTestCtx* ctx, i64 init) { - const Type* I32 = T_i32(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(init, I32), I32); - Reg p = T->alloc_reg(T, RC_INT, PI32); - T->addr_of(T, REG_op(p, PI32), LOCAL_op(x, I32)); - return (KCtx){tf, x, p}; -} - -/* MemAccess for a 4-byte i32 atomic at &x. */ -static MemAccess k_ma32(CgTestCtx* ctx) { - MemAccess ma = {0}; - ma.type = T_i32(ctx); - ma.size = 4; - ma.align = 4; - ma.flags = MF_ATOMIC; - ma.alias.kind = ALIAS_LOCAL; - return ma; -} - -/* Reload x and return; helper for the post-state oracle. */ -static void k_close_load_x(KCtx* k) { - CgTestCtx* ctx = k->tf->ctx; - const Type* I32 = T_i32(ctx); - Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - cgtest_load_local(k->tf, REG_op(r, I32), k->x, I32); - cgtest_ret_reg(k->tf, r, I32); - cgtest_end(k->tf); -} - -/* k01_atomic_load_relaxed — return atomic_load(&x=42, RELAXED). */ -void build_k01_atomic_load_relaxed(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 42); - const Type* I32 = T_i32(ctx); - Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - ctx->target->atomic_load(ctx->target, REG_op(r, I32), - REG_op(k.p_addr, T_ptr(ctx, I32)), k_ma32(ctx), - MO_RELAXED); - cgtest_ret_reg(k.tf, r, I32); - cgtest_end(k.tf); -} - -/* k02_atomic_store_load_acq — atomic_store(&x, 42, RELEASE) then - * atomic_load(&x, ACQUIRE). */ -void build_k02_atomic_store_load_acq(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 0); - const Type* I32 = T_i32(ctx); - CGTarget* T = ctx->target; - T->atomic_store(T, REG_op(k.p_addr, T_ptr(ctx, I32)), IMM_op(42, I32), - k_ma32(ctx), MO_RELEASE); - Reg r = T->alloc_reg(T, RC_INT, I32); - T->atomic_load(T, REG_op(r, I32), REG_op(k.p_addr, T_ptr(ctx, I32)), - k_ma32(ctx), MO_ACQUIRE); - cgtest_ret_reg(k.tf, r, I32); - cgtest_end(k.tf); -} - -/* k03_atomic_load_seq_cst — full-barrier load. */ -void build_k03_atomic_load_seq_cst(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 42); - const Type* I32 = T_i32(ctx); - Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - ctx->target->atomic_load(ctx->target, REG_op(r, I32), - REG_op(k.p_addr, T_ptr(ctx, I32)), k_ma32(ctx), - MO_SEQ_CST); - cgtest_ret_reg(k.tf, r, I32); - cgtest_end(k.tf); -} - -/* Shared body for the rmw post-state cases (k04..k10). */ -static void k_rmw_post(CgTestCtx* ctx, AtomicOp op, i64 init, i64 val) { - KCtx k = k_open_i32(ctx, init); - const Type* I32 = T_i32(ctx); - Reg prior = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - ctx->target->atomic_rmw(ctx->target, op, REG_op(prior, I32), - REG_op(k.p_addr, T_ptr(ctx, I32)), IMM_op(val, I32), - k_ma32(ctx), MO_SEQ_CST); - k_close_load_x(&k); -} - -void build_k04_atomic_rmw_add(CgTestCtx* c) { k_rmw_post(c, AO_ADD, 40, 2); } -void build_k05_atomic_rmw_xchg(CgTestCtx* c) { k_rmw_post(c, AO_XCHG, 99, 42); } -void build_k06_atomic_rmw_and(CgTestCtx* c) { - k_rmw_post(c, AO_AND, 0xFF, 0x2A); -} -void build_k07_atomic_rmw_or(CgTestCtx* c) { k_rmw_post(c, AO_OR, 0x20, 0x0A); } -void build_k08_atomic_rmw_xor(CgTestCtx* c) { - k_rmw_post(c, AO_XOR, 0xFF, 0xD5); -} -void build_k09_atomic_rmw_sub(CgTestCtx* c) { k_rmw_post(c, AO_SUB, 44, 2); } - -/* k10_atomic_rmw_nand — post-state low 8: ~(0xFF & 0xD5) & 0xFF = 0x2A = 42. */ -void build_k10_atomic_rmw_nand(CgTestCtx* c) { - k_rmw_post(c, AO_NAND, 0xFF, 0xD5); -} - -/* k11_atomic_cas_success — x=10; cas(&x, exp=10, des=42) → ok=1; load → 42. */ -void build_k11_atomic_cas_success(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 10); - const Type* I32 = T_i32(ctx); - CGTarget* T = ctx->target; - Reg prior = T->alloc_reg(T, RC_INT, I32); - Reg ok = T->alloc_reg(T, RC_INT, I32); - T->atomic_cas(T, REG_op(prior, I32), REG_op(ok, I32), - REG_op(k.p_addr, T_ptr(ctx, I32)), IMM_op(10, I32), - IMM_op(42, I32), k_ma32(ctx), MO_SEQ_CST, MO_RELAXED); - k_close_load_x(&k); -} - -/* k12_atomic_cas_failure — x=10; cas(&x, exp=99, des=42) → ok=0; x unchanged. - */ -void build_k12_atomic_cas_failure(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 10); - const Type* I32 = T_i32(ctx); - CGTarget* T = ctx->target; - Reg prior = T->alloc_reg(T, RC_INT, I32); - Reg ok = T->alloc_reg(T, RC_INT, I32); - T->atomic_cas(T, REG_op(prior, I32), REG_op(ok, I32), - REG_op(k.p_addr, T_ptr(ctx, I32)), IMM_op(99, I32), - IMM_op(42, I32), k_ma32(ctx), MO_SEQ_CST, MO_RELAXED); - k_close_load_x(&k); -} - -/* k13_atomic_load_i64 — i64 atomic load of 0x1_0000_002A; return low 32 = 42. - */ -void build_k13_atomic_load_i64(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* I64 = T_i64(ctx); - const Type* PI64 = T_ptr(ctx, I64); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - - FrameSlot x = cgtest_local(tf, I64, FSF_ADDR_TAKEN); - /* Materialize via load_imm into a 64-bit reg, then store. */ - Reg init = T->alloc_reg(T, RC_INT, I64); - T->load_imm(T, REG_op(init, I64), 0x10000002Aull); - cgtest_store_local(tf, x, REG_op(init, I64), I64); - - Reg p = T->alloc_reg(T, RC_INT, PI64); - T->addr_of(T, REG_op(p, PI64), LOCAL_op(x, I64)); - - MemAccess ma = {.type = I64, - .size = 8, - .align = 8, - .flags = MF_ATOMIC, - .alias.kind = ALIAS_LOCAL}; - Reg r64 = T->alloc_reg(T, RC_INT, I64); - T->atomic_load(T, REG_op(r64, I64), REG_op(p, PI64), ma, MO_SEQ_CST); - - 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); -} - -/* k14_atomic_rmw_prior — return the prior value rmw produced (40), not the - * post-state. */ -void build_k14_atomic_rmw_prior(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 40); - const Type* I32 = T_i32(ctx); - Reg prior = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - ctx->target->atomic_rmw(ctx->target, AO_ADD, REG_op(prior, I32), - REG_op(k.p_addr, T_ptr(ctx, I32)), IMM_op(2, I32), - k_ma32(ctx), MO_SEQ_CST); - cgtest_ret_reg(k.tf, prior, I32); - cgtest_end(k.tf); -} - -/* k15_fence_seq_cst — fence between two plain atomic stores; load checks. */ -void build_k15_fence_seq_cst(CgTestCtx* ctx) { - KCtx k = k_open_i32(ctx, 0); - const Type* I32 = T_i32(ctx); - CGTarget* T = ctx->target; - T->atomic_store(T, REG_op(k.p_addr, T_ptr(ctx, I32)), IMM_op(42, I32), - k_ma32(ctx), MO_RELAXED); - T->fence(T, MO_SEQ_CST); - Reg r = T->alloc_reg(T, RC_INT, I32); - T->atomic_load(T, REG_op(r, I32), REG_op(k.p_addr, T_ptr(ctx, I32)), - k_ma32(ctx), MO_RELAXED); - cgtest_ret_reg(k.tf, r, I32); - cgtest_end(k.tf); -} diff --git a/test/cg/harness/cases_l.c b/test/cg/harness/cases_l.c @@ -1,396 +0,0 @@ -/* Group L — intrinsics. - * See CORPUS.md for the case list and expected values. */ - -#include "cg_test.h" - -/* ============================================================ - * Group L: compiler intrinsics - * - * Drives CGTarget.intrinsic across every IntrinKind. Operand shapes - * follow arch.h's documentation: - * POPCOUNT/CTZ/CLZ/BSWAP* : dsts[0] REG, args[0] REG - * MEMCPY/MEMMOVE : args = (dst_addr, src_addr, n_bytes) - * MEMSET : args = (dst_addr, byte_value, n_bytes) - * PREFETCH : args = (addr) - * ASSUME_ALIGNED : dsts[0] REG, args = (ptr, align) - * EXPECT : dsts[0] REG, args = (val, expected) - * UNREACHABLE / TRAP : no dsts, no args - * *_OVERFLOW : dsts[0] result, dsts[1] i1 ovf; args = (a, b) - * ============================================================ */ - -/* helper: emit a single-result bit-op intrinsic on `in` (returns dst reg). */ -static Reg l_bitop(CgTestCtx* ctx, IntrinKind kind, const Type* arg_ty, - i64 imm) { - const Type* I32 = T_i32(ctx); - CGTarget* T = ctx->target; - Reg src = T->alloc_reg(T, RC_INT, arg_ty); - T->load_imm(T, REG_op(src, arg_ty), imm); - Reg dst = T->alloc_reg(T, RC_INT, I32); - Operand dsts[1] = {REG_op(dst, I32)}; - Operand args[1] = {REG_op(src, arg_ty)}; - T->intrinsic(T, kind, dsts, 1, args, 1); - return dst; -} - -/* l01_popcount_u32 — popcount(0xFF) → 8. */ -void build_l01_popcount_u32(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U32 = T_u32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_bitop(ctx, INTRIN_POPCOUNT, U32, 0xFF); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l02_popcount_u64 — popcount((u64)-1) → 64. */ -void build_l02_popcount_u64(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U64 = T_u64(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_bitop(ctx, INTRIN_POPCOUNT, U64, -1); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l03_ctz_u32 — ctz(0x80) → 7. */ -void build_l03_ctz_u32(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U32 = T_u32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_bitop(ctx, INTRIN_CTZ, U32, 0x80); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l04_clz_u32 — clz(0xFF) over 32 bits → 24. */ -void build_l04_clz_u32(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U32 = T_u32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_bitop(ctx, INTRIN_CLZ, U32, 0xFF); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l05_bswap16 — bswap16(0x1234) → 0x3412 (low 8 = 0x12 = 18). */ -void build_l05_bswap16(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U16 = T_u16(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_bitop(ctx, INTRIN_BSWAP16, U16, 0x1234); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l06_bswap32 — bswap32(0x11223344) → 0x44332211 (low 8 = 0x11 = 17). */ -void build_l06_bswap32(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U32 = T_u32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_bitop(ctx, INTRIN_BSWAP32, U32, 0x11223344); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l07_bswap64 — bswap64(0x1122334455667788) → 0x8877665544332211; low 8 = 17. - */ -void build_l07_bswap64(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* U64 = T_u64(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - - Reg src = T->alloc_reg(T, RC_INT, U64); - T->load_imm(T, REG_op(src, U64), 0x1122334455667788ll); - Reg dst64 = T->alloc_reg(T, RC_INT, U64); - Operand dsts[1] = {REG_op(dst64, U64)}; - Operand args[1] = {REG_op(src, U64)}; - T->intrinsic(T, INTRIN_BSWAP64, dsts, 1, args, 1); - - Reg r32 = T->alloc_reg(T, RC_INT, I32); - T->convert(T, CV_TRUNC, REG_op(r32, I32), REG_op(dst64, U64)); - cgtest_ret_reg(tf, r32, I32); - cgtest_end(tf); -} - -/* l08_memcpy_4 — int src=42; memcpy(&dst,&src,4); return dst. */ -void build_l08_memcpy_4(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 src = cgtest_local(tf, I32, FSF_ADDR_TAKEN); - FrameSlot dst = cgtest_local(tf, I32, FSF_ADDR_TAKEN); - cgtest_store_local(tf, src, IMM_op(42, I32), I32); - - Reg ps = T->alloc_reg(T, RC_INT, PI32); - Reg pd = T->alloc_reg(T, RC_INT, PI32); - T->addr_of(T, REG_op(ps, PI32), LOCAL_op(src, I32)); - T->addr_of(T, REG_op(pd, PI32), LOCAL_op(dst, I32)); - - Operand args[3] = { - REG_op(pd, PI32), - REG_op(ps, PI32), - IMM_op(4, I64), - }; - T->intrinsic(T, INTRIN_MEMCPY, NULL, 0, args, 3); - - Reg r = T->alloc_reg(T, RC_INT, I32); - cgtest_load_local(tf, REG_op(r, I32), dst, I32); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l09_memmove_overlap — int a[5]={1..5}; memmove(a+1,a,16); return a[4]→4. */ -void build_l09_memmove_overlap(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* I64 = T_i64(ctx); - const Type* U8 = T_u8(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - - /* alloca 5*4 = 20 bytes, aligned to 4. */ - Reg buf = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); - T->alloca_(T, REG_op(buf, T_ptr(ctx, I32)), IMM_op(20, I64), 4); - - MemAccess ma = { - .type = I32, .size = 4, .align = 4, .alias.kind = ALIAS_LOCAL}; - for (int i = 0; i < 5; ++i) { - T->store(T, IND_op(buf, (i32)(i * 4), I32), IMM_op(i + 1, I32), ma); - } - - /* dst = a + 4 (one i32 forward); use byte arithmetic for the addr. */ - Reg dst = T->alloc_reg(T, RC_INT, T_ptr(ctx, U8)); - T->binop(T, BO_IADD, REG_op(dst, T_ptr(ctx, U8)), - REG_op(buf, T_ptr(ctx, I32)), IMM_op(4, I64)); - - Operand args[3] = { - REG_op(dst, T_ptr(ctx, U8)), - REG_op(buf, T_ptr(ctx, I32)), - IMM_op(16, I64), - }; - T->intrinsic(T, INTRIN_MEMMOVE, NULL, 0, args, 3); - - /* return a[4] (byte offset 16 from buf — old a[3]=4 was copied here). */ - Reg r = T->alloc_reg(T, RC_INT, I32); - T->load(T, REG_op(r, I32), IND_op(buf, 16, I32), ma); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l10_memset_zero — int b[4]; memset(b,0,16); return b[2] → 0. */ -void build_l10_memset_zero(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* I64 = T_i64(ctx); - const Type* U8 = T_u8(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - - Reg buf = T->alloc_reg(T, RC_INT, T_ptr(ctx, I32)); - T->alloca_(T, REG_op(buf, T_ptr(ctx, I32)), IMM_op(16, I64), 4); - - /* Pre-poison so the memset is observable. */ - MemAccess ma = { - .type = I32, .size = 4, .align = 4, .alias.kind = ALIAS_LOCAL}; - for (int i = 0; i < 4; ++i) - T->store(T, IND_op(buf, (i32)(i * 4), I32), IMM_op(0xDEAD, I32), ma); - - Operand args[3] = { - REG_op(buf, T_ptr(ctx, I32)), - IMM_op(0, U8), - IMM_op(16, I64), - }; - T->intrinsic(T, INTRIN_MEMSET, NULL, 0, args, 3); - - Reg r = T->alloc_reg(T, RC_INT, I32); - T->load(T, REG_op(r, I32), IND_op(buf, 8, I32), ma); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l11_memset_ff — int b; memset(&b,0xFF,4); load → 0xFFFFFFFF; low 8 = 255. */ -void build_l11_memset_ff(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - const Type* I64 = T_i64(ctx); - const Type* U8 = T_u8(ctx); - const Type* PI32 = T_ptr(ctx, I32); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - - FrameSlot b = 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(b, I32)); - - Operand args[3] = { - REG_op(p, PI32), - IMM_op(0xFF, U8), - IMM_op(4, I64), - }; - T->intrinsic(T, INTRIN_MEMSET, NULL, 0, args, 3); - - Reg r = T->alloc_reg(T, RC_INT, I32); - cgtest_load_local(tf, REG_op(r, I32), b, I32); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l12_expect_taken — int x = expect(1==1, 1); if (x) return 42; else return 99. - */ -void build_l12_expect_taken(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - CGTarget* T = ctx->target; - - Reg cond = T->alloc_reg(T, RC_INT, I32); - T->load_imm(T, REG_op(cond, I32), 1); - - Reg out = T->alloc_reg(T, RC_INT, I32); - Operand dsts[1] = {REG_op(out, I32)}; - Operand args[2] = {REG_op(cond, I32), IMM_op(1, I32)}; - T->intrinsic(T, INTRIN_EXPECT, dsts, 1, args, 2); - - Label miss = T->label_new(T); - T->cmp_branch(T, CMP_EQ, REG_op(out, I32), IMM_op(0, I32), miss); - cgtest_ret_imm(tf, 42, I32); - T->label_place(T, miss); - cgtest_ret_imm(tf, 99, I32); - cgtest_end(tf); -} - -/* l13_unreachable_live — if(x) return 42; else __builtin_unreachable(). */ -void build_l13_unreachable_live(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - 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), 1); - - Label dead = T->label_new(T); - T->cmp_branch(T, CMP_EQ, REG_op(x, I32), IMM_op(0, I32), dead); - cgtest_ret_imm(tf, 42, I32); - - T->label_place(T, dead); - T->intrinsic(T, INTRIN_UNREACHABLE, NULL, 0, NULL, 0); - cgtest_end(tf); -} - -/* l14_trap_live — if(x) return 42; else __builtin_trap(). */ -void build_l14_trap_live(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - 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), 1); - - Label trap_lbl = T->label_new(T); - T->cmp_branch(T, CMP_EQ, REG_op(x, I32), IMM_op(0, I32), trap_lbl); - cgtest_ret_imm(tf, 42, I32); - - T->label_place(T, trap_lbl); - T->intrinsic(T, INTRIN_TRAP, NULL, 0, NULL, 0); - cgtest_end(tf); -} - -/* l15_prefetch_noop — prefetch(&x); *p=42; return *p. */ -void build_l15_prefetch_noop(CgTestCtx* ctx) { - const Type* I32 = T_i32(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); - Reg p = T->alloc_reg(T, RC_INT, PI32); - T->addr_of(T, REG_op(p, PI32), LOCAL_op(x, I32)); - - Operand pf_args[1] = {REG_op(p, PI32)}; - T->intrinsic(T, INTRIN_PREFETCH, NULL, 0, pf_args, 1); - - cgtest_store_local(tf, x, IMM_op(42, I32), I32); - 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); -} - -/* l16_assume_aligned — p = assume_aligned(p, 8); *p = 42; return *p. */ -void build_l16_assume_aligned(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(8, I64), 8); - - Reg p2 = T->alloc_reg(T, RC_INT, PI32); - Operand dsts[1] = {REG_op(p2, PI32)}; - Operand args[2] = {REG_op(p, PI32), IMM_op(8, I32)}; - T->intrinsic(T, INTRIN_ASSUME_ALIGNED, dsts, 1, args, 2); - - MemAccess ma = { - .type = I32, .size = 4, .align = 8, .alias.kind = ALIAS_LOCAL}; - T->store(T, IND_op(p2, 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(p2, 0, I32), ma); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* Helper: emit a 2-operand checked-arith intrinsic; return either the result - * or the overflow bit per `which`. */ -static Reg l_chkarith(CgTestCtx* ctx, IntrinKind kind, i64 a, i64 b, - int which /*0=value,1=ovf*/) { - const Type* I32 = T_i32(ctx); - CGTarget* T = ctx->target; - Reg ra = T->alloc_reg(T, RC_INT, I32); - Reg rb = T->alloc_reg(T, RC_INT, I32); - T->load_imm(T, REG_op(ra, I32), a); - T->load_imm(T, REG_op(rb, I32), b); - Reg val = T->alloc_reg(T, RC_INT, I32); - Reg ovf = T->alloc_reg(T, RC_INT, I32); - Operand dsts[2] = {REG_op(val, I32), REG_op(ovf, I32)}; - Operand args[2] = {REG_op(ra, I32), REG_op(rb, I32)}; - T->intrinsic(T, kind, dsts, 2, args, 2); - return which ? ovf : val; -} - -/* l17_add_overflow_no — add_overflow(20,22) → val=42, ovf=0; return val. */ -void build_l17_add_overflow_no(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_chkarith(ctx, INTRIN_ADD_OVERFLOW, 20, 22, 0); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l18_add_overflow_yes — add_overflow(INT_MAX,1) → ovf=1; return ovf. */ -void build_l18_add_overflow_yes(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_chkarith(ctx, INTRIN_ADD_OVERFLOW, 0x7FFFFFFF, 1, 1); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l19_sub_overflow_yes — sub_overflow(INT_MIN,1) → ovf=1. */ -void build_l19_sub_overflow_yes(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_chkarith(ctx, INTRIN_SUB_OVERFLOW, (i64)(i32)0x80000000, 1, 1); - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* l20_mul_overflow_no — mul_overflow(6,7) → val=42, ovf=0; return val. */ -void build_l20_mul_overflow_no(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = l_chkarith(ctx, INTRIN_MUL_OVERFLOW, 6, 7, 0); - 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 @@ -1,24 +0,0 @@ -/* 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_n.c b/test/cg/harness/cases_n.c @@ -1,286 +0,0 @@ -/* 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 @@ -1,381 +0,0 @@ -/* 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 "cases_shared.h" -#include "cg_test.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_p.c b/test/cg/harness/cases_p.c @@ -1,132 +0,0 @@ -/* Group P — set_loc / debug. - * See CORPUS.md for the case list and expected values. - * - * Group P's oracle is metadata, not exit code: the case still returns 42 - * (so D/E/J keep passing) but the *real* assertion runs through path W, - * which opens the emitted obj with cfree_dwarf_open and checks the line - * program against the (file, line) pairs the case set via cgtest_set_loc. - * - * The harness is the parser stand-in per doc/DWARF.md §3.1: cgtest_set_loc - * fans the loc to both CGTarget (which forwards to MCEmitter so per-insn - * emit gets attribution) and Debug (debug_set_pending_loc). Group P cases - * register dwarf-check directives in cases.c so cg-runner emits them on - * --dwarf-checks NAME for the W path runner. */ - -#include "cg_test.h" -#include "core/core.h" - -/* p01_line_one_inst — one instruction at a known SrcLoc. - * - * Registers a synthetic source file "p01.c" with the SourceManager, - * stamps line 10 onto a single load_imm via cgtest_set_loc, and returns - * 42. Path W asserts that the emitted obj's .debug_line maps some PC - * inside test_main back to (p01.c, 10). */ -void build_p01_line_one_inst(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - - u32 file_id = source_add_memory(ctx->c->sources, "p01.c"); - SrcLoc loc = {file_id, 10, 0}; - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - cgtest_set_loc(ctx, loc); - 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); -} - -/* p02_line_monotone — three lines, three rows. - * - * Three statement-level set_loc transitions on the same file; each - * straddles at least one emitted instruction. The W path checks all - * three (file, line) pairs round-trip via line_to_addr / addr_to_line. - * Verifies the line program advances PC and line monotonically. */ -void build_p02_line_monotone(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - u32 file_id = source_add_memory(ctx->c->sources, "p02.c"); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - - cgtest_set_loc(ctx, (SrcLoc){file_id, 1, 0}); - ctx->target->load_imm(ctx->target, REG_op(r, I32), 1); - - cgtest_set_loc(ctx, (SrcLoc){file_id, 2, 0}); - ctx->target->load_imm(ctx->target, REG_op(r, I32), 2); - - cgtest_set_loc(ctx, (SrcLoc){file_id, 3, 0}); - ctx->target->load_imm(ctx->target, REG_op(r, I32), 42); - - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* p03_line_repeat — same line on two distinct PCs. - * - * Two statement-level set_loc transitions onto (p03.c, 7) interleaved - * with intervening emits at a different line. Per doc/DWARF.md §3.4 the - * line program records a row whenever PC advances, even if the line - * doesn't change; one round-trip directive is enough to assert the - * binding survives. */ -void build_p03_line_repeat(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - u32 file_id = source_add_memory(ctx->c->sources, "p03.c"); - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - - cgtest_set_loc(ctx, (SrcLoc){file_id, 7, 0}); - ctx->target->load_imm(ctx->target, REG_op(r, I32), 1); - - cgtest_set_loc(ctx, (SrcLoc){file_id, 8, 0}); - ctx->target->load_imm(ctx->target, REG_op(r, I32), 2); - - cgtest_set_loc(ctx, (SrcLoc){file_id, 7, 0}); - ctx->target->load_imm(ctx->target, REG_op(r, I32), 42); - - cgtest_ret_reg(tf, r, I32); - cgtest_end(tf); -} - -/* p05_func_pc_range — exercise the (low_pc, high_pc) bounds. - * - * Body is identical to p01; the directive set adds `pc_range` which - * checks the subprogram's range covers more than one instruction (i.e. - * cgtest_end's debug_func_pc_range handed off real bounds). */ -void build_p05_func_pc_range(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - u32 file_id = source_add_memory(ctx->c->sources, "p05.c"); - SrcLoc loc = {file_id, 11, 0}; - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - cgtest_set_loc(ctx, loc); - 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); -} - -/* p07_local_loc — variable-location query. - * - * Allocates a single i32 local named "my_local", stores 42 into it, and - * reloads before return. cgtest_local_named registers a DW_TAG_variable - * with a DW_OP_fbreg location; the W path's `var` directive checks the - * round-trip kind (frame) but accepts any encoded offset (`*`). The - * frame_ofs passed here is a synthetic value — backends don't expose a - * real fp-relative offset for a FrameSlot. */ -void build_p07_local_loc(CgTestCtx* ctx) { - const Type* I32 = T_i32(ctx); - u32 file_id = source_add_memory(ctx->c->sources, "p07.c"); - SrcLoc loc = {file_id, 5, 0}; - - CgTestFn* tf = cgtest_begin_main(ctx, I32); - cgtest_set_loc(ctx, loc); - - FrameSlot slot = cgtest_local_named(tf, I32, FSF_NONE, "my_local", loc, -8); - cgtest_store_local(tf, slot, IMM_op(42, I32), I32); - - Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32); - cgtest_load_local(tf, REG_op(r, I32), slot, I32); - 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 @@ -1,473 +0,0 @@ -/* 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)); - - /* Accumulate into r_int rather than allocating a fresh sum reg — keeps - * concurrent Val count at 6, matching the x64 INT pool (RBX, R12..R15, - * R10). A 7th alloc would return REG_NONE here, and opt_cgtarget's - * replay alloc-on-first-use exhausts the same pool at -O1. */ - T->binop(T, BO_IADD, REG_op(r_int, I32), REG_op(r_int, I32), - REG_op(r_long_lo, I32)); - T->binop(T, BO_IADD, REG_op(r_int, I32), REG_op(r_int, I32), - REG_op(r_void, I32)); - T->binop(T, BO_IADD, REG_op(r_int, I32), REG_op(r_int, I32), - REG_op(r_zero, I32)); - cgtest_ret_reg(tf, r_int, 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/harness/cases_shared.c b/test/cg/harness/cases_shared.c @@ -1,14 +0,0 @@ -/* 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 @@ -1,17 +0,0 @@ -/* 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/harness/cg_check_dwarf.c b/test/cg/harness/cg_check_dwarf.c @@ -1,429 +0,0 @@ -/* cg_check_dwarf — path W oracle for the test/cg harness. - * - * cg_check_dwarf <obj_path> # reads directives from stdin, one per line - * - * Directives (see cg_test.h for the contract): - * - * line FILE LINE - * Some PC inside the object's text must map to (FILE, LINE) via - * cfree_dwarf_addr_to_line, and cfree_dwarf_line_to_addr(FILE, LINE) - * must return a PC that maps back to (FILE, LINE). - * - * subprogram NAME - * cfree_dwarf_subprogram_at must report a non-empty pc range whose - * name equals NAME. - * - * pc_range FILE LINE MIN_SIZE MAX_SIZE - * Resolve (FILE, LINE) -> pc, then call subprogram_at(pc) and - * require (high_pc - low_pc) to fall in [MIN_SIZE, MAX_SIZE]. This - * sanity-checks that debug_func_pc_range fed real bounds and - * neither under- nor over-flowed. - * - * var PC NAME EXPECT_KIND EXPECT_VALUE - * cfree_dwarf_var_at(pc=PC, name=NAME) must succeed. EXPECT_KIND - * is one of: reg, frame, global. EXPECT_VALUE is parsed against - * the kind: an unsigned integer for reg / global, a signed integer - * for frame. The "*" wildcard accepts any value of that kind. - * - * Exit code: 0 if every directive passes; 1 if any directive fails or the - * object cannot be opened. Blank lines and lines beginning with '#' are - * ignored. */ - -#include <cfree.h> -#include <fcntl.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> - -/* ---- env ---- */ - -static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { - (void)h; - (void)a; - return n ? malloc(n) : NULL; -} -static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) { - (void)h; - (void)o; - (void)a; - return realloc(p, n); -} -static void h_free(CfreeHeap* h, void* p, size_t n) { - (void)h; - (void)n; - free(p); -} -static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; - -static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, - const char* fmt, va_list ap) { - static const char* names[] = {"note", "warning", "error", "fatal"}; - (void)s; - (void)loc; - fprintf(stderr, "%s: ", names[k]); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); -} -static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0}; - -/* ---- file slurp ---- */ - -static int slurp(const char* path, uint8_t** out, size_t* n_out) { - int fd = open(path, O_RDONLY); - if (fd < 0) { - perror(path); - return 1; - } - struct stat st; - if (fstat(fd, &st) != 0) { - perror("fstat"); - close(fd); - return 1; - } - size_t n = (size_t)st.st_size; - uint8_t* buf = (uint8_t*)malloc(n); - if (!buf) { - close(fd); - return 1; - } - size_t off = 0; - while (off < n) { - ssize_t k = read(fd, buf + off, n - off); - if (k <= 0) { - perror("read"); - close(fd); - free(buf); - return 1; - } - off += (size_t)k; - } - close(fd); - *out = buf; - *n_out = n; - return 0; -} - -/* ---- directive checks ---- */ - -typedef struct Ctx { - CfreeCompiler* cc; - CfreeDebugInfo* di; - int fails; -} Ctx; - -static void fail(Ctx* c, const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - fputs("FAIL ", stdout); - vfprintf(stdout, fmt, ap); - fputc('\n', stdout); - va_end(ap); - c->fails++; -} - -static void pass(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - fputs("PASS ", stdout); - vfprintf(stdout, fmt, ap); - fputc('\n', stdout); - va_end(ap); -} - -static void check_line(Ctx* c, const char* file, uint32_t line) { - uint64_t pc = 0; - if (cfree_dwarf_line_to_addr(c->di, file, line, &pc) != 0) { - fail(c, "line %s:%u — line_to_addr returned no PC", file, line); - return; - } - const char* got_file = NULL; - uint32_t got_line = 0, got_col = 0; - if (cfree_dwarf_addr_to_line(c->di, pc, &got_file, &got_line, &got_col) != - 0) { - fail(c, "line %s:%u — addr_to_line(0x%llx) returned no entry", file, line, - (unsigned long long)pc); - return; - } - if (!got_file || strcmp(got_file, file) != 0 || got_line != line) { - fail(c, "line %s:%u — round-tripped to %s:%u (pc=0x%llx)", file, line, - got_file ? got_file : "(null)", got_line, (unsigned long long)pc); - return; - } - pass("line %s:%u (pc=0x%llx)", file, line, (unsigned long long)pc); -} - -static void check_pc_range(Ctx* c, const char* file, uint32_t line, - uint64_t min_size, uint64_t max_size) { - uint64_t pc = 0; - if (cfree_dwarf_line_to_addr(c->di, file, line, &pc) != 0) { - fail(c, "pc_range %s:%u — line_to_addr returned no PC", file, line); - return; - } - CfreeDwarfSubprogram sp; - if (cfree_dwarf_subprogram_at(c->di, pc, &sp) != 0) { - fail(c, "pc_range %s:%u — subprogram_at(0x%llx) returned no entry", file, - line, (unsigned long long)pc); - return; - } - if (sp.high_pc <= sp.low_pc) { - fail(c, "pc_range %s:%u — empty pc range [0x%llx, 0x%llx)", file, line, - (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc); - return; - } - uint64_t size = sp.high_pc - sp.low_pc; - if (size < min_size || size > max_size) { - fail(c, "pc_range %s:%u — size %llu not in [%llu, %llu]", file, line, - (unsigned long long)size, (unsigned long long)min_size, - (unsigned long long)max_size); - return; - } - pass("pc_range %s:%u size=%llu", file, line, (unsigned long long)size); -} - -static const char* loc_kind_str(CfreeDwarfLocKind k) { - switch (k) { - case CFREE_DLOC_REG: - return "reg"; - case CFREE_DLOC_FRAME_OFS: - return "frame"; - case CFREE_DLOC_GLOBAL: - return "global"; - case CFREE_DLOC_EXPR: - return "expr"; - } - return "?"; -} - -static void check_var(Ctx* c, uint64_t pc, const char* name, - const char* expect_kind, const char* expect_value) { - CfreeDwarfVarLoc loc; - memset(&loc, 0, sizeof loc); - if (cfree_dwarf_var_at(c->di, pc, name, &loc) != 0) { - fail(c, "var 0x%llx %s — var_at returned no entry", (unsigned long long)pc, - name); - return; - } - - CfreeDwarfLocKind want; - if (strcmp(expect_kind, "reg") == 0) - want = CFREE_DLOC_REG; - else if (strcmp(expect_kind, "frame") == 0) - want = CFREE_DLOC_FRAME_OFS; - else if (strcmp(expect_kind, "global") == 0) - want = CFREE_DLOC_GLOBAL; - else { - fail(c, "var %s — unknown expect_kind %s", name, expect_kind); - return; - } - if (loc.kind != want) { - fail(c, "var %s — kind %s, expected %s", name, loc_kind_str(loc.kind), - expect_kind); - return; - } - - if (strcmp(expect_value, "*") != 0) { - if (want == CFREE_DLOC_REG) { - uint32_t want_r = (uint32_t)strtoul(expect_value, NULL, 0); - if (loc.v.reg != want_r) { - fail(c, "var %s — reg %u, expected %u", name, loc.v.reg, want_r); - return; - } - } else if (want == CFREE_DLOC_FRAME_OFS) { - int32_t want_o = (int32_t)strtol(expect_value, NULL, 0); - if (loc.v.frame_ofs != want_o) { - fail(c, "var %s — frame_ofs %d, expected %d", name, loc.v.frame_ofs, - want_o); - return; - } - } else if (want == CFREE_DLOC_GLOBAL) { - uint64_t want_g = strtoull(expect_value, NULL, 0); - if (loc.v.global != want_g) { - fail(c, "var %s — global 0x%llx, expected 0x%llx", name, - (unsigned long long)loc.v.global, (unsigned long long)want_g); - return; - } - } - } - pass("var %s kind=%s", name, expect_kind); -} - -static void check_subprogram(Ctx* c, const char* name) { - /* No "find subprogram by name" entry exists in cfree_dwarf_*; we have - * subprogram_at(pc, ...). Walk a small probe range starting at 0 and - * accept the first hit whose name matches. This is a stopgap that - * keeps the directive vocabulary stable; once a name-keyed query - * lands we'll switch to it. */ - CfreeDwarfSubprogram sp; - for (uint64_t pc = 0; pc < 0x10000ull; pc += 4) { - if (cfree_dwarf_subprogram_at(c->di, pc, &sp) != 0) continue; - if (sp.name && strcmp(sp.name, name) == 0) { - if (sp.high_pc <= sp.low_pc) { - fail(c, "subprogram %s — empty pc range [0x%llx, 0x%llx)", name, - (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc); - return; - } - pass("subprogram %s [0x%llx, 0x%llx)", name, - (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc); - return; - } - } - fail(c, "subprogram %s — not found in first 64KB of text", name); -} - -static void run_directive(Ctx* c, char* line) { - while (*line == ' ' || *line == '\t') line++; - if (*line == '\0' || *line == '\n' || *line == '#') return; - - /* strip trailing newline */ - size_t n = strlen(line); - while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) line[--n] = 0; - - char* sp = strchr(line, ' '); - if (!sp) { - fail(c, "bad directive: %s", line); - return; - } - *sp = 0; - const char* op = line; - char* rest = sp + 1; - - if (strcmp(op, "line") == 0) { - char* sp2 = strchr(rest, ' '); - if (!sp2) { - fail(c, "line: expected FILE LINE"); - return; - } - *sp2 = 0; - const char* file = rest; - long ln = strtol(sp2 + 1, NULL, 10); - if (ln <= 0) { - fail(c, "line: bad line number"); - return; - } - check_line(c, file, (uint32_t)ln); - } else if (strcmp(op, "subprogram") == 0) { - check_subprogram(c, rest); - } else if (strcmp(op, "pc_range") == 0) { - /* pc_range FILE LINE MIN_SIZE MAX_SIZE */ - char* tok[4]; - int ntok = 0; - char* p = rest; - while (ntok < 4) { - tok[ntok++] = p; - char* nxt = strchr(p, ' '); - if (!nxt) break; - *nxt = 0; - p = nxt + 1; - } - if (ntok != 4) { - fail(c, "pc_range: expected FILE LINE MIN_SIZE MAX_SIZE"); - return; - } - const char* file = tok[0]; - long ln = strtol(tok[1], NULL, 10); - unsigned long long mn = strtoull(tok[2], NULL, 0); - unsigned long long mx = strtoull(tok[3], NULL, 0); - if (ln <= 0) { - fail(c, "pc_range: bad line number"); - return; - } - check_pc_range(c, file, (uint32_t)ln, mn, mx); - } else if (strcmp(op, "var") == 0) { - /* var PC NAME EXPECT_KIND EXPECT_VALUE */ - char* tok[4]; - int ntok = 0; - char* p = rest; - while (ntok < 4) { - tok[ntok++] = p; - char* nxt = strchr(p, ' '); - if (!nxt) break; - *nxt = 0; - p = nxt + 1; - } - if (ntok != 4) { - fail(c, "var: expected PC NAME EXPECT_KIND EXPECT_VALUE"); - return; - } - uint64_t pc = strtoull(tok[0], NULL, 0); - check_var(c, pc, tok[1], tok[2], tok[3]); - } else { - fail(c, "unknown directive: %s", op); - } -} - -/* ---- main ---- */ - -int main(int argc, char** argv) { - if (argc != 2) { - fprintf(stderr, "usage: cg_check_dwarf <obj_path>\n"); - return 2; - } - const char* obj_path = argv[1]; - - /* Slurp the obj. */ - uint8_t* bytes = NULL; - size_t nbytes = 0; - if (slurp(obj_path, &bytes, &nbytes) != 0) return 1; - - CfreeTarget target; - memset(&target, 0, sizeof target); - target.arch = CFREE_ARCH_ARM_64; - target.os = CFREE_OS_LINUX; - target.obj = CFREE_OBJ_ELF; - target.ptr_size = 8; - target.ptr_align = 8; - CfreeEnv env; - memset(&env, 0, sizeof env); - env.heap = &g_heap; - env.diag = &g_diag; - env.now = -1; - - CfreeCompiler* cc = cfree_compiler_new(target, &env); - if (!cc) { - fprintf(stderr, "cg_check_dwarf: compiler_new failed\n"); - free(bytes); - return 1; - } - - CfreeBytesInput in; - memset(&in, 0, sizeof in); - in.name = obj_path; - in.data = bytes; - in.len = nbytes; - CfreeObjFile* obj = cfree_obj_open(&env, &in); - if (!obj) { - fprintf(stderr, "cg_check_dwarf: cannot open %s as object\n", obj_path); - cfree_compiler_free(cc); - free(bytes); - return 1; - } - - CfreeDebugInfo* di = cfree_dwarf_open(cc, obj); - if (!di) { - fprintf(stderr, - "cg_check_dwarf: %s has no DWARF (cfree_dwarf_open returned " - "NULL)\n", - obj_path); - cfree_obj_close(obj); - cfree_compiler_free(cc); - free(bytes); - return 1; - } - - Ctx ctx = {cc, di, 0}; - - /* Stream directives from stdin. */ - char buf[1024]; - while (fgets(buf, sizeof buf, stdin)) { - run_directive(&ctx, buf); - } - - cfree_dwarf_close(di); - cfree_obj_close(obj); - cfree_compiler_free(cc); - free(bytes); - return ctx.fails ? 1 : 0; -} diff --git a/test/cg/harness/cg_runner.c b/test/cg/harness/cg_runner.c @@ -1,657 +0,0 @@ -/* cg_runner — multi-mode test runner for the cg/CGTarget/MCEmitter stack. - * - * cg-runner --list # print every registered case name - * cg-runner --expected NAME # print expected exit code (stdout) - * cg-runner --emit NAME OUT.o # build, emit_elf, write to OUT.o - * cg-runner --jit NAME # build, link, JIT, call test_main; - * # exit code = test_main's return - * - * The --jit path uses link_add_obj on the in-process ObjBuilder, so it - * exercises the live OB → JIT mapping (no .o serialization). The --emit - * path produces a .o that the existing test/link harness binaries - * (cfree-roundtrip, link-exe-runner, jit-runner) consume to drive paths - * R, E, and J. The shell harness compares the exit codes of those runs - * against the value reported by --expected. */ - -#include <cfree.h> -#include <fcntl.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "abi/abi.h" -#include "arch/arch.h" -#include "cg_test.h" -#include "core/core.h" -#include "core/pool.h" -#include "debug/debug.h" -#include "lib/cfree_test_target.h" -#include "link/link.h" -#include "obj/obj.h" -#include "opt/opt.h" -#include "type/type.h" - -/* --opt-level N: wrap the constructed CGTarget with opt_cgtarget_new(level) - * before each case runs. 0 (default) drives the backend directly; 1 / 2 - * exercise the opt pipeline. The corpus is the equivalence oracle — every - * case's exit code at level 0 must match levels 1 / 2. */ -static int g_opt_level = 0; - -/* ---- env ---- */ - -static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { - (void)h; - (void)a; - return n ? malloc(n) : NULL; -} -static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) { - (void)h; - (void)o; - (void)a; - return realloc(p, n); -} -static void h_free(CfreeHeap* h, void* p, size_t n) { - (void)h; - (void)n; - free(p); -} -static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; - -static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, - const char* fmt, va_list ap) { - static const char* names[] = {"note", "warning", "error", "fatal"}; - (void)s; - (void)loc; - fprintf(stderr, "%s: ", names[k]); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); -} -static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0}; - -/* posix-backed CfreeExecMem for the JIT path. Mirrors driver/env.c — see - * that file for the strict-W^X dual-mapping rationale. Apple uses - * mach_vm_remap; Linux uses memfd_create + dual mmap; other POSIX falls - * back to a single mapping with mprotect transitions. */ -#if defined(__APPLE__) -#include <mach/mach.h> -#include <mach/mach_vm.h> -#define XM_DUAL_APPLE 1 -#else -#define XM_DUAL_APPLE 0 -#endif -#if defined(__linux__) -#include <sys/syscall.h> -#define XM_DUAL_LINUX 1 -#else -#define XM_DUAL_LINUX 0 -#endif -static int xm_to_posix(int p) { - int q = 0; - if (p & CFREE_PROT_READ) q |= PROT_READ; - if (p & CFREE_PROT_WRITE) q |= PROT_WRITE; - if (p & CFREE_PROT_EXEC) q |= PROT_EXEC; - return q; -} -typedef struct XmTok { - void* w; - void* r; - size_t n; -} XmTok; -static int xm_reserve_single(size_t n, CfreeExecMemRegion* out) { - void* p = - mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (p == MAP_FAILED) return 1; - out->write = out->runtime = p; - out->size = n; - out->token = NULL; - return 0; -} -static int xm_reserve(void* u, size_t n, int p, CfreeExecMemRegion* out) { - (void)u; - if (!out || !n) return 1; - if (!(p & CFREE_PROT_EXEC)) return xm_reserve_single(n, out); -#if XM_DUAL_APPLE - { - void* w = - mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - mach_vm_address_t r = 0; - vm_prot_t cur = 0, max = 0; - XmTok* tok; - if (w == MAP_FAILED) return 1; - if (mach_vm_remap(mach_task_self(), &r, (mach_vm_size_t)n, 0, - VM_FLAGS_ANYWHERE, mach_task_self(), - (mach_vm_address_t)(uintptr_t)w, FALSE, &cur, &max, - VM_INHERIT_NONE) != KERN_SUCCESS) { - munmap(w, n); - return 1; - } - if (mprotect((void*)(uintptr_t)r, n, PROT_READ) != 0) { - munmap((void*)(uintptr_t)r, n); - munmap(w, n); - return 1; - } - tok = (XmTok*)malloc(sizeof(*tok)); - if (!tok) { - munmap((void*)(uintptr_t)r, n); - munmap(w, n); - return 1; - } - tok->w = w; - tok->r = (void*)(uintptr_t)r; - tok->n = n; - out->write = w; - out->runtime = (void*)(uintptr_t)r; - out->size = n; - out->token = tok; - return 0; - } -#elif XM_DUAL_LINUX - { - int fd = (int)syscall(SYS_memfd_create, "cfree-jit-test", 0u); - void *w, *r; - XmTok* tok; - if (fd < 0) return 1; - if (ftruncate(fd, (off_t)n) != 0) { - close(fd); - return 1; - } - w = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (w == MAP_FAILED) { - close(fd); - return 1; - } - r = mmap(NULL, n, PROT_READ, MAP_SHARED, fd, 0); - close(fd); - if (r == MAP_FAILED) { - munmap(w, n); - return 1; - } - tok = (XmTok*)malloc(sizeof(*tok)); - if (!tok) { - munmap(r, n); - munmap(w, n); - return 1; - } - tok->w = w; - tok->r = r; - tok->n = n; - out->write = w; - out->runtime = r; - out->size = n; - out->token = tok; - return 0; - } -#else - return xm_reserve_single(n, out); -#endif -} -static int xm_protect(void* u, void* a, size_t n, int p) { - (void)u; - return mprotect(a, n, xm_to_posix(p)); -} -static void xm_release(void* u, CfreeExecMemRegion* region) { - (void)u; - if (!region || !region->size) return; - if (region->token) { - XmTok* tok = (XmTok*)region->token; - if (tok->r && tok->r != tok->w) munmap(tok->r, tok->n); - if (tok->w) munmap(tok->w, tok->n); - free(tok); - } else if (region->write) { - munmap(region->write, region->size); - } - region->write = region->runtime = NULL; - region->size = 0; - region->token = NULL; -} -static void xm_flush(void* u, void* a, size_t n) { - (void)u; -#if defined(__aarch64__) || defined(__arm__) - __builtin___clear_cache((char*)a, (char*)a + n); -#else - (void)a; - (void)n; -#endif -} -static CfreeExecMem g_execmem = { - 16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL, -}; - -/* ---- helpers ---- */ - -static const CgCase* find_case(const char* name) { - for (unsigned i = 0; i < cg_cases_count; ++i) { - if (strcmp(cg_cases[i].name, name) == 0) return &cg_cases[i]; - } - return NULL; -} - -static void target_from_env(CfreeTarget* t) { - if (cfree_test_target_init(t) != 0) { - fprintf(stderr, "cg-runner: cfree_test_target_init failed\n"); - exit(2); - } -} - -/* Has this case registered any path-W DWARF directives? Used to decide - * whether to construct a Debug producer for the build. */ -static int case_wants_dwarf(const char* name) { - for (unsigned i = 0; i < cg_dwarf_checks_count; ++i) { - if (strcmp(cg_dwarf_checks[i].case_name, name) == 0) return 1; - } - return 0; -} - -/* Build the ObjBuilder for a case. On success returns 0 and fills *ob_out; - * on panic returns nonzero (the diagnostic was already emitted). */ -typedef struct BuildState { - Compiler* c; - ObjBuilder* ob; - MCEmitter* mc; - CGTarget* target; - Debug* debug; - CgTestCtx ctx; -} BuildState; - -static int build_case(BuildState* st, const CgCase* cc) { - Compiler* c = st->c; - - if (setjmp(c->panic)) { - compiler_run_cleanups(c); - return 1; - } - - st->ob = obj_new(c); - st->mc = mc_new(c, st->ob); - - if (cc->kind != CG_CASE_MC_ONLY) { - st->target = cgtarget_new(c, st->ob, st->mc); - if (g_opt_level > 0) { - st->target = opt_cgtarget_new(c, st->target, g_opt_level); - } - } else { - st->target = NULL; - } - - /* Construct a Debug producer for cases that register W-path directives. - * The harness is the parser stand-in per doc/DWARF.md §3.1; it owns - * Class-1 (debug_func_begin) and Class-3 (debug_func_pc_range) calls, - * dispatched from cgtest_begin_func / cgtest_end. The backend's - * Class-2 line-row fanout is reached through the Debug pointer we hand - * to MCEmitter and CGTarget below. */ - if (case_wants_dwarf(cc->name) && st->target) { - st->debug = debug_new(c, st->ob); - st->mc->debug = st->debug; - st->target->debug = st->debug; - } else { - st->debug = NULL; - } - - Sym text_name = pool_intern_cstr(c->global, ".text"); - ObjSecId text_sec = - obj_section(st->ob, text_name, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); - - st->ctx.c = c; - st->ctx.ob = st->ob; - st->ctx.mc = st->mc; - st->ctx.target = st->target; - st->ctx.text_sec = text_sec; - st->ctx.pool = c->global; - st->ctx.debug = st->debug; - - if (st->target) { - st->mc->set_section(st->mc, text_sec); - } - - cc->build(&st->ctx); - - if (st->target) cgtarget_finalize(st->target); - /* debug_emit must run after the backend has finished writing text but - * before obj_finalize, per doc/DWARF.md §3 / debug.h contract. */ - if (st->debug) debug_emit(st->debug); - obj_finalize(st->ob); - return 0; -} - -/* ---- modes ---- */ - -static int mode_list(void) { - for (unsigned i = 0; i < cg_cases_count; ++i) { - fprintf(stdout, "%s\n", cg_cases[i].name); - } - return 0; -} - -static int mode_expected(const char* name) { - const CgCase* cc = find_case(name); - if (!cc) { - fprintf(stderr, "cg-runner: unknown case '%s'\n", name); - return 2; - } - fprintf(stdout, "%d\n", cc->expected); - return 0; -} - -/* --arches NAME — print one arch token per line for the named case. - * Used by test/cg/run.sh to decide which exec_target backend to dispatch - * path E through. Empty/zero arches in the registry mean CG_ARCH_DEFAULT - * (aarch64 today). */ -static int mode_arches(const char* name) { - const CgCase* cc = find_case(name); - if (!cc) { - fprintf(stderr, "cg-runner: unknown case '%s'\n", name); - return 2; - } - unsigned arches = cc->arches ? cc->arches : (unsigned)CG_ARCH_DEFAULT; - if (arches & CG_ARCH_AARCH64) fputs("aarch64\n", stdout); - if (arches & CG_ARCH_X64) fputs("x64\n", stdout); - if (arches & CG_ARCH_RV64) fputs("rv64\n", stdout); - return 0; -} - -/* CfreeWriter that wraps stdout; used by --dump-tape. */ -typedef struct StdoutWriter { - CfreeWriter base; -} StdoutWriter; - -static void sw_write(CfreeWriter* w, const void* data, size_t n) { - (void)w; - fwrite(data, 1, n, stdout); -} -static void sw_seek(CfreeWriter* w, uint64_t off) { - (void)w; - (void)off; -} -static uint64_t sw_tell(CfreeWriter* w) { - (void)w; - return 0; -} -static int sw_error(CfreeWriter* w) { - (void)w; - return 0; -} -static void sw_close(CfreeWriter* w) { (void)w; } - -static StdoutWriter g_stdout_writer = {{sw_write, sw_seek, sw_tell, sw_error, - sw_close}}; - -/* --dump-tape NAME — build the case at the current --opt-level (must be - * >= 1) and print each function's recorded tape to stdout instead of - * just running the equivalence path. Useful for ad-hoc inspection and - * golden-file diffs. */ -static int mode_dump_tape(const char* name) { - const CgCase* cc = find_case(name); - if (!cc) { - fprintf(stderr, "cg-runner: unknown case '%s'\n", name); - return 2; - } - if (g_opt_level < 1) { - fprintf(stderr, "cg-runner: --dump-tape requires --opt-level >= 1\n"); - return 2; - } - - CfreeTarget target; - target_from_env(&target); - CfreeEnv env; - memset(&env, 0, sizeof env); - env.heap = &g_heap; - env.diag = &g_diag; - env.execmem = &g_execmem; - env.now = -1; - - CfreeCompiler* cc_ = cfree_compiler_new(target, &env); - if (!cc_) return 2; - - BuildState st; - memset(&st, 0, sizeof st); - st.c = (Compiler*)cc_; - - /* Pre-empt build_case so we can install the dump writer before the - * case runs through func_begin/func_end. */ - Compiler* c = st.c; - if (setjmp(c->panic)) { - compiler_run_cleanups(c); - cfree_compiler_free(cc_); - return 1; - } - st.ob = obj_new(c); - st.mc = mc_new(c, st.ob); - st.target = cgtarget_new(c, st.ob, st.mc); - st.target = opt_cgtarget_new(c, st.target, g_opt_level); - opt_set_dump_writer(st.target, &g_stdout_writer.base); - - Sym text_name = pool_intern_cstr(c->global, ".text"); - ObjSecId text_sec = - obj_section(st.ob, text_name, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); - - st.ctx.c = c; - st.ctx.ob = st.ob; - st.ctx.mc = st.mc; - st.ctx.target = st.target; - st.ctx.text_sec = text_sec; - st.ctx.pool = c->global; - st.ctx.debug = NULL; - st.mc->set_section(st.mc, text_sec); - cc->build(&st.ctx); - cgtarget_finalize(st.target); - - cfree_compiler_free(cc_); - return 0; -} - -/* --dwarf-checks NAME — print the W-path directive blob registered for - * NAME, or nothing if the case has no DWARF checks. The shell harness - * pipes this into cg_check_dwarf <obj>. */ -static int mode_dwarf_checks(const char* name) { - for (unsigned i = 0; i < cg_dwarf_checks_count; ++i) { - if (strcmp(cg_dwarf_checks[i].case_name, name) == 0) { - fputs(cg_dwarf_checks[i].directives, stdout); - return 0; - } - } - return 0; /* not registered → empty stdout, harness skips W */ -} - -static int mode_emit(const char* name, const char* out_path) { - const CgCase* cc = find_case(name); - if (!cc) { - fprintf(stderr, "cg-runner: unknown case '%s'\n", name); - return 2; - } - - CfreeTarget target; - target_from_env(&target); - CfreeEnv env; - memset(&env, 0, sizeof env); - env.heap = &g_heap; - env.diag = &g_diag; - env.execmem = &g_execmem; - env.now = -1; - - CfreeCompiler* cc_ = cfree_compiler_new(target, &env); - if (!cc_) { - fprintf(stderr, "cg-runner: compiler_new failed\n"); - return 2; - } - - BuildState st; - memset(&st, 0, sizeof st); - st.c = (Compiler*)cc_; - if (build_case(&st, cc)) { - cfree_compiler_free(cc_); - return 1; - } - - /* Emit ELF to a memory writer, then dump to OUT_PATH. */ - CfreeWriter* w = cfree_writer_mem(&g_heap); - emit_elf(st.c, st.ob, w); - - size_t len = 0; - const uint8_t* data = cfree_writer_mem_bytes(w, &len); - - int rc = 0; - int fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - perror(out_path); - rc = 2; - } else { - size_t off = 0; - while (off < len) { - ssize_t k = write(fd, data + off, len - off); - if (k <= 0) { - perror("write"); - rc = 2; - break; - } - off += (size_t)k; - } - close(fd); - } - cfree_writer_close(w); - cfree_compiler_free(cc_); - return rc; -} - -static int mode_jit(const char* name) { - const CgCase* cc = find_case(name); - if (!cc) { - fprintf(stderr, "cg-runner: unknown case '%s'\n", name); - return 2; - } - - CfreeTarget target; - target_from_env(&target); - CfreeEnv env; - memset(&env, 0, sizeof env); - env.heap = &g_heap; - env.diag = &g_diag; - env.execmem = &g_execmem; - env.now = -1; - - CfreeCompiler* cc_ = cfree_compiler_new(target, &env); - if (!cc_) { - fprintf(stderr, "cg-runner: compiler_new failed\n"); - return 2; - } - - BuildState st; - memset(&st, 0, sizeof st); - st.c = (Compiler*)cc_; - if (build_case(&st, cc)) { - cfree_compiler_free(cc_); - return 1; - } - - /* Direct in-process link: hand the ObjBuilder to the linker. */ - Compiler* c = st.c; - if (setjmp(c->panic)) { - compiler_run_cleanups(c); - cfree_compiler_free(cc_); - return 1; - } - - Linker* lk = link_new(c); - link_add_obj(lk, st.ob); - link_set_entry(lk, "test_main"); - LinkImage* img = link_resolve(lk); - if (!img) { - link_free(lk); - cfree_compiler_free(cc_); - return 1; - } - CfreeJit* jit = cfree_jit_from_image(img); - if (!jit) { - link_free(lk); - cfree_compiler_free(cc_); - return 1; - } - - int (*fn)(void) = (int (*)(void))cfree_jit_lookup(jit, "test_main"); - - /* AArch64 TLS Local-Exec setup, mirroring jit_runner.c. Build a - * thread-local image (16-byte TCB + .tdata copy + .tbss zero-fill) and - * point TPIDR_EL0 at it just before invoking test_main. On Darwin, - * libc functions clobber TPIDR_EL0 (probably via dyld stub binding / - * locale TSD), so msr → call() must be back-to-back with NO libc - * invocations between. */ -#if defined(__aarch64__) || defined(__arm64__) - static char tls_block[8192] __attribute__((aligned(16))); - { - char* td_start = (char*)cfree_jit_lookup(jit, "__tdata_start"); - char* td_end = (char*)cfree_jit_lookup(jit, "__tdata_end"); - unsigned long bs_n = - (unsigned long)(unsigned long long)cfree_jit_lookup(jit, "__tbss_size"); - if (td_start && td_end) { - unsigned long td_n = (unsigned long)(td_end - td_start); - unsigned long i; - /* Plain loops at -O0 stay loops; do NOT use memcpy/memset - * here — those go through dyld's stub binder on first call - * and clobber TPIDR_EL0. */ - for (i = 0; i < td_n; ++i) tls_block[16 + i] = td_start[i]; - for (i = 0; i < bs_n; ++i) tls_block[16 + td_n + i] = 0; - } - } -#endif - - int result; - if (fn) { -#if defined(__aarch64__) || defined(__arm64__) - __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory"); -#endif - result = fn(); - } else { - result = 1; - } - - cfree_jit_free(jit); - link_free(lk); - cfree_compiler_free(cc_); - return result; -} - -/* ---- main ---- */ - -static int usage(void) { - fprintf(stderr, - "usage: cg-runner [--opt-level N] --list\n" - " cg-runner [--opt-level N] --expected NAME\n" - " cg-runner [--opt-level N] --arches NAME\n" - " cg-runner [--opt-level N] --dwarf-checks NAME\n" - " cg-runner [--opt-level N] --emit NAME OUT.o\n" - " cg-runner [--opt-level N] --jit NAME\n" - " cg-runner --opt-level N --dump-tape NAME\n"); - return 2; -} - -int main(int argc, char** argv) { - { - long ps = sysconf(_SC_PAGESIZE); - if (ps > 0) g_execmem.page_size = (size_t)ps; - } - /* Optional leading --opt-level N flag. */ - if (argc >= 3 && !strcmp(argv[1], "--opt-level")) { - g_opt_level = atoi(argv[2]); - argc -= 2; - argv += 2; - } - if (argc < 2) return usage(); - if (!strcmp(argv[1], "--list")) - return mode_list(); - else if (!strcmp(argv[1], "--expected") && argc == 3) - return mode_expected(argv[2]); - else if (!strcmp(argv[1], "--arches") && argc == 3) - return mode_arches(argv[2]); - else if (!strcmp(argv[1], "--dwarf-checks") && argc == 3) - return mode_dwarf_checks(argv[2]); - else if (!strcmp(argv[1], "--emit") && argc == 4) - return mode_emit(argv[2], argv[3]); - else if (!strcmp(argv[1], "--jit") && argc == 3) - return mode_jit(argv[2]); - else if (!strcmp(argv[1], "--dump-tape") && argc == 3) - return mode_dump_tape(argv[2]); - return usage(); -} diff --git a/test/cg/harness/cg_test.c b/test/cg/harness/cg_test.c @@ -1,456 +0,0 @@ -/* test/cg fixture API implementation. - * - * Drives the same building blocks the parser will: pool-interned Types, - * abi_func_info classification, and the CGTarget lowering interface. No - * ABI mocks; CGFuncDesc/CGParamDesc/CGCallDesc/CGABIValue are populated - * from `c->abi` exactly as the parser will populate them. */ - -#include "cg_test.h" - -#include <string.h> - -#include "core/arena.h" -#include "core/pool.h" -#include "debug/c_debug.h" -#include "debug/debug.h" - -/* ---- pre-interned type accessors ---- */ - -const Type* T_void(CgTestCtx* x) { return type_void(x->pool); } -const Type* T_i8(CgTestCtx* x) { return type_prim(x->pool, TY_SCHAR); } -const Type* T_u8(CgTestCtx* x) { return type_prim(x->pool, TY_UCHAR); } -const Type* T_i16(CgTestCtx* x) { return type_prim(x->pool, TY_SHORT); } -const Type* T_u16(CgTestCtx* x) { return type_prim(x->pool, TY_USHORT); } -const Type* T_i32(CgTestCtx* x) { return type_prim(x->pool, TY_INT); } -const Type* T_u32(CgTestCtx* x) { return type_prim(x->pool, TY_UINT); } -const Type* T_i64(CgTestCtx* x) { return type_prim(x->pool, TY_LLONG); } -const Type* T_u64(CgTestCtx* x) { return type_prim(x->pool, TY_ULLONG); } -const Type* T_f32(CgTestCtx* x) { return type_prim(x->pool, TY_FLOAT); } -const Type* T_f64(CgTestCtx* x) { return type_prim(x->pool, TY_DOUBLE); } -const Type* T_ptr_void(CgTestCtx* x) { - return type_ptr(x->pool, type_void(x->pool)); -} -const Type* T_ptr(CgTestCtx* x, const Type* p) { return type_ptr(x->pool, p); } - -/* ---- operand sugar ---- */ - -Operand IMM_op(i64 v, const Type* ty) { - Operand o = {0}; - o.kind = OPK_IMM; - o.cls = (ty && (ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE || - ty->kind == TY_LDOUBLE)) - ? RC_FP - : RC_INT; - o.type = ty; - o.v.imm = v; - return o; -} -Operand REG_op(Reg r, const Type* ty) { - Operand o = {0}; - o.kind = OPK_REG; - o.cls = (ty && (ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE || - ty->kind == TY_LDOUBLE)) - ? RC_FP - : RC_INT; - o.type = ty; - o.v.reg = r; - return o; -} -Operand LOCAL_op(FrameSlot s, const Type* ty) { - Operand o = {0}; - o.kind = OPK_LOCAL; - o.cls = RC_INT; /* address class is INT */ - o.type = ty; - o.v.frame_slot = s; - return o; -} -Operand IND_op(Reg base, i32 ofs, const Type* ty) { - Operand o = {0}; - o.kind = OPK_INDIRECT; - o.cls = (ty && (ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE || - ty->kind == TY_LDOUBLE)) - ? RC_FP - : RC_INT; - o.type = ty; - o.v.ind.base = base; - o.v.ind.ofs = ofs; - return o; -} -Operand GLOBAL_op(ObjSymId sym, i64 addend) { - Operand o = {0}; - o.kind = OPK_GLOBAL; - o.cls = RC_INT; - o.type = NULL; - o.v.global.sym = sym; - o.v.global.addend = addend; - return o; -} - -void cgtest_set_loc(CgTestCtx* ctx, SrcLoc loc) { - /* CGTarget.set_loc forwards to MCEmitter, which is what subsequent - * emit32 calls read for line-row attribution. Debug gets the same loc - * so that a row whose offset hasn't been emitted yet picks up the - * right pending value. */ - if (ctx->target) ctx->target->set_loc(ctx->target, loc); - if (ctx->debug) debug_set_pending_loc(ctx->debug, loc); -} - -/* ---- internal helpers ---- */ - -static MemAccess default_memaccess(CgTestCtx* ctx, const Type* ty) { - MemAccess ma = {0}; - ma.type = ty; - ma.size = abi_sizeof(ctx->c->abi, ty); - ma.align = abi_alignof(ctx->c->abi, ty); - ma.flags = MF_NONE; - ma.alias.kind = ALIAS_LOCAL; - return ma; -} - -/* ---- function-fixture helpers ---- */ - -CgTestFn* cgtest_begin_main(CgTestCtx* ctx, const Type* ret_ty) { - return cgtest_begin_func(ctx, "test_main", ret_ty, NULL, 0); -} - -ObjSymId cgtest_decl_func(CgTestCtx* ctx, const char* name) { - Sym sname = pool_intern_cstr(ctx->pool, name); - return obj_symbol(ctx->ob, sname, SB_GLOBAL, SK_FUNC, OBJ_SEC_NONE, 0, 0); -} - -CgTestFn* cgtest_begin_func(CgTestCtx* ctx, const char* name, - const Type* ret_ty, const Type* const* param_types, - u32 nparams) { - return cgtest_begin_func_at(ctx, cgtest_decl_func(ctx, name), ret_ty, - param_types, nparams); -} - -CgTestFn* cgtest_begin_func_at(CgTestCtx* ctx, ObjSymId pre_sym, - const Type* ret_ty, - const Type* const* param_types, u32 nparams) { - CgTestFn* tf = arena_new(ctx->c->tu, CgTestFn); - memset(tf, 0, sizeof *tf); - tf->ctx = ctx; - tf->ret_ty = ret_ty; - - /* Build TY_FUNC and classify with the live TargetABI. */ - const Type** ptypes = NULL; - if (nparams) { - ptypes = arena_array(ctx->c->tu, const Type*, nparams); - for (u32 i = 0; i < nparams; ++i) ptypes[i] = param_types[i]; - } - tf->fn_type = type_func(ctx->pool, ret_ty, ptypes, (u16)nparams, 0); - tf->abi_info = abi_func_info(ctx->c->abi, tf->fn_type); - - tf->sym = pre_sym; - - /* Param slots + descriptors. Frame slots must be allocated against the - * function's frame, which begins at func_begin — so we do this AFTER - * func_begin below. We pre-allocate the descriptor array here. */ - CGParamDesc* pds = NULL; - if (nparams) { - tf->params = arena_array(ctx->c->tu, CgTestParam, nparams); - memset(tf->params, 0, sizeof(CgTestParam) * nparams); - pds = arena_array(ctx->c->tu, CGParamDesc, nparams); - memset(pds, 0, sizeof(CGParamDesc) * nparams); - for (u32 i = 0; i < nparams; ++i) { - tf->params[i].type = ptypes[i]; - tf->params[i].abi = &tf->abi_info->params[i]; - pds[i].index = i; - pds[i].name = 0; - pds[i].type = ptypes[i]; - pds[i].slot = FRAME_SLOT_NONE; /* filled below */ - pds[i].abi = &tf->abi_info->params[i]; - pds[i].incoming = tf->abi_info->params[i].parts; - pds[i].nincoming = tf->abi_info->params[i].nparts; - pds[i].loc = (SrcLoc){0, 0, 0}; - } - } - tf->nparams = nparams; - - tf->fd.sym = tf->sym; - tf->fd.text_section_id = ctx->text_sec; - tf->fd.group_id = OBJ_GROUP_NONE; - tf->fd.fn_type = tf->fn_type; - tf->fd.abi = tf->abi_info; - tf->fd.params = pds; - tf->fd.nparams = nparams; - tf->fd.loc = (SrcLoc){0, 0, 0}; - - /* Class-1 (parser-driven) DWARF event: a new subprogram opens. The - * harness doesn't run c_debug_type on the function's TY_FUNC — the W - * directives that exist today (`subprogram`, `pc_range`) only need - * (name, low_pc, high_pc), so we pass DEBUG_TYPE_NONE and skip the type - * DIE for the function itself. Capture the entry text offset so - * cgtest_end can hand (begin_ofs, end_ofs) to debug_func_pc_range. */ - tf->func_begin_ofs = obj_pos(ctx->ob, ctx->text_sec); - if (ctx->debug) { - debug_func_begin(ctx->debug, tf->sym, DEBUG_TYPE_NONE, tf->fd.loc); - } - - ctx->target->func_begin(ctx->target, &tf->fd); - - /* Allocate FS_PARAM slots and dispatch param() in declaration order. */ - for (u32 i = 0; i < nparams; ++i) { - FrameSlotDesc fsd = { - .type = ptypes[i], - .name = 0, - .loc = (SrcLoc){0, 0, 0}, - .size = abi_sizeof(ctx->c->abi, ptypes[i]), - .align = abi_alignof(ctx->c->abi, ptypes[i]), - .kind = FS_PARAM, - .flags = FSF_NONE, - }; - FrameSlot s = ctx->target->frame_slot(ctx->target, &fsd); - tf->params[i].slot = s; - pds[i].slot = s; - ctx->target->param(ctx->target, &pds[i]); - } - return tf; -} - -FrameSlot cgtest_param_slot(CgTestFn* tf, u32 idx) { - return tf->params[idx].slot; -} - -/* ---- frame slots and memory ---- */ - -FrameSlot cgtest_local(CgTestFn* tf, const Type* ty, u16 flags) { - FrameSlotDesc fsd = { - .type = ty, - .name = 0, - .loc = (SrcLoc){0, 0, 0}, - .size = abi_sizeof(tf->ctx->c->abi, ty), - .align = abi_alignof(tf->ctx->c->abi, ty), - .kind = FS_LOCAL, - .flags = flags, - }; - return tf->ctx->target->frame_slot(tf->ctx->target, &fsd); -} - -FrameSlot cgtest_local_named(CgTestFn* tf, const Type* ty, u16 flags, - const char* name, SrcLoc decl, i32 frame_ofs) { - CgTestCtx* ctx = tf->ctx; - Sym name_sym = pool_intern_cstr(ctx->pool, name); - FrameSlotDesc fsd = { - .type = ty, - .name = name_sym, - .loc = decl, - .size = abi_sizeof(ctx->c->abi, ty), - .align = abi_alignof(ctx->c->abi, ty), - .kind = FS_LOCAL, - .flags = flags, - }; - FrameSlot s = ctx->target->frame_slot(ctx->target, &fsd); - if (ctx->debug) { - DebugTypeId tid = c_debug_type(ctx->debug, ctx->c->abi, ty); - DebugVarLoc vloc = {0}; - vloc.kind = DVL_FRAME; - vloc.v.frame_ofs = frame_ofs; - debug_local(ctx->debug, name_sym, tid, decl, vloc); - } - return s; -} - -void cgtest_load_local(CgTestFn* tf, Operand dst_reg, FrameSlot s, - const Type* ty) { - MemAccess ma = default_memaccess(tf->ctx, ty); - tf->ctx->target->load(tf->ctx->target, dst_reg, LOCAL_op(s, ty), ma); -} - -void cgtest_store_local(CgTestFn* tf, FrameSlot s, Operand src, - const Type* ty) { - MemAccess ma = default_memaccess(tf->ctx, ty); - tf->ctx->target->store(tf->ctx->target, LOCAL_op(s, ty), src, ma); -} - -/* ---- return ---- */ - -void cgtest_ret_reg(CgTestFn* tf, Reg r, const Type* ty) { - CGABIValue v = {0}; - v.type = ty; - v.abi = &tf->abi_info->ret; - v.storage = REG_op(r, ty); - v.parts = NULL; - v.nparts = 0; - tf->ctx->target->ret(tf->ctx->target, &v); -} - -void cgtest_ret_imm(CgTestFn* tf, i64 imm, const Type* ty) { - CGABIValue v = {0}; - v.type = ty; - v.abi = &tf->abi_info->ret; - v.storage = IMM_op(imm, ty); - v.parts = NULL; - v.nparts = 0; - tf->ctx->target->ret(tf->ctx->target, &v); -} - -void cgtest_ret_void(CgTestFn* tf) { - tf->ctx->target->ret(tf->ctx->target, NULL); -} - -void cgtest_ret_indirect(CgTestFn* tf, FrameSlot addr_local) { - CGABIValue v = {0}; - v.type = tf->ret_ty; - v.abi = &tf->abi_info->ret; - v.storage = LOCAL_op(addr_local, tf->ret_ty); - v.parts = NULL; - v.nparts = 0; - tf->ctx->target->ret(tf->ctx->target, &v); -} - -void cgtest_ret_struct_in_regs(CgTestFn* tf, const Reg* part_regs, u32 nparts) { - CGABIValue v = {0}; - const ABIArgInfo* a = &tf->abi_info->ret; - CGABIPart* parts = arena_array(tf->ctx->c->tu, CGABIPart, nparts); - memset(parts, 0, sizeof(CGABIPart) * nparts); - for (u32 i = 0; i < nparts; ++i) { - parts[i].abi_part = &a->parts[i]; - parts[i].op = REG_op(part_regs[i], NULL); - parts[i].src_offset = a->parts[i].src_offset; - parts[i].size = a->parts[i].size; - parts[i].flags = CG_ABI_PART_NONE; - } - v.type = tf->ret_ty; - v.abi = a; - v.storage = (Operand){0}; - v.parts = parts; - v.nparts = nparts; - tf->ctx->target->ret(tf->ctx->target, &v); -} - -void cgtest_end(CgTestFn* tf) { - CgTestCtx* ctx = tf->ctx; - ctx->target->func_end(ctx->target); - if (ctx->debug) { - /* Class-3 fanout: function bounds are known only after func_end has - * finalized the function size. doc/DWARF.md §3.1 puts the call to - * debug_func_pc_range in cg_func_end after target->func_end returns — - * the harness mirrors that, since it's the CG stand-in here. */ - u32 end_ofs = obj_pos(ctx->ob, ctx->text_sec); - debug_func_pc_range(ctx->debug, ctx->text_sec, tf->func_begin_ofs, end_ofs); - debug_func_end(ctx->debug); - } -} - -/* ---- calls ---- */ - -/* Shared body for direct and indirect calls. Direct sets callee.kind = - * OPK_GLOBAL; indirect sets OPK_REG. Everything else is identical. */ -static void cgtest_call_with_callee(CgTestFn* caller, Operand callee, - const Type* ret_ty, - const Type* const* arg_types, - const CgTestArg* args, u32 nargs, - Operand ret_storage) { - CgTestCtx* ctx = caller->ctx; - - /* Build callee fn_type and ABIFuncInfo independently of the caller's. */ - const Type** ptypes = NULL; - if (nargs) { - ptypes = arena_array(ctx->c->tu, const Type*, nargs); - for (u32 i = 0; i < nargs; ++i) ptypes[i] = arg_types[i]; - } - const Type* fn_ty = type_func(ctx->pool, ret_ty, ptypes, (u16)nargs, 0); - const ABIFuncInfo* info = abi_func_info(ctx->c->abi, fn_ty); - - /* Materialize a CGABIValue per arg. */ - CGABIValue* avs = NULL; - if (nargs) { - avs = arena_array(ctx->c->tu, CGABIValue, nargs); - memset(avs, 0, sizeof(CGABIValue) * nargs); - for (u32 i = 0; i < nargs; ++i) { - CGABIValue* av = &avs[i]; - av->type = arg_types[i]; - av->abi = &info->params[i]; - av->parts = NULL; - av->nparts = 0; - switch (args[i].kind) { - case CGT_ARG_IMM: - av->storage = IMM_op(args[i].v.imm, arg_types[i]); - break; - case CGT_ARG_REG: - av->storage = REG_op(args[i].v.reg, arg_types[i]); - break; - case CGT_ARG_LOCAL_VALUE: { - /* Load into a fresh reg; storage is the reg. */ - Reg r = ctx->target->alloc_reg( - ctx->target, - (av->abi->parts && av->abi->parts[0].cls == ABI_CLASS_FP) - ? RC_FP - : RC_INT, - arg_types[i]); - cgtest_load_local(caller, REG_op(r, arg_types[i]), args[i].v.slot, - arg_types[i]); - av->storage = REG_op(r, arg_types[i]); - break; - } - case CGT_ARG_BYVAL_LOCAL: - case CGT_ARG_INDIRECT_LOCAL: - /* Storage is the address of the local; backend reads - * abi.flags (BYVAL/INDIRECT) and copies as needed. */ - av->storage = LOCAL_op(args[i].v.slot, arg_types[i]); - break; - default: - break; - } - } - } - - CGCallDesc desc; - memset(&desc, 0, sizeof desc); - desc.fn_type = fn_ty; - desc.abi = info; - desc.callee = callee; - desc.args = avs; - desc.nargs = nargs; - desc.flags = CG_CALL_NONE; - desc.ret.type = ret_ty; - desc.ret.abi = &info->ret; - desc.ret.storage = ret_storage; - desc.ret.parts = NULL; - desc.ret.nparts = 0; - - ctx->target->call(ctx->target, &desc); -} - -void cgtest_call(CgTestFn* caller, ObjSymId callee_sym, const Type* ret_ty, - const Type* const* arg_types, const CgTestArg* args, u32 nargs, - Operand ret_storage) { - cgtest_call_with_callee(caller, GLOBAL_op(callee_sym, 0), ret_ty, arg_types, - args, nargs, ret_storage); -} - -void cgtest_call_indirect(CgTestFn* caller, Reg callee, const Type* ret_ty, - const Type* const* arg_types, const CgTestArg* args, - u32 nargs, Operand ret_storage) { - /* Function-pointer type for the callee operand; the backend reads - * desc.fn_type for ABI but uses callee.kind == OPK_REG to know it's - * indirect. The Type on the operand is informational. type_func wants - * a non-const argv, so copy through a fresh array. */ - const Type** ptypes_for_op = NULL; - if (nargs) { - ptypes_for_op = arena_array(caller->ctx->c->tu, const Type*, nargs); - for (u32 i = 0; i < nargs; ++i) ptypes_for_op[i] = arg_types[i]; - } - const Type* fn_ty_for_op = - type_func(caller->ctx->pool, ret_ty, ptypes_for_op, (u16)nargs, 0); - const Type* fnp_ty = type_ptr(caller->ctx->pool, fn_ty_for_op); - cgtest_call_with_callee(caller, REG_op(callee, fnp_ty), ret_ty, arg_types, - args, nargs, ret_storage); -} - -/* ---- MC-only case helpers ---- */ - -ObjSymId cgtest_mc_begin_main(CgTestCtx* ctx) { - Sym name = pool_intern_cstr(ctx->pool, "test_main"); - ObjSymId sym = - obj_symbol(ctx->ob, name, SB_GLOBAL, SK_FUNC, OBJ_SEC_NONE, 0, 0); - return sym; -} - -void cgtest_mc_end_main(CgTestCtx* ctx, ObjSymId sym, u32 start_pos) { - u32 end = ctx->mc->pos(ctx->mc); - obj_symbol_define(ctx->ob, sym, ctx->text_sec, (u64)start_pos, - (u64)(end - start_pos)); -} diff --git a/test/cg/harness/cg_test.h b/test/cg/harness/cg_test.h @@ -1,300 +0,0 @@ -/* test/cg fixture API. - * - * Each case is a small builder function that constructs `int test_main(void)` - * (or another named function returning int) by driving CGTarget directly, - * MCEmitter directly, or — once cg.h is implemented — the cg.h value-stack - * API. The runner finds the case by name, runs build(), finalizes the - * ObjBuilder, and exposes it through one of three exit paths: - * - * --emit NAME OUT.o : emit_elf to OUT.o (used by R/E/J path scripts) - * --jit NAME : link in-process and call test_main, exit with result - * --list : list every registered case name - * - * The harness drives the same building blocks the parser will use: - * type_* — to construct interned Types (TY_INT, TY_PTR, TY_FUNC, ...) - * abi_* — to classify return + parameters (abi_func_info) - * CGTarget — to lower function lifecycle, params, locals, calls, ret - * MCEmitter — for raw byte emission (mc_smoke and similar) - * - * No ABI mocks: the harness asks the live TargetABI for ABIFuncInfo from a - * pool-interned function Type. That is the same contract the parser will - * rely on, so test cases here double as a behavioral spec for those - * interfaces. Cases requiring features the lib does not yet implement - * (type_func, abi_func_info, the call/param/aggregate methods on CGTarget) - * fail at link/runtime until the dependencies land — that is intentional. */ - -#ifndef CFREE_TEST_CG_TEST_H -#define CFREE_TEST_CG_TEST_H - -#include "abi/abi.h" -#include "arch/arch.h" -#include "core/core.h" -#include "obj/obj.h" -#include "type/type.h" - -/* ---- ctx + case registry ---- */ - -/* Forward decl — included by harness sources that need it; cases that only - * touch ctx->debug as an opaque pointer don't need debug/debug.h. */ -typedef struct Debug Debug; - -typedef struct CgTestCtx { - Compiler* c; - ObjBuilder* ob; - MCEmitter* mc; - CGTarget* target; - ObjSecId text_sec; - Pool* pool; - - /* Optional Debug producer. The cg-runner constructs one for cases that - * register DWARF checks (path W) and leaves it NULL otherwise. The - * harness is the parser stand-in per doc/DWARF.md §3.1, so it owns the - * Class-1 calls (debug_func_begin / debug_func_pc_range — emitted from - * cgtest_begin_func / cgtest_end when debug != NULL) and Class-2's - * pending-loc fanout (cgtest_set_loc). */ - Debug* debug; -} CgTestCtx; - -typedef void (*CgCaseFn)(CgTestCtx*); - -typedef enum { - CG_CASE_DEFAULT = 0, /* uses CGTarget (default) */ - CG_CASE_MC_ONLY = 1, /* uses MCEmitter only — no CGTarget construction */ -} CgCaseKind; - -/* Per-case arch mask. Cases tagged with the arches they're known to run - * on; path E (exec) dispatches to the runner for whichever arches the - * case advertises. Today every case is aarch64-only — x86_64 cases - * arrive alongside x64 codegen in MULTIARCH phase 3. CG_ARCH_DEFAULT - * exists so the registry doesn't need a tag on every row. */ -enum { - CG_ARCH_AARCH64 = 1u << 0, - CG_ARCH_X64 = 1u << 1, - CG_ARCH_RV64 = 1u << 2, - /* Default = portable across all implemented backends. Cases that emit - * hand-crafted bytes for a specific arch (mc_smoke today) must set - * their arch mask explicitly. */ - CG_ARCH_DEFAULT = CG_ARCH_AARCH64 | CG_ARCH_X64 | CG_ARCH_RV64, -}; - -typedef struct CgCase { - const char* name; - CgCaseFn build; - int expected; /* test_main return value (default 0) */ - unsigned kind; /* CgCaseKind */ - unsigned arches; /* CG_ARCH_* mask; 0 = CG_ARCH_DEFAULT */ -} CgCase; - -extern const CgCase cg_cases[]; -extern const unsigned cg_cases_count; - -/* ---- DWARF checks (path W) ---- - * Optional per-case directives consumed by test/cg/harness/cg_check_dwarf - * after --emit. Each entry pairs a case name with a directive blob: one - * directive per line, blank lines ignored. Cases not listed here are - * skipped on path W. Supported directives: - * - * line FILE LINE - * Some PC inside the obj's text must map to (FILE, LINE) and the - * inverse line_to_addr must round-trip. - * - * subprogram NAME - * cfree_dwarf_subprogram_at must report a non-empty pc range for - * the named symbol. - * - * The cfree_dwarf_* consumers are stubbed today (src/api/stubs.c), so - * every directive currently fails — that's intentional. */ -typedef struct CgDwarfCheck { - const char* case_name; - const char* directives; -} CgDwarfCheck; - -extern const CgDwarfCheck cg_dwarf_checks[]; -extern const unsigned cg_dwarf_checks_count; - -/* ---- pre-interned type accessors ---- - * Resolved once per ctx via type_prim/type_void/type_ptr against - * ctx->pool. Sugar so cases don't repeat the lookup. */ -const Type* T_void(CgTestCtx*); -const Type* T_i8(CgTestCtx*); -const Type* T_u8(CgTestCtx*); -const Type* T_i16(CgTestCtx*); -const Type* T_u16(CgTestCtx*); -const Type* T_i32(CgTestCtx*); -const Type* T_u32(CgTestCtx*); -const Type* T_i64(CgTestCtx*); -const Type* T_u64(CgTestCtx*); -const Type* T_f32(CgTestCtx*); -const Type* T_f64(CgTestCtx*); -const Type* T_ptr_void(CgTestCtx*); -const Type* T_ptr(CgTestCtx*, const Type* pointee); - -/* ---- operand sugar ---- */ -Operand IMM_op(i64 v, const Type* ty); -Operand REG_op(Reg r, const Type* ty); -Operand LOCAL_op(FrameSlot s, const Type* ty); -Operand IND_op(Reg base, i32 ofs, const Type* ty); -Operand GLOBAL_op(ObjSymId sym, i64 addend); - -/* ---- function-fixture helpers ---- */ - -typedef struct CgTestParam { - const Type* type; - FrameSlot slot; /* FS_PARAM home, allocated by helper */ - const ABIArgInfo* abi; /* points into ABIFuncInfo.params[i] */ -} CgTestParam; - -typedef struct CgTestFn { - CgTestCtx* ctx; - const Type* fn_type; /* TY_FUNC; built from ret + param types */ - const Type* ret_ty; - const ABIFuncInfo* abi_info; /* abi_func_info(c->abi, fn_type) */ - ObjSymId sym; - CGFuncDesc fd; - CgTestParam* params; - u32 nparams; - u32 func_begin_ofs; /* obj_pos at func_begin entry; used to compute the - (begin, end) PC range passed to debug_func_pc_range - in cgtest_end when ctx->debug != NULL. Mirrors the - field doc/DWARF.md §3.1 expects on CG. */ -} CgTestFn; - -/* Set the pending source loc, fanning out to both CGTarget (which forwards - * to MCEmitter) and Debug (debug_set_pending_loc). The harness is the - * parser stand-in per doc/DWARF.md §3.1; this is the parser-half of the - * Class-2 line-row protocol. Cases that need to stamp specific (file, - * line) onto an instruction range should call this rather than - * target->set_loc directly so the Debug fanout happens. */ -void cgtest_set_loc(CgTestCtx* ctx, SrcLoc loc); - -/* Begin a function returning ret_ty with no parameters. test_main is the - * canonical entry; the runner casts it to int(*)(void). Internally calls - * cgtest_begin_func with name="test_main" and zero params. */ -CgTestFn* cgtest_begin_main(CgTestCtx* ctx, const Type* ret_ty); - -/* Begin an arbitrary named function. param_types[i] is the type of param i. - * - * - Builds fn_type via type_func(pool, ret_ty, param_types, nparams, 0). - * - Computes ABIFuncInfo via abi_func_info(c->abi, fn_type). - * - Allocates an FS_PARAM frame slot for each param (size/align from - * abi_sizeof/abi_alignof on the param type). - * - Constructs CGParamDesc{index,name=0,type,slot,abi=info->params[i], - * incoming=info->params[i]->parts, nincoming=info->params[i]->nparts, - * loc=0} and stores into fd.params[]. - * - Calls target->func_begin(target, &fd). - * - For each param, calls target->param(target, &fd.params[i]). - * - * Returns a CgTestFn the body can use; cgtest_param_slot(tf,i) reads the - * home slot for param i. */ -CgTestFn* cgtest_begin_func(CgTestCtx* ctx, const char* name, - const Type* ret_ty, const Type* const* param_types, - u32 nparams); - -/* Like cgtest_begin_func, but uses an already-allocated ObjSymId instead of - * creating one. Lets a case forward-declare a symbol with cgtest_decl_func - * (so a mutually-recursive partner can refer to it before its body emits) - * and then attach the definition here. */ -CgTestFn* cgtest_begin_func_at(CgTestCtx* ctx, ObjSymId pre_sym, - const Type* ret_ty, - const Type* const* param_types, u32 nparams); - -/* Forward-declare a function symbol with the given name. Returns an - * ObjSymId callable via cgtest_call before its body is emitted. The symbol - * is defined later by cgtest_begin_func_at(..., pre_sym, ...). */ -ObjSymId cgtest_decl_func(CgTestCtx*, const char* name); - -FrameSlot cgtest_param_slot(CgTestFn*, u32 idx); - -/* ---- frame slots and memory ---- */ - -/* Allocate a local frame slot of the given type with default size/align - * from the live TargetABI. flags is a FrameSlotFlag mask (FSF_ADDR_TAKEN, - * etc.). */ -FrameSlot cgtest_local(CgTestFn*, const Type* ty, u16 flags); - -/* Like cgtest_local but additionally registers a DW_TAG_variable when the - * harness was constructed with Debug. The caller supplies the source-level - * decl name and SrcLoc; the variable's location is encoded as DW_OP_fbreg - * with the supplied frame_ofs. The harness has no public API to read a - * FrameSlot's actual fp-relative offset, so callers wanting a specific - * encoded value pass it explicitly — directives that don't care use 0 and - * accept the wildcard "*". */ -FrameSlot cgtest_local_named(CgTestFn*, const Type* ty, u16 flags, - const char* name, SrcLoc decl, i32 frame_ofs); - -/* Convenience wrappers around target->load/store with a default MemAccess - * derived from `ty` (size/align from TargetABI, alias=ALIAS_LOCAL). */ -void cgtest_load_local(CgTestFn*, Operand dst_reg, FrameSlot, const Type*); -void cgtest_store_local(CgTestFn*, FrameSlot, Operand src, const Type*); - -/* ---- return ---- */ - -void cgtest_ret_reg(CgTestFn*, Reg r, const Type* ty); -void cgtest_ret_imm(CgTestFn*, i64 imm, const Type* ty); -void cgtest_ret_void(CgTestFn*); -/* Aggregate / sret return: result lives at the address held in addr_local - * (typically an FS_LOCAL of the ret type). Builds CGABIValue with - * abi=fn->abi_info->ret, storage=OPK_LOCAL{addr_local}, parts=NULL. */ -void cgtest_ret_indirect(CgTestFn*, FrameSlot addr_local); -/* For a struct return that is split into two registers per ABI: caller has - * already loaded each part into a register; this packs them into the - * CGABIValue.parts array so the backend can place them in the ABI-classed - * registers. */ -void cgtest_ret_struct_in_regs(CgTestFn*, const Reg* part_regs, u32 nparts); - -void cgtest_end(CgTestFn*); - -/* ---- direct calls ---- */ - -typedef enum { - CGT_ARG_IMM, /* scalar immediate */ - CGT_ARG_REG, /* scalar register */ - CGT_ARG_LOCAL_VALUE, /* scalar value loaded from a local slot */ - CGT_ARG_BYVAL_LOCAL, /* aggregate by value: backend reads from &local */ - CGT_ARG_INDIRECT_LOCAL, /* aggregate indirect: pointer to &local */ -} CgTestArgKind; - -typedef struct CgTestArg { - u8 kind; /* CgTestArgKind */ - const Type* type; - union { - i64 imm; - Reg reg; - FrameSlot slot; - } v; -} CgTestArg; - -/* Emit a direct call to `callee_sym` whose signature matches the function - * defined by cgtest_begin_func with `ret_ty` + `arg_types`. Internally: - * - * - Builds fn_type = type_func(pool, ret_ty, arg_types, nargs, 0). - * - Looks up abi_func_info(c->abi, fn_type) to classify ret + each arg. - * - Materializes a CGABIValue for each arg using args[i] (IMM / REG / - * LOCAL_VALUE pack into storage; BYVAL_LOCAL/INDIRECT_LOCAL pack the - * local's address as storage). - * - Builds CGCallDesc.callee = OPK_GLOBAL{callee_sym, 0} for direct call. - * - Sets CGCallDesc.ret with ret_storage as the destination operand: - * scalar return : REG_op(dst, ret_ty) - * sret return : LOCAL_op(sret_slot, ret_ty) - * void return : IMM_op(0, T_void) (storage unused) - * - Calls target->call(target, &desc). */ -void cgtest_call(CgTestFn* caller, ObjSymId callee_sym, const Type* ret_ty, - const Type* const* arg_types, const CgTestArg* args, u32 nargs, - Operand ret_storage); - -/* Like cgtest_call, but the callee is held in a register at runtime — emits - * an indirect call. CGCallDesc.callee.kind is set to OPK_REG (as opposed to - * OPK_GLOBAL); the rest of the wiring (ABI, args, ret) is identical. */ -void cgtest_call_indirect(CgTestFn* caller, Reg callee, const Type* ret_ty, - const Type* const* arg_types, const CgTestArg* args, - u32 nargs, Operand ret_storage); - -/* ---- low-level helpers (used by mc_smoke and similar) ---- */ - -/* Define a function symbol at the current MCEmitter section position with - * size = current_pos - start_pos. Used by MC-only cases that emit bytes - * directly without a CGTarget. */ -ObjSymId cgtest_mc_begin_main(CgTestCtx*); -void cgtest_mc_end_main(CgTestCtx*, ObjSymId, u32 start_pos); - -#endif diff --git a/test/cg/run.sh b/test/cg/run.sh @@ -1,652 +0,0 @@ -#!/usr/bin/env bash -# test/cg/run.sh — fixture-driven cg / CGTarget / MCEmitter test harness. -# -# For each registered case (cg-runner --list), runs up to four paths: -# -# D in-process JIT — cg-runner --jit NAME → exit code matches expected. -# No file I/O. aarch64 host only. -# R ELF roundtrip — cg-runner --emit NAME → cfree-roundtrip → readelf+ -# normalize diff. Validates emitter+reader fidelity. -# E exec via qemu — cg-runner --emit + start.o → link-exe-runner → qemu/ -# podman → exit code. Cross-host friendly. -# J jit-via-file — cg-runner --emit + jit-runner. aarch64 host. -# W DWARF check — cg-runner --emit + cg-runner --dwarf-checks NAME | -# cg_check_dwarf OBJ. Group P only; cases that don't -# register checks are silently skipped. Today every -# check fails by design — debug_emit and the -# cfree_dwarf_* consumers are stubs. -# S asm roundtrip — for every cg-emitted aarch64 binary, walk .text -# through cfree_disasm_iter_*, re-assemble the -# resulting text via the asm-runner, byte-compare. -# Phase 1 (per doc/ASM.md §5): always reports -# SKIP — the disasm iterator and asm parser are -# stubs in src/api/stubs.c. S is opt-in (not in -# the default DREJW path matrix) until phase 4 -# lands; run with `./run.sh '' S` or -# CFREE_TEST_PATHS=DREJWS. -# -# Reuses the existing test/link harness binaries (link-exe-runner, -# jit-runner, cfree-roundtrip) verbatim. -# -# Skip-vs-fail follows test/link convention: skipped layers are treated as -# failures unless CFREE_TEST_ALLOW_SKIP=1. -# -# Filtering: -# ./run.sh [name_filter] [paths] -# name_filter substring match against case name (e.g. "a01", "add") -# paths subset of "DREJW" (default "DREJW") -# Equivalent env vars: CFREE_TEST_FILTER, CFREE_TEST_PATHS. -# -# Parallelism: -# default run in parallel with a capped CPU-count default. -# CFREE_TEST_JOBS=N run up to N cases per opt level concurrently. -# CFREE_TEST_JOBS=auto same as the default. - -set -u - -ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -TEST_DIR="$ROOT/test/cg" -LINK_TEST_DIR="$ROOT/test/link" -BUILD_DIR="$ROOT/build/test" -LIB_AR="$ROOT/build/libcfree.a" - -CG_RUNNER="$BUILD_DIR/cg-runner" -ROUNDTRIP_BIN="$BUILD_DIR/cfree-roundtrip" -LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner" -JIT_RUNNER="$BUILD_DIR/jit-runner" -DWARF_CHECK="$BUILD_DIR/cg-check-dwarf" -NORMALIZE="$ROOT/test/elf/normalize.py" - -# shellcheck source=../lib/parallel.sh -source "$ROOT/test/lib/parallel.sh" - -# CFREE_TEST_ARCH and CFREE_TEST_OBJ together select the cross-target -# the harness drives the compiler at. Defaults aa64+elf preserve -# historical behavior. The runners (cg-runner / link-exe-runner / -# jit-runner) read the same env vars via test/lib/cfree_test_target.h, -# so the C side and the shell side stay in lockstep. -CFREE_TEST_ARCH="${CFREE_TEST_ARCH:-aa64}" -CFREE_TEST_OBJ="${CFREE_TEST_OBJ:-elf}" -case "$CFREE_TEST_ARCH" in - aa64|aarch64|arm64) TEST_ARCH=aa64; EXEC_ARCH=aarch64 ;; - x64|x86_64|amd64) TEST_ARCH=x64; EXEC_ARCH=x64 ;; - rv64|riscv64) TEST_ARCH=rv64; EXEC_ARCH=rv64 ;; - *) printf 'unknown CFREE_TEST_ARCH=%s\n' "$CFREE_TEST_ARCH" >&2; exit 2 ;; -esac -case "$CFREE_TEST_OBJ" in - elf) - EXEC_OS=linux - case "$TEST_ARCH" in - aa64) CLANG_TRIPLE=aarch64-linux-gnu ;; - x64) CLANG_TRIPLE=x86_64-linux-gnu ;; - rv64) CLANG_TRIPLE=riscv64-linux-gnu ;; - esac - ;; - macho) - EXEC_OS=macos - case "$TEST_ARCH" in - aa64) CLANG_TRIPLE=arm64-apple-macos ;; - x64) CLANG_TRIPLE=x86_64-apple-macos ;; - rv64) printf 'CFREE_TEST_OBJ=macho has no rv64 target\n' >&2; exit 2 ;; - esac - ;; - *) printf 'unknown CFREE_TEST_OBJ=%s\n' "$CFREE_TEST_OBJ" >&2; exit 2 ;; -esac -EXEC_TAG="${EXEC_ARCH}-${EXEC_OS}" -export CFREE_TEST_ARCH CFREE_TEST_OBJ - -CLANG_TARGET="--target=$CLANG_TRIPLE" -CC="${CC:-cc}" -CFREE_CFLAGS="-I$ROOT/include -I$ROOT/src -I$ROOT/test -I$TEST_DIR/harness" -ALLOW_SKIP="${CFREE_TEST_ALLOW_SKIP:-0}" - -# Filters (env vars or positional args; args win): -# $1 / CFREE_TEST_FILTER — substring match against case name -# $2 / CFREE_TEST_PATHS — subset of "DREJ" (default "DREJ") -# CFREE_OPT_LEVELS — space-separated opt levels to exercise. Default -# "0 1": directly against the backend (level 0) -# and through the opt_cgtarget wrapper (level 1). -# Level 2 (Phase 3 dry-run build_cfg + build_ssa, -# discarded before replay) is opt-in via -# CFREE_OPT_LEVELS="0 1 2". -# Path W (DWARF) only runs at level 0 — opt-level -# DWARF equivalence is a later phase concern. -FILTER="${1:-${CFREE_TEST_FILTER:-}}" -PATHS="${2:-${CFREE_TEST_PATHS:-DREJW}}" -OPT_LEVELS="${CFREE_OPT_LEVELS:-0 1}" -case "$PATHS" in *D*) RUN_D=1;; *) RUN_D=0;; esac -case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac -case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac -case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac -case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac -case "$PATHS" in *S*) RUN_S=1;; *) RUN_S=0;; esac -T_D=0; T_R=0; T_E=0; T_J=0; T_W=0; T_S=0 # accumulated wall-clock seconds per path -now_ms() { python3 -c 'import time;print(int(time.time()*1000))'; } - -mkdir -p "$BUILD_DIR" "$BUILD_DIR/cg" - -TEST_JOBS="$(cfree_parallel_jobs)" || exit 2 -PARALLEL_DIR="$BUILD_DIR/cg.parallel/$$" -mkdir -p "$PARALLEL_DIR" - -PASS=0; FAIL=0; SKIP=0 -FAIL_NAMES=(); SKIP_NAMES=() - -color_red() { printf '\033[31m%s\033[0m' "$1"; } -color_grn() { printf '\033[32m%s\033[0m' "$1"; } -color_yel() { printf '\033[33m%s\033[0m' "$1"; } - -note_pass() { PASS=$((PASS+1)); printf ' %s %s\n' "$(color_grn PASS)" "$1"; } -note_fail() { FAIL=$((FAIL+1)); FAIL_NAMES+=("$1"); printf ' %s %s\n' "$(color_red FAIL)" "$1"; } -note_skip() { SKIP=$((SKIP+1)); SKIP_NAMES+=("$1"); printf ' %s %s — %s\n' "$(color_yel SKIP)" "$1" "$2"; } - -event_path() { printf '%s/%s.%04d.events' "$PARALLEL_DIR" "$1" "$2"; } -worker_stdout_path() { printf '%s/%s.%04d.stdout' "$PARALLEL_DIR" "$1" "$2"; } -worker_stderr_path() { printf '%s/%s.%04d.stderr' "$PARALLEL_DIR" "$1" "$2"; } - -emit_event() { - local file="$1" kind="$2" - shift 2 - printf '%s' "$kind" >> "$file" - while [ $# -gt 0 ]; do - printf '\t%s' "$1" >> "$file" - shift - done - printf '\n' >> "$file" -} - -replay_events() { - local event="$1" stdout_log="$2" stderr_log="$3" - local kind a b c d e f - - if [ ! -s "$event" ]; then - note_fail "internal: missing worker result $event" - if [ -s "$stdout_log" ]; then sed 's/^/ | /' "$stdout_log"; fi - if [ -s "$stderr_log" ]; then sed 's/^/ | /' "$stderr_log"; fi - return - fi - - while IFS=$'\t' read -r kind a b c d e f; do - case "$kind" in - NOOP) : ;; - PASS) note_pass "$a" ;; - FAIL) note_fail "$a" ;; - SKIP) note_skip "$a" "$b" ;; - TIME) - case "$a" in - D) T_D=$(( T_D + b )) ;; - R) T_R=$(( T_R + b )) ;; - E) T_E=$(( T_E + b )) ;; - J) T_J=$(( T_J + b )) ;; - W) T_W=$(( T_W + b )) ;; - S) T_S=$(( T_S + b )) ;; - esac - ;; - QUEUE_E) - E_NAMES+=("$a") - E_WORK+=("$b") - E_LINK_MS+=("$c") - E_EXPECTED+=("$d") - T_E=$(( T_E + c )) - exec_target_queue "$f" "$e" "$b/linked.exe" \ - "$b/exec.out" "$b/exec.err" "$b/exec.rc" - ;; - *) - note_fail "internal: malformed worker event in $event" - ;; - esac - done < "$event" -} - -run_parallel_items() { - local layer="$1" worker="$2" - shift 2 - - local events=() - local stdout_logs=() - local stderr_logs=() - local idx=0 - local item event stdout_log stderr_log - - for item in "$@"; do - event="$(event_path "$layer" "$idx")" - stdout_log="$(worker_stdout_path "$layer" "$idx")" - stderr_log="$(worker_stderr_path "$layer" "$idx")" - : > "$event" - : > "$stdout_log" - : > "$stderr_log" - events+=("$event") - stdout_logs+=("$stdout_log") - stderr_logs+=("$stderr_log") - cfree_parallel_run "$TEST_JOBS" "$worker" "$idx" "$item" "$event" \ - > "$stdout_log" 2> "$stderr_log" - idx=$((idx+1)) - done - - cfree_parallel_wait_all || true - - idx=0 - while [ $idx -lt ${#events[@]} ]; do - replay_events "${events[$idx]}" "${stdout_logs[$idx]}" "${stderr_logs[$idx]}" - idx=$((idx+1)) - done -} - -# ---- tool detection -------------------------------------------------------- - -have_clang_cross=0 -have_readelf=0 -have_python3=0 -have_qemu=0 -have_podman=0 -have_runner=0 -have_roundtrip=0 -have_exe_runner=0 -have_jit_runner=0 -is_aarch64=0 - -if clang $CLANG_TARGET -c -x c - -o /dev/null < /dev/null 2>/dev/null; then - have_clang_cross=1 -fi -command -v llvm-readelf >/dev/null 2>&1 && have_readelf=1 -command -v readelf >/dev/null 2>&1 && have_readelf=1 -command -v python3 >/dev/null 2>&1 && have_python3=1 - -QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)" -[ -n "$QEMU_BIN" ] && have_qemu=1 -command -v podman >/dev/null 2>&1 && have_podman=1 -{ [ $have_qemu -eq 1 ] || [ $have_podman -eq 1 ]; } && have_runner=1 - -arch_raw="$(uname -m 2>/dev/null || true)" -{ [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1 - -# is_native_target=1 when the cross-target arch matches the host arch. -# Path D (in-process JIT) and path J (jit-runner) require native execution -# of cfree-emitted code; on a non-matching host we skip them. -is_native_target=0 -case "$TEST_ARCH" in - aa64) [ $is_aarch64 -eq 1 ] && is_native_target=1 ;; - x64) { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_native_target=1 ;; - rv64) [ "$arch_raw" = "riscv64" ] && is_native_target=1 ;; -esac - -READELF_BIN="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)" - -# Shared per-arch exec helper — see test/lib/exec_target.sh. Path E -# queues each linked.exe and we drain the queue in a single batched -# podman run per arch after the case loop, amortizing the per-launch -# podman overhead across all ~200 cg cases. -EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR" -# shellcheck source=../lib/exec_target.sh -source "$ROOT/test/lib/exec_target.sh" - -# ---- build harness binaries ------------------------------------------------ - -printf 'Building harness...\n' - -if [ ! -f "$LIB_AR" ]; then - printf ' FATAL: %s not found — run "make lib" first\n' "$LIB_AR" >&2 - exit 1 -fi - -# cg-runner -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" \ - "$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_p.c" \ - "$TEST_DIR/harness/cases_q.c" \ - "$TEST_DIR/harness/cases_asm.c" \ - "$LIB_AR" -o "$CG_RUNNER" 2>"$BUILD_DIR/cg-runner.err"; then - printf ' %s cg-runner\n' "$(color_grn built)" -else - printf ' %s cg-runner (see %s)\n' \ - "$(color_red FATAL)" "$BUILD_DIR/cg-runner.err" >&2 - exit 1 -fi - -# cfree-roundtrip — for path R. test/elf/run.sh builds this; skip path R if -# we can't find or build it. -if [ ! -x "$ROUNDTRIP_BIN" ]; then - if $CC -I"$ROOT/include" -I"$ROOT/src" \ - "$ROOT/test/elf/cfree-roundtrip.c" "$LIB_AR" \ - -o "$ROUNDTRIP_BIN" 2>"$BUILD_DIR/cfree-roundtrip.err"; then - have_roundtrip=1 - printf ' %s cfree-roundtrip\n' "$(color_grn built)" - else - printf ' %s cfree-roundtrip (see %s)\n' \ - "$(color_yel warn)" "$BUILD_DIR/cfree-roundtrip.err" >&2 - fi -else - have_roundtrip=1 -fi - -# link-exe-runner — for path E. -if [ ! -x "$LINK_EXE_RUNNER" ]; then - if $CC -I"$ROOT/include" -I"$ROOT/test" \ - "$LINK_TEST_DIR/harness/link_exe_runner.c" \ - "$LIB_AR" -o "$LINK_EXE_RUNNER" 2>"$BUILD_DIR/link-exe-runner.err"; then - have_exe_runner=1 - printf ' %s link-exe-runner\n' "$(color_grn built)" - else - printf ' %s link-exe-runner (see %s)\n' \ - "$(color_yel warn)" "$BUILD_DIR/link-exe-runner.err" >&2 - fi -else - have_exe_runner=1 -fi - -# jit-runner — for path J. Only when the host arch matches the cross-target -# (otherwise the JIT can't execute the emitted code natively). -if [ $is_native_target -eq 1 ]; then - if [ ! -x "$JIT_RUNNER" ]; then - if $CC -I"$ROOT/include" -I"$ROOT/test" "$LINK_TEST_DIR/harness/jit_runner.c" \ - "$LIB_AR" -o "$JIT_RUNNER" 2>"$BUILD_DIR/jit-runner.err"; then - have_jit_runner=1 - printf ' %s jit-runner\n' "$(color_grn built)" - else - printf ' %s jit-runner (see %s)\n' \ - "$(color_yel warn)" "$BUILD_DIR/jit-runner.err" >&2 - fi - else - have_jit_runner=1 - fi -fi - -# cg-check-dwarf — for path W. Always rebuild (small file, picks up -# changes alongside the rest of the harness). -have_dwarf_check=0 -if $CC -I"$ROOT/include" "$TEST_DIR/harness/cg_check_dwarf.c" \ - "$LIB_AR" -o "$DWARF_CHECK" 2>"$BUILD_DIR/cg-check-dwarf.err"; then - have_dwarf_check=1 - printf ' %s cg-check-dwarf\n' "$(color_grn built)" -else - printf ' %s cg-check-dwarf (see %s)\n' \ - "$(color_yel warn)" "$BUILD_DIR/cg-check-dwarf.err" >&2 -fi - -# Cached start.o — every case used to recompile this from the same source -# (~40 ms × N cases). Build it once for the whole harness run. -START_OBJ="$BUILD_DIR/cg_start.o" -have_start_obj=0 -if [ $have_clang_cross -eq 1 ]; then - if clang $CLANG_TARGET -O1 -ffreestanding -fno-stack-protector \ - -fno-PIC -fno-pie \ - -c "$LINK_TEST_DIR/harness/start.c" -o "$START_OBJ" 2>/dev/null; then - have_start_obj=1 - fi -fi - -CASES="$($CG_RUNNER --list)" - -run_cg_case() { - local _idx="$1" name="$2" event="$3" - local work case_arches expected expected_byte case_tag obj t0 dt - local d_rc rt r_ok r_msg exe link_dt j_rc w_rc - : "$_idx" - - work="$BUILD_DIR/$WORK_SUB/$name" - mkdir -p "$work" - - # Filter cases whose declared arch mask excludes the test arch. - # cg-runner --arches NAME prints one token per arch the case - # supports; skip if our $EXEC_ARCH isn't listed. - case_arches="$("${CG_RUN[@]}" --arches "$name" 2>/dev/null)" - if [ -n "$case_arches" ] && \ - ! printf '%s\n' "$case_arches" | grep -qx "$EXEC_ARCH"; then - emit_event "$event" NOOP - return 0 - fi - - expected="$("${CG_RUN[@]}" --expected "$name" 2>/dev/null)" - expected="${expected:-0}" - # Exit codes are mod 256 on POSIX; mask the expected the same way so - # negative-return cases compare correctly. - expected_byte=$(( expected & 0xff )) - - # Path E target tag. The shell drives every case at the - # (CFREE_TEST_ARCH, CFREE_TEST_OBJ)-selected target — emit panics - # on stub backends surface as case failures rather than harness - # skips, which is the multi-arch/multi-obj contract through - # Phase 2. cg-runner's --arches output is informational at this - # stage. - case_tag="$EXEC_TAG" - - # ---- Path D: in-process JIT (only when host arch == cross-target) ---- - if [ $RUN_D -eq 1 ]; then - if [ $is_native_target -eq 1 ]; then - t0=$(now_ms) - "${CG_RUN[@]}" --jit "$name" >"$work/d.out" 2>"$work/d.err" - d_rc=$? - dt=$(( $(now_ms) - t0 )) - emit_event "$event" TIME D "$dt" - if [ "$d_rc" -eq "$expected_byte" ]; then - emit_event "$event" PASS "$name/D${TAG} (${dt}ms)" - else - emit_event "$event" FAIL "$name/D${TAG} (expected $expected_byte got $d_rc, ${dt}ms)" - fi - else - emit_event "$event" SKIP "$name/D${TAG}" "host arch != $TEST_ARCH (no native JIT)" - fi - fi - - # ---- emit (needed by R/E/J/W) ----------------------------------------- - obj="$work/$name.o" - if [ $RUN_R -eq 1 ] || [ $RUN_E -eq 1 ] || [ $RUN_J -eq 1 ] \ - || [ $RUN_W -eq 1 ]; then - if ! "${CG_RUN[@]}" --emit "$name" "$obj" 2>"$work/emit.err"; then - emit_event "$event" FAIL "$name/emit${TAG} (cg-runner --emit failed; see $work/emit.err)" - return 0 - fi - fi - - # ---- Path R: ELF roundtrip -------------------------------------------- - if [ $RUN_R -eq 1 ]; then - if [ $have_roundtrip -eq 1 ] && [ $have_readelf -eq 1 ] && [ $have_python3 -eq 1 ]; then - t0=$(now_ms) - rt="$work/$name.rt.o" - r_ok=1; r_msg="" - if ! "$ROUNDTRIP_BIN" "$obj" "$rt" 2>"$work/rt.err"; then - r_ok=0; r_msg=" (roundtrip failed)" - else - "$READELF_BIN" -aW "$obj" | python3 "$NORMALIZE" >"$work/golden.norm" 2>/dev/null - "$READELF_BIN" -aW "$rt" | python3 "$NORMALIZE" >"$work/rt.norm" 2>/dev/null - diff -u "$work/golden.norm" "$work/rt.norm" >"$work/r.diff" 2>&1 || r_ok=0 - fi - dt=$(( $(now_ms) - t0 )) - emit_event "$event" TIME R "$dt" - if [ $r_ok -eq 1 ]; then emit_event "$event" PASS "$name/R${TAG} (${dt}ms)" - else emit_event "$event" FAIL "$name/R${TAG}${r_msg} (${dt}ms)"; fi - else - emit_event "$event" SKIP "$name/R${TAG}" "missing roundtrip/readelf/python3" - fi - fi - - # ---- Path E: link + (batched) qemu/podman ------------------------------ - # Link now (per case); the run is queued for the post-loop flush. - if [ $RUN_E -eq 1 ]; then - if [ $have_exe_runner -eq 1 ] && [ $have_clang_cross -eq 1 ] \ - && [ $have_start_obj -eq 1 ]; then - t0=$(now_ms) - exe="$work/linked.exe" - if ! "$LINK_EXE_RUNNER" -o "$exe" "$obj" "$START_OBJ" \ - >"$work/exec_link.out" 2>"$work/exec_link.err"; then - dt=$(( $(now_ms) - t0 )) - emit_event "$event" TIME E "$dt" - emit_event "$event" FAIL "$name/E${TAG} (link failed, ${dt}ms)" - elif exec_target_supported "$case_tag"; then - link_dt=$(( $(now_ms) - t0 )) - # Queue with a level-tagged key so cases at different - # opt levels don't collide in the batched runner. - emit_event "$event" QUEUE_E "$name" "$work" "$link_dt" \ - "$expected_byte" "L${OPT_LEVEL}_${name}" "$case_tag" - else - emit_event "$event" SKIP "$name/E${TAG}" "no runner for $case_tag" - fi - else - emit_event "$event" SKIP "$name/E${TAG}" "no link-exe-runner, aarch64 clang, or start.o" - fi - fi - - # ---- Path J: jit-via-file --------------------------------------------- - if [ $RUN_J -eq 1 ]; then - if [ $have_jit_runner -eq 1 ]; then - t0=$(now_ms) - "$JIT_RUNNER" "$obj" >"$work/jit.out" 2>"$work/jit.err" - j_rc=$? - dt=$(( $(now_ms) - t0 )) - emit_event "$event" TIME J "$dt" - if [ "$j_rc" -eq "$expected_byte" ]; then - emit_event "$event" PASS "$name/J${TAG} (${dt}ms)" - else - emit_event "$event" FAIL "$name/J${TAG} (expected $expected_byte got $j_rc, ${dt}ms)" - fi - else - emit_event "$event" SKIP "$name/J${TAG}" "no jit-runner (host arch != $TEST_ARCH)" - fi - fi - - # ---- Path W: DWARF check ---------------------------------------------- - # Cases that don't register directives produce empty stdout from - # --dwarf-checks; we silently skip those (no SKIP entry, since W is - # opt-in per case rather than per host). DWARF / opt-level - # equivalence is a Phase 5+ concern, so skip W when level > 0. - if [ $RUN_W -eq 1 ] && [ "$OPT_LEVEL" = "0" ]; then - "${CG_RUN[@]}" --dwarf-checks "$name" >"$work/w.directives" \ - 2>"$work/w.dc.err" - if [ -s "$work/w.directives" ]; then - if [ $have_dwarf_check -eq 1 ]; then - t0=$(now_ms) - "$DWARF_CHECK" "$obj" <"$work/w.directives" \ - >"$work/w.out" 2>"$work/w.err" - w_rc=$? - dt=$(( $(now_ms) - t0 )) - emit_event "$event" TIME W "$dt" - if [ "$w_rc" -eq 0 ]; then - emit_event "$event" PASS "$name/W (${dt}ms)" - else - emit_event "$event" FAIL "$name/W (see $work/w.out, $work/w.err; ${dt}ms)" - fi - else - emit_event "$event" SKIP "$name/W" "no cg-check-dwarf" - fi - fi - fi - - # ---- Path S: asm roundtrip (phase-1 stub) ----------------------------- - # Walks .text through cfree_disasm_iter_*, reassembles via - # asm-runner --encode, byte-compares against the emitted bytes. - # Phase 1 per doc/ASM.md §5: the iterator and parse_asm are still - # stubs, so we report SKIP unconditionally when S is requested. - # When phase 3+4 land, replace this block with the real - # disasm/reassemble pipeline. - if [ $RUN_S -eq 1 ]; then - emit_event "$event" SKIP "$name/S${TAG}" "phase 1: cfree_disasm_iter_* / parse_asm are stubs" - fi - - # W-only runs intentionally produce no output for cases without DWARF - # directives. Mark those as handled so replay can still distinguish them - # from workers that failed before writing a result. - emit_event "$event" NOOP - return 0 -} - -# Each level wraps cg-runner with --opt-level N. Level 0 drives the AArch64 -# backend directly; level >0 inserts opt_cgtarget. Cases tagged with /L<N> -# in the output when level>0 so failures localize to the level. -for OPT_LEVEL in $OPT_LEVELS; do - if [ "$OPT_LEVEL" = "0" ]; then - CG_RUN=("$CG_RUNNER") - TAG="" - WORK_SUB="cg" - else - CG_RUN=("$CG_RUNNER" "--opt-level" "$OPT_LEVEL") - TAG="/L${OPT_LEVEL}" - WORK_SUB="cg-L${OPT_LEVEL}" - fi - - printf 'Running cases (opt-level %s, %s jobs)...\n' "$OPT_LEVEL" "$TEST_JOBS" - - # Path E result bookkeeping (per level — flushed at end of this iteration). - E_NAMES=() - E_WORK=() - E_LINK_MS=() - E_EXPECTED=() - - FILTERED_CASES=() - for name in $CASES; do - [ -n "$FILTER" ] && [[ "$name" != *"$FILTER"* ]] && continue - FILTERED_CASES+=("$name") - done - - run_parallel_items "cg-L${OPT_LEVEL}" run_cg_case "${FILTERED_CASES[@]}" - - # ---- batched path-E flush + verification (per level) ------------------- - # Run every queued case in a single podman invocation per arch, then - # iterate the queue to read each exit code and emit PASS/FAIL. - if [ "$(exec_target_queue_size)" -gt 0 ]; then - printf 'Running path E%s (%d cases batched)...\n' \ - "$TAG" "$(exec_target_queue_size)" - t0=$(now_ms) - exec_target_flush - DELTA=$(( $(now_ms) - t0 )) - T_E_BATCH=$(( ${T_E_BATCH:-0} + DELTA )); T_E=$(( T_E + DELTA )) - - i=0 - while [ $i -lt ${#E_NAMES[@]} ]; do - name="${E_NAMES[$i]}" - work="${E_WORK[$i]}" - link_dt="${E_LINK_MS[$i]}" - expected_byte="${E_EXPECTED[$i]}" - if [ ! -f "$work/exec.rc" ]; then - note_fail "$name/E${TAG} (no rc; podman batch did not produce results)" - else - RUN_RC="$(cat "$work/exec.rc")" - if [ "$RUN_RC" -eq "$expected_byte" ]; then - note_pass "$name/E${TAG} (link ${link_dt}ms)" - else - note_fail "$name/E${TAG} (expected $expected_byte got $RUN_RC, link ${link_dt}ms)" - fi - fi - i=$((i+1)) - done - fi -done - -T_E_BATCH=${T_E_BATCH:-0} - -# ---- summary --------------------------------------------------------------- - -if [ ${#FAIL_NAMES[@]} -gt 0 ]; then - printf '\nFailed:\n' - for n in "${FAIL_NAMES[@]}"; do printf ' %s\n' "$n"; done -fi - -if [ ${#SKIP_NAMES[@]} -gt 0 ] && [ "$ALLOW_SKIP" != "1" ]; then - printf '\nSkipped (treat as failure; set CFREE_TEST_ALLOW_SKIP=1 to allow):\n' - for n in "${SKIP_NAMES[@]}"; do printf ' %s\n' "$n"; done -fi - -printf '\nResults: %s pass, %s fail, %s skip\n' "$PASS" "$FAIL" "$SKIP" -printf 'Time: D=%dms R=%dms E=%dms (batch %dms) J=%dms W=%dms S=%dms\n' \ - "$T_D" "$T_R" "$T_E" "$T_E_BATCH" "$T_J" "$T_W" "$T_S" - -if [ $FAIL -gt 0 ]; then exit 1; fi -if [ $SKIP -gt 0 ] && [ "$ALLOW_SKIP" != "1" ]; then exit 1; fi -exit 0 diff --git a/test/test.mk b/test/test.mk @@ -15,13 +15,9 @@ # - test-link: linker + JIT behavioral harness in test/link/; three paths # per case (roundtrip R, ELF exec E, JIT J). Depends only on libcfree.a. # Set CFREE_TEST_ALLOW_SKIP=1 to allow skipped layers. -# - test-cg: cg / CGTarget / MCEmitter behavioral harness in test/cg/; -# four paths per case (D direct-JIT, R roundtrip, E exec, J jit-via-file). -# Depends only on libcfree.a; reuses test/link harness binaries. # - test-parse / test-parse-err: file-driven C parser harness in -# test/parse/; same path matrix as test-cg, but each case is a .c -# source file rather than a hand-built ObjBuilder fixture. Built -# against the public cfree.h surface; reuses cfree-roundtrip, +# test/parse/; each case is a .c source file. Built against the public +# cfree.h surface; reuses cfree-roundtrip, # link-exe-runner, and jit-runner. # - test-asm: file-driven assembler/disassembler harness in test/asm/. # Three sub-corpora (encode/, decode/, listing/), one mode per @@ -29,9 +25,9 @@ # parse_asm / cfree_disasm_iter_* are still stubs; the harness builds # and runs end-to-end so the wiring stays exercised. See doc/ASM.md. -.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-cg-api test-cg-binder test-toy test-opt test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-libc test-musl test-glibc test-lib-deps test-smoke-x64 test-smoke-rv64 +.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg-api test-toy test-opt test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-libc test-musl test-glibc test-lib-deps test-smoke-x64 test-smoke-rv64 -test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg test-cg-binder test-toy test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-lib-deps +test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-toy test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-lib-deps test-lex: bin @CFREE=$(abspath $(BIN)) test/lex/run.sh @@ -79,8 +75,7 @@ $(DWARF_TEST_BIN): test/dwarf/dwarf_test.c $(LIB_AR) # debug_emit, asserts the produced sections have valid DWARF 5 structure # (length fields, version, address sizes, expected relocations against # function symbol). Deliberately bypasses the consumer (cfree_dwarf_open) -# so encoder bugs aren't masked by matching decoder bugs — end-to-end -# round-trip lives in test/cg path W. +# so encoder bugs aren't masked by matching decoder bugs. DEBUG_TEST_BIN = build/test/debug_roundtrip_unit test-debug: $(DEBUG_TEST_BIN) @@ -103,12 +98,6 @@ $(AA64_ISA_TEST_BIN): test/arch/aa64_isa_test.c $(LIB_AR) @mkdir -p $(dir $@) $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/aa64_isa_test.c $(LIB_AR) -o $@ -# cg_inline_asm constraint binder unit test (doc/INLINEASM.md Track B). -# Drives cg_inline_asm against a stand-in CGTarget that records every -# operand handed to asm_block; covers r/=r/+r/=&r/i/m/0, the "memory" -# clobber spill behaviour, register-name passthrough, and "cc" no-op. -# Internal cg/ + arch/ surface — needs -Isrc. -CG_BINDER_TEST_BIN = build/test/cg_binder_test CG_API_TEST_BIN = build/test/cg_api_test test-cg-api: $(CG_API_TEST_BIN) @@ -118,13 +107,6 @@ $(CG_API_TEST_BIN): test/api/cg_type_test.c $(LIB_AR) @mkdir -p $(dir $@) $(CC) $(DRIVER_CFLAGS) test/api/cg_type_test.c $(LIB_AR) -o $@ -test-cg-binder: $(CG_BINDER_TEST_BIN) - $(CG_BINDER_TEST_BIN) - -$(CG_BINDER_TEST_BIN): test/cg/binder_test.c $(LIB_AR) - @mkdir -p $(dir $@) - $(CC) $(DRIVER_CFLAGS) -Isrc test/cg/binder_test.c $(LIB_AR) -o $@ - test-toy: bin @CFREE=$(abspath $(BIN)) test/toy/run.sh @@ -142,7 +124,7 @@ $(AA64_INLINE_TEST_BIN): test/arch/aa64_inline_test.c $(LIB_AR) @mkdir -p $(dir $@) $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/aa64_inline_test.c $(LIB_AR) -o $@ -# Test harness binaries shared by test-elf, test-link, and test-cg. +# Test harness binaries shared by test-elf and test-link. # Declared as Make targets (not built by the run.sh scripts) so they pick # up libcfree.a changes deterministically. # @@ -185,9 +167,6 @@ test-elf: lib bin-soft $(ROUNDTRIP_BIN) test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER) bash test/link/run.sh -test-cg: lib $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) - bash test/cg/run.sh - OPT_TEST_BIN = build/test/opt_test test-opt: $(OPT_TEST_BIN)