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 }