kit

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

cg_fp_cmp_test.c (10637B)


      1 /* cg_fp_cmp_test — drives every public KitCgFpCmpOp predicate through
      2  * kit_cg_fp_cmp. The six discriminating predicates (ULT/ULE/UGT/UGE/UEQ/ONE)
      3  * cannot be produced by any C or toy source — they are reachable *only* through
      4  * this entry point — so this is the guard against the "advertise-but-ignore"
      5  * gap the public surface used to have (api_map_fp_cmp collapsed all 12 down to
      6  * 6, dropping the ordered/unordered distinction before any backend saw it).
      7  *
      8  * Two kinds of coverage:
      9  *
     10  *   Execution — build `int f(double,double){ return pred(a,b); }` (and an f128
     11  *     variant that fpext's the operands to long double), capture each into an
     12  *     in-process interpreter, and assert per-predicate results for a spread of
     13  *     NaN / ordinary / signed-zero operands. Host-independent: the interpreter
     14  *     runs the target-independent IR, so this validates the public->internal
     15  *     mapping and the engine for all 12 predicates, scalar and f128 (§6).
     16  *
     17  *   Emission — build the same functions for every native and special backend
     18  *     (aarch64 / x86-64 / riscv64 / wasm32 / C-source) at -O0 and -O1 and
     19  *     finalize the object. A backend that mishandles a new unordered opcode
     20  *     panics, which aborts (and fails) the test — this is what catches the
     21  *     wasm FP-eq/ne gap (§5) and the silent default-arm dispatch tables.
     22  *
     23  * Run by: make test-cg-api
     24  */
     25 
     26 #include <kit/cg.h>
     27 #include <kit/core.h>
     28 #include <kit/interp.h>
     29 #include <kit/object.h>
     30 #include <stdint.h>
     31 #include <stdio.h>
     32 #include <string.h>
     33 
     34 #include "lib/kit_unit.h"
     35 
     36 static KitUnit g_u;
     37 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     38 
     39 /* ---- the 12 predicates, in KitCgFpCmpOp order ---------------------- */
     40 
     41 typedef struct {
     42   KitCgFpCmpOp op;
     43   const char* name;
     44 } Pred;
     45 
     46 static const Pred PREDS[] = {
     47     {KIT_CG_FP_OEQ, "oeq"}, {KIT_CG_FP_ONE, "one"}, {KIT_CG_FP_OLT, "olt"},
     48     {KIT_CG_FP_OLE, "ole"}, {KIT_CG_FP_OGT, "ogt"}, {KIT_CG_FP_OGE, "oge"},
     49     {KIT_CG_FP_UEQ, "ueq"}, {KIT_CG_FP_UNE, "une"}, {KIT_CG_FP_ULT, "ult"},
     50     {KIT_CG_FP_ULE, "ule"}, {KIT_CG_FP_UGT, "ugt"}, {KIT_CG_FP_UGE, "uge"},
     51 };
     52 enum { NPRED = (int)(sizeof PREDS / sizeof PREDS[0]) };
     53 
     54 /* IEEE-correct reference result (mirrors src/interp/engine.c do_cmp). */
     55 static int fp_expected(KitCgFpCmpOp op, double a, double b) {
     56   int uno = (a != a) || (b != b); /* either operand is NaN */
     57   switch (op) {
     58     case KIT_CG_FP_OEQ:
     59       return a == b;
     60     case KIT_CG_FP_ONE:
     61       return !uno && (a != b);
     62     case KIT_CG_FP_OLT:
     63       return a < b;
     64     case KIT_CG_FP_OLE:
     65       return a <= b;
     66     case KIT_CG_FP_OGT:
     67       return a > b;
     68     case KIT_CG_FP_OGE:
     69       return a >= b;
     70     case KIT_CG_FP_UEQ:
     71       return uno || (a == b);
     72     case KIT_CG_FP_UNE:
     73       return a != b;
     74     case KIT_CG_FP_ULT:
     75       return uno || (a < b);
     76     case KIT_CG_FP_ULE:
     77       return uno || (a <= b);
     78     case KIT_CG_FP_UGT:
     79       return uno || (a > b);
     80     case KIT_CG_FP_UGE:
     81       return uno || (a >= b);
     82   }
     83   return 0;
     84 }
     85 
     86 /* ---- operand spread: bit patterns so NaN/-0.0 are exact ------------- */
     87 
     88 typedef struct {
     89   uint64_t bits;
     90   const char* desc;
     91 } Operand64;
     92 
     93 static const Operand64 OPS[] = {
     94     {0x7ff8000000000000ull, "nan"}, {0x3ff0000000000000ull, "1.0"},
     95     {0x4000000000000000ull, "2.0"}, {0x8000000000000000ull, "-0.0"},
     96     {0x0000000000000000ull, "0.0"},
     97 };
     98 enum { NOPS = (int)(sizeof OPS / sizeof OPS[0]) };
     99 
    100 static double bits_to_double(uint64_t b) {
    101   double d;
    102   memcpy(&d, &b, sizeof d);
    103   return d;
    104 }
    105 
    106 /* ---- build `int f(double,double){ return pred(a,b); }` -------------- *
    107  * When use_f128 is set the loaded f64 operands are fpext'd to long double
    108  * before the compare, exercising the soft-float libcall path. Returns the
    109  * declared symbol; the function is captured by `name` for interp lookup. */
    110 static void build_cmp_fn(KitCompiler* c, KitCg* cg, const char* name,
    111                          KitCgFpCmpOp op, int use_f128) {
    112   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    113   KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32];
    114   KitCgTypeId f64 = bi.id[KIT_CG_BUILTIN_F64];
    115   KitCgTypeId f128 = bi.id[KIT_CG_BUILTIN_F128];
    116   KitCgFuncParam params[2];
    117   KitCgFuncResult result;
    118   KitCgFuncSig sig;
    119   KitCgDecl decl;
    120   KitCgSym sym;
    121   KitCgLocalAttrs la;
    122   KitCgLocal p0, p1;
    123   KitCgMemAccess ma;
    124 
    125   memset(params, 0, sizeof params);
    126   params[0].type = f64;
    127   params[1].type = f64;
    128   memset(&result, 0, sizeof result);
    129   result.type = i32;
    130   memset(&sig, 0, sizeof sig);
    131   sig.result = result;
    132   sig.params = params;
    133   sig.nparams = 2;
    134   sig.call_conv = KIT_CG_CC_TARGET_C;
    135 
    136   memset(&decl, 0, sizeof decl);
    137   decl.kind = KIT_CG_DECL_FUNC;
    138   decl.linkage_name = kit_sym_intern(c, kit_slice_cstr(name));
    139   decl.display_name = decl.linkage_name;
    140   decl.type = kit_cg_type_func(c, sig);
    141   decl.sym.bind = KIT_SB_GLOBAL;
    142   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    143   sym = kit_cg_decl(cg, decl);
    144   EXPECT(sym != KIT_CG_SYM_NONE, "%s: decl failed", name);
    145 
    146   kit_cg_func_begin(cg, sym);
    147   memset(&la, 0, sizeof la);
    148   p0 = kit_cg_param(cg, 0, f64, la);
    149   p1 = kit_cg_param(cg, 1, f64, la);
    150 
    151   memset(&ma, 0, sizeof ma);
    152   ma.type = f64;
    153   ma.align = kit_cg_type_align(c, f64);
    154 
    155   kit_cg_push_local(cg, p0);
    156   kit_cg_load(cg, ma);
    157   if (use_f128) kit_cg_fpext(cg, f128);
    158   kit_cg_push_local(cg, p1);
    159   kit_cg_load(cg, ma);
    160   if (use_f128) kit_cg_fpext(cg, f128);
    161   kit_cg_fp_cmp(cg, op);
    162   kit_cg_ret(cg);
    163   kit_cg_func_end(cg);
    164 }
    165 
    166 /* ---- Execution coverage (in-process interpreter) -------------------- */
    167 
    168 /* The interpreter captures functions during the optimizer's emit pass, which
    169  * only runs at opt_level >= 1 (matching `kit run --no-jit`), and the capture
    170  * happens for the non-aarch64 codegen path — so we build for x86-64 here. The
    171  * interpreter runs the *target-independent* IR, so the result is the host-
    172  * independent semantics of every predicate regardless of this capture target.
    173  *
    174  * Scalar (f64) only: the f128 path lowers each compare to a soft-float libcall
    175  * (__eqtf2/__unordtf2/...), which the bare interpreter cannot resolve without a
    176  * linked runtime. The f128 lowering is covered by run_emit (emission) and, end
    177  * to end, by the `long double` cases in the parse suite under JIT. */
    178 static void run_exec(void) {
    179   KitTargetSpec tgt =
    180       kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    181   KitCompiler* c = NULL;
    182   KitInterpProgram* pp;
    183   KitObjBuilder* ob = NULL;
    184   KitCg* cg = NULL;
    185   KitCodeOptions opts;
    186   const char* tag = "f64";
    187   int i, j, k;
    188   char nm[32];
    189 
    190   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    191     EXPECT(0, "%s: compiler_new failed", tag);
    192     return;
    193   }
    194   pp = kit_interp_program_new(c);
    195   EXPECT(pp != NULL, "%s: interp_program_new failed", tag);
    196   kit_interp_program_attach(pp, c);
    197 
    198   EXPECT(kit_obj_builder_new(c, &ob) == KIT_OK && ob, "%s: obj_new", tag);
    199   EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "%s: cg_new", tag);
    200   memset(&opts, 0, sizeof opts);
    201   opts.opt_level = 1; /* interp capture requires the optimizer pass */
    202   kit_cg_begin(cg, ob, &opts);
    203 
    204   for (i = 0; i < NPRED; ++i) {
    205     snprintf(nm, sizeof nm, "cmp_%s_%d", tag, i);
    206     build_cmp_fn(c, cg, nm, PREDS[i].op, /*use_f128=*/0);
    207   }
    208   EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "%s: finish", tag);
    209   EXPECT(kit_cg_detach(cg) == KIT_OK, "%s: detach", tag);
    210 
    211   for (i = 0; i < NPRED; ++i) {
    212     KitInterpFunc* fn;
    213     snprintf(nm, sizeof nm, "cmp_%s_%d", tag, i);
    214     fn = kit_interp_lookup(pp, kit_slice_cstr(nm));
    215     EXPECT(fn != NULL, "%s: %s not captured", tag, PREDS[i].name);
    216     if (!fn) continue;
    217     for (j = 0; j < NOPS; ++j) {
    218       for (k = 0; k < NOPS; ++k) {
    219         uint64_t args[2] = {OPS[j].bits, OPS[k].bits};
    220         int64_t ret = -1;
    221         int want = fp_expected(PREDS[i].op, bits_to_double(OPS[j].bits),
    222                                bits_to_double(OPS[k].bits));
    223         KitInterpStatus s = kit_interp_call_args(pp, fn, args, 2, &ret);
    224         EXPECT(s == KIT_INTERP_DONE && (int)ret == want,
    225                "%s %s(%s,%s): want %d got %lld (status %d)", tag, PREDS[i].name,
    226                OPS[j].desc, OPS[k].desc, want, (long long)ret, (int)s);
    227       }
    228     }
    229   }
    230 
    231   kit_cg_free(cg);
    232   kit_obj_builder_free(ob);
    233   kit_interp_program_free(pp);
    234   kit_compiler_free(c);
    235 }
    236 
    237 /* ---- Emission coverage (every backend, no execution) ---------------- */
    238 
    239 static void run_emit(KitArchKind arch, KitOSKind os, KitObjFmt fmt,
    240                      const char* tag, int opt_level, int do_f128) {
    241   KitTargetSpec tgt = kit_unit_target(arch, os, fmt);
    242   KitCompiler* c = NULL;
    243   KitObjBuilder* ob = NULL;
    244   KitCg* cg = NULL;
    245   KitCodeOptions opts;
    246   int i;
    247   char nm[40];
    248 
    249   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    250     EXPECT(0, "%s/O%d: compiler_new failed", tag, opt_level);
    251     return;
    252   }
    253   EXPECT(kit_obj_builder_new(c, &ob) == KIT_OK && ob, "%s: obj_new", tag);
    254   EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "%s: cg_new", tag);
    255   memset(&opts, 0, sizeof opts);
    256   opts.opt_level = opt_level;
    257   kit_cg_begin(cg, ob, &opts);
    258 
    259   for (i = 0; i < NPRED; ++i) {
    260     snprintf(nm, sizeof nm, "emit_%s_o%d_f64_%d", tag, opt_level, i);
    261     build_cmp_fn(c, cg, nm, PREDS[i].op, /*use_f128=*/0);
    262     if (do_f128) {
    263       snprintf(nm, sizeof nm, "emit_%s_o%d_f128_%d", tag, opt_level, i);
    264       build_cmp_fn(c, cg, nm, PREDS[i].op, /*use_f128=*/1);
    265     }
    266   }
    267   /* If any backend mishandles a new opcode it panics here (aborting the test);
    268    * otherwise the object finalizes cleanly. */
    269   EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "%s/O%d: finish failed", tag,
    270          opt_level);
    271   EXPECT(kit_cg_detach(cg) == KIT_OK, "%s/O%d: detach failed", tag,
    272          opt_level);
    273 
    274   kit_cg_free(cg);
    275   kit_obj_builder_free(ob);
    276   kit_compiler_free(c);
    277 }
    278 
    279 int main(void) {
    280   int opt;
    281   kit_unit_init(&g_u);
    282 
    283   /* Execution: public mapping + interp semantics for all 12 scalar predicates.
    284    */
    285   run_exec();
    286 
    287   /* Emission: every native backend lowers every predicate (f64 + f128) without
    288    * panicking, at both opt levels. */
    289   for (opt = 0; opt <= 1; ++opt) {
    290     run_emit(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF, "aa64", opt, 1);
    291     run_emit(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF, "x64", opt, 1);
    292     run_emit(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF, "rv64", opt, 1);
    293   }
    294   /* wasm: O0 only (the O1 optimizer is native-target only), f64 only —
    295    * exercises the from-scratch FP eq/ne + unordered arms in the wasm backend
    296    * (emit_fp_cmp), which previously had no float eq/ne path at all. */
    297   run_emit(KIT_ARCH_WASM, KIT_OS_WASI, KIT_OBJ_WASM, "wasm", 0, 0);
    298 
    299   kit_unit_summary(&g_u, "cg_fp_cmp_test");
    300   return kit_unit_status(&g_u);
    301 }