kit

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

frame_value_backend_test.c (37165B)


      1 /* Real-backend regression for typed FRAME_VALUE address components.
      2  *
      3  * Spill-slot coloring is allowed to reuse a physical frame slot across values
      4  * with different type IDs.  NativeAddr therefore carries base_type/index_type
      5  * as the value-width authority: a backend must not infer a reload width from
      6  * the (possibly wider) slot descriptor.  Build two deliberately-wide i64
      7  * slots, consume them as a pointer base plus an i32 scaled index, and inspect
      8  * only the body bytes emitted by each native backend.
      9  *
     10  * This also pins the RV64 load_addr path: it used to load the frame base but
     11  * silently omit an INDEX_FRAME_VALUE.  The final sh2add proves the index
     12  * participates in the address.
     13  */
     14 
     15 #include <stdio.h>
     16 #include <stdlib.h>
     17 #include <string.h>
     18 
     19 #include "arch/aa64/aa64.h"
     20 #include "arch/arch.h"
     21 #include "arch/arm32/arm32.h"
     22 #include "arch/riscv/rv64.h"
     23 #include "arch/x64/isa.h"
     24 #include "arch/x64/x64.h"
     25 #include "cg/type.h"
     26 #include "core/pool.h"
     27 #include "lib/kit_unit.h"
     28 #include "obj/obj.h"
     29 #include "opt/opt.h"
     30 
     31 /* Keep NativeTarget callbacks on the semantic descriptor while naming the
     32  * optimizer-private descriptor explicitly in the forced-emitter test. */
     33 #undef CGFuncDesc
     34 
     35 typedef NativeTarget *(*NativeCtor)(Compiler *, ObjBuilder *, MCEmitter *);
     36 
     37 typedef struct DecodedInsn {
     38   char mnemonic[20];
     39   char operands[96];
     40 } DecodedInsn;
     41 
     42 typedef struct BackendCase {
     43   KitArchKind arch;
     44   const char *name;
     45   NativeCtor ctor;
     46   Reg dst_reg;
     47   u8 ptr_size;
     48 } BackendCase;
     49 
     50 static void slice_copy(KitSlice src, char *dst, size_t cap) {
     51   size_t n = src.len < cap - 1u ? src.len : cap - 1u;
     52   if (n && src.s)
     53     memcpy(dst, src.s, n);
     54   dst[n] = '\0';
     55 }
     56 
     57 static u32 decode_body(KitUnit *unit, Compiler *c, ObjBuilder *obj,
     58                        ObjSecId text, u32 begin, u32 end, DecodedInsn *out,
     59                        u32 cap, const char *tag) {
     60   const Section *sec = obj_section_get(obj, text);
     61   ArchDisasm *dis = NULL;
     62   u8 *flat = NULL;
     63   u32 size;
     64   u32 pos = begin;
     65   u32 nout = 0;
     66 
     67   CU_CHECK(unit, sec != NULL, "%s: missing text section", tag);
     68   if (!sec)
     69     return 0;
     70   size = buf_pos(&sec->bytes);
     71   CU_CHECK(unit, begin <= end && end <= size,
     72            "%s: invalid body range [%u,%u) in %u-byte text", tag, begin, end,
     73            size);
     74   if (begin > end || end > size)
     75     return 0;
     76   flat = size ? (u8 *)malloc(size) : NULL;
     77   CU_CHECK(unit, size == 0u || flat != NULL,
     78            "%s: could not allocate flattened text", tag);
     79   if (size && !flat)
     80     return 0;
     81   if (size)
     82     buf_flatten(&sec->bytes, flat);
     83   dis = arch_disasm_new(c);
     84   CU_CHECK(unit, dis != NULL, "%s: could not create disassembler", tag);
     85   if (!dis) {
     86     free(flat);
     87     return 0;
     88   }
     89 
     90   while (pos < end) {
     91     KitInsn insn;
     92     u32 used;
     93     memset(&insn, 0, sizeof insn);
     94     used = arch_disasm_decode(dis, flat + pos, end - pos, pos, &insn);
     95     CU_CHECK(unit, used != 0u, "%s: undecodable body byte at offset %u", tag,
     96              pos);
     97     if (!used)
     98       break;
     99     CU_CHECK(unit, nout < cap, "%s: body exceeds %u decoded instructions", tag,
    100              cap);
    101     if (nout < cap) {
    102       slice_copy(insn.mnemonic, out[nout].mnemonic, sizeof out[nout].mnemonic);
    103       slice_copy(insn.operands, out[nout].operands, sizeof out[nout].operands);
    104       ++nout;
    105     }
    106     pos += used;
    107   }
    108   CU_EXPECT(unit, pos == end, "%s: decoder stopped at %u, body ends at %u", tag,
    109             pos, end);
    110   arch_disasm_free(dis);
    111   free(flat);
    112   return nout;
    113 }
    114 
    115 static int has_insn(const DecodedInsn *insns, u32 ninsns, const char *mnemonic,
    116                     const char *operand) {
    117   for (u32 i = 0; i < ninsns; ++i) {
    118     if (mnemonic && !strstr(insns[i].mnemonic, mnemonic))
    119       continue;
    120     if (operand && !strstr(insns[i].operands, operand))
    121       continue;
    122     return 1;
    123   }
    124   return 0;
    125 }
    126 
    127 static i32 find_insn(const DecodedInsn *insns, u32 ninsns,
    128                      const char *mnemonic, const char *operand) {
    129   for (u32 i = 0; i < ninsns; ++i) {
    130     if (mnemonic && !strstr(insns[i].mnemonic, mnemonic))
    131       continue;
    132     if (operand && !strstr(insns[i].operands, operand))
    133       continue;
    134     return (i32)i;
    135   }
    136   return -1;
    137 }
    138 
    139 static u32 count_insns(const DecodedInsn *insns, u32 ninsns,
    140                        const char *mnemonic) {
    141   u32 count = 0;
    142   for (u32 i = 0; i < ninsns; ++i)
    143     if (strstr(insns[i].mnemonic, mnemonic))
    144       ++count;
    145   return count;
    146 }
    147 
    148 static u32 collect_relocs_to(const ObjBuilder *obj, ObjSecId section,
    149                              u32 begin, u32 end, ObjSymId sym, Reloc *out,
    150                              u32 cap) {
    151   u32 n = 0;
    152   for (u32 i = 0; i < obj_reloc_total(obj); ++i) {
    153     const Reloc *r = obj_reloc_at(obj, i);
    154     if (!r || r->removed || r->section_id != section || r->offset < begin ||
    155         r->offset >= end || r->sym != sym)
    156       continue;
    157     if (n < cap)
    158       out[n] = *r;
    159     ++n;
    160   }
    161   return n;
    162 }
    163 
    164 static void check_location_address_roles(KitUnit *unit) {
    165   NativeLoc storage = {0};
    166   NativeLoc frame_addr = {0};
    167   NativeLoc global = {0};
    168   NativeLoc imm = {0};
    169   NativeAddr addr;
    170   i64 part = 0;
    171 
    172   storage.kind = NATIVE_LOC_STACK;
    173   storage.type = 11u;
    174   storage.v.stack.slot = 7u;
    175   storage.v.stack.offset = 5;
    176   CU_EXPECT(unit,
    177             native_loc_addr_role(storage) == NATIVE_LOC_ADDR_ROLE_STORAGE &&
    178                 native_loc_storage_addr(storage, 3, &addr) &&
    179                 addr.base_kind == NATIVE_ADDR_BASE_FRAME &&
    180                 addr.base.frame == 7u && addr.offset == 8,
    181             "STACK must resolve only as offset storage");
    182   CU_EXPECT(unit, !native_loc_address_value(storage, &addr),
    183             "storage location was accepted as an address value");
    184 
    185   frame_addr.kind = NATIVE_LOC_FRAME_ADDR;
    186   frame_addr.type = 12u;
    187   frame_addr.v.frame = 9u;
    188   CU_EXPECT(unit,
    189             native_loc_addr_role(frame_addr) == NATIVE_LOC_ADDR_ROLE_VALUE &&
    190                 native_loc_address_value(frame_addr, &addr) &&
    191                 addr.base_kind == NATIVE_ADDR_BASE_FRAME &&
    192                 addr.base.frame == 9u && addr.offset == 0,
    193             "FRAME_ADDR must resolve only as a scalar address recipe");
    194   CU_EXPECT(unit, !native_loc_storage_addr(frame_addr, 0, &addr),
    195             "frame address value was accepted as storage");
    196 
    197   global.kind = NATIVE_LOC_GLOBAL;
    198   global.type = 13u;
    199   global.v.global.sym = 17u;
    200   global.v.global.addend = 37;
    201   CU_EXPECT(unit,
    202             native_loc_address_value(global, &addr) &&
    203                 addr.base_kind == NATIVE_ADDR_BASE_GLOBAL &&
    204                 addr.base.global.sym == 17u &&
    205                 addr.base.global.addend == 37,
    206             "GLOBAL address recipe lost its symbol/addend");
    207   CU_EXPECT(unit, !native_loc_storage_addr(global, 0, &addr),
    208             "global address value was accepted as storage");
    209 
    210   imm.kind = NATIVE_LOC_IMM;
    211   imm.v.imm = (i64)0x1122334455667788ull;
    212   CU_EXPECT(unit,
    213             native_loc_imm_part(imm, 4u, 4u, &part) &&
    214                 (u64)part == 0x11223344u,
    215             "immediate ABI part selection returned the wrong high word");
    216   CU_EXPECT(unit, !native_loc_imm_part(imm, 7u, 2u, &part),
    217             "immediate ABI part selection accepted an out-of-bounds lane");
    218 }
    219 
    220 static void check_typed_frame_address(KitUnit *unit, const BackendCase *tc) {
    221   KitTargetSpec spec = kit_unit_target(tc->arch, KIT_OS_LINUX, KIT_OBJ_ELF);
    222   KitCompiler *kit_c = NULL;
    223   Compiler *c;
    224   ObjBuilder *obj = NULL;
    225   MCEmitter *mc = NULL;
    226   NativeTarget *native;
    227   ObjSecId text, data;
    228   ObjSymId sym, global_sym;
    229   KitCgTypeId void_type, i8_type, i32_type, i64_type, ptr_type, tiny3_type,
    230       block8_type, fn_type, call_fn_type, wide_call_fn_type,
    231       exact_call_fn_type, forced_vararg_fn_type;
    232   KitCgFuncParam call_param, exact_call_param;
    233   KitCgFuncResult result;
    234   KitCgFuncSig sig, call_sig, forced_vararg_sig;
    235   CGFuncDesc func;
    236   NativeFrameSlotDesc slot_descs[2];
    237   NativeKnownFrameDesc frame;
    238   NativeFrameSlot slots[2];
    239   NativeAddr addr;
    240   NativeLoc dst;
    241   NativeLoc call_arg;
    242   NativeLoc exact_arg;
    243   NativeCallDesc call_desc;
    244   NativeCallPhase call_plan;
    245   DecodedInsn body[12];
    246   DecodedInsn call_body[8];
    247   DecodedInsn global_body[12];
    248   DecodedInsn imm_body[12];
    249   DecodedInsn exact_call_body[24];
    250   DecodedInsn forced_vararg_body[32];
    251   DecodedInsn copy_frame_value_body[16];
    252   DecodedInsn set_frame_value_body[24];
    253   DecodedInsn indexed_frame_value_body[12];
    254   Reloc global_relocs[4];
    255   u32 body_begin, body_end, nbody;
    256   u32 call_begin, call_end, ncall;
    257   u32 global_begin, global_end, nglobal, nglobal_relocs;
    258   u32 imm_begin = 0, imm_end = 0, nimm = 0;
    259   u32 exact_call_begin = 0, exact_call_end = 0, nexact_call = 0;
    260   u32 forced_vararg_begin = 0, forced_vararg_end = 0, nforced_vararg = 0;
    261   u32 copy_frame_value_begin = 0, copy_frame_value_end = 0,
    262       ncopy_frame_value = 0;
    263   u32 set_frame_value_begin = 0, set_frame_value_end = 0,
    264       nset_frame_value = 0;
    265   u32 indexed_frame_value_begin = 0, indexed_frame_value_end = 0,
    266       nindexed_frame_value = 0;
    267 
    268   spec.ptr_size = tc->ptr_size;
    269   spec.ptr_align = tc->ptr_size;
    270   CU_CHECK(unit, kit_unit_compiler_new(unit, spec, &kit_c) == KIT_OK && kit_c,
    271            "%s: compiler allocation failed", tc->name);
    272   if (!kit_c)
    273     return;
    274   c = (Compiler *)kit_c;
    275   obj = obj_new(c);
    276   CU_CHECK(unit, obj != NULL, "%s: object allocation failed", tc->name);
    277   if (!obj)
    278     goto done;
    279   mc = mc_new(c, obj);
    280   CU_CHECK(unit, mc != NULL, "%s: MC allocation failed", tc->name);
    281   if (!mc)
    282     goto done;
    283   native = tc->ctor(c, obj, mc);
    284   CU_CHECK(unit, native != NULL, "%s: native target allocation failed",
    285            tc->name);
    286   if (!native)
    287     goto done;
    288 
    289   text = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".text")),
    290                      SEC_TEXT, SF_EXEC | SF_ALLOC, 16u);
    291   data = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".data")),
    292                      SEC_DATA, SF_WRITE | SF_ALLOC, 16u);
    293   sym = obj_symbol(obj, pool_intern_slice(c->global, SLICE_LIT("frame_value")),
    294                    SB_GLOBAL, SK_FUNC, text, 0u, 0u);
    295   global_sym = obj_symbol(
    296       obj, pool_intern_slice(c->global, SLICE_LIT("address_value_global")),
    297       SB_LOCAL, SK_OBJ, data, 0u, 64u);
    298   void_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_VOID);
    299   i8_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I8);
    300   i32_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I32);
    301   i64_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I64);
    302   ptr_type = kit_cg_type_ptr(kit_c, void_type, 0u);
    303   tiny3_type = kit_cg_type_array(kit_c, i8_type, 3u);
    304   block8_type = kit_cg_type_array(kit_c, i8_type, 8u);
    305   memset(&result, 0, sizeof result);
    306   result.type = void_type;
    307   memset(&sig, 0, sizeof sig);
    308   sig.result = result;
    309   sig.call_conv = KIT_CG_CC_TARGET_C;
    310   fn_type = kit_cg_type_func(kit_c, sig);
    311   memset(&call_param, 0, sizeof call_param);
    312   call_param.type = ptr_type;
    313   memset(&call_sig, 0, sizeof call_sig);
    314   call_sig.result = result;
    315   call_sig.params = &call_param;
    316   call_sig.nparams = 1u;
    317   call_sig.call_conv = KIT_CG_CC_TARGET_C;
    318   call_fn_type = kit_cg_type_func(kit_c, call_sig);
    319   call_param.type = i64_type;
    320   wide_call_fn_type = kit_cg_type_func(kit_c, call_sig);
    321   memset(&exact_call_param, 0, sizeof exact_call_param);
    322   exact_call_param.type = tiny3_type;
    323   call_sig.params = &exact_call_param;
    324   exact_call_fn_type = kit_cg_type_func(kit_c, call_sig);
    325   memset(&forced_vararg_sig, 0, sizeof forced_vararg_sig);
    326   forced_vararg_sig.result = result;
    327   forced_vararg_sig.call_conv = KIT_CG_CC_TARGET_C;
    328   forced_vararg_sig.abi_variadic = true;
    329   forced_vararg_fn_type = kit_cg_type_func(kit_c, forced_vararg_sig);
    330 
    331   memset(&func, 0, sizeof func);
    332   func.sym = sym;
    333   func.text_section_id = text;
    334   func.fn_type = fn_type;
    335   func.result_type = void_type;
    336 
    337   /* Both colored homes advertise i64/8.  The address contract below is the
    338    * authority: pointer-width base, four-byte index. */
    339   memset(slot_descs, 0, sizeof slot_descs);
    340   for (u32 i = 0; i < 2u; ++i) {
    341     slot_descs[i].type = i64_type;
    342     slot_descs[i].size = 8u;
    343     slot_descs[i].align = 8u;
    344     slot_descs[i].kind = NATIVE_FRAME_SLOT_SPILL;
    345   }
    346   memset(&frame, 0, sizeof frame);
    347   frame.slots = slot_descs;
    348   frame.nslots = 2u;
    349   /* The RV-only forced-vararg construction below owns one rounded 16-byte
    350    * outgoing region. Reserve it before known-frame finalization just as the
    351    * optimizer's frame pre-pass does for a real call. */
    352   if (tc->arch == KIT_ARCH_RV64) frame.max_outgoing = 16u;
    353   /* Keep a real frame record on every target so frame-relative body reloads
    354    * have a settled anchor independent of leaf-frame elision. */
    355   frame.is_leaf = 0u;
    356   native->func_begin_known_frame(native, &func, &frame, slots);
    357 
    358   memset(&addr, 0, sizeof addr);
    359   addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE;
    360   addr.base.frame = slots[0];
    361   addr.base_type = ptr_type;
    362   addr.cls = NATIVE_REG_INT;
    363   addr.index_kind = NATIVE_ADDR_INDEX_FRAME_VALUE;
    364   addr.index.frame = slots[1];
    365   addr.index_type = i32_type;
    366   addr.index_cls = NATIVE_REG_INT;
    367   addr.log2_scale = 2u;
    368   addr.offset = 12;
    369   dst = native_loc_reg(ptr_type, NATIVE_REG_INT, tc->dst_reg);
    370   body_begin = mc_pos(mc);
    371   native->load_addr(native, dst, addr);
    372   body_end = mc_pos(mc);
    373 
    374   /* A post-allocation rematerialization may present `&local` directly to the
    375    * ABI marshaller.  It is an address value, not the scalar stored in the
    376    * frame slot, and must not require an optimizer-owned staging register. */
    377   memset(&call_arg, 0, sizeof call_arg);
    378   call_arg.kind = NATIVE_LOC_FRAME_ADDR;
    379   call_arg.cls = NATIVE_REG_INT;
    380   call_arg.type = ptr_type;
    381   call_arg.v.frame = slots[0];
    382   memset(&call_desc, 0, sizeof call_desc);
    383   call_desc.fn_type = call_fn_type;
    384   call_desc.args = &call_arg;
    385   call_desc.nargs = 1u;
    386   memset(&call_plan, 0, sizeof call_plan);
    387   call_begin = mc_pos(mc);
    388   native->marshal_call(native, &call_desc, &call_plan);
    389   call_end = mc_pos(mc);
    390 
    391   /* A GLOBAL NativeLoc is the scalar address value `&symbol + addend`, never
    392    * the bytes stored at that address.  Exercise the ABI boundary directly:
    393    * ordinary C currently materializes globals before constructing its call
    394    * descriptor, so a source-level call cannot pin this lower-level contract. */
    395   memset(&call_arg, 0, sizeof call_arg);
    396   call_arg.kind = NATIVE_LOC_GLOBAL;
    397   call_arg.cls = NATIVE_REG_INT;
    398   call_arg.type = ptr_type;
    399   call_arg.v.global.sym = global_sym;
    400   call_arg.v.global.addend = 37;
    401   memset(&call_desc, 0, sizeof call_desc);
    402   call_desc.fn_type = call_fn_type;
    403   call_desc.args = &call_arg;
    404   call_desc.nargs = 1u;
    405   memset(&call_plan, 0, sizeof call_plan);
    406   global_begin = mc_pos(mc);
    407   native->marshal_call(native, &call_desc, &call_plan);
    408   global_end = mc_pos(mc);
    409 
    410   /* ARM32 splits an i64 direct argument into low/high 32-bit ABI lanes.  A
    411    * NativeLoc immediate denotes the complete scalar, so each lane must select
    412    * its own source bits instead of rematerializing the low word twice. */
    413   if (tc->arch == KIT_ARCH_ARM_32) {
    414     memset(&call_arg, 0, sizeof call_arg);
    415     call_arg.kind = NATIVE_LOC_IMM;
    416     call_arg.cls = NATIVE_REG_INT;
    417     call_arg.type = i64_type;
    418     call_arg.v.imm = (i64)0x1122334455667788ull;
    419     memset(&call_desc, 0, sizeof call_desc);
    420     call_desc.fn_type = wide_call_fn_type;
    421     call_desc.args = &call_arg;
    422     call_desc.nargs = 1u;
    423     memset(&call_plan, 0, sizeof call_plan);
    424     imm_begin = mc_pos(mc);
    425     native->marshal_call(native, &call_desc, &call_plan);
    426     imm_end = mc_pos(mc);
    427   }
    428 
    429   /* x64 lowerers may legally hand the ABI boundary an exact-width aggregate
    430    * in NATIVE_LOC_ADDR storage whose explicit base is also its destination ABI
    431    * register. Keep a register-indirect callee live across that whole marshal:
    432    * the 3-byte argument forces address stabilization through r11, which was
    433    * formerly also the parked call target. This backend-level construction pins
    434    * the allowed shape even when current source lowering chooses a frame home. */
    435   if (tc->arch == KIT_ARCH_X86_64) {
    436     memset(&exact_arg, 0, sizeof exact_arg);
    437     exact_arg.kind = NATIVE_LOC_ADDR;
    438     exact_arg.cls = NATIVE_REG_INT;
    439     exact_arg.type = tiny3_type;
    440     exact_arg.v.addr.base_kind = NATIVE_ADDR_BASE_REG;
    441     exact_arg.v.addr.base.reg = X64_RDI;
    442     exact_arg.v.addr.base_type = ptr_type;
    443     exact_arg.v.addr.cls = NATIVE_REG_INT;
    444     memset(&call_desc, 0, sizeof call_desc);
    445     call_desc.fn_type = exact_call_fn_type;
    446     call_desc.callee = native_loc_reg(ptr_type, NATIVE_REG_INT, X64_RCX);
    447     call_desc.args = &exact_arg;
    448     call_desc.nargs = 1u;
    449     memset(&call_plan, 0, sizeof call_plan);
    450     exact_call_begin = mc_pos(mc);
    451     native->marshal_call(native, &call_desc, &call_plan);
    452     native->emit_call(native, &call_plan);
    453     exact_call_end = mc_pos(mc);
    454   }
    455 
    456   /* RISC-V does not currently select vararg_on_stack, but it still implements
    457    * the shared ABI trait and must not encode a latent rounded-slot overread.
    458    * Synthesize that trait for one three-byte unnamed argument: the physical
    459    * carrier is eight bytes (and the whole outgoing area rounds to 16), while
    460    * source and destination accesses must remain exactly 2+1 bytes. Keep the
    461    * indirect target in a STACK home at the same time: current lowering usually
    462    * materializes that shape before this boundary, but NativeCallDesc permits it
    463    * and the backend must park it outside the exact/address temporary bank. */
    464   if (tc->arch == KIT_ARCH_RV64) {
    465     const ABIFuncInfo* classified =
    466         abi_cg_func_info(c->abi, forced_vararg_fn_type);
    467     ABIFuncInfo* synthetic = (ABIFuncInfo*)classified;
    468     u8 saved_vararg_on_stack;
    469     CU_CHECK(unit, synthetic != NULL,
    470              "rv64: variadic function classification failed");
    471     if (synthetic) {
    472       saved_vararg_on_stack = synthetic->vararg_on_stack;
    473       synthetic->vararg_on_stack = 1u;
    474       exact_arg = native_loc_stack(tiny3_type, slots[0], 0);
    475       memset(&call_desc, 0, sizeof call_desc);
    476       call_desc.fn_type = forced_vararg_fn_type;
    477       call_desc.callee = native_loc_stack(ptr_type, slots[1], 0);
    478       call_desc.args = &exact_arg;
    479       call_desc.nargs = 1u;
    480       memset(&call_plan, 0, sizeof call_plan);
    481       forced_vararg_begin = mc_pos(mc);
    482       native->marshal_call(native, &call_desc, &call_plan);
    483       native->emit_call(native, &call_plan);
    484       forced_vararg_end = mc_pos(mc);
    485       synthetic->vararg_on_stack = saved_vararg_on_stack;
    486     }
    487   }
    488 
    489   /* The AArch64 aggregate primitives carry their payload in x16. A
    490    * FRAME_VALUE destination must therefore materialize its pointer through a
    491    * different address scratch. Otherwise both copy_bytes and widened
    492    * set_bytes degenerate to `str x16, [x16]`, storing the pointer value rather
    493    * than the aggregate payload. Keep this at the backend boundary so the
    494    * contract stays independent of optimizer register-allocation choices. */
    495   if (tc->arch == KIT_ARCH_ARM_64) {
    496     AggregateAccess access;
    497     NativeAddr frame_value_dst;
    498     NativeAddr frame_src;
    499     NativeLoc fill;
    500     MemAccess indexed_mem;
    501     memset(&access, 0, sizeof access);
    502     access.type = block8_type;
    503     access.size = 8u;
    504     access.align = 8u;
    505     access.mem.type = block8_type;
    506     access.mem.size = 8u;
    507     access.mem.align = 8u;
    508     memset(&frame_value_dst, 0, sizeof frame_value_dst);
    509     frame_value_dst.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE;
    510     frame_value_dst.base.frame = slots[0];
    511     frame_value_dst.base_type = ptr_type;
    512     frame_value_dst.cls = NATIVE_REG_INT;
    513     memset(&frame_src, 0, sizeof frame_src);
    514     frame_src.base_kind = NATIVE_ADDR_BASE_FRAME;
    515     frame_src.base.frame = slots[1];
    516     frame_src.base_type = ptr_type;
    517     frame_src.cls = NATIVE_REG_INT;
    518     fill = native_loc_reg(i8_type, NATIVE_REG_INT, 9u);
    519 
    520     copy_frame_value_begin = mc_pos(mc);
    521     native->copy_bytes(native, frame_value_dst, frame_src, access);
    522     copy_frame_value_end = mc_pos(mc);
    523     set_frame_value_begin = mc_pos(mc);
    524     native->set_bytes(native, frame_value_dst, fill, access);
    525     set_frame_value_end = mc_pos(mc);
    526 
    527     /* A register index remains live until the final addressing-mode
    528      * instruction. Pin x16 as that index and require base materialization to
    529      * select x17 even when the store payload itself uses neither scratch. */
    530     frame_value_dst.index_kind = NATIVE_ADDR_INDEX_REG;
    531     frame_value_dst.index.reg = 16u;
    532     frame_value_dst.index_type = i64_type;
    533     frame_value_dst.index_cls = NATIVE_REG_INT;
    534     memset(&indexed_mem, 0, sizeof indexed_mem);
    535     indexed_mem.type = i64_type;
    536     indexed_mem.size = 8u;
    537     indexed_mem.align = 8u;
    538     indexed_frame_value_begin = mc_pos(mc);
    539     native->store(native, frame_value_dst,
    540                   native_loc_reg(i64_type, NATIVE_REG_INT, 9u), indexed_mem);
    541     indexed_frame_value_end = mc_pos(mc);
    542   }
    543   native->func_end(native);
    544 
    545   memset(body, 0, sizeof body);
    546   nbody = decode_body(unit, c, obj, text, body_begin, body_end, body,
    547                       (u32)(sizeof body / sizeof body[0]), tc->name);
    548   memset(call_body, 0, sizeof call_body);
    549   ncall = decode_body(unit, c, obj, text, call_begin, call_end, call_body,
    550                       (u32)(sizeof call_body / sizeof call_body[0]), tc->name);
    551   memset(global_body, 0, sizeof global_body);
    552   nglobal = decode_body(unit, c, obj, text, global_begin, global_end,
    553                         global_body,
    554                         (u32)(sizeof global_body / sizeof global_body[0]),
    555                         tc->name);
    556   memset(global_relocs, 0, sizeof global_relocs);
    557   nglobal_relocs = collect_relocs_to(
    558       obj, text, global_begin, global_end, global_sym, global_relocs,
    559       (u32)(sizeof global_relocs / sizeof global_relocs[0]));
    560   if (tc->arch == KIT_ARCH_ARM_32) {
    561     memset(imm_body, 0, sizeof imm_body);
    562     nimm = decode_body(unit, c, obj, text, imm_begin, imm_end, imm_body,
    563                        (u32)(sizeof imm_body / sizeof imm_body[0]), tc->name);
    564   }
    565   if (tc->arch == KIT_ARCH_X86_64) {
    566     memset(exact_call_body, 0, sizeof exact_call_body);
    567     nexact_call = decode_body(
    568         unit, c, obj, text, exact_call_begin, exact_call_end, exact_call_body,
    569         (u32)(sizeof exact_call_body / sizeof exact_call_body[0]), tc->name);
    570   }
    571   if (tc->arch == KIT_ARCH_RV64) {
    572     memset(forced_vararg_body, 0, sizeof forced_vararg_body);
    573     nforced_vararg = decode_body(
    574         unit, c, obj, text, forced_vararg_begin, forced_vararg_end,
    575         forced_vararg_body,
    576         (u32)(sizeof forced_vararg_body / sizeof forced_vararg_body[0]),
    577         tc->name);
    578   }
    579   if (tc->arch == KIT_ARCH_ARM_64) {
    580     memset(copy_frame_value_body, 0, sizeof copy_frame_value_body);
    581     ncopy_frame_value = decode_body(
    582         unit, c, obj, text, copy_frame_value_begin, copy_frame_value_end,
    583         copy_frame_value_body,
    584         (u32)(sizeof copy_frame_value_body / sizeof copy_frame_value_body[0]),
    585         "aa64-copy-frame-value");
    586     memset(set_frame_value_body, 0, sizeof set_frame_value_body);
    587     nset_frame_value = decode_body(
    588         unit, c, obj, text, set_frame_value_begin, set_frame_value_end,
    589         set_frame_value_body,
    590         (u32)(sizeof set_frame_value_body / sizeof set_frame_value_body[0]),
    591         "aa64-set-frame-value");
    592     memset(indexed_frame_value_body, 0, sizeof indexed_frame_value_body);
    593     nindexed_frame_value = decode_body(
    594         unit, c, obj, text, indexed_frame_value_begin,
    595         indexed_frame_value_end, indexed_frame_value_body,
    596         (u32)(sizeof indexed_frame_value_body /
    597               sizeof indexed_frame_value_body[0]),
    598         "aa64-indexed-frame-value");
    599   }
    600   CU_EXPECT(unit, nbody >= 3u, "%s: expected typed base/index/add sequence",
    601             tc->name);
    602   CU_EXPECT(unit, ncall != 0u,
    603             "%s: frame-address call argument emitted no materialization",
    604             tc->name);
    605   CU_EXPECT(unit, nglobal != 0u,
    606             "%s: global-address call argument emitted no materialization",
    607             tc->name);
    608 
    609   switch (tc->arch) {
    610   case KIT_ARCH_X86_64:
    611     CU_EXPECT(unit, has_insn(body, nbody, "mov", "r11d"),
    612               "x64: i32 frame index was not loaded through r11d");
    613     CU_EXPECT(unit, has_insn(body, nbody, "lea", NULL),
    614               "x64: scaled frame index was not folded into LEA");
    615     CU_EXPECT(unit,
    616               nglobal_relocs == 1u && global_relocs[0].kind == R_PC32 &&
    617                   global_relocs[0].addend == 33,
    618               "x64: GLOBAL call arg lost symbol or +37 addend");
    619     CU_EXPECT(unit, has_insn(global_body, nglobal, "lea", NULL),
    620               "x64: GLOBAL call arg was not materialized with LEA");
    621     CU_EXPECT(unit, nglobal == 1u,
    622               "x64: GLOBAL call arg unexpectedly dereferenced storage");
    623     {
    624       i32 push = find_insn(exact_call_body, nexact_call, "push", "rcx");
    625       i32 exact_load =
    626           find_insn(exact_call_body, nexact_call, "mov", "(%r11)");
    627       i32 pop = find_insn(exact_call_body, nexact_call, "pop", "r11");
    628       i32 call = find_insn(exact_call_body, nexact_call, "call", "r11");
    629       CU_EXPECT(unit, push >= 0,
    630                 "x64: indirect callee was not parked before marshalling");
    631       CU_EXPECT(unit, exact_load > push,
    632                 "x64: exact address argument did not consume stabilized r11");
    633       CU_EXPECT(unit, pop > exact_load,
    634                 "x64: indirect callee was restored before argument transport");
    635       CU_EXPECT(unit, call > pop,
    636                 "x64: restored indirect callee was not the call target");
    637     }
    638     break;
    639   case KIT_ARCH_ARM_64:
    640     CU_EXPECT(unit, has_insn(body, nbody, "ldr", "w17"),
    641               "aa64: i32 frame index was not loaded through w17");
    642     CU_EXPECT(unit, has_insn(body, nbody, "add", "lsl #2"),
    643               "aa64: scaled frame index was not folded into the address");
    644     CU_EXPECT(unit,
    645               nglobal_relocs == 2u &&
    646                   global_relocs[0].kind == R_AARCH64_ADR_PREL_PG_HI21 &&
    647                   global_relocs[1].kind == R_AARCH64_ADD_ABS_LO12_NC &&
    648                   global_relocs[0].addend == 37 &&
    649                   global_relocs[1].addend == 37,
    650               "aa64: GLOBAL call arg lost symbol or +37 addend");
    651     CU_EXPECT(unit, nglobal == 2u,
    652               "aa64: GLOBAL call arg unexpectedly dereferenced storage");
    653     CU_EXPECT(unit,
    654               has_insn(copy_frame_value_body, ncopy_frame_value, "str",
    655                        "x16, [x17") &&
    656                   !has_insn(copy_frame_value_body, ncopy_frame_value, "str",
    657                             "x16, [x16"),
    658               "aa64: aggregate copy clobbered x16 while materializing its "
    659               "FRAME_VALUE destination");
    660     CU_EXPECT(unit,
    661               has_insn(set_frame_value_body, nset_frame_value, "str",
    662                        "x16, [x17") &&
    663                   !has_insn(set_frame_value_body, nset_frame_value, "str",
    664                             "x16, [x16"),
    665               "aa64: widened aggregate set clobbered x16 while materializing "
    666               "its FRAME_VALUE destination");
    667     CU_EXPECT(unit,
    668               has_insn(indexed_frame_value_body, nindexed_frame_value, "ldr",
    669                        "x17") &&
    670                   has_insn(indexed_frame_value_body, nindexed_frame_value,
    671                            "str", "x9, [x17, x16"),
    672               "aa64: FRAME_VALUE base materialization clobbered its x16 "
    673               "register index");
    674     break;
    675   case KIT_ARCH_RV64:
    676     CU_EXPECT(unit, has_insn(body, nbody, "lw", "t1"),
    677               "rv64: i32 frame index was not loaded with lw");
    678     /* The RV decoder does not yet describe Zba, so it intentionally renders
    679      * sh2add a0,t1,a0 as its exact raw word.  Pinning that word is stronger
    680      * than counting instructions: the old dropped-index path had no fourth
    681      * instruction at all. */
    682     CU_EXPECT(unit, has_insn(body, nbody, ".word", "0x20a34533"),
    683               "rv64: frame index was dropped from load_addr");
    684     CU_EXPECT(unit,
    685               nglobal_relocs == 1u &&
    686                   global_relocs[0].kind == R_RV_PCREL_HI20,
    687               "rv64: GLOBAL call arg did not retain its symbol relocation");
    688     CU_EXPECT(unit, has_insn(global_body, nglobal, "addi", "37"),
    689               "rv64: GLOBAL call arg lost its +37 addend");
    690     CU_EXPECT(unit, nglobal == 3u &&
    691                         !has_insn(global_body, nglobal, "ld", NULL) &&
    692                         !has_insn(global_body, nglobal, "lw", NULL),
    693               "rv64: GLOBAL call arg unexpectedly dereferenced storage");
    694     {
    695       i32 park = find_insn(forced_vararg_body, nforced_vararg, "ld", "ra");
    696       i32 exact_load =
    697           find_insn(forced_vararg_body, nforced_vararg, "lhu", NULL);
    698       i32 exact_store =
    699           find_insn(forced_vararg_body, nforced_vararg, "sh", NULL);
    700       i32 call =
    701           find_insn(forced_vararg_body, nforced_vararg, "jalr", "ra, 0(ra)");
    702       CU_EXPECT(unit, park >= 0,
    703                 "rv64: indirect callee was not parked outside transport temps");
    704       CU_EXPECT(unit, exact_load > park &&
    705                           has_insn(forced_vararg_body, nforced_vararg, "lbu",
    706                                    NULL),
    707                 "rv64: 3-byte forced vararg did not use exact 2+1 loads");
    708       CU_EXPECT(unit, exact_store > exact_load &&
    709                           has_insn(forced_vararg_body, nforced_vararg, "sb",
    710                                    NULL),
    711                 "rv64: 3-byte forced vararg did not use exact 2+1 stores");
    712       CU_EXPECT(unit,
    713                 count_insns(forced_vararg_body, nforced_vararg, "ld") == 1u &&
    714                     !has_insn(forced_vararg_body, nforced_vararg, "sd", NULL),
    715                 "rv64: rounded vararg carrier became a semantic access width");
    716       CU_EXPECT(unit, call > exact_store,
    717                 "rv64: parked indirect target did not survive argument transport");
    718     }
    719     break;
    720   case KIT_ARCH_ARM_32:
    721     CU_EXPECT(unit, count_insns(body, nbody, "ldr") == 2u,
    722               "arm32: exact-width base/index should require two word loads");
    723     CU_EXPECT(unit, has_insn(body, nbody, "add", "lsl #2"),
    724               "arm32: scaled frame index was not folded into the address");
    725     CU_EXPECT(unit,
    726               nglobal_relocs == 2u &&
    727                   global_relocs[0].kind == R_ARM_THM_MOVW_ABS_NC &&
    728                   global_relocs[1].kind == R_ARM_THM_MOVT_ABS,
    729               "arm32: GLOBAL call arg did not retain MOVW/MOVT relocations");
    730     CU_EXPECT(unit, has_insn(global_body, nglobal, "add", "37"),
    731               "arm32: GLOBAL call arg lost its +37 addend");
    732     CU_EXPECT(unit, nglobal == 3u &&
    733                         !has_insn(global_body, nglobal, "ldr", NULL),
    734               "arm32: GLOBAL call arg unexpectedly dereferenced storage");
    735     CU_EXPECT(unit, nimm >= 4u,
    736               "arm32: split i64 immediate did not materialize both lanes");
    737     CU_EXPECT(unit, has_insn(imm_body, nimm, "movw", "r0, #30600") &&
    738                         has_insn(imm_body, nimm, "movt", "r0, #21862"),
    739               "arm32: i64 immediate low lane is not 0x55667788");
    740     CU_EXPECT(unit, has_insn(imm_body, nimm, "movw", "r1, #13124") &&
    741                         has_insn(imm_body, nimm, "movt", "r1, #4386"),
    742               "arm32: i64 immediate high lane is not 0x11223344");
    743     break;
    744   default:
    745     CU_EXPECT(unit, 0, "%s: unhandled architecture", tc->name);
    746     break;
    747   }
    748 
    749 done:
    750   if (mc)
    751     mc_free(mc);
    752   if (obj)
    753     obj_free(obj);
    754   kit_compiler_free(kit_c);
    755 }
    756 
    757 static Operand forced_stack_op(FrameSlot slot, KitCgTypeId type) {
    758   Operand op;
    759   memset(&op, 0, sizeof op);
    760   op.kind = OPK_STACK;
    761   op.cls = RC_INT;
    762   op.type = type;
    763   op.v.frame_slot = slot;
    764   return op;
    765 }
    766 
    767 static FrameSlot forced_slot_new(Func *f, KitCgTypeId type) {
    768   FrameSlotDesc desc;
    769   memset(&desc, 0, sizeof desc);
    770   desc.type = type;
    771   desc.size = 4u;
    772   desc.align = 4u;
    773   desc.kind = FS_SPILL;
    774   return ir_frame_slot_new(f, &desc);
    775 }
    776 
    777 static const NativeAllocClassInfo *int_class_info(const NativeTarget *native) {
    778   if (!native || !native->regs) return NULL;
    779   for (u32 i = 0; i < native->regs->nclasses; ++i)
    780     if (native->regs->classes[i].cls == NATIVE_REG_INT)
    781       return &native->regs->classes[i];
    782   return NULL;
    783 }
    784 
    785 static void check_forced_scavenger(KitUnit *unit, const BackendCase *tc) {
    786   KitTargetSpec spec = kit_unit_target(tc->arch, KIT_OS_LINUX, KIT_OBJ_ELF);
    787   KitCompiler *kit_c = NULL;
    788   Compiler *c;
    789   ObjBuilder *obj = NULL;
    790   MCEmitter *mc = NULL;
    791   NativeTarget *native;
    792   const NativeAllocClassInfo *ci;
    793   ObjSecId text;
    794   ObjSymId sym;
    795   KitCgTypeId void_type, i32_type, fn_type;
    796   KitCgFuncResult result;
    797   KitCgFuncSig sig;
    798   OptCGFuncDesc desc;
    799   Func *f;
    800   FrameSlot dst_slot, lhs_slot, rhs_slot;
    801   Inst *in;
    802   u32 block;
    803   Reg emit_temp, scavenge_temp;
    804 
    805   CU_CHECK(unit, unit->ctx.profiler != NULL,
    806            "%s: profiler allocation failed", tc->name);
    807   if (!unit->ctx.profiler) return;
    808   spec.ptr_size = tc->ptr_size;
    809   spec.ptr_align = tc->ptr_size;
    810   CU_CHECK(unit, kit_unit_compiler_new(unit, spec, &kit_c) == KIT_OK && kit_c,
    811            "%s: forced-scavenger compiler allocation failed", tc->name);
    812   if (!kit_c) return;
    813   c = (Compiler *)kit_c;
    814   obj = obj_new(c);
    815   CU_CHECK(unit, obj != NULL, "%s: forced-scavenger object allocation failed",
    816            tc->name);
    817   if (!obj) goto done;
    818   mc = mc_new(c, obj);
    819   CU_CHECK(unit, mc != NULL, "%s: forced-scavenger MC allocation failed",
    820            tc->name);
    821   if (!mc) goto done;
    822   native = tc->ctor(c, obj, mc);
    823   CU_CHECK(unit, native != NULL,
    824            "%s: forced-scavenger native target allocation failed", tc->name);
    825   if (!native) goto done;
    826   ci = int_class_info(native);
    827   CU_CHECK(unit, ci != NULL && ci->nemit_temps >= 2u,
    828            "%s: forced-scavenger test needs two target-legal integer temps",
    829            tc->name);
    830   if (!ci || ci->nemit_temps < 2u) goto done;
    831   emit_temp = ci->emit_temps[0];
    832   scavenge_temp = ci->emit_temps[1];
    833 
    834   text = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".text")),
    835                      SEC_TEXT, SF_EXEC | SF_ALLOC, 16u);
    836   sym = obj_symbol(obj,
    837                    pool_intern_slice(c->global,
    838                                      SLICE_LIT("forced_native_scavenger")),
    839                    SB_LOCAL, SK_FUNC, text, 0u, 0u);
    840   void_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_VOID);
    841   i32_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I32);
    842   memset(&result, 0, sizeof result);
    843   result.type = void_type;
    844   memset(&sig, 0, sizeof sig);
    845   sig.result = result;
    846   sig.call_conv = KIT_CG_CC_TARGET_C;
    847   fn_type = kit_cg_type_func(kit_c, sig);
    848 
    849   memset(&desc, 0, sizeof desc);
    850   desc.sym = sym;
    851   desc.text_section_id = text;
    852   desc.fn_type = fn_type;
    853   desc.result_type = void_type;
    854   f = ir_func_new(c, &desc);
    855   block = ir_block_new(f);
    856   f->entry = block;
    857   ir_note_emit(f, block);
    858   f->opt_rewritten = 1u;
    859 
    860   /* Narrow the instruction bank to one register, then lend a second
    861    * target-legal caller-saved register through the allocator list. An all-stack
    862    * binary op must hold lhs and rhs simultaneously, deterministically entering
    863    * the liveness-guarded scavenger before calling the real backend hook. */
    864   f->emit_temp_regs[RC_INT][0] = emit_temp;
    865   f->emit_temp_reg_count[RC_INT] = 1u;
    866   f->opt_reserved_regs[RC_INT] = 1u << emit_temp;
    867   f->opt_hard_regs[RC_INT][0] = scavenge_temp;
    868   f->opt_hard_reg_count[RC_INT] = 1u;
    869   f->opt_caller_saved[RC_INT] = 1u << scavenge_temp;
    870 
    871   dst_slot = forced_slot_new(f, i32_type);
    872   lhs_slot = forced_slot_new(f, i32_type);
    873   rhs_slot = forced_slot_new(f, i32_type);
    874   in = ir_emit(f, block, IR_BINOP);
    875   in->nopnds = 3u;
    876   in->opnds = arena_zarray(f->arena, Operand, in->nopnds);
    877   in->opnds[0] = forced_stack_op(dst_slot, i32_type);
    878   in->opnds[1] = forced_stack_op(lhs_slot, i32_type);
    879   in->opnds[2] = forced_stack_op(rhs_slot, i32_type);
    880   in->extra.imm = BO_IADD;
    881 
    882   kit_profiler_reset(unit->ctx.profiler);
    883   opt_emit_native(c, f, native);
    884   CU_EXPECT(unit,
    885             unit->ctx.profiler
    886                     ->counters[KIT_PROFILE_COUNTER_OPT_NATIVE_EMIT_SCAVENGES] >
    887                 0u,
    888             "%s: forced pressure did not enter native scavenger", tc->name);
    889   CU_EXPECT(unit, mc_pos(mc) != 0u,
    890             "%s: forced-scavenger backend emitted no code", tc->name);
    891 
    892 done:
    893   if (mc) mc_free(mc);
    894   if (obj) obj_free(obj);
    895   kit_compiler_free(kit_c);
    896 }
    897 
    898 int main(void) {
    899   static const BackendCase cases[] = {
    900       {KIT_ARCH_X86_64, "x64", x64_native_target_new, 0u, 8u},
    901       {KIT_ARCH_ARM_64, "aa64", aa64_native_target_new, 0u, 8u},
    902       {KIT_ARCH_RV64, "rv64", rv64_native_target_new, 10u, 8u},
    903       {KIT_ARCH_ARM_32, "arm32", arm32_native_target_new, 0u, 4u},
    904   };
    905   KitUnit unit;
    906   KitProfiler *profiler;
    907   kit_unit_init(&unit);
    908   profiler = (KitProfiler *)malloc(sizeof *profiler);
    909   CU_CHECK(&unit, profiler != NULL, "profiler allocation failed");
    910   if (profiler) {
    911     kit_profiler_reset(profiler);
    912     unit.ctx.profiler = profiler;
    913   }
    914   check_location_address_roles(&unit);
    915   for (u32 i = 0; i < (u32)(sizeof cases / sizeof cases[0]); ++i) {
    916     check_typed_frame_address(&unit, &cases[i]);
    917     check_forced_scavenger(&unit, &cases[i]);
    918   }
    919   kit_unit_summary(&unit, "frame-value-backends");
    920   free(profiler);
    921   return kit_unit_status(&unit);
    922 }