kit

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

reg_effects_test.c (38511B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 
      5 #include <kit/frontend.h>
      6 
      7 #include "arch/aa64/aa64.h"
      8 #include "arch/arm32/arm32.h"
      9 #include "arch/riscv/rv64.h"
     10 #include "arch/x64/x64.h"
     11 #include "core/pool.h"
     12 #include "lib/kit_unit.h"
     13 #include "obj/obj.h"
     14 #include "opt/opt_internal.h"
     15 
     16 static Operand reg_op(u8 cls, Reg reg) {
     17   Operand op;
     18   memset(&op, 0, sizeof op);
     19   op.kind = OPK_REG;
     20   op.cls = cls;
     21   op.v.reg = reg;
     22   return op;
     23 }
     24 
     25 static Operand indirect_op(Reg base, Reg index) {
     26   Operand op;
     27   memset(&op, 0, sizeof op);
     28   op.kind = OPK_INDIRECT;
     29   op.cls = RC_INT;
     30   op.v.ind.base = base;
     31   op.v.ind.index = index;
     32   return op;
     33 }
     34 
     35 static u32 reg_bit(Reg reg) { return 1u << reg; }
     36 
     37 static void expect_mask_effects_match(KitUnit* u, Func* f, Inst* in,
     38                                       const char* what) {
     39   OptRegEffects detailed;
     40   OptRegEffectMasks masks;
     41   OptHardRegSet detailed_kills;
     42   OptHardRegSet mask_kills;
     43   OptHardRegSet direct_kills;
     44   opt_inst_reg_effects(f, in, &detailed);
     45   opt_inst_reg_effect_masks(f, in, &masks);
     46   opt_reg_effect_kills(&detailed, &detailed_kills);
     47   opt_reg_effect_mask_kills(&masks, &mask_kills);
     48   opt_inst_reg_kills(f, in, &direct_kills);
     49   CU_EXPECT(u,
     50             !memcmp(&detailed.uses, &masks.uses, sizeof masks.uses) &&
     51                 !memcmp(&detailed.defs, &masks.defs, sizeof masks.defs) &&
     52                 !memcmp(&detailed.clobbers, &masks.clobbers,
     53                         sizeof masks.clobbers) &&
     54                 !memcmp(&detailed_kills, &mask_kills, sizeof mask_kills) &&
     55                 !memcmp(&detailed_kills, &direct_kills,
     56                         sizeof direct_kills),
     57             "%s masks-only effects diverged from detailed effects", what);
     58 }
     59 
     60 typedef struct MachinizeRunCtx {
     61   Func* f;
     62   NativeTarget* target;
     63 } MachinizeRunCtx;
     64 
     65 static int invalid_machine_effect(
     66     NativeTarget* target, const NativeMachineOp* op,
     67     u32 mask[NATIVE_REG_CLASS_COUNT]) {
     68   (void)target;
     69   (void)op;
     70   mask[0] = mask[1] = mask[2] = 0;
     71   mask[NATIVE_REG_INT] = reg_bit(31u);
     72   return 1;
     73 }
     74 
     75 static KitStatus run_machinize(KitCompiler* compiler, void* arg) {
     76   MachinizeRunCtx* run = (MachinizeRunCtx*)arg;
     77   (void)compiler;
     78   opt_machinize_native(run->f, run->target);
     79   return KIT_OK;
     80 }
     81 
     82 static Operand typed_reg_op(u8 cls, Reg reg, KitCgTypeId type) {
     83   Operand op = reg_op(cls, reg);
     84   op.type = type;
     85   return op;
     86 }
     87 
     88 static void expect_body_bytes(KitUnit* u, const Section* sec, u32 begin,
     89                               const u8* expected, u32 expected_size,
     90                               const char* what) {
     91   u32 size = sec ? buf_pos(&sec->bytes) : 0u;
     92   u8* bytes = size ? (u8*)malloc(size) : NULL;
     93   CU_EXPECT(u, sec != NULL && begin <= size && size - begin == expected_size,
     94             "%s size mismatch: begin=%u size=%u expected=%u", what, begin,
     95             size, expected_size);
     96   if (!sec || begin > size || size - begin != expected_size ||
     97       (size && !bytes)) {
     98     free(bytes);
     99     return;
    100   }
    101   if (size) buf_flatten(&sec->bytes, bytes);
    102   CU_EXPECT(u, !memcmp(bytes + begin, expected, expected_size),
    103             "%s encoding mismatch", what);
    104   free(bytes);
    105 }
    106 
    107 static void x64_dying_fixed_sources_are_staged(KitUnit* u) {
    108   static const u8 div_expected[] = {
    109       0x49, 0x89, 0xc3, /* mov r11, rax (save divisor) */
    110       0x48, 0x89, 0xd0, /* mov rax, rdx (publish dividend) */
    111       0x48, 0x99,       /* cqo */
    112       0x49, 0xf7, 0xfb, /* idiv r11 */
    113       0x49, 0x89, 0xc0, /* mov r8, rax */
    114   };
    115   static const u8 shift_expected[] = {
    116       0x49, 0x89, 0xf2, /* mov r10, rsi (stage value) */
    117       0x49, 0xd3, 0xe2, /* shl r10, cl */
    118       0x4c, 0x89, 0xd1, /* mov rcx, r10 */
    119   };
    120   static const u8 copy_to_rax_addr_expected[] = {
    121       0x44, 0x0f, 0xb6, 0x12,       /* movzx r10d, byte [rdx] */
    122       0x66, 0x45, 0x0f, 0x6e, 0xf2, /* movd xmm14, r10d */
    123       0x66, 0x45, 0x0f, 0x7e, 0xf2, /* movd r10d, xmm14 */
    124       0x44, 0x88, 0x10,             /* mov byte [rax], r10b */
    125   };
    126   static const u8 copy_from_rax_addr_expected[] = {
    127       0x44, 0x0f, 0xb6, 0x10,       /* movzx r10d, byte [rax] */
    128       0x66, 0x45, 0x0f, 0x6e, 0xf2, /* movd xmm14, r10d */
    129       0x66, 0x45, 0x0f, 0x7e, 0xf2, /* movd r10d, xmm14 */
    130       0x44, 0x88, 0x12,             /* mov byte [rdx], r10b */
    131   };
    132   static const u8 set_rax_addr_expected[] = {
    133       0x41, 0x89, 0xca,                         /* mov r10d, ecx */
    134       0x41, 0x81, 0xe2, 0xff, 0x00, 0x00, 0x00, /* and r10d, 255 */
    135       0x66, 0x45, 0x0f, 0x6e, 0xf2,             /* movd xmm14, r10d */
    136       0x66, 0x45, 0x0f, 0x60, 0xf6,             /* punpcklbw xmm14 */
    137       0x66, 0x45, 0x0f, 0x61, 0xf6,             /* punpcklwd xmm14 */
    138       0x66, 0x45, 0x0f, 0x62, 0xf6,             /* punpckldq xmm14 */
    139       0x66, 0x4d, 0x0f, 0x7e, 0xf2,             /* movq r10, xmm14 */
    140       0x4c, 0x89, 0x10,                         /* mov [rax], r10 */
    141   };
    142   KitCompiler* kc = NULL;
    143   Compiler* c;
    144   ObjBuilder* obj;
    145   MCEmitter* mc;
    146   NativeTarget* target;
    147   const Section* sec;
    148   ObjSecId text;
    149   KitCgTypeId i64_type;
    150   NativeLoc rax, rcx, rdx, rsi, r8;
    151   NativeAddr src_addr, dst_addr;
    152   AggregateAccess byte_copy;
    153   u32 begin;
    154 
    155   CU_EXPECT(u,
    156             kit_unit_compiler_new(
    157                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    158                 &kc) == KIT_OK &&
    159                 kc != NULL,
    160             "compiler allocation failed for x64 fixed-source staging test");
    161   if (!kc) return;
    162   c = (Compiler*)kc;
    163   obj = obj_new(c);
    164   mc = obj ? mc_new(c, obj) : NULL;
    165   target = mc ? x64_native_target_new(c, obj, mc) : NULL;
    166   CU_EXPECT(u, obj && mc && target,
    167             "x64 fixed-source staging backend allocation failed");
    168   if (!obj || !mc || !target) {
    169     kit_compiler_free(kc);
    170     return;
    171   }
    172   text = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".text")),
    173                      SEC_TEXT, SF_EXEC | SF_ALLOC, 16u);
    174   mc_set_section(mc, text);
    175   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    176   rax = native_loc_reg(i64_type, NATIVE_REG_INT, 0u);
    177   rcx = native_loc_reg(i64_type, NATIVE_REG_INT, 1u);
    178   rdx = native_loc_reg(i64_type, NATIVE_REG_INT, 2u);
    179   rsi = native_loc_reg(i64_type, NATIVE_REG_INT, 6u);
    180   r8 = native_loc_reg(i64_type, NATIVE_REG_INT, 8u);
    181 
    182   begin = mc_pos(mc);
    183   /* The divisor dies in RAX while the dividend arrives in RDX. A naive
    184    * dividend-first move destroys the divisor before IDIV can consume it. */
    185   target->binop(target, BO_SDIV, r8, rdx, rax);
    186   sec = obj_section_get(obj, text);
    187   expect_body_bytes(u, sec, begin, div_expected, sizeof div_expected,
    188                     "x64 crossed division operands");
    189 
    190   begin = mc_pos(mc);
    191   /* RCX is both the dying count and the requested result. CL must remain the
    192    * count until the shift finishes, so the result is computed in private R10
    193    * and published afterward. */
    194   target->binop(target, BO_SHL, rcx, rsi, rcx);
    195   sec = obj_section_get(obj, text);
    196   expect_body_bytes(u, sec, begin, shift_expected, sizeof shift_expected,
    197                     "x64 RCX-result variable shift");
    198 
    199   memset(&src_addr, 0, sizeof src_addr);
    200   memset(&dst_addr, 0, sizeof dst_addr);
    201   memset(&byte_copy, 0, sizeof byte_copy);
    202   src_addr.base_kind = NATIVE_ADDR_BASE_REG;
    203   src_addr.base.reg = 2u; /* RDX */
    204   src_addr.base_type = i64_type;
    205   dst_addr.base_kind = NATIVE_ADDR_BASE_REG;
    206   dst_addr.base.reg = 0u; /* RAX */
    207   dst_addr.base_type = i64_type;
    208   byte_copy.size = 1u;
    209 
    210   begin = mc_pos(mc);
    211   target->copy_bytes(target, dst_addr, src_addr, byte_copy);
    212   sec = obj_section_get(obj, text);
    213   expect_body_bytes(u, sec, begin, copy_to_rax_addr_expected,
    214                     sizeof copy_to_rax_addr_expected,
    215                     "x64 copy to RAX-based address");
    216 
    217   begin = mc_pos(mc);
    218   target->copy_bytes(target, src_addr, dst_addr, byte_copy);
    219   sec = obj_section_get(obj, text);
    220   expect_body_bytes(u, sec, begin, copy_from_rax_addr_expected,
    221                     sizeof copy_from_rax_addr_expected,
    222                     "x64 copy from RAX-based address");
    223 
    224   byte_copy.size = 8u;
    225   begin = mc_pos(mc);
    226   target->set_bytes(target, dst_addr, rcx, byte_copy);
    227   sec = obj_section_get(obj, text);
    228   expect_body_bytes(u, sec, begin, set_rax_addr_expected,
    229                     sizeof set_rax_addr_expected,
    230                     "x64 set through RAX-based address");
    231   mc_free(mc);
    232   kit_compiler_free(kc);
    233 }
    234 
    235 static void explicit_and_machine_effects(KitUnit* u) {
    236   Func f;
    237   Inst in;
    238   Operand opnds[3];
    239   OptInstClobberMask machine_clobbers[4];
    240   OptRegEffects effects;
    241 
    242   memset(&f, 0, sizeof f);
    243   memset(&in, 0, sizeof in);
    244   memset(machine_clobbers, 0, sizeof machine_clobbers);
    245   f.opt_rewritten = 1;
    246   f.emit_temp_regs[RC_INT][0] = 8;
    247   f.emit_temp_regs[RC_INT][1] = 9;
    248   f.emit_temp_reg_count[RC_INT] = 2;
    249   f.inst_clobbers = machine_clobbers;
    250   f.inst_clobbers_cap = 4;
    251 
    252   in.op = IR_BINOP;
    253   in.id = 2;
    254   in.opnds = opnds;
    255   in.nopnds = 3;
    256   opnds[0] = reg_op(RC_INT, 1);
    257   opnds[1] = reg_op(RC_INT, 2);
    258   opnds[2] = indirect_op(8, 8);
    259   machine_clobbers[in.id][RC_INT] = reg_bit(5);
    260 
    261   expect_mask_effects_match(u, &f, &in, "explicit/machine");
    262   opt_inst_reg_effects(&f, &in, &effects);
    263   CU_EXPECT(u, effects.uses.cls[RC_INT] ==
    264                    (reg_bit(2) | reg_bit(8)),
    265             "explicit/indirect uses mismatch: %#x", effects.uses.cls[RC_INT]);
    266   CU_EXPECT(u, effects.use_count[RC_INT][8] == 2,
    267             "indirect base+index use count should saturate at two, got %u",
    268             (unsigned)effects.use_count[RC_INT][8]);
    269   CU_EXPECT(u, effects.defs.cls[RC_INT] == reg_bit(1),
    270             "explicit defs mismatch: %#x", effects.defs.cls[RC_INT]);
    271   CU_EXPECT(u, effects.clobbers.cls[RC_INT] == reg_bit(5),
    272             "machine clobbers mismatch: %#x",
    273             effects.clobbers.cls[RC_INT]);
    274 
    275   opnds[2].v.ind.base_kind = OPT_INDIRECT_FRAME;
    276   opnds[2].v.ind.index_kind = OPT_INDIRECT_FRAME_ADDR;
    277   opt_inst_reg_effects(&f, &in, &effects);
    278   CU_EXPECT(u, effects.uses.cls[RC_INT] == reg_bit(2),
    279             "frame-backed indirect components are not hard uses: %#x",
    280             effects.uses.cls[RC_INT]);
    281   CU_EXPECT(u, effects.clobbers.cls[RC_INT] == reg_bit(5),
    282             "frame-backed components do not alter machine clobbers: %#x",
    283             effects.clobbers.cls[RC_INT]);
    284 }
    285 
    286 static void emission_temps_are_not_ir_effects(KitUnit* u) {
    287   Func f;
    288   Inst in;
    289   Operand opnds[2];
    290   OptRegEffects effects;
    291 
    292   memset(&f, 0, sizeof f);
    293   memset(&in, 0, sizeof in);
    294   f.emit_temp_regs[RC_INT][0] = 8;
    295   f.emit_temp_regs[RC_INT][1] = 9;
    296   f.emit_temp_reg_count[RC_INT] = 2;
    297   in.op = IR_COPY;
    298   in.opnds = opnds;
    299   in.nopnds = 2;
    300   opnds[0] = reg_op(RC_INT, 8);
    301   opnds[1] = reg_op(RC_INT, 2);
    302 
    303   opt_inst_reg_effects(&f, &in, &effects);
    304   CU_EXPECT(u, effects.clobbers.cls[RC_INT] == 0,
    305             "emission temps must not clobber virtual HIR: %#x",
    306             effects.clobbers.cls[RC_INT]);
    307 
    308   f.opt_rewritten = 1;
    309   opt_inst_reg_effects(&f, &in, &effects);
    310   CU_EXPECT(u, effects.clobbers.cls[RC_INT] == 0,
    311             "emission temps must remain private in location MIR: %#x",
    312             effects.clobbers.cls[RC_INT]);
    313 }
    314 
    315 static void call_and_asm_effects(KitUnit* u) {
    316   Func f;
    317   Inst call;
    318   IRCallAux call_aux;
    319   CGABIValue arg;
    320   OptRegEffects effects;
    321 
    322   memset(&f, 0, sizeof f);
    323   memset(&call, 0, sizeof call);
    324   memset(&call_aux, 0, sizeof call_aux);
    325   memset(&arg, 0, sizeof arg);
    326   f.opt_caller_saved[RC_INT] = reg_bit(0) | reg_bit(1);
    327   call.op = IR_CALL;
    328   call.extra.aux = &call_aux;
    329   call_aux.desc.callee = reg_op(RC_INT, 10);
    330   call_aux.desc.args = &arg;
    331   call_aux.desc.nargs = 1;
    332   arg.storage = reg_op(RC_INT, 11);
    333   call_aux.desc.ret.storage = reg_op(RC_INT, 0);
    334 
    335   expect_mask_effects_match(u, &f, &call, "call");
    336   opt_inst_reg_effects(&f, &call, &effects);
    337   CU_EXPECT(u, effects.uses.cls[RC_INT] ==
    338                    (reg_bit(10) | reg_bit(11)),
    339             "call uses mismatch: %#x", effects.uses.cls[RC_INT]);
    340   CU_EXPECT(u, effects.defs.cls[RC_INT] == reg_bit(0),
    341             "call explicit results mismatch: %#x", effects.defs.cls[RC_INT]);
    342   CU_EXPECT(u, effects.clobbers.cls[RC_INT] ==
    343                    (reg_bit(0) | reg_bit(1)),
    344             "call ABI clobbers mismatch: %#x", effects.clobbers.cls[RC_INT]);
    345 
    346   call_aux.plan_valid = 1;
    347   call_aux.plan.clobber_mask[RC_INT] = reg_bit(6);
    348   opt_inst_reg_effects(&f, &call, &effects);
    349   CU_EXPECT(u, effects.clobbers.cls[RC_INT] == reg_bit(6),
    350             "planned call clobbers must override ABI fallback: %#x",
    351             effects.clobbers.cls[RC_INT]);
    352 
    353   Inst block;
    354   IRAsmAux asm_aux;
    355   Operand inputs[1];
    356   Operand outputs[1];
    357   memset(&block, 0, sizeof block);
    358   memset(&asm_aux, 0, sizeof asm_aux);
    359   block.op = IR_ASM_BLOCK;
    360   block.extra.aux = &asm_aux;
    361   inputs[0] = reg_op(RC_INT, 2);
    362   outputs[0] = reg_op(RC_INT, 3);
    363   asm_aux.in_ops = inputs;
    364   asm_aux.nin = 1;
    365   asm_aux.out_ops = outputs;
    366   asm_aux.nout = 1;
    367   asm_aux.clobber_mask[RC_INT] = reg_bit(4);
    368 
    369   expect_mask_effects_match(u, &f, &block, "asm");
    370   opt_inst_reg_effects(&f, &block, &effects);
    371   CU_EXPECT(u, effects.uses.cls[RC_INT] == reg_bit(2),
    372             "asm uses mismatch: %#x", effects.uses.cls[RC_INT]);
    373   CU_EXPECT(u, effects.defs.cls[RC_INT] == reg_bit(3),
    374             "asm output defs mismatch: %#x", effects.defs.cls[RC_INT]);
    375   CU_EXPECT(u, effects.clobbers.cls[RC_INT] == reg_bit(4),
    376             "asm clobbers mismatch: %#x", effects.clobbers.cls[RC_INT]);
    377 }
    378 
    379 static void machinize_rebuilds_derived_clobbers(KitUnit* u) {
    380   KitCompiler* kc = NULL;
    381   Compiler* c;
    382   Func* f;
    383   NativeTarget* target;
    384   CGFuncDesc desc;
    385   Inst* in;
    386   InstId id;
    387   u32 entry;
    388 
    389   CU_EXPECT(u,
    390             kit_unit_compiler_new(
    391                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    392                 &kc) == KIT_OK &&
    393                 kc != NULL,
    394             "compiler allocation failed for machinize rebuild test");
    395   if (!kc) return;
    396   c = (Compiler*)kc;
    397   memset(&desc, 0, sizeof desc);
    398   desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    399   f = ir_func_new(c, &desc);
    400   entry = ir_block_new(f);
    401   f->entry = entry;
    402   ir_note_emit(f, entry);
    403   in = ir_emit(f, entry, IR_BINOP);
    404   in->extra.imm = BO_SDIV;
    405   id = in->id;
    406   target = x64_native_target_new(c, NULL, NULL);
    407 
    408   opt_machinize_native(f, target);
    409   CU_EXPECT(u,
    410             f->inst_clobbers && id < f->inst_clobbers_cap &&
    411                 f->inst_clobbers[id][RC_INT] != 0,
    412             "first machinization did not derive the division clobber set");
    413 
    414   /* Change the semantic instruction and rerun the derivation. The old table
    415    * must not survive merely because the new instruction reports no fixed
    416    * machine effect. */
    417   f->blocks[entry].insts[0].op = IR_COPY;
    418   opt_machinize_native(f, target);
    419   CU_EXPECT(u, f->inst_clobbers == NULL && f->inst_clobbers_cap == 0,
    420             "machinization retained a stale per-instruction clobber table");
    421 
    422   f->blocks[entry].insts[0].op = IR_BINOP;
    423   f->blocks[entry].insts[0].extra.imm = BO_SDIV;
    424   opt_machinize_native(f, target);
    425   CU_EXPECT(u,
    426             f->inst_clobbers && id < f->inst_clobbers_cap &&
    427                 f->inst_clobbers[id][RC_INT] != 0,
    428             "machinization did not rebuild a cleared clobber table");
    429   kit_compiler_free(kc);
    430 }
    431 
    432 static void post_machinize_loop_imm_refreshes_shift_clobber(KitUnit* u) {
    433   KitCompiler* kc = NULL;
    434   Compiler* c;
    435   Func* f;
    436   NativeTarget* target;
    437   CGFuncDesc desc;
    438   KitCgTypeId i64_type;
    439   PReg dst;
    440   PReg lhs;
    441   Inst* shift;
    442   Inst* lowered_shift = NULL;
    443   InstId shift_id;
    444   OptRegEffects effects;
    445   u32 entry;
    446   u32 initial_mask;
    447   u32 stale_mask;
    448 
    449   CU_EXPECT(u,
    450             kit_unit_compiler_new(
    451                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    452                 &kc) == KIT_OK &&
    453                 kc != NULL,
    454             "compiler allocation failed for post-machinize effect refresh");
    455   if (!kc) return;
    456   c = (Compiler*)kc;
    457   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    458   memset(&desc, 0, sizeof desc);
    459   desc.fn_type = i64_type;
    460   desc.result_type = i64_type;
    461   f = ir_func_new(c, &desc);
    462   entry = ir_block_new(f);
    463   f->entry = entry;
    464   ir_note_emit(f, entry);
    465   f->blocks[entry].loop_depth = 1;
    466 
    467   dst = ir_alloc_preg(f, i64_type, RC_INT);
    468   lhs = ir_alloc_preg(f, i64_type, RC_INT);
    469   shift = ir_emit(f, entry, IR_BINOP);
    470   shift->type = i64_type;
    471   shift->def = dst;
    472   shift->extra.imm = BO_SHL;
    473   shift->nopnds = 3;
    474   shift->opnds = arena_array(f->arena, Operand, shift->nopnds);
    475   shift->opnds[0] = typed_reg_op(RC_INT, dst, i64_type);
    476   shift->opnds[1] = typed_reg_op(RC_INT, lhs, i64_type);
    477   memset(&shift->opnds[2], 0, sizeof shift->opnds[2]);
    478   shift->opnds[2].kind = OPK_IMM;
    479   shift->opnds[2].cls = RC_INT;
    480   shift->opnds[2].type = i64_type;
    481   shift->opnds[2].v.imm = 64;
    482   shift_id = shift->id;
    483 
    484   target = x64_native_target_new(c, NULL, NULL);
    485   opt_machinize_native(f, target);
    486   initial_mask = f->inst_clobbers && shift_id < f->inst_clobbers_cap
    487                      ? f->inst_clobbers[shift_id][RC_INT]
    488                      : 0u;
    489   CU_EXPECT(u, initial_mask == 0,
    490             "immediate shift unexpectedly acquired an RCX clobber: %#x",
    491             initial_mask);
    492 
    493   /* This is the production pipeline order: machinize first, then lift a
    494    * non-foldable loop immediate into a register. On x64 that changes the
    495    * encoding to the CL form, so the final HIR boundary must rederive RCX. */
    496   opt_lower_loop_imm_operands(f, target);
    497   for (u32 i = 0; i < f->blocks[entry].ninsts; ++i) {
    498     Inst* in = &f->blocks[entry].insts[i];
    499     if (in->id == shift_id) lowered_shift = in;
    500   }
    501   CU_EXPECT(u,
    502             lowered_shift && lowered_shift->nopnds == 3 &&
    503                 lowered_shift->opnds[2].kind == OPK_REG,
    504             "loop immediate was not lowered to a variable shift");
    505 
    506   stale_mask = f->inst_clobbers && shift_id < f->inst_clobbers_cap
    507                    ? f->inst_clobbers[shift_id][RC_INT]
    508                    : 0u;
    509   CU_EXPECT(u, stale_mask == 0,
    510             "modeled pre-refresh table was not the stale immediate-shift form: "
    511             "%#x",
    512             stale_mask);
    513 
    514   opt_refresh_machine_clobbers(f, target);
    515   CU_EXPECT(u,
    516             f->inst_clobbers && shift_id < f->inst_clobbers_cap &&
    517                 f->inst_clobbers[shift_id][RC_INT] == reg_bit(1u),
    518             "post-lowering x64 shift clobbers %#x, expected RCX",
    519             f->inst_clobbers && shift_id < f->inst_clobbers_cap
    520                 ? f->inst_clobbers[shift_id][RC_INT]
    521                 : 0u);
    522   if (lowered_shift) {
    523     opt_inst_reg_effects(f, lowered_shift, &effects);
    524     CU_EXPECT(u, effects.clobbers.cls[RC_INT] == reg_bit(1u),
    525               "canonical variable-shift effects omit RCX: %#x",
    526               effects.clobbers.cls[RC_INT]);
    527   }
    528   kit_compiler_free(kc);
    529 }
    530 
    531 static void machinize_rejects_unknown_effect_registers(KitUnit* u) {
    532   KitCompiler* kc = NULL;
    533   Compiler* c;
    534   Func* f;
    535   NativeTarget* target;
    536   CGFuncDesc desc;
    537   MachinizeRunCtx run;
    538   KitStatus status;
    539   Inst* in;
    540   u32 entry;
    541 
    542   CU_EXPECT(u,
    543             kit_unit_compiler_new(
    544                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    545                 &kc) == KIT_OK &&
    546                 kc != NULL,
    547             "compiler allocation failed for invalid machine-effect test");
    548   if (!kc) return;
    549   c = (Compiler*)kc;
    550   memset(&desc, 0, sizeof desc);
    551   desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    552   f = ir_func_new(c, &desc);
    553   entry = ir_block_new(f);
    554   f->entry = entry;
    555   ir_note_emit(f, entry);
    556   in = ir_emit(f, entry, IR_BINOP);
    557   in->extra.imm = BO_IADD;
    558   target = x64_native_target_new(c, NULL, NULL);
    559   target->machine_op_clobbers = invalid_machine_effect;
    560   run.f = f;
    561   run.target = target;
    562 
    563   u->last_diag[0] = '\0';
    564   ++u->suppress_fatal;
    565   status = kit_frontend_run(kc, run_machinize, &run);
    566   --u->suppress_fatal;
    567   CU_EXPECT(u, status == KIT_ERR,
    568             "unknown target machine-effect register was accepted");
    569   CU_EXPECT(u,
    570             strstr(u->last_diag,
    571                    "machine clobber mask names unknown physical register") !=
    572                 NULL,
    573             "unexpected invalid machine-effect diagnostic: %s", u->last_diag);
    574   kit_compiler_free(kc);
    575 }
    576 
    577 static void regalloc_rebuilds_derived_constraints_and_hints(KitUnit* u) {
    578   KitCompiler* kc = NULL;
    579   Compiler* c;
    580   Func* f;
    581   NativeTarget* target;
    582   CGFuncDesc desc;
    583   IRRetAux* ret_aux;
    584   Inst* keep_def;
    585   Inst* lhs_def;
    586   Inst* rhs_def;
    587   Inst* div;
    588   Inst* keep_use;
    589   Inst* ret;
    590   PReg keep;
    591   PReg lhs;
    592   PReg rhs;
    593   PReg quotient;
    594   PReg keep_sink;
    595   KitCgTypeId i64_type;
    596   u32 entry;
    597   u32 first_frequency;
    598   const u32 x64_div_clobbers = reg_bit(0u) | reg_bit(2u); /* rax, rdx */
    599 
    600   CU_EXPECT(u,
    601             kit_unit_compiler_new(
    602                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    603                 &kc) == KIT_OK &&
    604                 kc != NULL,
    605             "compiler allocation failed for regalloc rebuild test");
    606   if (!kc) return;
    607   c = (Compiler*)kc;
    608   i64_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    609   memset(&desc, 0, sizeof desc);
    610   desc.fn_type = i64_type;
    611   desc.result_type = i64_type;
    612   f = ir_func_new(c, &desc);
    613   entry = ir_block_new(f);
    614   f->entry = entry;
    615   ir_note_emit(f, entry);
    616 
    617   keep = ir_alloc_preg(f, i64_type, RC_INT);
    618   lhs = ir_alloc_preg(f, i64_type, RC_INT);
    619   rhs = ir_alloc_preg(f, i64_type, RC_INT);
    620   quotient = ir_alloc_preg(f, i64_type, RC_INT);
    621   keep_sink = ir_alloc_preg(f, i64_type, RC_INT);
    622 
    623   keep_def = ir_emit(f, entry, IR_LOAD_IMM);
    624   keep_def->type = i64_type;
    625   keep_def->def = keep;
    626   keep_def->nopnds = 1;
    627   keep_def->opnds = arena_array(f->arena, Operand, 1);
    628   keep_def->opnds[0] = typed_reg_op(RC_INT, keep, i64_type);
    629   keep_def->extra.imm = 7;
    630 
    631   lhs_def = ir_emit(f, entry, IR_LOAD_IMM);
    632   lhs_def->type = i64_type;
    633   lhs_def->def = lhs;
    634   lhs_def->nopnds = 1;
    635   lhs_def->opnds = arena_array(f->arena, Operand, 1);
    636   lhs_def->opnds[0] = typed_reg_op(RC_INT, lhs, i64_type);
    637   lhs_def->extra.imm = 84;
    638 
    639   rhs_def = ir_emit(f, entry, IR_LOAD_IMM);
    640   rhs_def->type = i64_type;
    641   rhs_def->def = rhs;
    642   rhs_def->nopnds = 1;
    643   rhs_def->opnds = arena_array(f->arena, Operand, 1);
    644   rhs_def->opnds[0] = typed_reg_op(RC_INT, rhs, i64_type);
    645   rhs_def->extra.imm = 2;
    646 
    647   div = ir_emit(f, entry, IR_BINOP);
    648   div->type = i64_type;
    649   div->def = quotient;
    650   div->extra.imm = BO_SDIV;
    651   div->nopnds = 3;
    652   div->opnds = arena_array(f->arena, Operand, 3);
    653   div->opnds[0] = typed_reg_op(RC_INT, quotient, i64_type);
    654   div->opnds[1] = typed_reg_op(RC_INT, lhs, i64_type);
    655   div->opnds[2] = typed_reg_op(RC_INT, rhs, i64_type);
    656 
    657   keep_use = ir_emit(f, entry, IR_COPY);
    658   keep_use->type = i64_type;
    659   keep_use->def = keep_sink;
    660   keep_use->nopnds = 2;
    661   keep_use->opnds = arena_array(f->arena, Operand, 2);
    662   keep_use->opnds[0] = typed_reg_op(RC_INT, keep_sink, i64_type);
    663   keep_use->opnds[1] = typed_reg_op(RC_INT, keep, i64_type);
    664 
    665   ret = ir_emit(f, entry, IR_RET);
    666   ret_aux = arena_zarray(f->arena, IRRetAux, 1);
    667   ret->extra.aux = ret_aux;
    668   ret_aux->present = 1;
    669   ret_aux->val.storage = typed_reg_op(RC_INT, quotient, i64_type);
    670 
    671   target = x64_native_target_new(c, NULL, NULL);
    672   opt_build_cfg(f);
    673   f->blocks[entry].frequency = 100;
    674   opt_machinize_native(f, target);
    675   opt_regalloc_locations(f, NULL);
    676   first_frequency = f->preg_info[keep].frequency;
    677   CU_EXPECT(u,
    678             (f->preg_info[keep].forbidden_hard_regs & x64_div_clobbers) ==
    679                 x64_div_clobbers,
    680             "value live across div did not acquire rax/rdx forbids: %#x",
    681             f->preg_info[keep].forbidden_hard_regs);
    682   CU_EXPECT(u, f->preg_info[quotient].preferred_hard_reg == 0,
    683             "returned quotient did not acquire the rax ABI hint: %d",
    684             (int)f->preg_info[quotient].preferred_hard_reg);
    685 
    686   /* Both fields above are derived from this particular instruction stream.
    687    * Remove their sources and rerun the supported allocation entry point. A
    688    * previous allocation must not become an implicit input to the next one. */
    689   div->extra.imm = BO_IADD;
    690   ret_aux->present = 0;
    691   f->blocks[entry].frequency = 1;
    692   opt_machinize_native(f, target);
    693   opt_regalloc_locations(f, NULL);
    694   CU_EXPECT(u, f->preg_info[keep].forbidden_hard_regs == 0,
    695             "regalloc retained stale fixed-register forbids: %#x",
    696             f->preg_info[keep].forbidden_hard_regs);
    697   CU_EXPECT(u, f->preg_info[quotient].preferred_hard_reg < 0,
    698             "regalloc retained a stale ABI preference: %d",
    699             (int)f->preg_info[quotient].preferred_hard_reg);
    700   CU_EXPECT(u, f->preg_info[keep].frequency < first_frequency,
    701             "regalloc retained stale range frequency %u after recomputing %u",
    702             first_frequency, f->preg_info[keep].frequency);
    703 
    704   kit_compiler_free(kc);
    705 }
    706 
    707 static void rv64_syscall_fixed_effects(KitUnit* u) {
    708   KitCompiler* kc = NULL;
    709   Compiler* c;
    710   Func* f;
    711   NativeTarget* target;
    712   CGFuncDesc desc;
    713   IRIntrinAux aux;
    714   Inst* in;
    715   InstId id;
    716   u32 entry;
    717   const u32 syscall_regs = (1u << 10u) | (1u << 11u) | (1u << 12u) |
    718                            (1u << 13u) | (1u << 14u) | (1u << 15u) |
    719                            (1u << 17u);
    720 
    721   CU_EXPECT(u,
    722             kit_unit_compiler_new(
    723                 u, kit_unit_target(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF),
    724                 &kc) == KIT_OK &&
    725                 kc != NULL,
    726             "compiler allocation failed for rv64 syscall effect test");
    727   if (!kc) return;
    728   c = (Compiler*)kc;
    729   memset(&desc, 0, sizeof desc);
    730   memset(&aux, 0, sizeof aux);
    731   desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    732   f = ir_func_new(c, &desc);
    733   entry = ir_block_new(f);
    734   f->entry = entry;
    735   ir_note_emit(f, entry);
    736   in = ir_emit(f, entry, IR_INTRINSIC);
    737   in->extra.aux = &aux;
    738   aux.kind = INTRIN_SYSCALL;
    739   id = in->id;
    740   target = rv64_native_target_new(c, NULL, NULL);
    741 
    742   opt_machinize_native(f, target);
    743   CU_EXPECT(u,
    744             f->inst_clobbers && id < f->inst_clobbers_cap &&
    745                 f->inst_clobbers[id][RC_INT] == syscall_regs,
    746             "rv64 syscall fixed effects mismatch: got %#x, expected %#x",
    747             f->inst_clobbers && id < f->inst_clobbers_cap
    748                 ? f->inst_clobbers[id][RC_INT]
    749                 : 0u,
    750             syscall_regs);
    751   kit_compiler_free(kc);
    752 }
    753 
    754 static void aa64_syscall_fixed_effects(KitUnit* u) {
    755   KitCompiler* kc = NULL;
    756   Compiler* c;
    757   Func* f;
    758   NativeTarget* target;
    759   CGFuncDesc desc;
    760   IRIntrinAux aux;
    761   Inst* in;
    762   InstId id;
    763   u32 entry;
    764   const u32 syscall_regs = (1u << 0u) | (1u << 1u) | (1u << 2u) |
    765                            (1u << 3u) | (1u << 4u) | (1u << 5u) |
    766                            (1u << 8u);
    767 
    768   CU_EXPECT(u,
    769             kit_unit_compiler_new(
    770                 u, kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    771                 &kc) == KIT_OK &&
    772                 kc != NULL,
    773             "compiler allocation failed for aa64 syscall effect test");
    774   if (!kc) return;
    775   c = (Compiler*)kc;
    776   memset(&desc, 0, sizeof desc);
    777   memset(&aux, 0, sizeof aux);
    778   desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    779   f = ir_func_new(c, &desc);
    780   entry = ir_block_new(f);
    781   f->entry = entry;
    782   ir_note_emit(f, entry);
    783   in = ir_emit(f, entry, IR_INTRINSIC);
    784   in->extra.aux = &aux;
    785   aux.kind = INTRIN_SYSCALL;
    786   id = in->id;
    787   target = aa64_native_target_new(c, NULL, NULL);
    788 
    789   opt_machinize_native(f, target);
    790   CU_EXPECT(u,
    791             f->inst_clobbers && id < f->inst_clobbers_cap &&
    792                 f->inst_clobbers[id][RC_INT] == syscall_regs,
    793             "aa64 syscall fixed effects mismatch: got %#x, expected %#x",
    794             f->inst_clobbers && id < f->inst_clobbers_cap
    795                 ? f->inst_clobbers[id][RC_INT]
    796                 : 0u,
    797             syscall_regs);
    798   kit_compiler_free(kc);
    799 }
    800 
    801 static void x64_tls_model_fixed_effects(KitUnit* u) {
    802   static const struct {
    803     KitOSKind os;
    804     KitObjFmt obj;
    805     u32 expected;
    806     const char* name;
    807   } cases[] = {
    808       {KIT_OS_MACOS, KIT_OBJ_MACHO, (1u << 0u) | (1u << 7u),
    809        "Mach-O descriptor"},
    810       {KIT_OS_LINUX, KIT_OBJ_ELF, 0u, "ELF local-exec"},
    811   };
    812 
    813   for (u32 i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
    814     KitCompiler* kc = NULL;
    815     Compiler* c;
    816     Func* f;
    817     NativeTarget* target;
    818     CGFuncDesc desc;
    819     Inst* in;
    820     InstId id;
    821     u32 entry;
    822     u32 got;
    823 
    824     CU_EXPECT(u,
    825               kit_unit_compiler_new(
    826                   u,
    827                   kit_unit_target(KIT_ARCH_X86_64, cases[i].os, cases[i].obj),
    828                   &kc) == KIT_OK &&
    829                   kc != NULL,
    830               "compiler allocation failed for x64 %s TLS effect test",
    831               cases[i].name);
    832     if (!kc) continue;
    833     c = (Compiler*)kc;
    834     memset(&desc, 0, sizeof desc);
    835     desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    836     f = ir_func_new(c, &desc);
    837     entry = ir_block_new(f);
    838     f->entry = entry;
    839     ir_note_emit(f, entry);
    840     in = ir_emit(f, entry, IR_TLS_ADDR_OF);
    841     id = in->id;
    842     target = x64_native_target_new(c, NULL, NULL);
    843 
    844     opt_machinize_native(f, target);
    845     got = f->inst_clobbers && id < f->inst_clobbers_cap
    846               ? f->inst_clobbers[id][RC_INT]
    847               : 0u;
    848     CU_EXPECT(u, got == cases[i].expected,
    849               "x64 %s TLS fixed effects mismatch: got %#x, expected %#x",
    850               cases[i].name, got, cases[i].expected);
    851     if (!cases[i].expected)
    852       CU_EXPECT(u, !f->inst_clobbers && f->inst_clobbers_cap == 0,
    853                 "x64 %s TLS unexpectedly allocated a clobber table",
    854                 cases[i].name);
    855     kit_compiler_free(kc);
    856   }
    857 }
    858 
    859 static void aa64_tls_model_fixed_effects(KitUnit* u) {
    860   static const struct {
    861     KitOSKind os;
    862     KitObjFmt obj;
    863     u32 expected;
    864     const char* name;
    865   } cases[] = {
    866       {KIT_OS_MACOS, KIT_OBJ_MACHO, 1u << 0u, "Mach-O descriptor"},
    867       {KIT_OS_LINUX, KIT_OBJ_ELF, 0u, "ELF local-exec"},
    868   };
    869 
    870   for (u32 i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
    871     KitCompiler* kc = NULL;
    872     Compiler* c;
    873     Func* f;
    874     NativeTarget* target;
    875     CGFuncDesc desc;
    876     Inst* in;
    877     InstId id;
    878     u32 entry;
    879     u32 got;
    880 
    881     CU_EXPECT(u,
    882               kit_unit_compiler_new(
    883                   u,
    884                   kit_unit_target(KIT_ARCH_ARM_64, cases[i].os, cases[i].obj),
    885                   &kc) == KIT_OK &&
    886                   kc != NULL,
    887               "compiler allocation failed for aa64 %s TLS effect test",
    888               cases[i].name);
    889     if (!kc) continue;
    890     c = (Compiler*)kc;
    891     memset(&desc, 0, sizeof desc);
    892     desc.fn_type = kit_cg_type_builtin(kc, KIT_CG_BUILTIN_I64);
    893     f = ir_func_new(c, &desc);
    894     entry = ir_block_new(f);
    895     f->entry = entry;
    896     ir_note_emit(f, entry);
    897     in = ir_emit(f, entry, IR_TLS_ADDR_OF);
    898     id = in->id;
    899     target = aa64_native_target_new(c, NULL, NULL);
    900 
    901     opt_machinize_native(f, target);
    902     got = f->inst_clobbers && id < f->inst_clobbers_cap
    903               ? f->inst_clobbers[id][RC_INT]
    904               : 0u;
    905     CU_EXPECT(u, got == cases[i].expected,
    906               "aa64 %s TLS fixed effects mismatch: got %#x, expected %#x",
    907               cases[i].name, got, cases[i].expected);
    908     if (!cases[i].expected)
    909       CU_EXPECT(u, !f->inst_clobbers && f->inst_clobbers_cap == 0,
    910                 "aa64 %s TLS unexpectedly allocated a clobber table",
    911                 cases[i].name);
    912     kit_compiler_free(kc);
    913   }
    914 }
    915 
    916 static void x64_machine_effect_families(KitUnit* u) {
    917   static const struct {
    918     NativeMachineOp op;
    919     u32 expected;
    920     const char* name;
    921   } cases[] = {
    922       {{.kind = NATIVE_MOP_BINOP, .binop = BO_SDIV},
    923        (1u << 0u) | (1u << 2u), "signed division"},
    924       {{.kind = NATIVE_MOP_BINOP, .binop = BO_SHL, .second_is_reg = 1u},
    925        1u << 1u, "variable shift"},
    926       {{.kind = NATIVE_MOP_BINOP, .binop = BO_SHL}, 0u, "immediate shift"},
    927       {{.kind = NATIVE_MOP_BITFIELD_LOAD}, 0u, "bitfield load"},
    928       {{.kind = NATIVE_MOP_BITFIELD_STORE},
    929        (1u << 0u) | (1u << 1u) | (1u << 2u), "bitfield store"},
    930       {{.kind = NATIVE_MOP_ATOMIC_CAS},
    931        (1u << 0u) | (1u << 1u) | (1u << 2u), "atomic compare-exchange"},
    932       {{.kind = NATIVE_MOP_ATOMIC_RMW},
    933        (1u << 0u) | (1u << 1u) | (1u << 2u), "atomic read-modify-write"},
    934       {{.kind = NATIVE_MOP_VA_START}, 0u, "va_start"},
    935       {{.kind = NATIVE_MOP_VA_ARG}, 0u, "va_arg"},
    936       {{.kind = NATIVE_MOP_INTRINSIC, .intrin = INTRIN_UMUL_OVERFLOW},
    937        (1u << 0u) | (1u << 2u), "unsigned multiply overflow"},
    938       {{.kind = NATIVE_MOP_INTRINSIC, .intrin = INTRIN_READCYCLECOUNTER},
    939        (1u << 0u) | (1u << 2u), "cycle counter"},
    940       {{.kind = NATIVE_MOP_INTRINSIC, .intrin = INTRIN_SYSCALL},
    941        (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 6u) | (1u << 7u) |
    942            (1u << 8u) | (1u << 9u),
    943        "syscall"},
    944       {{.kind = NATIVE_MOP_TLS_ADDR}, 0u, "ELF local-exec TLS"},
    945   };
    946   KitCompiler* kc = NULL;
    947   NativeTarget* target;
    948 
    949   CU_EXPECT(u,
    950             kit_unit_compiler_new(
    951                 u, kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF),
    952                 &kc) == KIT_OK &&
    953                 kc != NULL,
    954             "compiler allocation failed for x64 machine-effect family test");
    955   if (!kc) return;
    956   target = x64_native_target_new((Compiler*)kc, NULL, NULL);
    957   for (u32 i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
    958     NativeRegMaskSet mask = {0};
    959     int any = target->machine_op_clobbers(target, &cases[i].op, mask);
    960     CU_EXPECT(u,
    961               mask[NATIVE_REG_INT] == cases[i].expected &&
    962                   any == (cases[i].expected != 0u),
    963               "x64 %s fixed effects mismatch: got %#x, expected %#x",
    964               cases[i].name, mask[NATIVE_REG_INT], cases[i].expected);
    965   }
    966   kit_compiler_free(kc);
    967 }
    968 
    969 typedef NativeTarget* (*RoleTargetNew)(Compiler*, ObjBuilder*, MCEmitter*);
    970 
    971 typedef struct RoleMaskCase {
    972   KitArchKind arch;
    973   KitOSKind os;
    974   KitObjFmt obj;
    975   RoleTargetNew target_new;
    976   const char* name;
    977   NativeRegMaskSet caller;
    978   NativeRegMaskSet callee;
    979   NativeRegMaskSet arg;
    980   NativeRegMaskSet ret;
    981   NativeRegMaskSet reserved;
    982 } RoleMaskCase;
    983 
    984 static void register_role_flags_are_authoritative(KitUnit* u) {
    985   static const RoleMaskCase cases[] = {
    986       {.arch = KIT_ARCH_X86_64,
    987        .os = KIT_OS_LINUX,
    988        .obj = KIT_OBJ_ELF,
    989        .target_new = x64_native_target_new,
    990        .name = "x64 SysV",
    991        .caller = {0x00000fc7u, 0x0000ffffu, 0u},
    992        .callee = {0x0000f008u, 0u, 0u},
    993        .arg = {0x000003c6u, 0x000000ffu, 0u},
    994        .ret = {0x00000005u, 0x00000003u, 0u},
    995        .reserved = {0x00001f39u, 0x0000f030u, 0u}},
    996       {.arch = KIT_ARCH_X86_64,
    997        .os = KIT_OS_WINDOWS,
    998        .obj = KIT_OBJ_COFF,
    999        .target_new = x64_native_target_new,
   1000        .name = "x64 Win64",
   1001        .caller = {0x00000f07u, 0x0000003fu, 0u},
   1002        .callee = {0x0000f0c8u, 0x0000ffc0u, 0u},
   1003        /* Argument/return flags describe the shared static x64 register table;
   1004         * caller/callee preservation is the only live-ABI override. */
   1005        .arg = {0x000003c6u, 0x000000ffu, 0u},
   1006        .ret = {0x00000005u, 0x00000003u, 0u},
   1007        .reserved = {0x00001f39u, 0x0000f030u, 0u}},
   1008       {.arch = KIT_ARCH_ARM_64,
   1009        .os = KIT_OS_LINUX,
   1010        .obj = KIT_OBJ_ELF,
   1011        .target_new = aa64_native_target_new,
   1012        .name = "aa64",
   1013        .caller = {0x0007ffffu, 0xffff00ffu, 0u},
   1014        .callee = {0x1ff80000u, 0x0000ff00u, 0u},
   1015        .arg = {0x000000ffu, 0x000000ffu, 0u},
   1016        .ret = {0x00000003u, 0x0000000fu, 0u},
   1017        .reserved = {0xe0070e00u, 0x00300000u, 0u}},
   1018       {.arch = KIT_ARCH_RV64,
   1019        .os = KIT_OS_LINUX,
   1020        .obj = KIT_OBJ_ELF,
   1021        .target_new = rv64_native_target_new,
   1022        .name = "rv64",
   1023        .caller = {0xf003fce0u, 0xf003fcffu, 0u},
   1024        .callee = {0x0ffc0300u, 0x0ffc0300u, 0u},
   1025        .arg = {0x0003fc00u, 0x0003fc00u, 0u},
   1026        .ret = {0x00000c00u, 0x00000c00u, 0u},
   1027        .reserved = {0xf00001ffu, 0x0000000fu, 0u}},
   1028       {.arch = KIT_ARCH_ARM_32,
   1029        .os = KIT_OS_FREESTANDING,
   1030        .obj = KIT_OBJ_ELF,
   1031        .target_new = arm32_native_target_new,
   1032        .name = "arm32",
   1033        .caller = {0x0000100fu, 0u, 0u},
   1034        .callee = {0x00000ff0u, 0u, 0u},
   1035        .arg = {0x0000000fu, 0u, 0u},
   1036        .ret = {0x00000003u, 0u, 0u},
   1037        .reserved = {0x0000f080u, 0u, 0u}},
   1038   };
   1039 
   1040   for (u32 i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
   1041     const RoleMaskCase* tc = &cases[i];
   1042     KitCompiler* kc = NULL;
   1043     NativeTarget* target;
   1044     CU_EXPECT(u,
   1045               kit_unit_compiler_new(
   1046                   u, kit_unit_target(tc->arch, tc->os, tc->obj), &kc) ==
   1047                       KIT_OK &&
   1048                   kc != NULL,
   1049               "compiler allocation failed for %s register roles", tc->name);
   1050     if (!kc) continue;
   1051     target = tc->target_new((Compiler*)kc, NULL, NULL);
   1052     CU_EXPECT(u, target != NULL, "%s target allocation failed", tc->name);
   1053     if (target) {
   1054       native_reg_info_validate((Compiler*)kc, target->regs);
   1055       for (u32 cls = 0; cls < NATIVE_REG_CLASS_COUNT; ++cls) {
   1056         CU_EXPECT(u,
   1057                   native_target_caller_saved_mask(
   1058                       target, (NativeAllocClass)cls) == tc->caller[cls] &&
   1059                       native_target_callee_saved_mask(
   1060                           target, (NativeAllocClass)cls) == tc->callee[cls] &&
   1061                       native_target_arg_mask(target, (NativeAllocClass)cls) ==
   1062                           tc->arg[cls] &&
   1063                       native_target_ret_mask(target, (NativeAllocClass)cls) ==
   1064                           tc->ret[cls] &&
   1065                       native_target_reserved_mask(
   1066                           target, (NativeAllocClass)cls) == tc->reserved[cls],
   1067                   "%s class %u derived register roles mismatch", tc->name,
   1068                   cls);
   1069       }
   1070     }
   1071     kit_compiler_free(kc);
   1072   }
   1073 }
   1074 
   1075 int main(void) {
   1076   KitUnit u;
   1077   kit_unit_init(&u);
   1078   explicit_and_machine_effects(&u);
   1079   emission_temps_are_not_ir_effects(&u);
   1080   call_and_asm_effects(&u);
   1081   machinize_rebuilds_derived_clobbers(&u);
   1082   post_machinize_loop_imm_refreshes_shift_clobber(&u);
   1083   machinize_rejects_unknown_effect_registers(&u);
   1084   regalloc_rebuilds_derived_constraints_and_hints(&u);
   1085   rv64_syscall_fixed_effects(&u);
   1086   aa64_syscall_fixed_effects(&u);
   1087   x64_tls_model_fixed_effects(&u);
   1088   aa64_tls_model_fixed_effects(&u);
   1089   x64_machine_effect_families(&u);
   1090   register_role_flags_are_authoritative(&u);
   1091   x64_dying_fixed_sources_are_staged(&u);
   1092   fprintf(stderr, "reg-effects: %d checks, %d failures\n", u.checks, u.fails);
   1093   return kit_unit_status(&u);
   1094 }