kit

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

ir_recorder_test.c (11296B)


      1 #include "cg/ir_recorder.h"
      2 
      3 #include <kit/core.h>
      4 #include <stdarg.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 
      9 #include "core/pool.h"
     10 #include "lib/kit_unit.h"
     11 
     12 /* One shared test context replaces the per-file heap/diag/counter globals.
     13  * EXPECT is aliased to CU_EXPECT so the call sites below are unchanged. The
     14  * table-of-tests harness re-creates a compiler per test via tc_init, but the
     15  * single g_u (with ctx.now == -1, set once in main) backs them all. */
     16 static KitUnit g_u;
     17 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     18 
     19 typedef struct TestCtx {
     20   Compiler* c;
     21   KitCgTypeId i32;
     22   KitCgTypeId ptr;
     23 } TestCtx;
     24 
     25 static void tc_init(TestCtx* tc) {
     26   KitTargetSpec target;
     27   KitCgBuiltinTypes b;
     28   memset(tc, 0, sizeof *tc);
     29   target = kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
     30   if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK ||
     31       !tc->c) {
     32     fprintf(stderr, "fatal: compiler allocation failed\n");
     33     abort();
     34   }
     35   b = kit_cg_builtin_types(tc->c);
     36   tc->i32 = b.id[KIT_CG_BUILTIN_I32];
     37   tc->ptr = kit_cg_type_ptr(tc->c, b.id[KIT_CG_BUILTIN_VOID], 0);
     38 }
     39 
     40 static void tc_fini(TestCtx* tc) {
     41   kit_compiler_free(tc->c);
     42   tc->c = NULL;
     43 }
     44 
     45 static Operand op_local(CGLocal local, KitCgTypeId type) {
     46   Operand o;
     47   memset(&o, 0, sizeof o);
     48   o.kind = OPK_LOCAL;
     49   o.type = type;
     50   o.v.local = local;
     51   return o;
     52 }
     53 
     54 static __attribute__((unused)) Operand op_imm(i64 value, KitCgTypeId type) {
     55   Operand o;
     56   memset(&o, 0, sizeof o);
     57   o.kind = OPK_IMM;
     58   o.type = type;
     59   o.v.imm = value;
     60   return o;
     61 }
     62 
     63 static Operand op_global(ObjSymId sym, KitCgTypeId type) {
     64   Operand o;
     65   memset(&o, 0, sizeof o);
     66   o.kind = OPK_GLOBAL;
     67   o.type = type;
     68   o.v.global.sym = sym;
     69   return o;
     70 }
     71 
     72 static CGLocal local_new(CgTarget* t, KitCgTypeId type, const char* name) {
     73   CGLocalDesc d;
     74   memset(&d, 0, sizeof d);
     75   d.type = type;
     76   d.name = name ? pool_intern_slice(t->c->global, kit_slice_cstr(name)) : 0;
     77   d.size = 4;
     78   d.align = 4;
     79   return t->local(t, &d);
     80 }
     81 
     82 static CGFuncDesc fn_desc(TestCtx* tc) {
     83   CGFuncDesc fd;
     84   KitCgFuncSig sig;
     85   KitCgFuncResult sig_result;
     86   memset(&fd, 0, sizeof fd);
     87   memset(&sig, 0, sizeof sig);
     88   memset(&sig_result, 0, sizeof sig_result);
     89   sig_result.type = tc->i32;
     90   sig.result = sig_result;
     91   sig.call_conv = KIT_CG_CC_TARGET_C;
     92   fd.fn_type = kit_cg_type_func(tc->c, sig);
     93   fd.loc.line = 3;
     94   fd.loc.col = 1;
     95   return fd;
     96 }
     97 
     98 typedef struct CallbackState {
     99   u32 count;
    100   CgIrFunc* last;
    101   const char* data_label_msg;
    102 } CallbackState;
    103 
    104 static void on_func(void* user, CgIrFunc* func) {
    105   CallbackState* s = user;
    106   ++s->count;
    107   s->last = func;
    108 }
    109 
    110 static const char* on_data_label_msg(void* user) {
    111   CallbackState* s = user;
    112   return s->data_label_msg;
    113 }
    114 
    115 static CgTarget* make_recorder(TestCtx* tc, CallbackState* cb) {
    116   CgIrRecorderConfig cfg;
    117   memset(&cfg, 0, sizeof cfg);
    118   cfg.func_recorded = on_func;
    119   cfg.data_label_addr_unsupported_msg = on_data_label_msg;
    120   cfg.user = cb;
    121   return cg_ir_recorder_new(tc->c, NULL, &cfg);
    122 }
    123 
    124 static void test_records_basic_function_shape(void) {
    125   TestCtx tc;
    126   CallbackState cb;
    127   CgTarget* t;
    128   CGFuncDesc fd;
    129   CGLocal a, b, dst;
    130   const CgIrModule* m;
    131   CgIrFunc* f;
    132   CgIrRetAux* ret;
    133   memset(&cb, 0, sizeof cb);
    134   tc_init(&tc);
    135   t = make_recorder(&tc, &cb);
    136   fd = fn_desc(&tc);
    137   t->func_begin(t, &fd);
    138   t->set_loc(t, (SrcLoc){.file_id = 9, .line = 7, .col = 5});
    139   a = local_new(t, tc.i32, "a");
    140   b = local_new(t, tc.i32, "b");
    141   dst = local_new(t, tc.i32, "dst");
    142   t->load_imm(t, op_local(a, tc.i32), 40);
    143   t->load_imm(t, op_local(b, tc.i32), 2);
    144   t->binop(t, BO_IADD, op_local(dst, tc.i32), op_local(a, tc.i32),
    145            op_local(b, tc.i32));
    146   t->ret(t, dst);
    147   t->func_end(t);
    148 
    149   m = cg_ir_recorder_module(t);
    150   EXPECT(m && m->nfuncs == 1, "expected one recorded function");
    151   f = m->funcs[0];
    152   EXPECT(f == cb.last && cb.count == 1, "callback should observe func_end");
    153   EXPECT(f->complete, "function should be complete");
    154   EXPECT(f->nlocals == 3, "expected 3 locals, got %u", f->nlocals);
    155   EXPECT(f->ninsts == 4, "expected 4 insts, got %u", f->ninsts);
    156   EXPECT(f->insts[0].op == CG_IR_LOAD_IMM && f->insts[0].extra.imm == 40,
    157          "first inst should be load_imm 40");
    158   EXPECT(f->insts[2].op == CG_IR_BINOP && f->insts[2].opnds[0].v.local == dst &&
    159              f->insts[2].opnds[1].v.local == a &&
    160              f->insts[2].opnds[2].v.local == b &&
    161              f->insts[2].extra.imm == BO_IADD,
    162          "binop should preserve semantic local operands");
    163   EXPECT(f->insts[2].loc.file_id == 9 && f->insts[2].loc.line == 7,
    164          "sticky source location should be stamped on insts");
    165   ret = (CgIrRetAux*)f->insts[3].extra.aux;
    166   EXPECT(ret && ret->present && ret->value == dst,
    167          "return should preserve semantic result local");
    168   tc_fini(&tc);
    169 }
    170 
    171 static void test_deep_copies_call_switch_and_const_payloads(void) {
    172   TestCtx tc;
    173   CallbackState cb;
    174   CgTarget* t;
    175   CGFuncDesc fd;
    176   CGLocal arg, result;
    177   CGCallDesc call;
    178   CGLocal call_args[1];
    179   CGSwitchDesc sw;
    180   CGSwitchCase cases[2];
    181   u8 bytes[4] = {1, 2, 3, 4};
    182   ConstBytes cbv;
    183   Label l0, l1;
    184   CgIrFunc* f;
    185   CgIrCallAux* call_aux;
    186   CgIrSwitchAux* switch_aux;
    187   memset(&cb, 0, sizeof cb);
    188   tc_init(&tc);
    189   t = make_recorder(&tc, &cb);
    190   fd = fn_desc(&tc);
    191   t->func_begin(t, &fd);
    192   arg = local_new(t, tc.i32, "arg");
    193   result = local_new(t, tc.i32, "result");
    194   l0 = t->label_new(t);
    195   l1 = t->label_new(t);
    196 
    197   memset(&call, 0, sizeof call);
    198   call_args[0] = arg;
    199   call.fn_type = fd.fn_type;
    200   call.callee = op_global(12, tc.ptr);
    201   call.args = call_args;
    202   call.result = result;
    203   call.nargs = 1;
    204   t->call(t, &call);
    205   call_args[0] = 999;
    206 
    207   memset(&sw, 0, sizeof sw);
    208   cases[0].value = 4;
    209   cases[0].label = l0;
    210   cases[1].value = 9;
    211   cases[1].label = l1;
    212   sw.selector = op_local(arg, tc.i32);
    213   sw.selector_type = tc.i32;
    214   sw.default_label = l1;
    215   sw.cases = cases;
    216   sw.ncases = 2;
    217   sw.hint = 7;
    218   t->switch_(t, &sw);
    219   cases[0].value = 400;
    220   cases[0].label = 400;
    221 
    222   memset(&cbv, 0, sizeof cbv);
    223   cbv.type = tc.i32;
    224   cbv.bytes = bytes;
    225   cbv.size = sizeof bytes;
    226   cbv.align = 4;
    227   t->load_const(t, op_local(result, tc.i32), cbv);
    228   bytes[0] = 99;
    229   t->func_end(t);
    230 
    231   f = cb.last;
    232   EXPECT(f->ninsts == 3, "expected call, switch, load_const");
    233   call_aux = (CgIrCallAux*)f->insts[0].extra.aux;
    234   EXPECT(call_aux && call_aux->desc.args[0] == arg &&
    235              call_aux->desc.result == result,
    236          "call descriptor should be deep-copied");
    237   switch_aux = (CgIrSwitchAux*)f->insts[1].extra.aux;
    238   EXPECT(switch_aux && switch_aux->ncases == 2 &&
    239              switch_aux->cases[0].value == 4 &&
    240              switch_aux->cases[0].label == l0 &&
    241              switch_aux->cases[1].label == l1,
    242          "switch cases should be deep-copied");
    243   EXPECT(f->insts[2].extra.cbytes.bytes[0] == 1 &&
    244              f->insts[2].extra.cbytes.size == 4,
    245          "const bytes should be deep-copied");
    246   tc_fini(&tc);
    247 }
    248 
    249 static void test_labels_scopes_and_address_taken_locals(void) {
    250   TestCtx tc;
    251   CallbackState cb;
    252   CgTarget* t;
    253   CGFuncDesc fd;
    254   CGLocal ptr, value;
    255   Label label;
    256   CGScope scope;
    257   CGScopeDesc sd;
    258   CgIrFunc* f;
    259   CgIrScopeAux* scope_aux;
    260   memset(&cb, 0, sizeof cb);
    261   tc_init(&tc);
    262   t = make_recorder(&tc, &cb);
    263   fd = fn_desc(&tc);
    264   t->func_begin(t, &fd);
    265   ptr = local_new(t, tc.ptr, "ptr");
    266   value = local_new(t, tc.i32, "value");
    267   label = t->label_new(t);
    268   t->label_place(t, label);
    269   t->load_label_addr(t, op_local(ptr, tc.ptr), label);
    270   t->addr_of(t, op_local(ptr, tc.ptr), op_local(value, tc.i32));
    271   memset(&sd, 0, sizeof sd);
    272   sd.kind = SCOPE_BLOCK;
    273   scope = t->scope_begin(t, &sd);
    274   t->break_to(t, scope);
    275   t->scope_end(t, scope);
    276   t->func_end(t);
    277 
    278   f = cb.last;
    279   EXPECT(f->nlabels == 1 && f->labels[0].nplaces == 1,
    280          "label placement should be tracked");
    281   EXPECT(f->insts[0].op == CG_IR_LABEL && f->insts[0].extra.imm == label,
    282          "label placement should be a linear IR inst");
    283   EXPECT(f->locals[value - 1u].address_taken,
    284          "addr_of local should mark the local address-taken");
    285   EXPECT(f->nscopes == 1 && f->scopes[0].id == scope,
    286          "scope table should preserve semantic scope ids");
    287   scope_aux = (CgIrScopeAux*)f->insts[3].extra.aux;
    288   EXPECT(scope_aux && scope_aux->scope == scope &&
    289              scope_aux->desc.kind == SCOPE_BLOCK,
    290          "scope_begin inst should carry scope metadata");
    291   tc_fini(&tc);
    292 }
    293 
    294 static void test_aliases_and_data_label_diagnostic_hook(void) {
    295   TestCtx tc;
    296   CallbackState cb;
    297   CgTarget* t;
    298   const CgIrModule* m;
    299   memset(&cb, 0, sizeof cb);
    300   cb.data_label_msg = "wasm target: custom label data diagnostic";
    301   tc_init(&tc);
    302   t = make_recorder(&tc, &cb);
    303   t->alias(t, (ObjSymId)7, (ObjSymId)3, tc.i32);
    304   m = cg_ir_recorder_module(t);
    305   EXPECT(m && m->naliases == 1, "expected one recorded alias");
    306   EXPECT(m->aliases[0].alias_sym == 7 && m->aliases[0].target_sym == 3 &&
    307              m->aliases[0].type == tc.i32,
    308          "alias record should preserve symbols and type");
    309   EXPECT(t->data_label_addr_unsupported_msg(t) == cb.data_label_msg,
    310          "recorder should use target-specific data-label diagnostic hook");
    311   tc_fini(&tc);
    312 }
    313 
    314 static void test_func_dump_renders_text(void) {
    315   TestCtx tc;
    316   CallbackState cb;
    317   CgTarget* t;
    318   CGFuncDesc fd;
    319   CGLocal a, b, dst;
    320   CgIrFunc* f;
    321   KitWriter* w = NULL;
    322   const uint8_t* bytes;
    323   size_t len = 0;
    324   char s[4096];
    325   memset(&cb, 0, sizeof cb);
    326   tc_init(&tc);
    327   t = make_recorder(&tc, &cb);
    328   fd = fn_desc(&tc);
    329   t->func_begin(t, &fd);
    330   a = local_new(t, tc.i32, "a");
    331   b = local_new(t, tc.i32, "b");
    332   dst = local_new(t, tc.i32, "dst");
    333   t->load_imm(t, op_local(a, tc.i32), 40);
    334   t->load_imm(t, op_local(b, tc.i32), 2);
    335   t->binop(t, BO_IADD, op_local(dst, tc.i32), op_local(a, tc.i32),
    336            op_local(b, tc.i32));
    337   t->ret(t, dst);
    338   t->func_end(t);
    339 
    340   f = cg_ir_recorder_module(t)->funcs[0];
    341   kit_writer_mem(&g_u.heap, &w);
    342   cg_ir_func_dump(f, w);
    343   bytes = kit_writer_mem_bytes(w, &len);
    344   EXPECT(len > 0 && bytes && len < sizeof s, "dump should produce output");
    345   /* NUL-terminate for strstr by copying into a sized buffer. */
    346   memcpy(s, bytes, len < sizeof s ? len : sizeof s - 1u);
    347   s[len < sizeof s ? len : sizeof s - 1u] = '\0';
    348   EXPECT(strstr(s, "func sym#") != NULL, "should print func header");
    349   EXPECT(strstr(s, "local L1 ") != NULL, "should list locals");
    350   EXPECT(strstr(s, "\"a\"") != NULL, "should print local names");
    351   EXPECT(strstr(s, "load_imm") != NULL, "should print load_imm op");
    352   EXPECT(strstr(s, "= 40") != NULL, "should print immediate value");
    353   EXPECT(strstr(s, "binop") != NULL, "should print binop op");
    354   EXPECT(strstr(s, "iadd") != NULL, "should name the binop kind");
    355   EXPECT(strstr(s, "ret value=L") != NULL, "should print ret value");
    356   kit_writer_close(w);
    357   tc_fini(&tc);
    358 }
    359 
    360 int main(void) {
    361   kit_unit_init(&g_u);
    362   g_u.ctx.now = -1;
    363   test_records_basic_function_shape();
    364   test_deep_copies_call_switch_and_const_payloads();
    365   test_labels_scopes_and_address_taken_locals();
    366   test_aliases_and_data_label_diagnostic_hook();
    367   test_func_dump_renders_text();
    368   fprintf(stderr, "ir-recorder: %d checks, %d failures\n", g_u.checks,
    369           g_u.fails);
    370   return g_u.fails ? 1 : 0;
    371 }