kit

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

strength_reduce_test.c (9658B)


      1 /* strength_reduce_test — asserts the -O0 semantic peephole (src/cg/fold.c)
      2  * turns multiply / unsigned-divide / unsigned-remainder by a power-of-two
      3  * constant into a shift / and, AND that the shift/and is emitted in its
      4  * immediate-operand form (no scratch register spent materializing the count).
      5  *
      6  * Each case builds a tiny
      7  *     i64 f(i64 x) { return x <op> <imm>; }
      8  * through the public CG API, emits an ELF object, and disassembles its .text.
      9  *
     10  * Two layers of assertion:
     11  *   1. The multiply/divide/remainder instruction disappears for power-of-two
     12  *      operands and survives for non-power-of-two (and for signed division,
     13  *      which is deliberately left to -O1+). Checked on mnemonic substrings
     14  *      ("mul"/"div"/"rem"), robust to per-arch aliasing.
     15  *   2. The resulting shift/and carries an immediate operand (x64 "$imm",
     16  *      aa64 "#imm", rv64 i-suffixed mnemonic) rather than a materialized
     17  *      register — the NativeDirectTarget imm_legal pass-through (Piece A) plus
     18  *      the aa64 immediate shift/bitmask encodings (Piece B).
     19  *
     20  * Run by: make test-cg-api
     21  */
     22 
     23 #include <kit/cg.h>
     24 #include <kit/disasm.h>
     25 #include <kit/frontend.h>
     26 #include <kit/object.h>
     27 #include <stdint.h>
     28 #include <stdio.h>
     29 #include <string.h>
     30 
     31 #include "lib/kit_unit.h"
     32 
     33 static KitUnit g_u;
     34 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     35 
     36 typedef struct EmitCtx {
     37   KitCgIntBinOp op;
     38   int64_t imm;
     39   KitObjBuilder* ob;
     40 } EmitCtx;
     41 
     42 /* Frontend callback: build `i64 f(i64 x) { return x op imm; }` into ctx->ob. */
     43 static KitStatus emit_binop_fn(KitCompiler* c, void* user) {
     44   EmitCtx* ctx = (EmitCtx*)user;
     45   KitCg* cg = NULL;
     46   KitCgBuiltinTypes bi;
     47   KitCgTypeId i64_ty;
     48   KitCgFuncParam param_desc;
     49   KitCgFuncResult sig_result;
     50   KitCgFuncSig sig;
     51   KitCgDecl decl;
     52   KitCgSym sym;
     53   KitCgLocalAttrs attrs;
     54   KitCgLocal param;
     55   KitCodeOptions opts;
     56 
     57   if (kit_obj_builder_new(c, &ctx->ob) != KIT_OK) return KIT_ERR;
     58   if (kit_cg_new(c, &cg) != KIT_OK || !cg) return KIT_ERR;
     59   memset(&opts, 0, sizeof opts);
     60   opts.opt_level = 0; /* the -O0 peephole is the subject under test */
     61   if (kit_cg_begin(cg, ctx->ob, &opts) != KIT_OK) return KIT_ERR;
     62 
     63   bi = kit_cg_builtin_types(c);
     64   i64_ty = bi.id[KIT_CG_BUILTIN_I64];
     65 
     66   memset(&param_desc, 0, sizeof param_desc);
     67   param_desc.type = i64_ty;
     68   memset(&sig_result, 0, sizeof sig_result);
     69   sig_result.type = i64_ty;
     70   memset(&sig, 0, sizeof sig);
     71   sig.params = &param_desc;
     72   sig.nparams = 1;
     73   sig.result = sig_result;
     74   sig.call_conv = KIT_CG_CC_TARGET_C;
     75 
     76   memset(&decl, 0, sizeof decl);
     77   decl.kind = KIT_CG_DECL_FUNC;
     78   decl.linkage_name = kit_sym_intern(c, kit_slice_cstr("f"));
     79   decl.display_name = decl.linkage_name;
     80   decl.type = kit_cg_type_func(c, sig);
     81   decl.sym.bind = KIT_SB_GLOBAL;
     82   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
     83   sym = kit_cg_decl(cg, decl);
     84   if (sym == KIT_CG_SYM_NONE) return KIT_ERR;
     85 
     86   kit_cg_func_begin(cg, sym);
     87   memset(&attrs, 0, sizeof attrs);
     88   attrs.name = kit_sym_intern(c, KIT_SLICE_LIT("x"));
     89   param = kit_cg_param(cg, 0, i64_ty, attrs);
     90   if (param == KIT_CG_LOCAL_NONE) return KIT_ERR;
     91 
     92   /* return x <op> imm; */
     93   kit_cg_push_local(cg, param);
     94   kit_cg_load(cg, (KitCgMemAccess){.type = i64_ty,
     95                                    .align = kit_cg_type_align(c, i64_ty)});
     96   kit_cg_push_int(cg, (uint64_t)ctx->imm, i64_ty);
     97   kit_cg_int_binop(cg, ctx->op, KIT_CG_INTOP_NONE);
     98   kit_cg_ret(cg);
     99   kit_cg_func_end(cg);
    100 
    101   if (kit_cg_finish(cg, NULL) != KIT_OK) return KIT_ERR;
    102   if (kit_cg_detach(cg) != KIT_OK) return KIT_ERR;
    103   kit_cg_free(cg);
    104   return KIT_OK;
    105 }
    106 
    107 typedef struct DisInsn {
    108   char mnem[16];
    109   char ops[64];
    110 } DisInsn;
    111 
    112 static void slice_to_buf(KitSlice s, char* buf, size_t cap) {
    113   size_t n = s.len < cap - 1 ? s.len : cap - 1;
    114   if (s.s && n) memcpy(buf, s.s, n);
    115   buf[n] = '\0';
    116 }
    117 
    118 /* Build the op and disassemble its .text into `out` (up to `cap` insns).
    119  * Returns the instruction count, or -1 on harness failure. */
    120 static int op_disasm(KitArchKind arch, KitCgIntBinOp op, int64_t imm,
    121                      DisInsn* out, int cap) {
    122   KitTargetSpec target = kit_unit_target(arch, KIT_OS_LINUX, KIT_OBJ_ELF);
    123   KitTargetOptions target_opts;
    124   KitTarget* kt = NULL;
    125   KitCompiler* c = NULL;
    126   EmitCtx ctx;
    127   KitWriter* writer = NULL;
    128   KitObjFile* file = NULL;
    129   KitSlice bytes;
    130   KitObjSection text_sec;
    131   const uint8_t* data = NULL;
    132   size_t len = 0;
    133   KitDisasmContext dc;
    134   KitDisasmIter* it = NULL;
    135   KitInsn insn;
    136   int result = -1;
    137   int n = 0;
    138 
    139   memset(&ctx, 0, sizeof ctx);
    140   ctx.op = op;
    141   ctx.imm = imm;
    142 
    143   memset(&target_opts, 0, sizeof target_opts);
    144   target_opts.spec = target;
    145   if (kit_target_new(&g_u.ctx, &target_opts, &kt) != KIT_OK || !kt) return -1;
    146   if (kit_compiler_new(kt, &g_u.ctx, &c) != KIT_OK || !c) goto done;
    147   if (kit_frontend_run(c, emit_binop_fn, &ctx) != KIT_OK) goto done;
    148   if (kit_writer_mem(&g_u.heap, &writer) != KIT_OK || !writer) goto done;
    149   if (kit_obj_builder_emit(ctx.ob, writer) != KIT_OK) goto done;
    150   bytes.data = kit_writer_mem_bytes(writer, &len);
    151   bytes.len = len;
    152   if (kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<sr-test>"), &bytes, &file) !=
    153       KIT_OK)
    154     goto done;
    155   if (kit_obj_section_by_name(file, KIT_SLICE_LIT(".text"), &text_sec) !=
    156       KIT_OK)
    157     goto done;
    158   if (kit_obj_section_data(file, text_sec, &data, &len) != KIT_OK) goto done;
    159 
    160   memset(&dc, 0, sizeof dc);
    161   dc.target = kt;
    162   dc.context = g_u.ctx;
    163   if (kit_disasm_iter_new(&dc, data, len, 0, file, &it) != KIT_OK || !it)
    164     goto done;
    165   while (n < cap && kit_disasm_iter_next(it, &insn) == KIT_ITER_ITEM) {
    166     slice_to_buf(insn.mnemonic, out[n].mnem, sizeof out[n].mnem);
    167     slice_to_buf(insn.operands, out[n].ops, sizeof out[n].ops);
    168     ++n;
    169   }
    170   result = n;
    171 
    172 done:
    173   if (it) kit_disasm_iter_free(it);
    174   if (file) kit_obj_free(file);
    175   if (writer) kit_writer_close(writer);
    176   if (ctx.ob) kit_obj_builder_free(ctx.ob);
    177   kit_compiler_free(c);
    178   kit_target_free(kt);
    179   return result;
    180 }
    181 
    182 /* 1 if any instruction's mnemonic contains `mnem`. */
    183 static int have_mnem(const DisInsn* in, int n, const char* mnem) {
    184   for (int i = 0; i < n; ++i)
    185     if (strstr(in[i].mnem, mnem)) return 1;
    186   return 0;
    187 }
    188 
    189 /* 1 if the first instruction whose mnemonic contains `mnem` has `marker` in its
    190  * operands (an empty marker matches any). This is how we tell an immediate
    191  * operand form (x64 "$", aa64 "#") from a register form, and — via the
    192  * i-suffixed RISC-V mnemonics — picks out slli/srli/andi over sll/srl/and. */
    193 static int imm_form(const DisInsn* in, int n, const char* mnem,
    194                     const char* marker) {
    195   for (int i = 0; i < n; ++i)
    196     if (strstr(in[i].mnem, mnem)) return strstr(in[i].ops, marker) != NULL;
    197   return 0;
    198 }
    199 
    200 typedef struct ArchExpect {
    201   KitArchKind arch;
    202   const char* name;
    203   const char* shl_mnem; /* immediate shift-left mnemonic (x*2^k) */
    204   const char* shr_mnem; /* immediate logical shift-right (x u/ 2^k) */
    205   const char* and_mnem; /* immediate bitwise-and (x u% 2^k) */
    206   const char* mark; /* operand marker for an immediate ("" if in mnemonic) */
    207 } ArchExpect;
    208 
    209 static void check_arch(const ArchExpect* ex) {
    210   const char* an = ex->name;
    211   DisInsn buf[128];
    212   int n;
    213 
    214   /* --- multiply --- */
    215   n = op_disasm(ex->arch, KIT_CG_INT_MUL, 8, buf, 128);
    216   EXPECT(n > 0, "[%s] mul8 disasm failed", an);
    217   EXPECT(!have_mnem(buf, n, "mul"),
    218          "[%s] x * 8 should strength-reduce to a shift (no 'mul')", an);
    219   EXPECT(imm_form(buf, n, ex->shl_mnem, ex->mark),
    220          "[%s] x * 8 should use the immediate shift form", an);
    221 
    222   n = op_disasm(ex->arch, KIT_CG_INT_MUL, 7, buf, 128);
    223   EXPECT(n > 0 && have_mnem(buf, n, "mul"), "[%s] x * 7 must stay a multiply",
    224          an);
    225 
    226   /* --- unsigned divide --- */
    227   n = op_disasm(ex->arch, KIT_CG_INT_UDIV, 8, buf, 128);
    228   EXPECT(n > 0, "[%s] udiv8 disasm failed", an);
    229   EXPECT(!have_mnem(buf, n, "div"),
    230          "[%s] x u/ 8 should strength-reduce to a shift (no 'div')", an);
    231   EXPECT(imm_form(buf, n, ex->shr_mnem, ex->mark),
    232          "[%s] x u/ 8 should use the immediate shift form", an);
    233 
    234   n = op_disasm(ex->arch, KIT_CG_INT_UDIV, 7, buf, 128);
    235   EXPECT(n > 0 && have_mnem(buf, n, "div"), "[%s] x u/ 7 must stay a divide",
    236          an);
    237 
    238   /* --- unsigned remainder --- */
    239   n = op_disasm(ex->arch, KIT_CG_INT_UREM, 8, buf, 128);
    240   EXPECT(n > 0, "[%s] urem8 disasm failed", an);
    241   EXPECT(!have_mnem(buf, n, "div") && !have_mnem(buf, n, "rem"),
    242          "[%s] x u%% 8 should strength-reduce to an and (no 'div'/'rem')", an);
    243   EXPECT(imm_form(buf, n, ex->and_mnem, ex->mark),
    244          "[%s] x u%% 8 should use the immediate and form", an);
    245 
    246   n = op_disasm(ex->arch, KIT_CG_INT_UREM, 7, buf, 128);
    247   EXPECT(n > 0 && (have_mnem(buf, n, "div") || have_mnem(buf, n, "rem")),
    248          "[%s] x u%% 7 must stay a divide/remainder", an);
    249 
    250   /* --- signed divide: deliberately left for the optimizer --- */
    251   n = op_disasm(ex->arch, KIT_CG_INT_SDIV, 8, buf, 128);
    252   EXPECT(n > 0 && have_mnem(buf, n, "div"),
    253          "[%s] signed x / 8 is left as a divide at -O0", an);
    254 }
    255 
    256 int main(void) {
    257   /* x64 AT&T immediates read "$N"; aa64 reads "#N"; rv64 folds the immediate
    258    * into the mnemonic (slli/srli/andi), so its operand marker is empty. */
    259   static const ArchExpect arches[] = {
    260       {KIT_ARCH_X86_64, "x86_64", "shl", "shr", "and", "$"},
    261       {KIT_ARCH_ARM_64, "aarch64", "lsl", "lsr", "and", "#"},
    262       {KIT_ARCH_RV64, "riscv64", "slli", "srli", "andi", ""},
    263   };
    264   kit_unit_init(&g_u);
    265   g_u.ctx.now = -1;
    266   for (size_t i = 0; i < sizeof arches / sizeof arches[0]; ++i)
    267     check_arch(&arches[i]);
    268   kit_unit_summary(&g_u, "strength_reduce_test");
    269   return kit_unit_status(&g_u);
    270 }