kit

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

location_mir_test.c (43748B)


      1 #include <stdio.h>
      2 #include <string.h>
      3 
      4 #include <kit/frontend.h>
      5 
      6 #include "lib/kit_unit.h"
      7 #include "opt/opt_internal.h"
      8 
      9 typedef struct WalkSummary {
     10   FrameSlot stack_uses[4];
     11   u32 nstack_uses;
     12   u32 reserved_regs_seen;
     13   u32 reserved_mask[OPT_REG_CLASSES];
     14 } WalkSummary;
     15 
     16 typedef void (*PanicAction)(void*);
     17 
     18 typedef struct PanicInvocation {
     19   PanicAction action;
     20   void* arg;
     21 } PanicInvocation;
     22 
     23 static KitStatus run_panicking_action(KitCompiler* kc, void* arg) {
     24   PanicInvocation* call = (PanicInvocation*)arg;
     25   (void)kc;
     26   call->action(call->arg);
     27   return KIT_OK;
     28 }
     29 
     30 static int expect_compiler_panic(KitUnit* u, Compiler* c, PanicAction action,
     31                                  void* arg, const char* expected) {
     32   PanicInvocation call;
     33   KitStatus status;
     34   call.action = action;
     35   call.arg = arg;
     36   u->last_diag[0] = '\0';
     37   ++u->suppress_fatal;
     38   status = kit_frontend_run((KitCompiler*)c, run_panicking_action, &call);
     39   --u->suppress_fatal;
     40   return status == KIT_ERR && strstr(u->last_diag, expected) != NULL;
     41 }
     42 
     43 static Operand reg_op(PReg reg, KitCgTypeId type) {
     44   Operand op;
     45   memset(&op, 0, sizeof op);
     46   op.kind = OPK_REG;
     47   op.cls = RC_INT;
     48   op.type = type;
     49   op.v.reg = reg;
     50   return op;
     51 }
     52 
     53 static Operand indirect_op(PReg base, PReg index, KitCgTypeId base_type,
     54                            KitCgTypeId index_type) {
     55   Operand op;
     56   memset(&op, 0, sizeof op);
     57   op.kind = OPK_INDIRECT;
     58   op.cls = RC_INT;
     59   op.type = base_type;
     60   op.v.ind.base = base;
     61   op.v.ind.index = index;
     62   op.v.ind.log2_scale = 3;
     63   op.v.ind.base_type = base_type;
     64   op.v.ind.index_type = index_type;
     65   return op;
     66 }
     67 
     68 static FrameSlot spill_slot(Func* f, KitCgTypeId type) {
     69   FrameSlotDesc desc;
     70   memset(&desc, 0, sizeof desc);
     71   desc.type = type;
     72   desc.size = 8;
     73   desc.align = 8;
     74   desc.kind = FS_SPILL;
     75   return ir_frame_slot_new(f, &desc);
     76 }
     77 
     78 static FrameSlot local_slot(Func* f, KitCgTypeId type, u32 size, u32 align) {
     79   FrameSlotDesc desc;
     80   memset(&desc, 0, sizeof desc);
     81   desc.type = type;
     82   desc.size = size;
     83   desc.align = align;
     84   desc.kind = FS_LOCAL;
     85   return ir_frame_slot_new(f, &desc);
     86 }
     87 
     88 static void set_stack_loc(Func* f, PReg reg, FrameSlot slot) {
     89   f->preg_locs[reg].kind = OPT_LOC_STACK;
     90   f->preg_locs[reg].cls = RC_INT;
     91   f->preg_locs[reg].spill_slot = slot;
     92   f->preg_info[reg].alloc_kind = OPT_ALLOC_SPILL;
     93   f->preg_info[reg].cls = RC_INT;
     94   f->preg_info[reg].spill_slot = slot;
     95 }
     96 
     97 static void set_hard_loc(Func* f, PReg reg, Reg hard_reg) {
     98   f->preg_locs[reg].kind = OPT_LOC_HARD;
     99   f->preg_locs[reg].cls = RC_INT;
    100   f->preg_locs[reg].hard_reg = hard_reg;
    101   f->preg_info[reg].alloc_kind = OPT_ALLOC_HARD;
    102   f->preg_info[reg].cls = RC_INT;
    103   f->preg_info[reg].hard_reg = hard_reg;
    104 }
    105 
    106 typedef struct MachinizeInvocation {
    107   Func* f;
    108   NativeTarget* target;
    109 } MachinizeInvocation;
    110 
    111 static void run_machinize(void* arg) {
    112   MachinizeInvocation* call = (MachinizeInvocation*)arg;
    113   opt_machinize_native(call->f, call->target);
    114 }
    115 
    116 #ifndef NDEBUG
    117 static void run_mir_verify(void* arg) {
    118   opt_mir_verify((Func*)arg, "negative-temp-escape");
    119 }
    120 #endif
    121 
    122 static void machinize_rejects_cross_owner_registers(KitUnit* u) {
    123   KitCompiler* kc = NULL;
    124   Compiler* c;
    125   CGFuncDesc desc;
    126   Func* f;
    127   NativePhysRegInfo phys[2];
    128   NativeAllocClassInfo cls;
    129   NativeRegInfo regs;
    130   NativeTarget target;
    131   MachinizeInvocation call;
    132   Reg emit_temp = 1u;
    133   Reg asm_temp = 2u;
    134 
    135   CU_EXPECT(u,
    136             kit_unit_compiler_new(
    137                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    138                 &kc) == KIT_OK &&
    139                 kc != NULL,
    140             "compiler allocation failed for machinize ownership test");
    141   if (!kc) return;
    142   c = (Compiler*)kc;
    143   memset(&desc, 0, sizeof desc);
    144   desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    145   f = ir_func_new(c, &desc);
    146 
    147   memset(phys, 0, sizeof phys);
    148   phys[0].reg = emit_temp;
    149   phys[0].cls = NATIVE_REG_INT;
    150   phys[0].flags = NATIVE_REG_ALLOCABLE | NATIVE_REG_CALLER_SAVED;
    151   phys[1].reg = asm_temp;
    152   phys[1].cls = NATIVE_REG_INT;
    153   phys[1].flags = NATIVE_REG_CALLER_SAVED | NATIVE_REG_RESERVED;
    154   memset(&cls, 0, sizeof cls);
    155   cls.cls = NATIVE_REG_INT;
    156   cls.emit_temps = &emit_temp;
    157   cls.nemit_temps = 1u;
    158   cls.phys = phys;
    159   cls.nphys = 2u;
    160   memset(&regs, 0, sizeof regs);
    161   regs.classes = &cls;
    162   regs.nclasses = 1u;
    163   memset(&target, 0, sizeof target);
    164   target.c = c;
    165   target.regs = &regs;
    166   call.f = f;
    167   call.target = &target;
    168 
    169   CU_EXPECT(u,
    170             expect_compiler_panic(u, c, run_machinize, &call,
    171                                   "emit temp 1 is allocable"),
    172             "machinize accepted a register shared by allocation and the "
    173             "emitter-temp owner");
    174 
    175   phys[0].flags = NATIVE_REG_CALLER_SAVED;
    176   cls.asm_temps = &asm_temp;
    177   cls.nasm_temps = 1u;
    178   opt_machinize_native(f, &target);
    179   CU_EXPECT(u,
    180             (f->opt_reserved_regs[NATIVE_REG_INT] & (1u << emit_temp)) != 0,
    181             "machinize did not reserve the policy-owned emitter temp");
    182 
    183   /* The asm bank has a separate lifetime and must be statically reserved. */
    184   phys[1].flags = NATIVE_REG_CALLER_SAVED;
    185   CU_EXPECT(u,
    186             expect_compiler_panic(u, c, run_machinize, &call,
    187                                   "asm temp bank is not reserved"),
    188             "machinize accepted an asm-internal temp outside the reserved "
    189             "register owner");
    190 
    191   kit_compiler_free(kc);
    192 }
    193 
    194 static void mir_verify_rejects_private_temp_escape(KitUnit* u) {
    195 #ifdef NDEBUG
    196   (void)u;
    197 #else
    198   KitCompiler* kc = NULL;
    199   Compiler* c;
    200   KitCgTypeId i64_type;
    201   CGFuncDesc desc;
    202   Func* f;
    203   PReg value;
    204   Inst* load;
    205   u32 entry;
    206   Reg temp = 9u;
    207 
    208   CU_EXPECT(u,
    209             kit_unit_compiler_new(
    210                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    211                 &kc) == KIT_OK &&
    212                 kc != NULL,
    213             "compiler allocation failed for MIR temp-escape test");
    214   if (!kc) return;
    215   c = (Compiler*)kc;
    216   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    217   memset(&desc, 0, sizeof desc);
    218   desc.fn_type = i64_type;
    219   desc.result_type = i64_type;
    220   f = ir_func_new(c, &desc);
    221   entry = ir_block_new(f);
    222   f->entry = entry;
    223   ir_note_emit(f, entry);
    224   value = ir_alloc_preg(f, i64_type, RC_INT);
    225   f->preg_locs = arena_zarray(f->arena, OptLoc, f->npregs);
    226   f->preg_info = arena_zarray(f->arena, OptPRegInfo, f->npregs);
    227   set_hard_loc(f, value, temp);
    228   f->opt_phys_regs[RC_INT][0].reg = temp;
    229   f->opt_phys_regs[RC_INT][0].cls = RC_INT;
    230   f->opt_phys_reg_count[RC_INT] = 1u;
    231   f->emit_temp_regs[RC_INT][0] = temp;
    232   f->emit_temp_reg_count[RC_INT] = 1u;
    233   f->opt_reserved_regs[RC_INT] = 1u << temp;
    234 
    235   load = ir_emit(f, entry, IR_LOAD_IMM);
    236   load->type = i64_type;
    237   load->def = value;
    238   load->extra.imm = 42;
    239   load->nopnds = 1u;
    240   load->opnds = arena_array(f->arena, Operand, 1u);
    241   load->opnds[0] = reg_op(value, i64_type);
    242   opt_lower_to_mir(f, NULL);
    243   CU_EXPECT(u, f->mir != NULL,
    244             "temp-escape fixture did not lower to location MIR");
    245   if (f->mir) {
    246     CU_EXPECT(u,
    247               expect_compiler_panic(u, c, run_mir_verify, f,
    248                                     "emitter-owned temp escaped into MIR"),
    249               "MIR verifier accepted an emitter-owned temporary as a "
    250               "persistent value");
    251 
    252     f->emit_temp_reg_count[RC_INT] = 0u;
    253     f->asm_temp_mask[RC_INT] = 1u << temp;
    254     CU_EXPECT(u,
    255               expect_compiler_panic(u, c, run_mir_verify, f,
    256                                     "asm-internal temp escaped into MIR"),
    257               "MIR verifier accepted an asm-internal temporary as a "
    258               "persistent value");
    259   }
    260 
    261   kit_compiler_free(kc);
    262 #endif
    263 }
    264 
    265 static void mir_commit_propagates_instruction_namespace(KitUnit* u) {
    266   KitCompiler* kc = NULL;
    267   Compiler* c;
    268   CGFuncDesc desc;
    269   Func* f;
    270   Func view;
    271   InstId committed;
    272 
    273   CU_EXPECT(u,
    274             kit_unit_compiler_new(
    275                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    276                 &kc) == KIT_OK &&
    277                 kc != NULL,
    278             "compiler allocation failed for MIR commit contract test");
    279   if (!kc) return;
    280   c = (Compiler*)kc;
    281   memset(&desc, 0, sizeof desc);
    282   desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    283   f = ir_func_new(c, &desc);
    284   f->mir = arena_zarray(f->arena, MFunc, 1u);
    285 
    286   CU_EXPECT(u, opt_mir_view(f, &view),
    287             "MIR commit fixture did not produce a physical view");
    288   committed = f->next_inst_id + 7u;
    289   view.next_inst_id = committed;
    290   opt_mir_commit(f, &view);
    291   CU_EXPECT(u, f->next_inst_id == committed,
    292             "MIR commit lost the physical graph's instruction-id namespace");
    293 
    294   kit_compiler_free(kc);
    295 }
    296 
    297 static Inst* find_inst(Block* block, InstId id) {
    298   for (u32 i = 0; i < block->ninsts; ++i)
    299     if (block->insts[i].id == id)
    300       return &block->insts[i];
    301   return NULL;
    302 }
    303 
    304 static void summarize_location(Func* f, Inst* in, Operand* op, int is_def,
    305                                void* arg) {
    306   WalkSummary* summary = (WalkSummary*)arg;
    307   (void)f;
    308   (void)in;
    309   if (op->kind == OPK_STACK && !is_def &&
    310       summary->nstack_uses <
    311           (u32)(sizeof summary->stack_uses / sizeof summary->stack_uses[0])) {
    312     summary->stack_uses[summary->nstack_uses++] = op->v.frame_slot;
    313   }
    314   if (op->kind == OPK_REG && op->cls < OPT_REG_CLASSES && op->v.reg < 32u &&
    315       (summary->reserved_mask[op->cls] & (1u << op->v.reg))) {
    316     ++summary->reserved_regs_seen;
    317   }
    318 }
    319 
    320 static void spilled_values_remain_locations(KitUnit* u) {
    321   KitCompiler* kc = NULL;
    322   Compiler* c;
    323   Func* f;
    324   KitCgTypeId i64_type, ptr_type;
    325   CGFuncDesc desc;
    326   PReg bin_dst, bin_lhs, bin_rhs, load_dst, addr_base, addr_index;
    327   FrameSlot bin_dst_slot, bin_lhs_slot, bin_rhs_slot;
    328   FrameSlot addr_base_slot, addr_index_slot;
    329   u32 entry;
    330   Inst* bin;
    331   Inst* load;
    332   InstId bin_id;
    333   InstId load_id;
    334   Block* hir_blocks;
    335   Inst* hir_insts;
    336   Operand* hir_bin_opnds;
    337   Operand* hir_load_opnds;
    338   u32* hir_succ;
    339   Block* mir_block;
    340   WalkSummary all;
    341   WalkSummary address;
    342   Func view;
    343 
    344   CU_EXPECT(u,
    345             kit_unit_compiler_new(
    346                 u, kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    347                 &kc) == KIT_OK &&
    348                 kc != NULL,
    349             "compiler allocation failed");
    350   if (!kc)
    351     return;
    352   c = (Compiler*)kc;
    353   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    354   ptr_type = kit_cg_type_ptr(
    355       kc, kit_cg_type_builtin(kc, KIT_CG_BUILTIN_VOID), 0);
    356 
    357   memset(&desc, 0, sizeof desc);
    358   desc.fn_type = i64_type;
    359   desc.result_type = i64_type;
    360   f = ir_func_new(c, &desc);
    361   entry = ir_block_new(f);
    362   f->entry = entry;
    363   ir_note_emit(f, entry);
    364 
    365   bin_dst = ir_alloc_preg(f, i64_type, RC_INT);
    366   bin_lhs = ir_alloc_preg(f, i64_type, RC_INT);
    367   bin_rhs = ir_alloc_preg(f, i64_type, RC_INT);
    368   load_dst = ir_alloc_preg(f, i64_type, RC_INT);
    369   addr_base = ir_alloc_preg(f, ptr_type, RC_INT);
    370   addr_index = ir_alloc_preg(f, i64_type, RC_INT);
    371 
    372   bin_dst_slot = spill_slot(f, i64_type);
    373   bin_lhs_slot = spill_slot(f, i64_type);
    374   bin_rhs_slot = spill_slot(f, i64_type);
    375   /* Spill coloring may reuse a physical slot across unrelated type IDs. Keep
    376    * intentionally mismatched descriptor types here: location MIR must carry
    377    * the base/index value types rather than reconstructing them from the slot. */
    378   addr_base_slot = spill_slot(f, i64_type);
    379   addr_index_slot = spill_slot(f, ptr_type);
    380 
    381   f->preg_locs = arena_zarray(f->arena, OptLoc, f->npregs);
    382   f->preg_info = arena_zarray(f->arena, OptPRegInfo, f->npregs);
    383   set_stack_loc(f, bin_dst, bin_dst_slot);
    384   set_stack_loc(f, bin_lhs, bin_lhs_slot);
    385   set_stack_loc(f, bin_rhs, bin_rhs_slot);
    386   set_hard_loc(f, load_dst, 1);
    387   set_stack_loc(f, addr_base, addr_base_slot);
    388   set_stack_loc(f, addr_index, addr_index_slot);
    389 
    390   /* These are emitter-private temporaries. They may be selected while native
    391    * code is emitted, but must never become values in post-regalloc MIR. */
    392   f->emit_temp_regs[RC_INT][0] = 9;
    393   f->emit_temp_regs[RC_INT][1] = 10;
    394   f->emit_temp_regs[RC_INT][2] = 11;
    395   f->emit_temp_reg_count[RC_INT] = 3;
    396   f->opt_reserved_regs[RC_INT] = (1u << 9) | (1u << 10) | (1u << 11);
    397 
    398   bin = ir_emit(f, entry, IR_BINOP);
    399   bin->type = i64_type;
    400   bin->def = bin_dst;
    401   bin->extra.imm = BO_IADD;
    402   bin->nopnds = 3;
    403   bin->opnds = arena_array(f->arena, Operand, bin->nopnds);
    404   bin->opnds[0] = reg_op(bin_dst, i64_type);
    405   bin->opnds[1] = reg_op(bin_lhs, i64_type);
    406   bin->opnds[2] = reg_op(bin_rhs, i64_type);
    407   bin_id = bin->id;
    408 
    409   load = ir_emit(f, entry, IR_LOAD);
    410   load->type = i64_type;
    411   load->def = load_dst;
    412   load->nopnds = 2;
    413   load->opnds = arena_array(f->arena, Operand, load->nopnds);
    414   load->opnds[0] = reg_op(load_dst, i64_type);
    415   load->opnds[1] = indirect_op(addr_base, addr_index, ptr_type, i64_type);
    416   load->extra.mem.type = i64_type;
    417   load->extra.mem.size = 8;
    418   load->extra.mem.align = 8;
    419   load_id = load->id;
    420 
    421   /* Capture the semantic HIR allocation after the last ir_emit (which may
    422    * grow the block's instruction array). Lowering must build a physically
    423    * separate MIR graph and leave each virtual operand in this graph intact. */
    424   hir_blocks = f->blocks;
    425   hir_insts = f->blocks[entry].insts;
    426   hir_bin_opnds = find_inst(&f->blocks[entry], bin_id)->opnds;
    427   hir_load_opnds = find_inst(&f->blocks[entry], load_id)->opnds;
    428   hir_succ = f->blocks[entry].succ;
    429 
    430   opt_lower_to_mir(f, NULL);
    431   CU_EXPECT(u, f->mir != NULL, "lowering did not produce MIR");
    432   if (!f->mir) {
    433     kit_compiler_free(kc);
    434     return;
    435   }
    436   mir_block = &f->mir->blocks[entry];
    437   bin = find_inst(mir_block, bin_id);
    438   load = find_inst(mir_block, load_id);
    439 
    440   CU_EXPECT(u, f->blocks == hir_blocks && f->blocks != f->mir->blocks,
    441             "lowering did not preserve a distinct HIR block graph");
    442   CU_EXPECT(u,
    443             f->mir->blocks_cap == f->mir->nblocks &&
    444                 f->mir->emit_order_cap == f->mir->emit_order_n &&
    445                 f->mir->blocks[entry].cap ==
    446                     f->mir->blocks[entry].ninsts &&
    447                 f->mir->blocks[entry].succ_cap ==
    448                     f->mir->blocks[entry].nsucc &&
    449                 opt_mir_view(f, &view) && view.blocks == f->mir->blocks &&
    450                 view.blocks_cap == f->mir->blocks_cap && view.mir == NULL &&
    451                 view.scope_aux_inst == NULL && view.nscopes == 0,
    452             "MIR retained recording slack or stale HIR graph metadata");
    453   CU_EXPECT(u,
    454             f->blocks[entry].insts == hir_insts &&
    455                 f->mir->blocks[entry].insts != hir_insts,
    456             "lowering aliases HIR and MIR instruction arrays");
    457   CU_EXPECT(u,
    458             f->blocks[entry].succ == hir_succ &&
    459                 f->mir->blocks[entry].succ != hir_succ,
    460             "lowering aliases HIR and MIR CFG arrays");
    461   {
    462     Inst* hir_bin = find_inst(&f->blocks[entry], bin_id);
    463     Inst* hir_load = find_inst(&f->blocks[entry], load_id);
    464     CU_EXPECT(u,
    465               hir_bin && hir_bin->opnds == hir_bin_opnds &&
    466                   hir_bin->opnds[0].kind == OPK_REG &&
    467                   hir_bin->opnds[0].v.reg == bin_dst &&
    468                   hir_bin->opnds[1].v.reg == bin_lhs &&
    469                   hir_bin->opnds[2].v.reg == bin_rhs && bin &&
    470                   bin->opnds != hir_bin_opnds,
    471               "location rewrite changed or aliased HIR binop operands");
    472     CU_EXPECT(u,
    473               hir_load && hir_load->opnds == hir_load_opnds &&
    474                   hir_load->opnds[0].kind == OPK_REG &&
    475                   hir_load->opnds[0].v.reg == load_dst &&
    476                   hir_load->opnds[1].kind == OPK_INDIRECT &&
    477                   hir_load->opnds[1].v.ind.base == addr_base &&
    478                   hir_load->opnds[1].v.ind.index == addr_index && load &&
    479                   load->opnds != hir_load_opnds,
    480               "location rewrite changed or aliased HIR address operands");
    481   }
    482 
    483   CU_EXPECT(u, f->opt_rewrite_inserted_insts == 0,
    484             "location lowering inserted %llu reload/store instructions",
    485             (unsigned long long)f->opt_rewrite_inserted_insts);
    486   CU_EXPECT(u, mir_block->ninsts == 2,
    487             "location MIR should preserve two source instructions, got %u",
    488             mir_block->ninsts);
    489   CU_EXPECT(u, bin != NULL && bin->op == IR_BINOP,
    490             "original binop did not survive location lowering");
    491   CU_EXPECT(u, load != NULL && load->op == IR_LOAD,
    492             "original indirect load did not survive location lowering");
    493   CU_EXPECT(u,
    494             load && load->opnds[1].kind == OPK_INDIRECT &&
    495                 load->opnds[1].v.ind.base_kind == OPT_INDIRECT_FRAME &&
    496                 load->opnds[1].v.ind.index_kind == OPT_INDIRECT_FRAME &&
    497                 load->opnds[1].v.ind.base_type == ptr_type &&
    498                 load->opnds[1].v.ind.index_type == i64_type,
    499             "indirect component location/type provenance was not preserved");
    500 
    501   /* OPK_STACK is the post-allocation scalar-value location. OPK_LOCAL remains
    502    * a semantic frame lvalue/address, so accepting it here would collapse the
    503    * exact boundary this test protects. */
    504   if (bin && bin->nopnds == 3) {
    505     CU_EXPECT(u,
    506               bin->opnds[0].kind == OPK_STACK &&
    507                   bin->opnds[0].v.frame_slot == bin_dst_slot,
    508               "spilled scalar def is not its stable frame location");
    509     CU_EXPECT(u,
    510               bin->opnds[1].kind == OPK_STACK &&
    511                   bin->opnds[1].v.frame_slot == bin_lhs_slot,
    512               "first spilled scalar use is not its stable frame location");
    513     CU_EXPECT(u,
    514               bin->opnds[2].kind == OPK_STACK &&
    515                   bin->opnds[2].v.frame_slot == bin_rhs_slot,
    516               "second spilled scalar use is not its stable frame location");
    517   } else {
    518     CU_EXPECT(u, 0, "lowered binop has the wrong operand shape");
    519   }
    520 
    521   memset(&address, 0, sizeof address);
    522   address.reserved_mask[RC_INT] = f->opt_reserved_regs[RC_INT];
    523   if (load)
    524     opt_walk_inst_operands(f, load, summarize_location, &address);
    525   CU_EXPECT(u,
    526             address.nstack_uses == 2 &&
    527                 address.stack_uses[0] == addr_base_slot &&
    528                 address.stack_uses[1] == addr_index_slot,
    529             "spilled indirect base/index are not stable frame locations");
    530 
    531   memset(&all, 0, sizeof all);
    532   for (u32 cls = 0; cls < OPT_REG_CLASSES; ++cls)
    533     all.reserved_mask[cls] = f->opt_reserved_regs[cls];
    534   for (u32 i = 0; i < mir_block->ninsts; ++i)
    535     opt_walk_inst_operands(f, &mir_block->insts[i], summarize_location, &all);
    536   CU_EXPECT(u, all.reserved_regs_seen == 0,
    537             "MIR exposes %u emitter-private register operands",
    538             all.reserved_regs_seen);
    539 
    540   kit_compiler_free(kc);
    541 }
    542 
    543 static void spilled_frame_address_remains_recipe(KitUnit* u) {
    544   KitCompiler* kc = NULL;
    545   Compiler* c;
    546   Func* f;
    547   KitCgTypeId i32_type, ptr_type;
    548   CGFuncDesc desc;
    549   PReg address, copy;
    550   FrameSlot value_slot, address_spill, copy_spill;
    551   u32 entry;
    552   Inst* addr;
    553   Inst* move;
    554   InstId addr_id;
    555   InstId move_id;
    556 
    557   CU_EXPECT(u,
    558             kit_unit_compiler_new(
    559                 u, kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    560                 &kc) == KIT_OK &&
    561                 kc != NULL,
    562             "compiler allocation failed for frame-address recipe test");
    563   if (!kc) return;
    564   c = (Compiler*)kc;
    565   i32_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I32);
    566   ptr_type = kit_cg_type_ptr(kc, i32_type, 0);
    567 
    568   memset(&desc, 0, sizeof desc);
    569   desc.fn_type = ptr_type;
    570   desc.result_type = ptr_type;
    571   f = ir_func_new(c, &desc);
    572   entry = ir_block_new(f);
    573   f->entry = entry;
    574   ir_note_emit(f, entry);
    575 
    576   address = ir_alloc_preg(f, ptr_type, RC_INT);
    577   copy = ir_alloc_preg(f, ptr_type, RC_INT);
    578   value_slot = local_slot(f, i32_type, 4, 4);
    579   address_spill = spill_slot(f, ptr_type);
    580   copy_spill = spill_slot(f, ptr_type);
    581   f->preg_locs = arena_zarray(f->arena, OptLoc, f->npregs);
    582   f->preg_info = arena_zarray(f->arena, OptPRegInfo, f->npregs);
    583   set_stack_loc(f, address, address_spill);
    584   set_stack_loc(f, copy, copy_spill);
    585 
    586   addr = ir_emit(f, entry, IR_ADDR_OF);
    587   addr_id = addr->id;
    588   addr->type = ptr_type;
    589   addr->def = address;
    590   addr->nopnds = 2;
    591   addr->opnds = arena_zarray(f->arena, Operand, addr->nopnds);
    592   addr->opnds[0] = reg_op(address, ptr_type);
    593   addr->opnds[1].kind = OPK_LOCAL;
    594   addr->opnds[1].cls = RC_INT;
    595   addr->opnds[1].type = i32_type;
    596   addr->opnds[1].v.frame_slot = value_slot;
    597 
    598   move = ir_emit(f, entry, IR_COPY);
    599   move_id = move->id;
    600   move->type = ptr_type;
    601   move->def = copy;
    602   move->nopnds = 2;
    603   move->opnds = arena_zarray(f->arena, Operand, move->nopnds);
    604   move->opnds[0] = reg_op(copy, ptr_type);
    605   move->opnds[1] = reg_op(address, ptr_type);
    606 
    607   opt_lower_to_mir(f, NULL);
    608   opt_mir_combine(f, NULL);
    609   opt_mir_dce(f);
    610   opt_mir_jump_cleanup(f, OPT_JUMP_CLEANUP_CFG);
    611   opt_mir_build_cfg(f);
    612   opt_mir_jump_cleanup(f, OPT_JUMP_CLEANUP_LAYOUT);
    613   CU_EXPECT(u, f->mir != NULL, "frame-address lowering did not produce MIR");
    614   if (f->mir) {
    615     Block* block = &f->mir->blocks[entry];
    616     Inst* mir_addr = find_inst(block, addr_id);
    617     Inst* mir_move = find_inst(block, move_id);
    618     CU_EXPECT(u, mir_addr == NULL,
    619               "rematerialized frame-address producer survived MIR lowering");
    620     CU_EXPECT(u, block->ninsts == 1 && mir_move && mir_move->nopnds == 2,
    621               "frame-address recipe lowering has the wrong instruction shape");
    622     CU_EXPECT(u,
    623               mir_move && mir_move->opnds[0].kind == OPK_STACK &&
    624                   mir_move->opnds[0].v.frame_slot == copy_spill,
    625               "frame-address copy destination is not its spill home");
    626     CU_EXPECT(u,
    627               mir_move && mir_move->opnds[1].kind == OPK_FRAME_ADDR &&
    628                   mir_move->opnds[1].v.frame_slot == value_slot,
    629               "frame-address recipe was rewritten as an unrelated value");
    630   }
    631   addr = find_inst(&f->blocks[entry], addr_id);
    632   move = find_inst(&f->blocks[entry], move_id);
    633   CU_EXPECT(u,
    634             addr && addr->opnds[1].kind == OPK_LOCAL &&
    635                 addr->opnds[1].v.frame_slot == value_slot && move &&
    636                 move->opnds[1].kind == OPK_REG &&
    637                 move->opnds[1].v.reg == address,
    638             "frame-address location lowering mutated semantic HIR");
    639 
    640   kit_compiler_free(kc);
    641 }
    642 
    643 static void call_aux_is_independent(KitUnit* u) {
    644   KitCompiler* kc = NULL;
    645   Compiler* c;
    646   Func* f;
    647   KitCgTypeId i64_type;
    648   CGFuncDesc desc;
    649   PReg callee_reg, arg_reg, arg_part_reg, ret_reg, ret_part_reg;
    650   IRCallAux* hir_aux;
    651   IRCallAux* mir_aux;
    652   Inst* call;
    653   InstId call_id;
    654   u32 entry;
    655 
    656   CU_EXPECT(u,
    657             kit_unit_compiler_new(
    658                 u, kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    659                 &kc) == KIT_OK &&
    660                 kc != NULL,
    661             "compiler allocation failed for call-aux clone test");
    662   if (!kc) return;
    663   c = (Compiler*)kc;
    664   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    665 
    666   memset(&desc, 0, sizeof desc);
    667   desc.fn_type = i64_type;
    668   f = ir_func_new(c, &desc);
    669   entry = ir_block_new(f);
    670   f->entry = entry;
    671   ir_note_emit(f, entry);
    672 
    673   callee_reg = ir_alloc_preg(f, i64_type, RC_INT);
    674   arg_reg = ir_alloc_preg(f, i64_type, RC_INT);
    675   arg_part_reg = ir_alloc_preg(f, i64_type, RC_INT);
    676   ret_reg = ir_alloc_preg(f, i64_type, RC_INT);
    677   ret_part_reg = ir_alloc_preg(f, i64_type, RC_INT);
    678   f->preg_locs = arena_zarray(f->arena, OptLoc, f->npregs);
    679   f->preg_info = arena_zarray(f->arena, OptPRegInfo, f->npregs);
    680   set_hard_loc(f, callee_reg, 1);
    681   set_hard_loc(f, arg_reg, 2);
    682   set_hard_loc(f, arg_part_reg, 3);
    683   set_hard_loc(f, ret_reg, 4);
    684   set_hard_loc(f, ret_part_reg, 5);
    685 
    686   call = ir_emit(f, entry, IR_CALL);
    687   call_id = call->id;
    688   call->def = ret_reg;
    689   hir_aux = arena_znew(f->arena, IRCallAux);
    690   hir_aux->desc.callee = reg_op(callee_reg, i64_type);
    691   hir_aux->desc.nargs = 1;
    692   hir_aux->desc.args = arena_zarray(f->arena, CGABIValue, 1);
    693   hir_aux->desc.args[0].type = i64_type;
    694   hir_aux->desc.args[0].storage = reg_op(arg_reg, i64_type);
    695   hir_aux->desc.args[0].nparts = 1;
    696   hir_aux->desc.args[0].parts = arena_zarray(f->arena, CGABIPart, 1);
    697   hir_aux->desc.args[0].parts[0].op = reg_op(arg_part_reg, i64_type);
    698   hir_aux->desc.ret.type = i64_type;
    699   hir_aux->desc.ret.storage = reg_op(ret_reg, i64_type);
    700   hir_aux->desc.ret.nparts = 1;
    701   hir_aux->desc.ret.parts = arena_zarray(f->arena, CGABIPart, 1);
    702   hir_aux->desc.ret.parts[0].op = reg_op(ret_part_reg, i64_type);
    703   hir_aux->plan.callee = reg_op(callee_reg, i64_type);
    704   hir_aux->plan.nargs = 2;
    705   hir_aux->plan.args =
    706       arena_zarray(f->arena, CGCallPlanMove, hir_aux->plan.nargs);
    707   hir_aux->plan.args[0].src = reg_op(arg_reg, i64_type);
    708   hir_aux->plan.args[0].dst = reg_op(arg_part_reg, i64_type);
    709   hir_aux->plan.args[0].src_kind = CG_CALL_PLAN_SRC_VALUE;
    710   hir_aux->plan.args[0].dst_kind = CG_CALL_PLAN_REG;
    711   hir_aux->plan.args[0].dst_reg = 3;
    712   hir_aux->plan.args[1].src = reg_op(arg_part_reg, i64_type);
    713   hir_aux->plan.args[1].dst_kind = CG_CALL_PLAN_STACK;
    714   hir_aux->plan.args[1].stack_offset = 24;
    715   hir_aux->plan.nrets = 2;
    716   hir_aux->plan.rets =
    717       arena_zarray(f->arena, CGCallPlanRet, hir_aux->plan.nrets);
    718   hir_aux->plan.rets[0].dst = reg_op(ret_reg, i64_type);
    719   hir_aux->plan.rets[0].src_reg = 4;
    720   hir_aux->plan.rets[1].dst = reg_op(ret_part_reg, i64_type);
    721   hir_aux->plan.rets[1].src_reg = 5;
    722   hir_aux->plan_valid = 1;
    723   hir_aux->nresults = 1;
    724   hir_aux->results = arena_array(f->arena, Val, 1);
    725   hir_aux->results[0] = ret_reg;
    726   call->extra.aux = hir_aux;
    727 
    728   opt_lower_to_mir(f, NULL);
    729   call = f->mir ? find_inst(&f->mir->blocks[entry], call_id) : NULL;
    730   mir_aux = call ? (IRCallAux*)call->extra.aux : NULL;
    731   CU_EXPECT(u, call && mir_aux && mir_aux != hir_aux,
    732             "call aux object aliases HIR after MIR lowering");
    733   CU_EXPECT(u,
    734             mir_aux && mir_aux->desc.args != hir_aux->desc.args &&
    735                 mir_aux->desc.args[0].parts != hir_aux->desc.args[0].parts &&
    736                 mir_aux->desc.ret.parts != hir_aux->desc.ret.parts &&
    737                 mir_aux->plan.args != hir_aux->plan.args &&
    738                 mir_aux->plan.rets != hir_aux->plan.rets &&
    739                 mir_aux->results != hir_aux->results,
    740             "call aux nested arrays alias HIR after MIR lowering");
    741   CU_EXPECT(u,
    742             hir_aux->desc.callee.v.reg == callee_reg &&
    743                 hir_aux->desc.args[0].storage.v.reg == arg_reg &&
    744                 hir_aux->desc.args[0].parts[0].op.v.reg == arg_part_reg &&
    745                 hir_aux->desc.ret.storage.v.reg == ret_reg &&
    746                 hir_aux->desc.ret.parts[0].op.v.reg == ret_part_reg &&
    747                 mir_aux && mir_aux->desc.callee.v.reg == 1 &&
    748                 mir_aux->desc.args[0].storage.v.reg == 2 &&
    749                 mir_aux->desc.args[0].parts[0].op.v.reg == 3 &&
    750                 mir_aux->desc.ret.storage.v.reg == 4 &&
    751                 mir_aux->desc.ret.parts[0].op.v.reg == 5,
    752             "call operand rewrite crossed the HIR/MIR ownership boundary");
    753   CU_EXPECT(u,
    754             mir_aux && mir_aux->plan.nargs == 2 && mir_aux->plan.nrets == 2 &&
    755                 mir_aux->plan.args[0].src.v.reg == arg_reg &&
    756                 mir_aux->plan.args[1].stack_offset == 24 &&
    757                 mir_aux->plan.rets[0].dst.v.reg == ret_reg &&
    758                 mir_aux->plan.rets[1].src_reg == 5,
    759             "call plan arrays did not survive MIR cloning");
    760 
    761   if (mir_aux) {
    762     mir_aux->plan.args[0].src.kind = OPK_IMM;
    763     mir_aux->plan.args[0].src.v.imm = 91;
    764     mir_aux->plan.args[1].stack_offset = 88;
    765     mir_aux->plan.rets[0].dst.v.reg = 27;
    766     mir_aux->plan.rets[1].src_reg = 28;
    767   }
    768   CU_EXPECT(u,
    769             hir_aux->plan.args[0].src.kind == OPK_REG &&
    770                 hir_aux->plan.args[0].src.v.reg == arg_reg &&
    771                 hir_aux->plan.args[1].stack_offset == 24 &&
    772                 hir_aux->plan.rets[0].dst.v.reg == ret_reg &&
    773                 hir_aux->plan.rets[1].src_reg == 5,
    774             "mutating MIR call-plan arrays changed semantic HIR");
    775 
    776   hir_aux->plan.args[1].stack_offset = 104;
    777   hir_aux->plan.rets[1].src_reg = 30;
    778   CU_EXPECT(u,
    779             mir_aux && mir_aux->plan.args[1].stack_offset == 88 &&
    780                 mir_aux->plan.rets[1].src_reg == 28,
    781             "mutating HIR call-plan arrays changed location MIR");
    782 
    783   kit_compiler_free(kc);
    784 }
    785 
    786 static void structured_aux_arrays_are_independent(KitUnit* u) {
    787   KitCompiler* kc = NULL;
    788   Compiler* c;
    789   Func* f;
    790   KitCgTypeId i64_type;
    791   CGFuncDesc desc;
    792   PReg dst0, dst1, arg0, arg1;
    793   Inst* phi;
    794   Inst* sw;
    795   Inst* indirect;
    796   Inst* intrin;
    797   InstId phi_id, switch_id, indirect_id, intrin_id;
    798   IRPhiAux* hir_phi;
    799   IRPhiAux* mir_phi;
    800   IRSwitchAux* hir_switch;
    801   IRSwitchAux* mir_switch;
    802   IRIndirectAux* hir_indirect;
    803   IRIndirectAux* mir_indirect;
    804   IRIntrinAux* hir_intrin;
    805   IRIntrinAux* mir_intrin;
    806   u32 entry;
    807 
    808   CU_EXPECT(u,
    809             kit_unit_compiler_new(
    810                 u, kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    811                 &kc) == KIT_OK &&
    812                 kc != NULL,
    813             "compiler allocation failed for structured-aux clone test");
    814   if (!kc) return;
    815   c = (Compiler*)kc;
    816   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    817 
    818   memset(&desc, 0, sizeof desc);
    819   desc.fn_type = i64_type;
    820   f = ir_func_new(c, &desc);
    821   entry = ir_block_new(f);
    822   f->entry = entry;
    823   ir_note_emit(f, entry);
    824 
    825   dst0 = ir_alloc_preg(f, i64_type, RC_INT);
    826   dst1 = ir_alloc_preg(f, i64_type, RC_INT);
    827   arg0 = ir_alloc_preg(f, i64_type, RC_INT);
    828   arg1 = ir_alloc_preg(f, i64_type, RC_INT);
    829   f->preg_locs = arena_zarray(f->arena, OptLoc, f->npregs);
    830   f->preg_info = arena_zarray(f->arena, OptPRegInfo, f->npregs);
    831   set_hard_loc(f, dst0, 8);
    832   set_hard_loc(f, dst1, 9);
    833   set_hard_loc(f, arg0, 10);
    834   set_hard_loc(f, arg1, 11);
    835 
    836   phi = ir_emit(f, entry, IR_PHI);
    837   phi_id = phi->id;
    838   hir_phi = arena_znew(f->arena, IRPhiAux);
    839   hir_phi->npreds = 2;
    840   hir_phi->pred_blocks = arena_array(f->arena, u32, hir_phi->npreds);
    841   hir_phi->pred_vals = arena_array(f->arena, Val, hir_phi->npreds);
    842   hir_phi->pred_blocks[0] = 3;
    843   hir_phi->pred_blocks[1] = 4;
    844   hir_phi->pred_vals[0] = 13;
    845   hir_phi->pred_vals[1] = 14;
    846   phi->extra.aux = hir_phi;
    847 
    848   sw = ir_emit(f, entry, IR_SWITCH);
    849   switch_id = sw->id;
    850   hir_switch = arena_znew(f->arena, IRSwitchAux);
    851   hir_switch->ncases = 2;
    852   hir_switch->cases =
    853       arena_zarray(f->arena, IRSwitchAuxCase, hir_switch->ncases);
    854   hir_switch->cases[0].value = 21;
    855   hir_switch->cases[0].block = 5;
    856   hir_switch->cases[1].value = 22;
    857   hir_switch->cases[1].block = 6;
    858   sw->extra.aux = hir_switch;
    859 
    860   indirect = ir_emit(f, entry, IR_INDIRECT_BRANCH);
    861   indirect_id = indirect->id;
    862   hir_indirect = arena_znew(f->arena, IRIndirectAux);
    863   hir_indirect->ntargets = 2;
    864   hir_indirect->targets = arena_array(f->arena, u32, hir_indirect->ntargets);
    865   hir_indirect->targets[0] = 7;
    866   hir_indirect->targets[1] = 8;
    867   indirect->extra.aux = hir_indirect;
    868 
    869   intrin = ir_emit(f, entry, IR_INTRINSIC);
    870   intrin_id = intrin->id;
    871   hir_intrin = arena_znew(f->arena, IRIntrinAux);
    872   hir_intrin->kind = INTRIN_SADD_OVERFLOW;
    873   hir_intrin->ndst = 2;
    874   hir_intrin->narg = 2;
    875   hir_intrin->dsts = arena_zarray(f->arena, Operand, hir_intrin->ndst);
    876   hir_intrin->args = arena_zarray(f->arena, Operand, hir_intrin->narg);
    877   hir_intrin->result_vals = arena_array(f->arena, Val, hir_intrin->ndst);
    878   hir_intrin->dsts[0] = reg_op(dst0, i64_type);
    879   hir_intrin->dsts[1] = reg_op(dst1, i64_type);
    880   hir_intrin->args[0] = reg_op(arg0, i64_type);
    881   hir_intrin->args[1] = reg_op(arg1, i64_type);
    882   hir_intrin->result_vals[0] = 31;
    883   hir_intrin->result_vals[1] = 32;
    884   intrin->extra.aux = hir_intrin;
    885 
    886   opt_lower_to_mir(f, NULL);
    887   phi = f->mir ? find_inst(&f->mir->blocks[entry], phi_id) : NULL;
    888   sw = f->mir ? find_inst(&f->mir->blocks[entry], switch_id) : NULL;
    889   indirect =
    890       f->mir ? find_inst(&f->mir->blocks[entry], indirect_id) : NULL;
    891   intrin = f->mir ? find_inst(&f->mir->blocks[entry], intrin_id) : NULL;
    892   mir_phi = phi ? (IRPhiAux*)phi->extra.aux : NULL;
    893   mir_switch = sw ? (IRSwitchAux*)sw->extra.aux : NULL;
    894   mir_indirect = indirect ? (IRIndirectAux*)indirect->extra.aux : NULL;
    895   mir_intrin = intrin ? (IRIntrinAux*)intrin->extra.aux : NULL;
    896 
    897   CU_EXPECT(u,
    898             mir_phi && mir_phi != hir_phi &&
    899                 mir_phi->pred_blocks != hir_phi->pred_blocks &&
    900                 mir_phi->pred_vals != hir_phi->pred_vals,
    901             "phi aux arrays alias semantic HIR");
    902   CU_EXPECT(u,
    903             mir_switch && mir_switch != hir_switch &&
    904                 mir_switch->cases != hir_switch->cases,
    905             "switch case array aliases semantic HIR");
    906   CU_EXPECT(u,
    907             mir_indirect && mir_indirect != hir_indirect &&
    908                 mir_indirect->targets != hir_indirect->targets,
    909             "indirect-branch target array aliases semantic HIR");
    910   CU_EXPECT(u,
    911             mir_intrin && mir_intrin != hir_intrin &&
    912                 mir_intrin->dsts != hir_intrin->dsts &&
    913                 mir_intrin->args != hir_intrin->args &&
    914                 mir_intrin->result_vals != hir_intrin->result_vals,
    915             "intrinsic aux arrays alias semantic HIR");
    916   CU_EXPECT(u,
    917             mir_phi && mir_phi->pred_blocks[0] == 3 &&
    918                 mir_phi->pred_vals[1] == 14 && mir_switch &&
    919                 mir_switch->cases[1].value == 22 &&
    920                 mir_switch->cases[1].block == 6 && mir_indirect &&
    921                 mir_indirect->targets[1] == 8 && mir_intrin &&
    922                 mir_intrin->result_vals[0] == 31 &&
    923                 mir_intrin->dsts[0].v.reg == 8 &&
    924                 mir_intrin->dsts[1].v.reg == 9 &&
    925                 mir_intrin->args[0].v.reg == 10 &&
    926                 mir_intrin->args[1].v.reg == 11,
    927             "structured aux payloads did not survive location-MIR cloning");
    928 
    929   if (mir_phi) {
    930     mir_phi->pred_blocks[0] = 103;
    931     mir_phi->pred_vals[0] = 113;
    932   }
    933   if (mir_switch) {
    934     mir_switch->cases[0].value = 121;
    935     mir_switch->cases[0].block = 105;
    936   }
    937   if (mir_indirect) mir_indirect->targets[0] = 107;
    938   if (mir_intrin) {
    939     mir_intrin->dsts[0].kind = OPK_IMM;
    940     mir_intrin->dsts[0].v.imm = 131;
    941     mir_intrin->args[0].kind = OPK_IMM;
    942     mir_intrin->args[0].v.imm = 132;
    943     mir_intrin->result_vals[0] = 133;
    944   }
    945   CU_EXPECT(u,
    946             hir_phi->pred_blocks[0] == 3 && hir_phi->pred_vals[0] == 13 &&
    947                 hir_switch->cases[0].value == 21 &&
    948                 hir_switch->cases[0].block == 5 &&
    949                 hir_indirect->targets[0] == 7 &&
    950                 hir_intrin->dsts[0].kind == OPK_REG &&
    951                 hir_intrin->dsts[0].v.reg == dst0 &&
    952                 hir_intrin->args[0].kind == OPK_REG &&
    953                 hir_intrin->args[0].v.reg == arg0 &&
    954                 hir_intrin->result_vals[0] == 31,
    955             "mutating MIR structured aux arrays changed semantic HIR");
    956 
    957   hir_phi->pred_blocks[1] = 204;
    958   hir_phi->pred_vals[1] = 214;
    959   hir_switch->cases[1].value = 222;
    960   hir_switch->cases[1].block = 206;
    961   hir_indirect->targets[1] = 208;
    962   hir_intrin->dsts[1].v.reg = dst0;
    963   hir_intrin->args[1].v.reg = arg0;
    964   hir_intrin->result_vals[1] = 232;
    965   CU_EXPECT(u,
    966             mir_phi && mir_phi->pred_blocks[1] == 4 &&
    967                 mir_phi->pred_vals[1] == 14 && mir_switch &&
    968                 mir_switch->cases[1].value == 22 &&
    969                 mir_switch->cases[1].block == 6 && mir_indirect &&
    970                 mir_indirect->targets[1] == 8 && mir_intrin &&
    971                 mir_intrin->dsts[1].v.reg == 9 &&
    972                 mir_intrin->args[1].v.reg == 11 &&
    973                 mir_intrin->result_vals[1] == 32,
    974             "mutating HIR structured aux arrays changed location MIR");
    975 
    976   kit_compiler_free(kc);
    977 }
    978 
    979 static void asm_aux_resolved_metadata_is_independent(KitUnit* u) {
    980   KitCompiler* kc = NULL;
    981   Compiler* c;
    982   Func* f;
    983   KitCgTypeId i64_type;
    984   CGFuncDesc desc;
    985   PReg out_reg, in_reg;
    986   IRAsmAux* hir_aux;
    987   IRAsmAux* mir_aux;
    988   Inst* asm_in;
    989   InstId asm_id;
    990   FrameSlot memory_slot;
    991   FrameSlotDesc memory_desc;
    992   Sym out_name, in_name, clobber_name;
    993   u32 entry;
    994 
    995   CU_EXPECT(u,
    996             kit_unit_compiler_new(
    997                 u, kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    998                 &kc) == KIT_OK &&
    999                 kc != NULL,
   1000             "compiler allocation failed for asm-aux clone test");
   1001   if (!kc) return;
   1002   c = (Compiler*)kc;
   1003   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
   1004   out_name = kit_sym_intern(kc, KIT_SLICE_LIT("out"));
   1005   in_name = kit_sym_intern(kc, KIT_SLICE_LIT("in"));
   1006   clobber_name = kit_sym_intern(kc, KIT_SLICE_LIT("x12"));
   1007 
   1008   memset(&desc, 0, sizeof desc);
   1009   desc.fn_type = i64_type;
   1010   f = ir_func_new(c, &desc);
   1011   entry = ir_block_new(f);
   1012   f->entry = entry;
   1013   ir_note_emit(f, entry);
   1014 
   1015   out_reg = ir_alloc_preg(f, i64_type, RC_INT);
   1016   in_reg = ir_alloc_preg(f, i64_type, RC_INT);
   1017   f->preg_locs = arena_zarray(f->arena, OptLoc, f->npregs);
   1018   f->preg_info = arena_zarray(f->arena, OptPRegInfo, f->npregs);
   1019   set_hard_loc(f, out_reg, 6);
   1020   set_hard_loc(f, in_reg, 7);
   1021   memset(&memory_desc, 0, sizeof memory_desc);
   1022   memory_desc.type = i64_type;
   1023   memory_desc.size = 8;
   1024   memory_desc.align = 8;
   1025   memory_desc.kind = FS_LOCAL;
   1026   memory_slot = ir_frame_slot_new(f, &memory_desc);
   1027 
   1028   asm_in = ir_emit(f, entry, IR_ASM_BLOCK);
   1029   asm_id = asm_in->id;
   1030   asm_in->def = out_reg;
   1031   hir_aux = arena_znew(f->arena, IRAsmAux);
   1032   hir_aux->tmpl = "add %0, %1";
   1033   hir_aux->nout = 1;
   1034   hir_aux->nin = 2;
   1035   hir_aux->nclob = 1;
   1036   hir_aux->outs = arena_zarray(f->arena, AsmConstraint, hir_aux->nout);
   1037   hir_aux->ins = arena_zarray(f->arena, AsmConstraint, hir_aux->nin);
   1038   hir_aux->clobbers = arena_array(f->arena, Sym, hir_aux->nclob);
   1039   hir_aux->out_ops = arena_zarray(f->arena, Operand, hir_aux->nout);
   1040   hir_aux->in_ops = arena_zarray(f->arena, Operand, hir_aux->nin);
   1041   hir_aux->out_reg_reqs =
   1042       arena_zarray(f->arena, IRAsmRegRequirement, hir_aux->nout);
   1043   hir_aux->in_reg_reqs =
   1044       arena_zarray(f->arena, IRAsmRegRequirement, hir_aux->nin);
   1045   hir_aux->outs[0].str = "=r";
   1046   hir_aux->outs[0].name = out_name;
   1047   hir_aux->outs[0].type = i64_type;
   1048   hir_aux->outs[0].dir = KIT_CG_ASM_OUT;
   1049   hir_aux->ins[0].str = "r";
   1050   hir_aux->ins[0].name = in_name;
   1051   hir_aux->ins[0].type = i64_type;
   1052   hir_aux->ins[0].dir = KIT_CG_ASM_IN;
   1053   hir_aux->ins[1].str = "m";
   1054   hir_aux->ins[1].type = i64_type;
   1055   hir_aux->ins[1].dir = KIT_CG_ASM_IN;
   1056   hir_aux->clobbers[0] = clobber_name;
   1057   hir_aux->out_ops[0] = reg_op(out_reg, i64_type);
   1058   hir_aux->in_ops[0] = reg_op(in_reg, i64_type);
   1059   hir_aux->in_ops[1].kind = OPK_LOCAL;
   1060   hir_aux->in_ops[1].cls = RC_INT;
   1061   hir_aux->in_ops[1].type = i64_type;
   1062   hir_aux->in_ops[1].v.frame_slot = memory_slot;
   1063   hir_aux->out_reg_reqs[0].allowed_mask = 0x000000f0u;
   1064   hir_aux->out_reg_reqs[0].fixed_reg = 6;
   1065   hir_aux->out_reg_reqs[0].cls = RC_INT;
   1066   hir_aux->out_reg_reqs[0].present = 1;
   1067   hir_aux->in_reg_reqs[0].allowed_mask = 0x0000ff00u;
   1068   hir_aux->in_reg_reqs[0].fixed_reg = -1;
   1069   hir_aux->in_reg_reqs[0].cls = RC_INT;
   1070   hir_aux->in_reg_reqs[0].present = 1;
   1071   hir_aux->in_reg_reqs[1].fixed_reg = -1;
   1072   hir_aux->clobber_mask[RC_INT] = (1u << 3) | (1u << 12);
   1073   hir_aux->clobber_mask[RC_FP] = 1u << 9;
   1074   hir_aux->clobber_mask[RC_VEC] = 1u << 2;
   1075   hir_aux->clobber_abi_sets = KIT_CG_ASM_CLOBBER_ABI_CALLER_SAVED;
   1076   hir_aux->has_memory_constraint = 1;
   1077   asm_in->extra.aux = hir_aux;
   1078 
   1079   opt_lower_to_mir(f, NULL);
   1080   asm_in = f->mir ? find_inst(&f->mir->blocks[entry], asm_id) : NULL;
   1081   mir_aux = asm_in ? (IRAsmAux*)asm_in->extra.aux : NULL;
   1082   CU_EXPECT(u, asm_in && mir_aux && mir_aux != hir_aux,
   1083             "asm aux object aliases HIR after MIR lowering");
   1084   CU_EXPECT(u,
   1085             mir_aux && mir_aux->outs != hir_aux->outs &&
   1086                 mir_aux->ins != hir_aux->ins &&
   1087                 mir_aux->clobbers != hir_aux->clobbers &&
   1088                 mir_aux->out_ops != hir_aux->out_ops &&
   1089                 mir_aux->in_ops != hir_aux->in_ops &&
   1090                 mir_aux->out_reg_reqs != hir_aux->out_reg_reqs &&
   1091                 mir_aux->in_reg_reqs != hir_aux->in_reg_reqs,
   1092             "asm aux nested mutable arrays alias HIR");
   1093   CU_EXPECT(u,
   1094             mir_aux &&
   1095                 memcmp(mir_aux->clobber_mask, hir_aux->clobber_mask,
   1096                        sizeof hir_aux->clobber_mask) == 0 &&
   1097                 mir_aux->clobber_abi_sets == hir_aux->clobber_abi_sets &&
   1098                 mir_aux->has_memory_constraint ==
   1099                     hir_aux->has_memory_constraint &&
   1100                 memcmp(mir_aux->out_reg_reqs, hir_aux->out_reg_reqs,
   1101                        sizeof hir_aux->out_reg_reqs[0]) == 0 &&
   1102                 memcmp(mir_aux->in_reg_reqs, hir_aux->in_reg_reqs,
   1103                        sizeof hir_aux->in_reg_reqs[0] * hir_aux->nin) == 0,
   1104             "resolved asm placement metadata did not survive MIR cloning");
   1105   CU_EXPECT(u,
   1106             hir_aux->out_ops[0].kind == OPK_REG &&
   1107                 hir_aux->out_ops[0].v.reg == out_reg &&
   1108                 hir_aux->in_ops[0].kind == OPK_REG &&
   1109                 hir_aux->in_ops[0].v.reg == in_reg && mir_aux &&
   1110                 mir_aux->out_ops[0].kind == OPK_REG &&
   1111                 mir_aux->out_ops[0].v.reg == 6 &&
   1112                 mir_aux->in_ops[0].kind == OPK_REG &&
   1113                 mir_aux->in_ops[0].v.reg == 7 &&
   1114                 hir_aux->in_ops[1].kind == OPK_LOCAL &&
   1115                 hir_aux->in_ops[1].v.frame_slot == memory_slot &&
   1116                 mir_aux->in_ops[1].kind == OPK_LOCAL &&
   1117                 mir_aux->in_ops[1].v.frame_slot == memory_slot,
   1118             "asm operand rewrite crossed the HIR/MIR ownership boundary");
   1119 
   1120   if (mir_aux) {
   1121     mir_aux->out_reg_reqs[0].allowed_mask = 1u << 20;
   1122     mir_aux->out_reg_reqs[0].fixed_reg = 20;
   1123     mir_aux->in_reg_reqs[0].present = 0;
   1124     mir_aux->clobber_mask[RC_INT] = 1u << 22;
   1125     mir_aux->has_memory_constraint = 0;
   1126     mir_aux->outs[0].name = in_name;
   1127     mir_aux->ins[0].str = "m";
   1128     mir_aux->clobbers[0] = out_name;
   1129     mir_aux->out_ops[0].v.reg = 20;
   1130     mir_aux->in_ops[0].kind = OPK_IMM;
   1131     mir_aux->in_ops[0].v.imm = 42;
   1132   }
   1133   CU_EXPECT(u,
   1134             hir_aux->out_reg_reqs[0].allowed_mask == 0x000000f0u &&
   1135                 hir_aux->out_reg_reqs[0].fixed_reg == 6 &&
   1136                 hir_aux->in_reg_reqs[0].present == 1 &&
   1137                 hir_aux->clobber_mask[RC_INT] ==
   1138                     ((1u << 3) | (1u << 12)) &&
   1139                 hir_aux->has_memory_constraint == 1 &&
   1140                 hir_aux->outs[0].name == out_name &&
   1141                 strcmp(hir_aux->ins[0].str, "r") == 0 &&
   1142                 hir_aux->clobbers[0] == clobber_name &&
   1143                 hir_aux->out_ops[0].kind == OPK_REG &&
   1144                 hir_aux->out_ops[0].v.reg == out_reg &&
   1145                 hir_aux->in_ops[0].kind == OPK_REG &&
   1146                 hir_aux->in_ops[0].v.reg == in_reg,
   1147             "mutating MIR asm metadata changed the semantic HIR graph");
   1148 
   1149   /* Prove independence in the other direction as well: the already-mutated
   1150    * MIR requirements and operands must not observe later HIR changes. */
   1151   hir_aux->out_reg_reqs[0].allowed_mask = 1u;
   1152   hir_aux->in_ops[0].v.reg = out_reg;
   1153   CU_EXPECT(u,
   1154             mir_aux && mir_aux->out_reg_reqs[0].allowed_mask == (1u << 20) &&
   1155                 mir_aux->in_ops[0].kind == OPK_IMM &&
   1156                 mir_aux->in_ops[0].v.imm == 42,
   1157             "mutating HIR asm metadata changed the location MIR graph");
   1158 
   1159   kit_compiler_free(kc);
   1160 }
   1161 
   1162 int main(void) {
   1163   KitUnit u;
   1164   kit_unit_init(&u);
   1165   machinize_rejects_cross_owner_registers(&u);
   1166   mir_verify_rejects_private_temp_escape(&u);
   1167   mir_commit_propagates_instruction_namespace(&u);
   1168   spilled_values_remain_locations(&u);
   1169   spilled_frame_address_remains_recipe(&u);
   1170   call_aux_is_independent(&u);
   1171   structured_aux_arrays_are_independent(&u);
   1172   asm_aux_resolved_metadata_is_independent(&u);
   1173   kit_unit_summary(&u, "location-mir");
   1174   return kit_unit_status(&u);
   1175 }