kit

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

cg_fp_cmp_test.c (10710B)


      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   KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32);
    113   KitCgTypeId f64 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64);
    114   KitCgTypeId f128 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F128);
    115   KitCgFuncParam params[2];
    116   KitCgFuncResult result;
    117   KitCgFuncSig sig;
    118   KitCgDecl decl;
    119   KitCgSym sym;
    120   KitCgLocalAttrs la;
    121   KitCgLocal p0, p1;
    122   KitCgMemAccess ma;
    123 
    124   memset(params, 0, sizeof params);
    125   params[0].type = f64;
    126   params[1].type = f64;
    127   memset(&result, 0, sizeof result);
    128   result.type = i32;
    129   memset(&sig, 0, sizeof sig);
    130   sig.result = result;
    131   sig.params = params;
    132   sig.nparams = 2;
    133   sig.call_conv = KIT_CG_CC_TARGET_C;
    134 
    135   memset(&decl, 0, sizeof decl);
    136   decl.kind = KIT_CG_DECL_FUNC;
    137   decl.linkage_name = kit_sym_intern(c, kit_slice_cstr(name));
    138   decl.display_name = decl.linkage_name;
    139   decl.type = kit_cg_type_func(c, sig);
    140   decl.sym.bind = KIT_SB_GLOBAL;
    141   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    142   sym = kit_cg_decl(cg, decl);
    143   EXPECT(sym != KIT_CG_SYM_NONE, "%s: decl failed", name);
    144 
    145   kit_cg_func_begin(cg, sym);
    146   memset(&la, 0, sizeof la);
    147   p0 = kit_cg_param(cg, 0, f64, la);
    148   p1 = kit_cg_param(cg, 1, f64, la);
    149 
    150   memset(&ma, 0, sizeof ma);
    151   ma.type = f64;
    152   ma.align = kit_cg_type_align(c, f64);
    153 
    154   kit_cg_push_local(cg, p0);
    155   kit_cg_load(cg, ma);
    156   if (use_f128) kit_cg_fpext(cg, f128);
    157   kit_cg_push_local(cg, p1);
    158   kit_cg_load(cg, ma);
    159   if (use_f128) kit_cg_fpext(cg, f128);
    160   kit_cg_fp_cmp(cg, op);
    161   kit_cg_ret(cg);
    162   kit_cg_func_end(cg);
    163 }
    164 
    165 /* ---- Execution coverage (in-process interpreter) -------------------- */
    166 
    167 /* The interpreter captures functions during the optimizer's emit pass, which
    168  * only runs at opt_level >= 1 (matching `kit run --no-jit`), and the capture
    169  * happens for the non-aarch64 codegen path — so we build for x86-64 here. The
    170  * interpreter runs the *target-independent* IR, so the result is the host-
    171  * independent semantics of every predicate regardless of this capture target.
    172  *
    173  * Scalar (f64) only: the f128 path lowers each compare to a soft-float libcall
    174  * (__eqtf2/__unordtf2/...), which the bare interpreter cannot resolve without a
    175  * linked runtime. The f128 lowering is covered by run_emit (emission) and, end
    176  * to end, by the `long double` cases in the parse suite under JIT. */
    177 static void run_exec(void) {
    178   KitTargetSpec tgt =
    179       kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    180   KitCompiler* c = NULL;
    181   KitInterpProgram* pp;
    182   KitObjBuilder* ob = NULL;
    183   KitCg* cg = NULL;
    184   KitCodeOptions opts;
    185   const char* tag = "f64";
    186   int i, j, k;
    187   char nm[32];
    188 
    189   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    190     EXPECT(0, "%s: compiler_new failed", tag);
    191     return;
    192   }
    193   pp = kit_interp_program_new(c);
    194   EXPECT(pp != NULL, "%s: interp_program_new failed", tag);
    195   kit_interp_program_attach(pp, c);
    196 
    197   EXPECT(kit_obj_builder_new(c, &ob) == KIT_OK && ob, "%s: obj_new", tag);
    198   EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "%s: cg_new", tag);
    199   memset(&opts, 0, sizeof opts);
    200   opts.opt_level = 1; /* interp capture requires the optimizer pass */
    201   kit_cg_begin(cg, ob, &opts);
    202 
    203   for (i = 0; i < NPRED; ++i) {
    204     snprintf(nm, sizeof nm, "cmp_%s_%d", tag, i);
    205     build_cmp_fn(c, cg, nm, PREDS[i].op, /*use_f128=*/0);
    206   }
    207   EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "%s: finish", tag);
    208   EXPECT(kit_cg_detach(cg) == KIT_OK, "%s: detach", tag);
    209 
    210   for (i = 0; i < NPRED; ++i) {
    211     KitInterpFunc* fn;
    212     snprintf(nm, sizeof nm, "cmp_%s_%d", tag, i);
    213     fn = kit_interp_lookup(pp, kit_slice_cstr(nm));
    214     EXPECT(fn != NULL, "%s: %s not captured", tag, PREDS[i].name);
    215     if (!fn) continue;
    216     for (j = 0; j < NOPS; ++j) {
    217       for (k = 0; k < NOPS; ++k) {
    218         uint64_t args[2] = {OPS[j].bits, OPS[k].bits};
    219         int64_t ret = -1;
    220         int want = fp_expected(PREDS[i].op, bits_to_double(OPS[j].bits),
    221                                bits_to_double(OPS[k].bits));
    222         KitInterpStatus s = kit_interp_call_args(pp, fn, args, 2, &ret);
    223         EXPECT(s == KIT_INTERP_DONE && (int)ret == want,
    224                "%s %s(%s,%s): want %d got %lld (status %d)", tag, PREDS[i].name,
    225                OPS[j].desc, OPS[k].desc, want, (long long)ret, (int)s);
    226       }
    227     }
    228   }
    229 
    230   kit_cg_free(cg);
    231   kit_obj_builder_free(ob);
    232   kit_interp_program_free(pp);
    233   kit_compiler_free(c);
    234 }
    235 
    236 /* ---- Emission coverage (every backend, no execution) ---------------- */
    237 
    238 static void run_emit(KitArchKind arch, KitOSKind os, KitObjFmt fmt,
    239                      const char* tag, int opt_level, int do_f128) {
    240   KitTargetSpec tgt = kit_unit_target(arch, os, fmt);
    241   KitCompiler* c = NULL;
    242   KitObjBuilder* ob = NULL;
    243   KitCg* cg = NULL;
    244   KitCodeOptions opts;
    245   int i;
    246   char nm[40];
    247 
    248   if (arch == KIT_ARCH_WASM) {
    249     tgt.ptr_size = 4;
    250     tgt.ptr_align = 4;
    251   }
    252 
    253   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    254     EXPECT(0, "%s/O%d: compiler_new failed", tag, opt_level);
    255     return;
    256   }
    257   EXPECT(kit_obj_builder_new(c, &ob) == KIT_OK && ob, "%s: obj_new", tag);
    258   EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "%s: cg_new", tag);
    259   memset(&opts, 0, sizeof opts);
    260   opts.opt_level = opt_level;
    261   kit_cg_begin(cg, ob, &opts);
    262 
    263   for (i = 0; i < NPRED; ++i) {
    264     snprintf(nm, sizeof nm, "emit_%s_o%d_f64_%d", tag, opt_level, i);
    265     build_cmp_fn(c, cg, nm, PREDS[i].op, /*use_f128=*/0);
    266     if (do_f128) {
    267       snprintf(nm, sizeof nm, "emit_%s_o%d_f128_%d", tag, opt_level, i);
    268       build_cmp_fn(c, cg, nm, PREDS[i].op, /*use_f128=*/1);
    269     }
    270   }
    271   /* If any backend mishandles a new opcode it panics here (aborting the test);
    272    * otherwise the object finalizes cleanly. */
    273   EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "%s/O%d: finish failed", tag,
    274          opt_level);
    275   EXPECT(kit_cg_detach(cg) == KIT_OK, "%s/O%d: detach failed", tag, opt_level);
    276 
    277   kit_cg_free(cg);
    278   kit_obj_builder_free(ob);
    279   kit_compiler_free(c);
    280 }
    281 
    282 int main(void) {
    283   int opt;
    284   kit_unit_init(&g_u);
    285 
    286   /* Execution: public mapping + interp semantics for all 12 scalar predicates.
    287    */
    288   run_exec();
    289 
    290   /* Emission: every native backend lowers every predicate (f64 + f128) without
    291    * panicking, at both opt levels. */
    292   for (opt = 0; opt <= 1; ++opt) {
    293     run_emit(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF, "aa64", opt, 1);
    294     run_emit(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF, "x64", opt, 1);
    295     run_emit(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF, "rv64", opt, 1);
    296   }
    297   /* wasm: O0 only (the O1 optimizer is native-target only), f64 only —
    298    * exercises the from-scratch FP eq/ne + unordered arms in the wasm backend
    299    * (emit_fp_cmp), which previously had no float eq/ne path at all. */
    300   run_emit(KIT_ARCH_WASM, KIT_OS_WASI, KIT_OBJ_WASM, "wasm", 0, 0);
    301 
    302   kit_unit_summary(&g_u, "cg_fp_cmp_test");
    303   return kit_unit_status(&g_u);
    304 }