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 }