native_direct_target_test.c (19235B)
1 #include "cg/native_direct_target.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/arena.h" 10 #include "lib/kit_unit.h" 11 12 /* Shared test context replaces the per-file heap/diag/counter globals; 13 * EXPECT aliases CU_EXPECT so the call sites are unchanged. The original 14 * ctx.now of -1 is preserved (set once in main after kit_unit_init). */ 15 static KitUnit g_u; 16 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) 17 18 typedef struct TestCtx { 19 Compiler* c; 20 KitCgTypeId i32; 21 KitCgTypeId ptr; 22 } TestCtx; 23 24 static void tc_init(TestCtx* tc) { 25 KitTargetSpec target; 26 KitCgBuiltinTypes b; 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 b = kit_cg_builtin_types(tc->c); 35 tc->i32 = b.id[KIT_CG_BUILTIN_I32]; 36 tc->ptr = kit_cg_type_ptr(tc->c, b.id[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 typedef enum MockEventKind { 45 EV_FUNC_BEGIN, 46 EV_FUNC_END, 47 EV_FRAME_SLOT, 48 EV_LABEL_NEW, 49 EV_LABEL_PLACE, 50 EV_JUMP, 51 EV_CMP_BRANCH, 52 EV_LOAD, 53 EV_STORE, 54 EV_LOAD_IMM, 55 EV_BINOP, 56 EV_BARRIER, 57 EV_PLAN_CALL, 58 EV_EMIT_CALL, 59 EV_PLAN_RET, 60 EV_RET, 61 } MockEventKind; 62 63 typedef struct MockEvent { 64 u8 kind; 65 u8 a; 66 u16 b; 67 u32 c; 68 } MockEvent; 69 70 typedef struct MockNative { 71 NativeTarget base; 72 NativeFrameSlot next_slot; 73 MCLabel next_label; 74 MockEvent events[128]; 75 u32 nevents; 76 u32 barrier_flags; 77 u32 last_stack_arg_size; 78 } MockNative; 79 80 static const Reg mock_int_scratch[] = {1u, 2u, 3u}; 81 static const Reg mock_fp_scratch[] = {4u, 5u}; 82 static const Reg mock_int_allocable[] = {1u, 2u, 3u, 6u}; 83 static const Reg mock_fp_allocable[] = {4u, 5u, 6u}; 84 85 static const NativeAllocClassInfo mock_classes[] = { 86 {.cls = NATIVE_REG_INT, 87 .allocable = mock_int_allocable, 88 .nallocable = 4, 89 .scratch = mock_int_scratch, 90 .nscratch = 3, 91 .caller_saved_mask = (1u << 1) | (1u << 2) | (1u << 3), 92 .arg_mask = 1u << 0, 93 .ret_mask = 1u << 0}, 94 {.cls = NATIVE_REG_FP, 95 .allocable = mock_fp_allocable, 96 .nallocable = 3, 97 .scratch = mock_fp_scratch, 98 .nscratch = 2, 99 .caller_saved_mask = (1u << 4) | (1u << 5), 100 .arg_mask = 1u << 0, 101 .ret_mask = 1u << 0}, 102 }; 103 104 static const NativeRegInfo mock_reg_info = { 105 .classes = mock_classes, 106 .nclasses = sizeof mock_classes / sizeof mock_classes[0], 107 }; 108 109 static MockNative* mock_of(NativeTarget* t) { return (MockNative*)t; } 110 111 static void ev(MockNative* m, MockEventKind kind, u32 a, u32 b, u32 c) { 112 MockEvent* e; 113 if (m->nevents >= sizeof m->events / sizeof m->events[0]) abort(); 114 e = &m->events[m->nevents++]; 115 memset(e, 0, sizeof *e); 116 e->kind = (u8)kind; 117 e->a = (u8)a; 118 e->b = (u16)b; 119 e->c = c; 120 } 121 122 static NativeAllocClass mock_class_for_type(NativeTarget* t, KitCgTypeId type) { 123 (void)t; 124 (void)type; 125 return NATIVE_REG_INT; 126 } 127 128 static void mock_func_begin(NativeTarget* t, const CGFuncDesc* fd) { 129 (void)fd; 130 ev(mock_of(t), EV_FUNC_BEGIN, 0, 0, 0); 131 } 132 133 static void mock_func_end(NativeTarget* t) { 134 ev(mock_of(t), EV_FUNC_END, 0, 0, 0); 135 } 136 137 static NativeFrameSlot mock_frame_slot(NativeTarget* t, 138 const NativeFrameSlotDesc* d) { 139 NativeFrameSlot slot = ++mock_of(t)->next_slot; 140 ev(mock_of(t), EV_FRAME_SLOT, d->kind, d->size, slot); 141 return slot; 142 } 143 144 static MCLabel mock_label_new(NativeTarget* t) { 145 MCLabel label = ++mock_of(t)->next_label; 146 ev(mock_of(t), EV_LABEL_NEW, 0, 0, label); 147 return label; 148 } 149 150 static void mock_label_place(NativeTarget* t, MCLabel label) { 151 ev(mock_of(t), EV_LABEL_PLACE, 0, 0, label); 152 } 153 154 static void mock_jump(NativeTarget* t, MCLabel label) { 155 ev(mock_of(t), EV_JUMP, 0, 0, label); 156 } 157 158 static void mock_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc a, NativeLoc b, 159 MCLabel label) { 160 EXPECT(a.kind == NATIVE_LOC_REG && b.kind == NATIVE_LOC_REG, 161 "cmp_branch should receive materialized registers"); 162 ev(mock_of(t), EV_CMP_BRANCH, op, a.v.reg, label); 163 } 164 165 static void mock_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, 166 MemAccess mem) { 167 EXPECT(dst.kind == NATIVE_LOC_REG, "load destination should be a register"); 168 (void)mem; 169 ev(mock_of(t), EV_LOAD, addr.base_kind, dst.v.reg, addr.base.frame); 170 } 171 172 static void mock_store(NativeTarget* t, NativeAddr addr, NativeLoc src, 173 MemAccess mem) { 174 EXPECT(src.kind == NATIVE_LOC_REG, "store source should be a register"); 175 (void)mem; 176 ev(mock_of(t), EV_STORE, addr.base_kind, src.v.reg, addr.base.frame); 177 } 178 179 static void mock_load_imm(NativeTarget* t, NativeLoc dst, i64 imm) { 180 EXPECT(dst.kind == NATIVE_LOC_REG, "load_imm destination should be register"); 181 ev(mock_of(t), EV_LOAD_IMM, dst.v.reg, 0, (u32)imm); 182 } 183 184 static void mock_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc a, 185 NativeLoc b) { 186 EXPECT(dst.kind == NATIVE_LOC_REG && a.kind == NATIVE_LOC_REG && 187 b.kind == NATIVE_LOC_REG, 188 "binop operands should be materialized registers"); 189 ev(mock_of(t), EV_BINOP, op, dst.v.reg, (a.v.reg << 16) | b.v.reg); 190 } 191 192 static void mock_emit_call(NativeTarget* t, const NativeCallPlan* plan) { 193 EXPECT(plan->callee.kind == NATIVE_LOC_REG, 194 "frame callee should be materialized for call"); 195 ev(mock_of(t), EV_EMIT_CALL, plan->nargs, plan->nrets, plan->callee.v.reg); 196 } 197 198 static void mock_plan_ret(NativeTarget* t, const CGFuncDesc* fd, 199 const NativeLoc* value, 200 NativeCallPlanRet** out_rets, u32* out_nrets) { 201 NativeCallPlanRet* r; 202 (void)fd; 203 r = arena_zarray(t->c->tu, NativeCallPlanRet, 1); 204 if (value) { 205 r[0].src = *value; 206 r[0].dst.kind = NATIVE_LOC_REG; 207 r[0].dst.cls = NATIVE_REG_INT; 208 r[0].dst.type = value->type; 209 r[0].dst.v.reg = 0; 210 r[0].mem.type = value->type; 211 r[0].mem.size = 4; 212 r[0].mem.align = 4; 213 } 214 *out_rets = r; 215 *out_nrets = value ? 1u : 0u; 216 ev(mock_of(t), EV_PLAN_RET, 0, value ? 1u : 0u, 0); 217 } 218 219 static void mock_ret(NativeTarget* t) { ev(mock_of(t), EV_RET, 0, 0, 0); } 220 221 static void mock_native_init(MockNative* m, Compiler* c) { 222 memset(m, 0, sizeof *m); 223 m->base.c = c; 224 m->base.regs = &mock_reg_info; 225 m->base.class_for_type = mock_class_for_type; 226 m->base.func_begin = mock_func_begin; 227 m->base.func_end = mock_func_end; 228 m->base.frame_slot = mock_frame_slot; 229 m->base.label_new = mock_label_new; 230 m->base.label_place = mock_label_place; 231 m->base.jump = mock_jump; 232 m->base.cmp_branch = mock_cmp_branch; 233 m->base.load = mock_load; 234 m->base.store = mock_store; 235 m->base.load_imm = mock_load_imm; 236 m->base.binop = mock_binop; 237 m->base.emit_call = mock_emit_call; 238 m->base.plan_ret = mock_plan_ret; 239 m->base.ret = mock_ret; 240 } 241 242 static void mock_barrier(NativeDirectTarget* d, u32 flags) { 243 MockNative* m = (MockNative*)d->native; 244 m->barrier_flags |= flags; 245 ev(m, EV_BARRIER, 0, 0, flags); 246 } 247 248 static void mock_plan_call(NativeDirectTarget* d, const NativeCallDesc* desc, 249 NativeCallPlan* plan) { 250 MockNative* m = (MockNative*)d->native; 251 NativeCallPlanMove* args = 252 arena_zarray(d->base.c->tu, NativeCallPlanMove, desc->nargs); 253 NativeCallPlanRet* rets = 254 arena_zarray(d->base.c->tu, NativeCallPlanRet, desc->nresults); 255 memset(plan, 0, sizeof *plan); 256 plan->callee = desc->callee; 257 plan->args = args; 258 plan->rets = rets; 259 plan->nargs = desc->nargs; 260 plan->nrets = desc->nresults; 261 plan->stack_arg_size = 24; 262 for (u32 i = 0; i < desc->nargs; ++i) { 263 args[i].src = desc->args[i]; 264 args[i].dst.kind = NATIVE_LOC_REG; 265 args[i].dst.cls = NATIVE_REG_INT; 266 args[i].dst.type = desc->args[i].type; 267 args[i].dst.v.reg = i; 268 args[i].mem.type = desc->args[i].type; 269 args[i].mem.size = 4; 270 args[i].mem.align = 4; 271 } 272 for (u32 i = 0; i < desc->nresults; ++i) { 273 rets[i].src.kind = NATIVE_LOC_REG; 274 rets[i].src.cls = NATIVE_REG_INT; 275 rets[i].src.type = desc->results[i].type; 276 rets[i].src.v.reg = i; 277 rets[i].dst = desc->results[i]; 278 rets[i].mem.type = desc->results[i].type; 279 rets[i].mem.size = 4; 280 rets[i].mem.align = 4; 281 } 282 m->last_stack_arg_size = plan->stack_arg_size; 283 ev(m, EV_PLAN_CALL, desc->nargs, desc->nresults, plan->stack_arg_size); 284 } 285 286 static const NativeOps mock_ops = { 287 .plan_call = mock_plan_call, 288 .barrier = mock_barrier, 289 }; 290 291 static Operand op_local(CGLocal local, KitCgTypeId type) { 292 Operand o; 293 memset(&o, 0, sizeof o); 294 o.kind = OPK_LOCAL; 295 o.type = type; 296 o.v.local = local; 297 return o; 298 } 299 300 static Operand op_imm(i64 value, KitCgTypeId type) { 301 Operand o; 302 memset(&o, 0, sizeof o); 303 o.kind = OPK_IMM; 304 o.type = type; 305 o.v.imm = value; 306 return o; 307 } 308 309 static CGLocal local_new(CgTarget* t, KitCgTypeId type) { 310 CGLocalDesc d; 311 memset(&d, 0, sizeof d); 312 d.type = type; 313 d.size = 4; 314 d.align = 4; 315 return t->local(t, &d); 316 } 317 318 static CGLocal local_new_ptr(CgTarget* t, KitCgTypeId type) { 319 CGLocalDesc d; 320 memset(&d, 0, sizeof d); 321 d.type = type; 322 d.size = 8; 323 d.align = 8; 324 return t->local(t, &d); 325 } 326 327 static Operand op_indirect(CGLocal base, i32 ofs, KitCgTypeId type) { 328 Operand o; 329 memset(&o, 0, sizeof o); 330 o.kind = OPK_INDIRECT; 331 o.type = type; 332 o.v.ind.base = base; 333 o.v.ind.index = CG_LOCAL_NONE; 334 o.v.ind.ofs = ofs; 335 return o; 336 } 337 338 static MemAccess mem_scalar(KitCgTypeId type, u16 flags) { 339 MemAccess m; 340 memset(&m, 0, sizeof m); 341 m.type = type; 342 m.size = 4; 343 m.align = 4; 344 m.flags = flags; 345 return m; 346 } 347 348 /* Index of the first event of KIND at or after `from`, or -1. */ 349 static int event_index(const MockNative* m, MockEventKind kind, u32 from) { 350 for (u32 i = from; i < m->nevents; ++i) 351 if (m->events[i].kind == kind) return (int)i; 352 return -1; 353 } 354 355 static CGFuncDesc fn_desc(TestCtx* tc) { 356 CGFuncDesc fd; 357 KitCgFuncSig sig; 358 KitCgFuncResult sig_result; 359 memset(&fd, 0, sizeof fd); 360 memset(&sig, 0, sizeof sig); 361 memset(&sig_result, 0, sizeof sig_result); 362 sig_result.type = tc->i32; 363 sig.result = sig_result; 364 sig.call_conv = KIT_CG_CC_TARGET_C; 365 fd.fn_type = kit_cg_type_func(tc->c, sig); 366 fd.result_type = tc->i32; 367 return fd; 368 } 369 370 static CgTarget* make_target(TestCtx* tc, MockNative* native) { 371 NativeDirectTargetConfig cfg; 372 memset(&cfg, 0, sizeof cfg); 373 mock_native_init(native, tc->c); 374 cfg.native = &native->base; 375 cfg.ops = &mock_ops; 376 return native_direct_target_new(tc->c, NULL, &cfg); 377 } 378 379 static int count_event(const MockNative* m, MockEventKind kind) { 380 int count = 0; 381 for (u32 i = 0; i < m->nevents; ++i) 382 if (m->events[i].kind == kind) ++count; 383 return count; 384 } 385 386 static void test_frame_locals_scratch_storeback_and_branches(void) { 387 TestCtx tc; 388 MockNative native; 389 CgTarget* t; 390 CGFuncDesc fd; 391 CGLocal a, b, sum; 392 Label done; 393 tc_init(&tc); 394 t = make_target(&tc, &native); 395 fd = fn_desc(&tc); 396 t->func_begin(t, &fd); 397 a = local_new(t, tc.i32); 398 b = local_new(t, tc.i32); 399 sum = local_new(t, tc.i32); 400 EXPECT(a == 1 && b == 2 && sum == 3, "locals should be semantic ids"); 401 EXPECT(native.next_slot == 3, "locals should allocate frame homes"); 402 403 t->load_imm(t, op_local(a, tc.i32), 7); 404 t->load_imm(t, op_local(b, tc.i32), 9); 405 t->binop(t, BO_IADD, op_local(sum, tc.i32), op_local(a, tc.i32), 406 op_local(b, tc.i32)); 407 done = t->label_new(t); 408 t->cmp_branch(t, CMP_EQ, op_local(sum, tc.i32), op_imm(16, tc.i32), done); 409 t->jump(t, done); 410 t->label_place(t, done); 411 t->ret(t, sum); 412 t->func_end(t); 413 414 EXPECT(count_event(&native, EV_LOAD_IMM) == 3, 415 "two explicit immediates plus cmp imm materialization expected"); 416 /* With the local register cache, a/b/sum live in registers across the 417 * straight-line compute run: the binop reloads neither operand. They are 418 * spilled at the branch flush (EV_STORE), and sum is reloaded only after that 419 * flush — once for the cmp_branch and once for the return. */ 420 EXPECT(count_event(&native, EV_LOAD) == 2, 421 "sum is reloaded from its home for the cmp_branch and the return"); 422 EXPECT(count_event(&native, EV_STORE) >= 3, 423 "results should store back to frame homes"); 424 EXPECT(count_event(&native, EV_BINOP) == 1, "expected one native binop"); 425 EXPECT(count_event(&native, EV_LABEL_NEW) == 1, "expected one native label"); 426 EXPECT(count_event(&native, EV_CMP_BRANCH) == 1, 427 "expected one compare branch"); 428 EXPECT(count_event(&native, EV_JUMP) == 1, "expected one jump"); 429 EXPECT(count_event(&native, EV_PLAN_RET) == 1 && count_event(&native, EV_RET), 430 "return should plan moves and emit ret"); 431 tc_fini(&tc); 432 } 433 434 static void test_call_barrier_storeback_and_max_outgoing(void) { 435 TestCtx tc; 436 MockNative native; 437 CgTarget* t; 438 CGFuncDesc fd; 439 CGLocal arg, fnptr, result; 440 CGCallDesc call; 441 CGLocal args[1]; 442 NativeDirectTarget* nd; 443 tc_init(&tc); 444 t = make_target(&tc, &native); 445 fd = fn_desc(&tc); 446 t->func_begin(t, &fd); 447 arg = local_new(t, tc.i32); 448 fnptr = local_new(t, tc.ptr); 449 result = local_new(t, tc.i32); 450 t->load_imm(t, op_local(arg, tc.i32), 42); 451 t->load_imm(t, op_local(fnptr, tc.ptr), 0x1000); 452 453 memset(&call, 0, sizeof call); 454 args[0] = arg; 455 call.fn_type = fd.fn_type; 456 call.callee = op_local(fnptr, tc.ptr); 457 call.args = args; 458 call.result = result; 459 call.nargs = 1; 460 t->call(t, &call); 461 462 nd = (NativeDirectTarget*)t; 463 EXPECT(native.barrier_flags == 464 (NATIVE_DIRECT_BARRIER_CALL | NATIVE_DIRECT_BARRIER_MEMORY), 465 "call should request call+memory barrier"); 466 EXPECT(native.last_stack_arg_size == 24 && nd->max_outgoing == 24, 467 "call planning should track max outgoing stack size"); 468 EXPECT(count_event(&native, EV_PLAN_CALL) == 1, "expected one call plan"); 469 EXPECT(count_event(&native, EV_EMIT_CALL) == 1, "expected one emitted call"); 470 EXPECT(count_event(&native, EV_STORE) >= 3, 471 "arg setup and result should store through native writes"); 472 t->func_end(t); 473 tc_fini(&tc); 474 } 475 476 /* Design B: a cached pointer base is dereferenced straight from its register — 477 * no spill, no reload of the base from its home. The discriminator is the 478 * EV_LOAD count: an uncached base would emit a separate BASE_FRAME load to read 479 * the pointer, plus the dereference. A cached base emits only the dereference. 480 */ 481 static void test_b_cached_pointer_base_not_reloaded(void) { 482 TestCtx tc; 483 MockNative native; 484 CgTarget* t; 485 CGFuncDesc fd; 486 CGLocal p, d; 487 tc_init(&tc); 488 t = make_target(&tc, &native); 489 fd = fn_desc(&tc); 490 t->func_begin(t, &fd); 491 p = local_new_ptr(t, tc.ptr); 492 d = local_new(t, tc.i32); 493 t->load_imm(t, op_local(p, tc.ptr), 0x1000); /* p computed -> cached, dirty */ 494 t->load(t, op_local(d, tc.i32), op_indirect(p, 0, tc.i32), 495 mem_scalar(tc.i32, 0)); 496 497 EXPECT(count_event(&native, EV_LOAD) == 1, 498 "only the dereference loads; the cached base p is not reloaded"); 499 { 500 int li = event_index(&native, EV_LOAD, 0); 501 EXPECT(li >= 0 && native.events[li].a == NATIVE_ADDR_BASE_REG, 502 "dereference addresses the live cache register for p"); 503 } 504 EXPECT(count_event(&native, EV_STORE) == 1, 505 "p is not spilled; only the load result is written to d's home"); 506 t->func_end(t); 507 tc_fini(&tc); 508 } 509 510 /* Design B: the cache survives across a store through a pointer. Neither the 511 * base p nor the stored value a is spilled/reloaded, and a later use of a hits 512 * the cache. Under Design A the store's flush_all would force both to memory. 513 */ 514 static void test_b_cache_survives_store(void) { 515 TestCtx tc; 516 MockNative native; 517 CgTarget* t; 518 CGFuncDesc fd; 519 CGLocal p, a, b; 520 tc_init(&tc); 521 t = make_target(&tc, &native); 522 fd = fn_desc(&tc); 523 t->func_begin(t, &fd); 524 p = local_new_ptr(t, tc.ptr); 525 a = local_new(t, tc.i32); 526 b = local_new(t, tc.i32); 527 t->load_imm(t, op_local(p, tc.ptr), 0x2000); /* p cached */ 528 t->load_imm(t, op_local(a, tc.i32), 5); /* a cached, dirty */ 529 t->store(t, op_indirect(p, 0, tc.i32), op_local(a, tc.i32), 530 mem_scalar(tc.i32, 0)); 531 t->binop(t, BO_IADD, op_local(b, tc.i32), op_local(a, tc.i32), 532 op_local(a, tc.i32)); /* a is a cache hit */ 533 534 EXPECT(count_event(&native, EV_LOAD) == 0, 535 "no home reloads: p and a are read from registers across the store"); 536 { 537 int si = event_index(&native, EV_STORE, 0); 538 EXPECT(si >= 0 && native.events[si].a == NATIVE_ADDR_BASE_REG, 539 "the store dereferences p from its live register"); 540 } 541 EXPECT(count_event(&native, EV_STORE) == 1, 542 "only the user store is emitted; nothing is spilled"); 543 t->func_end(t); 544 tc_fini(&tc); 545 } 546 547 /* A volatile access must still flush the cache (it may observe memory) and emit 548 * the volatile barrier — the escape argument does not apply. */ 549 static void test_b_volatile_load_flushes(void) { 550 TestCtx tc; 551 MockNative native; 552 CgTarget* t; 553 CGFuncDesc fd; 554 CGLocal p, a, d; 555 tc_init(&tc); 556 t = make_target(&tc, &native); 557 fd = fn_desc(&tc); 558 t->func_begin(t, &fd); 559 p = local_new_ptr(t, tc.ptr); 560 a = local_new(t, tc.i32); 561 d = local_new(t, tc.i32); 562 t->load_imm(t, op_local(p, tc.ptr), 0x3000); 563 t->load_imm(t, op_local(a, tc.i32), 9); /* a cached, dirty */ 564 t->load(t, op_local(d, tc.i32), op_indirect(p, 0, tc.i32), 565 mem_scalar(tc.i32, MF_VOLATILE)); 566 567 EXPECT((native.barrier_flags & NATIVE_DIRECT_BARRIER_VOLATILE) != 0, 568 "volatile access emits a volatile barrier"); 569 EXPECT(count_event(&native, EV_STORE) >= 2, 570 "volatile flush spills the dirty cached locals (p and a)"); 571 t->func_end(t); 572 tc_fini(&tc); 573 } 574 575 /* A call still flushes the whole cache: a dirty cached local is spilled before 576 * the call is emitted (caller-saved registers die across the call). */ 577 static void test_b_call_still_flushes(void) { 578 TestCtx tc; 579 MockNative native; 580 CgTarget* t; 581 CGFuncDesc fd; 582 CGLocal a, fnptr; 583 CGCallDesc call; 584 tc_init(&tc); 585 t = make_target(&tc, &native); 586 fd = fn_desc(&tc); 587 t->func_begin(t, &fd); 588 a = local_new(t, tc.i32); 589 fnptr = local_new_ptr(t, tc.ptr); 590 t->load_imm(t, op_local(a, tc.i32), 5); /* a cached, dirty */ 591 t->load_imm(t, op_local(fnptr, tc.ptr), 0x1000); 592 memset(&call, 0, sizeof call); 593 call.fn_type = fd.fn_type; 594 call.callee = op_local(fnptr, tc.ptr); 595 t->call(t, &call); 596 597 { 598 int ci = event_index(&native, EV_EMIT_CALL, 0); 599 int si = event_index(&native, EV_STORE, 0); 600 EXPECT(ci >= 0 && si >= 0 && si < ci, 601 "dirty cached locals are spilled before the call is emitted"); 602 } 603 t->func_end(t); 604 tc_fini(&tc); 605 } 606 607 int main(void) { 608 kit_unit_init(&g_u); 609 g_u.ctx.now = -1; 610 test_frame_locals_scratch_storeback_and_branches(); 611 test_call_barrier_storeback_and_max_outgoing(); 612 test_b_cached_pointer_base_not_reloaded(); 613 test_b_cache_survives_store(); 614 test_b_volatile_load_flushes(); 615 test_b_call_still_flushes(); 616 if (g_u.fails) { 617 fprintf(stderr, "%d/%d checks failed\n", g_u.fails, g_u.checks); 618 return 1; 619 } 620 printf("native_direct_target_test: %d checks passed\n", g_u.checks); 621 return 0; 622 }