kit

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

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 }