interp_smoke_test.c (21565B)
1 /* Unit smoke test for the threaded-bytecode interpreter. 2 * 3 * Mirrors test/opt/cg_ir_lower_test.c: a self-contained heap/diag harness that 4 * builds tiny CG IR by hand, runs it through opt_run_o1_interp + interp_lower, 5 * executes it on an InterpStack, and asserts the returned value. This exercises 6 * the loader + engine directly (the broad differential coverage against the JIT 7 * lives in test/toy/run.sh's I-path). */ 8 9 #include <kit/core.h> 10 #include <kit/interp.h> 11 #include <stdarg.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 16 #include "cg/ir.h" 17 #include "interp/interp.h" 18 #include "lib/kit_unit.h" 19 #include "opt/opt.h" 20 21 #undef Operand 22 #undef CGFuncDesc 23 #undef CGParamDesc 24 #undef CGCallDesc 25 #undef CGLocalStorage 26 27 /* Shared test context replaces the per-file heap/diag/counter globals; 28 * EXPECT aliases CU_EXPECT so the call sites are unchanged. kit_unit_init 29 * runs once in main (ctx.now is then set to -1 to match the original). */ 30 static KitUnit g_u; 31 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) 32 33 typedef struct TestCtx { 34 Compiler* c; 35 KitCgTypeId i32; 36 KitCgTypeId i64; 37 KitCgTypeId f64; 38 } TestCtx; 39 40 static void tc_init(TestCtx* tc) { 41 KitTargetSpec target; 42 memset(tc, 0, sizeof *tc); 43 target = kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO); 44 if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK || 45 !tc->c) { 46 fprintf(stderr, "fatal: compiler allocation failed\n"); 47 abort(); 48 } 49 tc->i32 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I32); 50 tc->i64 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I64); 51 tc->f64 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_F64); 52 } 53 54 static void tc_fini(TestCtx* tc) { 55 kit_compiler_free(tc->c); 56 tc->c = NULL; 57 } 58 59 static Operand local_op(CGLocal local, KitCgTypeId type) { 60 Operand o; 61 memset(&o, 0, sizeof o); 62 o.kind = OPK_LOCAL; 63 o.type = type; 64 o.v.local = local; 65 return o; 66 } 67 static Operand imm_op(i64 value, KitCgTypeId type) { 68 Operand o; 69 memset(&o, 0, sizeof o); 70 o.kind = OPK_IMM; 71 o.type = type; 72 o.v.imm = value; 73 return o; 74 } 75 static CGLocal add_local(CgIrFunc* f, KitCgTypeId type) { 76 CGLocalDesc d; 77 memset(&d, 0, sizeof d); 78 d.type = type; 79 d.size = 8; 80 d.align = 8; 81 return cg_ir_func_add_local(f, &d, 0, 0); 82 } 83 static CgIrInst* emit_ops(CgIrFunc* f, CgIrOp op, const Operand* ops, u32 n) { 84 CgIrInst* in = cg_ir_emit(f, op, (SrcLoc){0, 0, 0}); 85 in->opnds = cg_ir_dup_operands(f->arena, ops, n); 86 in->nopnds = n; 87 return in; 88 } 89 90 /* Run a hand-built leaf CgIrFunc through the interp and return its scalar. */ 91 static KitInterpStatus run_leaf(TestCtx* tc, CgIrFunc* cg, int64_t* out) { 92 KitInterpProgram* prog = kit_interp_program_new(tc->c); 93 Func* f = opt_run_o1_interp(tc->c, cg); 94 InterpFunc* fn = 95 interp_lower((InterpProgram*)prog, f, OBJ_SYM_NONE, SLICE_NULL, NULL); 96 KitInterpStatus s = kit_interp_call(prog, (KitInterpFunc*)fn, 0, NULL, out); 97 kit_interp_program_free(prog); 98 return s; 99 } 100 101 static CgIrFunc* new_func(TestCtx* tc, KitCgTypeId ret_type) { 102 CGFuncDesc fd; 103 memset(&fd, 0, sizeof fd); 104 fd.fn_type = ret_type; 105 fd.result_type = ret_type; 106 return cg_ir_func_new(tc->c, &fd); 107 } 108 109 static void ret_local(CgIrFunc* cg, CGLocal v) { 110 CgIrRetAux* aux = arena_znew(cg->arena, CgIrRetAux); 111 CgIrInst* ret; 112 aux->value = v; 113 aux->present = 1; 114 ret = cg_ir_emit(cg, CG_IR_RET, (SrcLoc){0, 0, 0}); 115 ret->extra.aux = aux; 116 } 117 118 /* fn() : i64 { a = 2; a = a + 3; return a; } => 5 */ 119 static void interp_runs_arithmetic(void) { 120 TestCtx tc; 121 CgIrFunc* cg; 122 CGLocal a; 123 int64_t ret = -1; 124 KitInterpStatus s; 125 tc_init(&tc); 126 cg = new_func(&tc, tc.i64); 127 a = add_local(cg, tc.i64); 128 { 129 Operand o[] = {local_op(a, tc.i64)}; 130 CgIrInst* li = emit_ops(cg, CG_IR_LOAD_IMM, o, 1); 131 li->extra.imm = 2; 132 } 133 { 134 Operand o[] = {local_op(a, tc.i64), local_op(a, tc.i64), imm_op(3, tc.i64)}; 135 CgIrInst* bi = emit_ops(cg, CG_IR_BINOP, o, 3); 136 bi->extra.imm = BO_IADD; 137 } 138 ret_local(cg, a); 139 s = run_leaf(&tc, cg, &ret); 140 EXPECT(s == KIT_INTERP_DONE, "arithmetic: status %d", (int)s); 141 EXPECT(ret == 5, "arithmetic: expected 5, got %lld", (long long)ret); 142 tc_fini(&tc); 143 } 144 145 /* fn() : i64 { a = 7; if (a == 7) a = 11; return a; } => 11 146 * Exercises CMP_BRANCH + a join block + fallthrough succ edges. */ 147 static void interp_runs_branch(void) { 148 TestCtx tc; 149 CgIrFunc* cg; 150 CGLocal a; 151 Label done; 152 int64_t ret = -1; 153 KitInterpStatus s; 154 tc_init(&tc); 155 cg = new_func(&tc, tc.i64); 156 a = add_local(cg, tc.i64); 157 done = cg_ir_func_add_label(cg); 158 { 159 Operand o[] = {local_op(a, tc.i64)}; 160 CgIrInst* li = emit_ops(cg, CG_IR_LOAD_IMM, o, 1); 161 li->extra.imm = 7; 162 } 163 { 164 /* branch to `done` when a != 7 (i.e. skip the assignment) */ 165 Operand o[] = {local_op(a, tc.i64), imm_op(7, tc.i64)}; 166 CgIrInst* br = emit_ops(cg, CG_IR_CMP_BRANCH, o, 2); 167 CgIrCmpBranchAux* aux = arena_znew(cg->arena, CgIrCmpBranchAux); 168 aux->op = CMP_NE; 169 aux->target = done; 170 br->extra.aux = aux; 171 } 172 { 173 Operand o[] = {local_op(a, tc.i64)}; 174 CgIrInst* li = emit_ops(cg, CG_IR_LOAD_IMM, o, 1); 175 li->extra.imm = 11; 176 } 177 { 178 CgIrInst* label = cg_ir_emit(cg, CG_IR_LABEL, (SrcLoc){0, 0, 0}); 179 label->extra.imm = (i64)done; 180 cg_ir_func_note_label_place(cg, done, (SrcLoc){0, 0, 0}); 181 } 182 ret_local(cg, a); 183 s = run_leaf(&tc, cg, &ret); 184 EXPECT(s == KIT_INTERP_DONE, "branch: status %d", (int)s); 185 EXPECT(ret == 11, "branch: expected 11, got %lld", (long long)ret); 186 tc_fini(&tc); 187 } 188 189 /* ============================================================================ 190 * Spec conformance: the interpreter is the reference implementation of the IR. 191 * 192 * Each case builds a PARAMETERIZED CgIrFunc and runs it through 193 * opt_run_o1_interp + the engine with RUNTIME argument values, so the optimizer 194 * cannot constant-fold the operation away — the engine's own handler computes 195 * the result. We then assert the exact value the spec mandates for that edge 196 * (doc/IR.md "Well-definedness: edge-case semantics", portable mode). These 197 * lock the engine to the spec; a divergence turns a case red. 198 * ========================================================================== */ 199 200 static u32 ty_size(TestCtx* tc, KitCgTypeId t) { 201 return (u32)kit_cg_type_size((KitCompiler*)tc->c, t); 202 } 203 static u32 ty_align(TestCtx* tc, KitCgTypeId t) { 204 return (u32)kit_cg_type_align((KitCompiler*)tc->c, t); 205 } 206 207 /* New function with `np` scalar params; fills out_params[] with the param 208 * locals (readable directly as source operands). The interpreter assigns each 209 * param's storage home from the optimizer's local map (not from fn_type's ABI), 210 * so the leaf func type used here mirrors new_func and needs no real func type. 211 */ 212 static CgIrFunc* new_func_p(TestCtx* tc, KitCgTypeId ret, 213 const KitCgTypeId* ptypes, u32 np, 214 CGLocal* out_params) { 215 CGFuncDesc fd; 216 CGParamDesc* pds; 217 CgIrFunc* f; 218 u32 i; 219 memset(&fd, 0, sizeof fd); 220 pds = np ? arena_array(tc->c->tu, CGParamDesc, np) : NULL; 221 for (i = 0; i < np; ++i) { 222 memset(&pds[i], 0, sizeof pds[i]); 223 pds[i].index = i; 224 pds[i].type = ptypes[i]; 225 pds[i].size = ty_size(tc, ptypes[i]); 226 pds[i].align = ty_align(tc, ptypes[i]); 227 } 228 fd.fn_type = ret; 229 fd.result_type = ret; 230 fd.params = pds; 231 fd.nparams = np; 232 f = cg_ir_func_new(tc->c, &fd); 233 for (i = 0; i < np; ++i) { 234 CGLocalDesc ld; 235 CGLocal loc; 236 memset(&ld, 0, sizeof ld); 237 ld.type = ptypes[i]; 238 ld.size = ty_size(tc, ptypes[i]); 239 ld.align = ty_align(tc, ptypes[i]); 240 loc = cg_ir_func_add_local(f, &ld, 1, i); 241 cg_ir_func_add_param(f, loc, &pds[i]); 242 out_params[i] = loc; 243 } 244 return f; 245 } 246 247 static CGLocal add_local_ty(CgIrFunc* f, TestCtx* tc, KitCgTypeId t) { 248 CGLocalDesc d; 249 memset(&d, 0, sizeof d); 250 d.type = t; 251 d.size = ty_size(tc, t); 252 d.align = ty_align(tc, t); 253 return cg_ir_func_add_local(f, &d, 0, 0); 254 } 255 256 static KitInterpStatus run_args(TestCtx* tc, CgIrFunc* cg, const u64* args, 257 u32 nargs, int64_t* out) { 258 KitInterpProgram* prog = kit_interp_program_new(tc->c); 259 Func* f = opt_run_o1_interp(tc->c, cg); 260 InterpFunc* fn = 261 interp_lower((InterpProgram*)prog, f, OBJ_SYM_NONE, SLICE_NULL, NULL); 262 KitInterpStatus s = 263 kit_interp_call_args(prog, (KitInterpFunc*)fn, args, nargs, out); 264 kit_interp_program_free(prog); 265 return s; 266 } 267 268 static void emit_binop(CgIrFunc* f, BinOp op, CGLocal d, KitCgTypeId ty, 269 Operand a, Operand b) { 270 Operand o[3]; 271 CgIrInst* in; 272 o[0] = local_op(d, ty); 273 o[1] = a; 274 o[2] = b; 275 in = emit_ops(f, CG_IR_BINOP, o, 3); 276 in->extra.imm = (i64)op; 277 } 278 static void emit_unop(CgIrFunc* f, UnOp op, CGLocal d, KitCgTypeId ty, 279 Operand a) { 280 Operand o[2]; 281 CgIrInst* in; 282 o[0] = local_op(d, ty); 283 o[1] = a; 284 in = emit_ops(f, CG_IR_UNOP, o, 2); 285 in->extra.imm = (i64)op; 286 } 287 static void emit_cmp(CgIrFunc* f, CmpOp op, CGLocal d, KitCgTypeId dty, 288 Operand a, Operand b) { 289 Operand o[3]; 290 CgIrInst* in; 291 o[0] = local_op(d, dty); 292 o[1] = a; 293 o[2] = b; 294 in = emit_ops(f, CG_IR_CMP, o, 3); 295 in->extra.imm = (i64)op; 296 } 297 static void emit_convert(CgIrFunc* f, ConvKind k, CGLocal d, KitCgTypeId dty, 298 Operand src) { 299 Operand o[2]; 300 CgIrInst* in; 301 o[0] = local_op(d, dty); 302 o[1] = src; 303 in = emit_ops(f, CG_IR_CONVERT, o, 2); 304 in->extra.imm = (i64)k; 305 } 306 static void emit_intrin1(CgIrFunc* f, IntrinKind k, CGLocal d, KitCgTypeId dty, 307 Operand arg) { 308 CgIrInst* in = cg_ir_emit(f, CG_IR_INTRINSIC, (SrcLoc){0, 0, 0}); 309 CgIrIntrinsicAux* aux = arena_znew(f->arena, CgIrIntrinsicAux); 310 Operand dsts[1]; 311 Operand args[1]; 312 dsts[0] = local_op(d, dty); 313 args[0] = arg; 314 aux->kind = k; 315 aux->dsts = cg_ir_dup_operands(f->arena, dsts, 1); 316 aux->args = cg_ir_dup_operands(f->arena, args, 1); 317 aux->ndst = 1; 318 aux->narg = 1; 319 in->extra.aux = aux; 320 } 321 322 /* Run a unary i32->i32 op f(x)=OP(x); return the low 32 bits of the result. */ 323 static u32 run_un_i32(TestCtx* tc, BinOp bo, int use_unop, UnOp uo, u32 x) { 324 CGLocal p[1]; 325 CGLocal r; 326 CgIrFunc* f; 327 u64 args[1]; 328 int64_t out = 0; 329 KitCgTypeId i32 = tc->i32; 330 f = new_func_p(tc, i32, &i32, 1, p); 331 r = add_local_ty(f, tc, i32); 332 if (use_unop) 333 emit_unop(f, uo, r, i32, local_op(p[0], i32)); 334 else 335 emit_binop(f, bo, r, i32, local_op(p[0], i32), local_op(p[0], i32)); 336 ret_local(f, r); 337 args[0] = x; 338 (void)run_args(tc, f, args, 1, &out); 339 return (u32)(u64)out; 340 } 341 342 /* Run a binary i32 op f(x,y)=x OP y; report status + low-32 result. */ 343 static KitInterpStatus run_bin_i32(TestCtx* tc, BinOp bo, u32 x, u32 y, 344 u32* res) { 345 CGLocal p[2]; 346 CGLocal r; 347 CgIrFunc* f; 348 u64 args[2]; 349 int64_t out = 0; 350 KitInterpStatus s; 351 KitCgTypeId i32 = tc->i32; 352 KitCgTypeId pt[2]; 353 pt[0] = i32; 354 pt[1] = i32; 355 f = new_func_p(tc, i32, pt, 2, p); 356 r = add_local_ty(f, tc, i32); 357 emit_binop(f, bo, r, i32, local_op(p[0], i32), local_op(p[1], i32)); 358 ret_local(f, r); 359 args[0] = x; 360 args[1] = y; 361 s = run_args(tc, f, args, 2, &out); 362 *res = (u32)(u64)out; 363 return s; 364 } 365 366 /* integer wrapping + shift masking (spec: portable). */ 367 static void spec_int_wrap_shift(void) { 368 TestCtx tc; 369 u32 res = 0; 370 tc_init(&tc); 371 /* imul wraps mod 2^32: 0x10000 * 0x10000 = 2^32 -> 0 */ 372 EXPECT( 373 run_bin_i32(&tc, BO_IMUL, 0x10000u, 0x10000u, &res) == KIT_INTERP_DONE && 374 res == 0u, 375 "imul wrap: got 0x%08x", res); 376 /* iadd wraps: 0xffffffff + 1 = 0 */ 377 EXPECT(run_bin_i32(&tc, BO_IADD, 0xffffffffu, 1u, &res) == KIT_INTERP_DONE && 378 res == 0u, 379 "iadd wrap: got 0x%08x", res); 380 /* shl count reduced mod 32: 1 << 33 == 1 << 1 == 2 */ 381 EXPECT( 382 run_bin_i32(&tc, BO_SHL, 1u, 33u, &res) == KIT_INTERP_DONE && res == 2u, 383 "shl mask: got 0x%08x", res); 384 /* shr_u count mod 32: 0x80000000 >> 33 == >> 1 == 0x40000000 */ 385 EXPECT( 386 run_bin_i32(&tc, BO_SHR_U, 0x80000000u, 33u, &res) == KIT_INTERP_DONE && 387 res == 0x40000000u, 388 "shr_u mask: got 0x%08x", res); 389 /* shr_s arithmetic (sign-replicating): -256 >> 4 == -16 */ 390 EXPECT(run_bin_i32(&tc, BO_SHR_S, (u32)(-256), 4u, &res) == KIT_INTERP_DONE && 391 res == (u32)(-16), 392 "shr_s arith: got 0x%08x", res); 393 /* neg INT_MIN wraps to INT_MIN (two's complement, no trap) */ 394 EXPECT(run_un_i32(&tc, BO_IADD, 1, UO_NEG, 0x80000000u) == 0x80000000u, 395 "neg INT_MIN wrap"); 396 tc_fini(&tc); 397 } 398 399 /* division / remainder edges (spec: portable -> div-by-zero traps, 400 * INT_MIN/-1 wraps). */ 401 static void spec_div_edges(void) { 402 TestCtx tc; 403 u32 res = 0; 404 tc_init(&tc); 405 /* sdiv by zero traps */ 406 EXPECT(run_bin_i32(&tc, BO_SDIV, 10u, 0u, &res) == KIT_INTERP_TRAP, 407 "sdiv/0 should trap"); 408 /* udiv by zero traps */ 409 EXPECT(run_bin_i32(&tc, BO_UDIV, 10u, 0u, &res) == KIT_INTERP_TRAP, 410 "udiv/0 should trap"); 411 /* srem by zero traps */ 412 EXPECT(run_bin_i32(&tc, BO_SREM, 10u, 0u, &res) == KIT_INTERP_TRAP, 413 "srem/0 should trap"); 414 /* INT_MIN / -1 wraps to INT_MIN, no trap */ 415 EXPECT(run_bin_i32(&tc, BO_SDIV, 0x80000000u, 0xffffffffu, &res) == 416 KIT_INTERP_DONE && 417 res == 0x80000000u, 418 "INT_MIN/-1 wrap: got 0x%08x", res); 419 /* INT_MIN %% -1 == 0, no trap */ 420 EXPECT(run_bin_i32(&tc, BO_SREM, 0x80000000u, 0xffffffffu, &res) == 421 KIT_INTERP_DONE && 422 res == 0u, 423 "INT_MIN%%-1: got 0x%08x", res); 424 /* ordinary signed divide truncates toward zero: -7 / 2 == -3 */ 425 EXPECT(run_bin_i32(&tc, BO_SDIV, (u32)(-7), 2u, &res) == KIT_INTERP_DONE && 426 res == (u32)(-3), 427 "sdiv trunc: got 0x%08x", res); 428 tc_fini(&tc); 429 } 430 431 /* clz/ctz at zero are defined to equal the bit width (stronger than C). */ 432 static void spec_clz_ctz_zero(void) { 433 TestCtx tc; 434 CGLocal p[1]; 435 CGLocal r; 436 CgIrFunc* f; 437 u64 args[1]; 438 int64_t out; 439 KitCgTypeId i32; 440 tc_init(&tc); 441 i32 = tc.i32; 442 /* clz(0) == 32 */ 443 f = new_func_p(&tc, i32, &i32, 1, p); 444 r = add_local_ty(f, &tc, i32); 445 emit_intrin1(f, INTRIN_CLZ, r, i32, local_op(p[0], i32)); 446 ret_local(f, r); 447 args[0] = 0; 448 out = -1; 449 EXPECT(run_args(&tc, f, args, 1, &out) == KIT_INTERP_DONE && (u32)out == 32u, 450 "clz(0)==32: got %lld", (long long)out); 451 /* ctz(0) == 32 */ 452 f = new_func_p(&tc, i32, &i32, 1, p); 453 r = add_local_ty(f, &tc, i32); 454 emit_intrin1(f, INTRIN_CTZ, r, i32, local_op(p[0], i32)); 455 ret_local(f, r); 456 args[0] = 0; 457 out = -1; 458 EXPECT(run_args(&tc, f, args, 1, &out) == KIT_INTERP_DONE && (u32)out == 32u, 459 "ctz(0)==32: got %lld", (long long)out); 460 tc_fini(&tc); 461 } 462 463 static u64 dbits(double d) { 464 u64 u; 465 memcpy(&u, &d, 8); 466 return u; 467 } 468 static double bitsd(u64 u) { 469 double d; 470 memcpy(&d, &u, 8); 471 return d; 472 } 473 474 /* float->int conversion saturates; NaN -> 0 (spec: portable ftoi). */ 475 static u32 run_ftoi(TestCtx* tc, ConvKind k, double in, KitInterpStatus* sp) { 476 CGLocal p[1]; 477 CGLocal r; 478 CgIrFunc* f; 479 u64 args[1]; 480 int64_t out = 0; 481 KitCgTypeId f64 = tc->f64; 482 KitCgTypeId i32 = tc->i32; 483 f = new_func_p(tc, i32, &f64, 1, p); 484 r = add_local_ty(f, tc, i32); 485 emit_convert(f, k, r, i32, local_op(p[0], f64)); 486 ret_local(f, r); 487 args[0] = dbits(in); 488 *sp = run_args(tc, f, args, 1, &out); 489 return (u32)(u64)out; 490 } 491 492 static void spec_ftoi_sat(void) { 493 TestCtx tc; 494 KitInterpStatus s; 495 double nan = bitsd(0x7ff8000000000000ull); 496 tc_init(&tc); 497 EXPECT( 498 run_ftoi(&tc, CV_FTOI_S, 1e30, &s) == 0x7fffffffu && s == KIT_INTERP_DONE, 499 "ftoi_s overflow -> INT_MAX"); 500 EXPECT(run_ftoi(&tc, CV_FTOI_S, -1e30, &s) == 0x80000000u && 501 s == KIT_INTERP_DONE, 502 "ftoi_s underflow -> INT_MIN"); 503 EXPECT(run_ftoi(&tc, CV_FTOI_S, nan, &s) == 0u && s == KIT_INTERP_DONE, 504 "ftoi_s NaN -> 0"); 505 EXPECT( 506 run_ftoi(&tc, CV_FTOI_S, -7.9, &s) == (u32)(-7) && s == KIT_INTERP_DONE, 507 "ftoi_s trunc toward zero"); 508 EXPECT(run_ftoi(&tc, CV_FTOI_U, -1.0, &s) == 0u && s == KIT_INTERP_DONE, 509 "ftoi_u negative -> 0"); 510 EXPECT( 511 run_ftoi(&tc, CV_FTOI_U, 1e30, &s) == 0xffffffffu && s == KIT_INTERP_DONE, 512 "ftoi_u overflow -> UINT_MAX"); 513 tc_fini(&tc); 514 } 515 516 /* FP compares: relationals + eq are ordered (NaN -> false); ne is unordered 517 * (NaN -> true). */ 518 static int run_fcmp(TestCtx* tc, CmpOp op, double a, double b) { 519 CGLocal p[2]; 520 CGLocal r; 521 CgIrFunc* f; 522 u64 args[2]; 523 int64_t out = 0; 524 KitCgTypeId f64 = tc->f64; 525 KitCgTypeId i32 = tc->i32; 526 KitCgTypeId pt[2]; 527 pt[0] = f64; 528 pt[1] = f64; 529 f = new_func_p(tc, i32, pt, 2, p); 530 r = add_local_ty(f, tc, i32); 531 emit_cmp(f, op, r, i32, local_op(p[0], f64), local_op(p[1], f64)); 532 ret_local(f, r); 533 args[0] = dbits(a); 534 args[1] = dbits(b); 535 (void)run_args(tc, f, args, 2, &out); 536 return (int)(u32)out; 537 } 538 539 static void spec_fp_cmp_nan(void) { 540 TestCtx tc; 541 double nan = bitsd(0x7ff8000000000000ull); 542 tc_init(&tc); 543 /* Ordered relationals + OEQ are false on NaN; the unordered duals are true. 544 * Each predicate is checked against NaN-lhs, NaN-rhs, both-NaN, ordered, and 545 * a -0.0/0.0 boundary so the backend's ordered/unordered split is exercised 546 * end to end. */ 547 EXPECT(run_fcmp(&tc, CMP_OLT_F, nan, 1.0) == 0, "olt NaN-lhs -> false"); 548 EXPECT(run_fcmp(&tc, CMP_OGE_F, 1.0, nan) == 0, "oge NaN-rhs -> false"); 549 EXPECT(run_fcmp(&tc, CMP_OEQ_F, nan, nan) == 0, "oeq both-NaN -> false"); 550 EXPECT(run_fcmp(&tc, CMP_UNE_F, nan, nan) == 1, "une both-NaN -> true"); 551 EXPECT(run_fcmp(&tc, CMP_OEQ_F, -0.0, 0.0) == 1, "oeq -0.0 == 0.0 -> true"); 552 EXPECT(run_fcmp(&tc, CMP_OLT_F, 1.0, 2.0) == 1, "olt ordinary -> true"); 553 554 /* Ordered predicates: false on any NaN, normal otherwise. */ 555 EXPECT(run_fcmp(&tc, CMP_OEQ_F, 1.0, 1.0) == 1, "oeq 1==1 -> true"); 556 EXPECT(run_fcmp(&tc, CMP_ONE_F, 1.0, 2.0) == 1, "one 1!=2 -> true"); 557 EXPECT(run_fcmp(&tc, CMP_ONE_F, 1.0, 1.0) == 0, "one 1!=1 -> false"); 558 EXPECT(run_fcmp(&tc, CMP_ONE_F, nan, 1.0) == 0, "one NaN -> false"); 559 EXPECT(run_fcmp(&tc, CMP_OLE_F, 1.0, 1.0) == 1, "ole 1<=1 -> true"); 560 EXPECT(run_fcmp(&tc, CMP_OLE_F, 2.0, 1.0) == 0, "ole 2<=1 -> false"); 561 EXPECT(run_fcmp(&tc, CMP_OLE_F, nan, 1.0) == 0, "ole NaN -> false"); 562 EXPECT(run_fcmp(&tc, CMP_OGT_F, 2.0, 1.0) == 1, "ogt 2>1 -> true"); 563 EXPECT(run_fcmp(&tc, CMP_OGT_F, 1.0, nan) == 0, "ogt NaN-rhs -> false"); 564 EXPECT(run_fcmp(&tc, CMP_OGE_F, 1.0, 1.0) == 1, "oge 1>=1 -> true"); 565 566 /* Unordered predicates: true on any NaN, ordered result otherwise. */ 567 EXPECT(run_fcmp(&tc, CMP_UEQ_F, nan, 1.0) == 1, "ueq NaN -> true"); 568 EXPECT(run_fcmp(&tc, CMP_UEQ_F, 1.0, 2.0) == 0, "ueq 1==2 ordered -> false"); 569 EXPECT(run_fcmp(&tc, CMP_UEQ_F, 1.0, 1.0) == 1, "ueq 1==1 ordered -> true"); 570 EXPECT(run_fcmp(&tc, CMP_UNE_F, 1.0, 1.0) == 0, "une 1!=1 ordered -> false"); 571 EXPECT(run_fcmp(&tc, CMP_ULT_F, nan, 1.0) == 1, "ult NaN-lhs -> true"); 572 EXPECT(run_fcmp(&tc, CMP_ULT_F, 1.0, 2.0) == 1, "ult 1<2 -> true"); 573 EXPECT(run_fcmp(&tc, CMP_ULT_F, 2.0, 1.0) == 0, "ult 2<1 -> false"); 574 EXPECT(run_fcmp(&tc, CMP_ULE_F, 1.0, nan) == 1, "ule NaN-rhs -> true"); 575 EXPECT(run_fcmp(&tc, CMP_ULE_F, 1.0, 1.0) == 1, "ule 1<=1 -> true"); 576 EXPECT(run_fcmp(&tc, CMP_ULE_F, 2.0, 1.0) == 0, "ule 2<=1 -> false"); 577 EXPECT(run_fcmp(&tc, CMP_UGT_F, nan, nan) == 1, "ugt both-NaN -> true"); 578 EXPECT(run_fcmp(&tc, CMP_UGT_F, 2.0, 1.0) == 1, "ugt 2>1 -> true"); 579 EXPECT(run_fcmp(&tc, CMP_UGT_F, 1.0, 2.0) == 0, "ugt 1>2 -> false"); 580 EXPECT(run_fcmp(&tc, CMP_UGE_F, nan, 1.0) == 1, "uge NaN-lhs -> true"); 581 EXPECT(run_fcmp(&tc, CMP_UGE_F, 1.0, 1.0) == 1, "uge 1>=1 -> true"); 582 EXPECT(run_fcmp(&tc, CMP_UGE_F, 1.0, 2.0) == 0, "uge 1>=2 -> false"); 583 tc_fini(&tc); 584 } 585 586 /* fneg flips the sign bit (not 0 - x); fdiv follows IEEE. */ 587 static void spec_fneg_fdiv(void) { 588 TestCtx tc; 589 CGLocal p[2]; 590 CGLocal r; 591 CgIrFunc* f; 592 u64 args[2]; 593 int64_t out; 594 KitCgTypeId f64; 595 KitCgTypeId pt[2]; 596 tc_init(&tc); 597 f64 = tc.f64; 598 /* fneg(+0.0) -> -0.0 (sign bit set), proving it is not 0 - x */ 599 f = new_func_p(&tc, f64, &f64, 1, p); 600 r = add_local_ty(f, &tc, f64); 601 emit_unop(f, UO_FNEG, r, f64, local_op(p[0], f64)); 602 ret_local(f, r); 603 args[0] = dbits(0.0); 604 out = 0; 605 EXPECT(run_args(&tc, f, args, 1, &out) == KIT_INTERP_DONE && 606 (u64)out == 0x8000000000000000ull, 607 "fneg(+0.0) -> -0.0: got 0x%016llx", (unsigned long long)(u64)out); 608 /* fdiv 1.0/0.0 -> +inf */ 609 pt[0] = f64; 610 pt[1] = f64; 611 f = new_func_p(&tc, f64, pt, 2, p); 612 r = add_local_ty(f, &tc, f64); 613 emit_binop(f, BO_FDIV, r, f64, local_op(p[0], f64), local_op(p[1], f64)); 614 ret_local(f, r); 615 args[0] = dbits(1.0); 616 args[1] = dbits(0.0); 617 out = 0; 618 EXPECT(run_args(&tc, f, args, 2, &out) == KIT_INTERP_DONE && 619 (u64)out == 0x7ff0000000000000ull, 620 "fdiv 1/0 -> +inf: got 0x%016llx", (unsigned long long)(u64)out); 621 /* fdiv 0.0/0.0 -> NaN */ 622 args[0] = dbits(0.0); 623 args[1] = dbits(0.0); 624 out = 0; 625 (void)run_args(&tc, f, args, 2, &out); 626 EXPECT(bitsd((u64)out) != bitsd((u64)out), "fdiv 0/0 -> NaN"); 627 tc_fini(&tc); 628 } 629 630 int main(void) { 631 kit_unit_init(&g_u); 632 g_u.ctx.now = -1; 633 interp_runs_arithmetic(); 634 interp_runs_branch(); 635 spec_int_wrap_shift(); 636 spec_div_edges(); 637 spec_clz_ctz_zero(); 638 spec_ftoi_sat(); 639 spec_fp_cmp_nan(); 640 spec_fneg_fdiv(); 641 if (g_u.fails) { 642 fprintf(stderr, "interp-smoke: %d/%d failed\n", g_u.fails, g_u.checks); 643 return 1; 644 } 645 printf("interp-smoke: %d checks, 0 failures\n", g_u.checks); 646 return 0; 647 }