kit

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

ir_recorder_test.c (11310B)


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