kit

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

abi_classify_test.c (26799B)


      1 /* ABI classification regression tests for wide scalar ABI contracts.
      2  *
      3  * Locks in the behaviour described in doc/CBACKEND.md "Wide16 classification
      4  * is incomplete in some native ABIs". Each (target, type) case asserts the
      5  * shape the ABI vtable should produce for an argument and a return — i.e.
      6  * what the C frontend / CG layer would see if the wide16 CG-layer shortcut
      7  * were removed.
      8  *
      9  * Also pins the ABI-local split-lane scalar query used by generic CG lowering:
     10  * a target opts in when an otherwise scalar value must be represented as
     11  * multiple addressable machine-word lanes. */
     12 
     13 #include <kit/cg.h>
     14 #include <kit/core.h>
     15 #include <stdarg.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 #include "abi/abi.h"
     21 #include "core/core.h"
     22 #include "lib/kit_unit.h"
     23 
     24 /* Shared test context replaces the per-file heap/diag/counter globals;
     25  * EXPECT aliases CU_EXPECT so the call sites are unchanged. */
     26 static KitUnit g_u;
     27 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     28 
     29 static void expect_direct_1x_int(const char* tag, const ABIArgInfo* ai,
     30                                  u32 want_size);
     31 
     32 static KitTargetSpec test_target_spec(KitArchKind arch, KitOSKind os,
     33                                       KitObjFmt obj) {
     34   KitTargetSpec t = kit_unit_target(arch, os, obj);
     35   if (arch == KIT_ARCH_RV32 || arch == KIT_ARCH_WASM) {
     36     t.ptr_size = 4;
     37     t.ptr_align = 4;
     38   }
     39   return t;
     40 }
     41 
     42 static KitCompiler* new_compiler(KitArchKind arch, KitOSKind os,
     43                                  KitObjFmt obj) {
     44   KitTargetSpec t = test_target_spec(arch, os, obj);
     45   KitCompiler* c = NULL;
     46   if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) {
     47     fprintf(stderr, "compiler_new failed for arch=%d os=%d\n", (int)arch,
     48             (int)os);
     49     exit(2);
     50   }
     51   return c;
     52 }
     53 
     54 /* Build a function type `ret_ty fn(arg_ty)` and return its ABIFuncInfo. */
     55 static const ABIFuncInfo* classify_fn(KitCompiler* c, KitCgTypeId ret_ty,
     56                                       KitCgTypeId arg_ty) {
     57   KitCgFuncParam param;
     58   KitCgFuncSig sig;
     59   KitCgTypeId fn;
     60   KitCgFuncResult sig_result;
     61   memset(&param, 0, sizeof param);
     62   param.type = arg_ty;
     63   memset(&sig, 0, sizeof sig);
     64   if (ret_ty != kit_cg_builtin_types(c).id[KIT_CG_BUILTIN_VOID]) {
     65     memset(&sig_result, 0, sizeof sig_result);
     66     sig_result.type = ret_ty;
     67     sig.result = sig_result;
     68   }
     69   sig.params = &param;
     70   sig.nparams = 1;
     71   fn = kit_cg_type_func(c, sig);
     72   return abi_cg_func_info(((Compiler*)c)->abi, fn);
     73 }
     74 
     75 static const char* arch_name(KitArchKind a) {
     76   switch (a) {
     77     case KIT_ARCH_X86_64:
     78       return "x64";
     79     case KIT_ARCH_ARM_64:
     80       return "aarch64";
     81     case KIT_ARCH_RV32:
     82       return "rv32";
     83     case KIT_ARCH_RV64:
     84       return "rv64";
     85     case KIT_ARCH_WASM:
     86       return "wasm";
     87     default:
     88       return "?";
     89   }
     90 }
     91 static const char* os_name(KitOSKind o) {
     92   switch (o) {
     93     case KIT_OS_LINUX:
     94       return "linux";
     95     case KIT_OS_MACOS:
     96       return "macos";
     97     case KIT_OS_WINDOWS:
     98       return "windows";
     99     case KIT_OS_FREESTANDING:
    100       return "freestanding";
    101     case KIT_OS_WASI:
    102       return "wasi";
    103     default:
    104       return "?";
    105   }
    106 }
    107 
    108 /* Assert: arg/ret classify as DIRECT with two 8-byte INT parts at offsets 0/8.
    109  * This is the shape every native ABI uses for i128 (and for RV64, also f128).
    110  * Used as both the green case (RV64) and the post-fix expectation (SysV-x64).
    111  */
    112 static void expect_direct_2x_int8(const char* tag, const ABIArgInfo* ai) {
    113   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    114          (int)ai->kind);
    115   EXPECT(ai->nparts == 2, "%s: nparts=%u want 2", tag, (unsigned)ai->nparts);
    116   if (ai->nparts != 2 || !ai->parts) return;
    117   for (u32 i = 0; i < 2; ++i) {
    118     EXPECT(ai->parts[i].cls == ABI_CLASS_INT, "%s: parts[%u].cls=%d want INT",
    119            tag, i, (int)ai->parts[i].cls);
    120     EXPECT(ai->parts[i].size == 8, "%s: parts[%u].size=%u want 8", tag, i,
    121            (unsigned)ai->parts[i].size);
    122     EXPECT(ai->parts[i].src_offset == i * 8u,
    123            "%s: parts[%u].src_offset=%u want %u", tag, i,
    124            (unsigned)ai->parts[i].src_offset, (unsigned)(i * 8u));
    125   }
    126 }
    127 
    128 /* Assert: classifies as INDIRECT (memory image). */
    129 static void expect_indirect(const char* tag, const ABIArgInfo* ai,
    130                             int is_return) {
    131   EXPECT(ai->kind == ABI_ARG_INDIRECT, "%s: kind=%d want INDIRECT", tag,
    132          (int)ai->kind);
    133   EXPECT(ai->nparts == 0, "%s: nparts=%u want 0", tag, (unsigned)ai->nparts);
    134   EXPECT(ai->indirect_align >= 8, "%s: indirect_align=%u want >=8", tag,
    135          (unsigned)ai->indirect_align);
    136   u32 expected_flag = is_return ? ABI_AF_SRET : ABI_AF_BYVAL;
    137   EXPECT((ai->flags & expected_flag) != 0, "%s: flags=0x%x missing %s", tag,
    138          (unsigned)ai->flags, is_return ? "SRET" : "BYVAL");
    139 }
    140 
    141 /* Assert: DIRECT with a single FP part covering the full type. */
    142 static void expect_direct_1x_fp(const char* tag, const ABIArgInfo* ai,
    143                                 u32 want_size) {
    144   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    145          (int)ai->kind);
    146   EXPECT(ai->nparts == 1, "%s: nparts=%u want 1", tag, (unsigned)ai->nparts);
    147   if (ai->nparts != 1 || !ai->parts) return;
    148   EXPECT(ai->parts[0].cls == ABI_CLASS_FP, "%s: parts[0].cls=%d want FP", tag,
    149          (int)ai->parts[0].cls);
    150   EXPECT(ai->parts[0].size == want_size, "%s: parts[0].size=%u want %u", tag,
    151          (unsigned)ai->parts[0].size, want_size);
    152   EXPECT(ai->parts[0].src_offset == 0, "%s: parts[0].src_offset=%u want 0", tag,
    153          (unsigned)ai->parts[0].src_offset);
    154 }
    155 
    156 static void expect_direct_2(const char* tag, const ABIArgInfo* ai, u8 c0, u8 c1,
    157                             u32 s0, u32 s1) {
    158   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    159          (int)ai->kind);
    160   EXPECT(ai->nparts == 2, "%s: nparts=%u want 2", tag, (unsigned)ai->nparts);
    161   if (ai->nparts != 2 || !ai->parts) return;
    162   EXPECT(ai->parts[0].cls == c0, "%s: parts[0].cls=%d want %d", tag,
    163          (int)ai->parts[0].cls, (int)c0);
    164   EXPECT(ai->parts[1].cls == c1, "%s: parts[1].cls=%d want %d", tag,
    165          (int)ai->parts[1].cls, (int)c1);
    166   EXPECT(ai->parts[0].size == s0, "%s: parts[0].size=%u want %u", tag,
    167          (unsigned)ai->parts[0].size, s0);
    168   EXPECT(ai->parts[1].size == s1, "%s: parts[1].size=%u want %u", tag,
    169          (unsigned)ai->parts[1].size, s1);
    170   EXPECT(ai->parts[0].src_offset == 0, "%s: parts[0].src_offset=%u want 0", tag,
    171          (unsigned)ai->parts[0].src_offset);
    172   EXPECT(ai->parts[1].src_offset == 8, "%s: parts[1].src_offset=%u want 8", tag,
    173          (unsigned)ai->parts[1].src_offset);
    174 }
    175 
    176 static KitCgTypeId record2(KitCompiler* c, KitCgTypeId a, KitCgTypeId b) {
    177   KitCgField f[2];
    178   memset(f, 0, sizeof f);
    179   f[0].type = a;
    180   f[1].type = b;
    181   return kit_cg_type_record(c, 0, f, 2);
    182 }
    183 
    184 static void check_target(KitArchKind arch, KitOSKind os, KitObjFmt obj) {
    185   KitCompiler* c = new_compiler(arch, os, obj);
    186   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    187   KitCgTypeId i128_ty = bi.id[KIT_CG_BUILTIN_I128];
    188   KitCgTypeId f128_ty = bi.id[KIT_CG_BUILTIN_F128];
    189   EXPECT(i128_ty != KIT_CG_TYPE_NONE, "%s/%s: missing i128 builtin",
    190          arch_name(arch), os_name(os));
    191   EXPECT(f128_ty != KIT_CG_TYPE_NONE, "%s/%s: missing f128 builtin",
    192          arch_name(arch), os_name(os));
    193 
    194   char tag[64];
    195 
    196   /* i128 — every native ABI: DIRECT/2 INT parts of 8B. */
    197   {
    198     const ABIFuncInfo* fi = classify_fn(c, i128_ty, i128_ty);
    199     snprintf(tag, sizeof tag, "%s/%s i128 arg", arch_name(arch), os_name(os));
    200     expect_direct_2x_int8(tag, &fi->params[0]);
    201     snprintf(tag, sizeof tag, "%s/%s i128 ret", arch_name(arch), os_name(os));
    202     expect_direct_2x_int8(tag, &fi->ret);
    203     EXPECT(fi->has_sret == 0, "%s/%s: i128 should not set has_sret",
    204            arch_name(arch), os_name(os));
    205   }
    206 
    207   /* f128 (long double / __float128). Per-target expectations differ. */
    208   {
    209     const ABIFuncInfo* fi = classify_fn(c, f128_ty, f128_ty);
    210     snprintf(tag, sizeof tag, "%s/%s f128 arg", arch_name(arch), os_name(os));
    211     if (arch == KIT_ARCH_X86_64 && os == KIT_OS_WINDOWS) {
    212       /* Win64: long double is 64-bit double. Front end normally lowers
    213        * f128 before classification; defensive path treats size-16 FP as
    214        * a size-8 double — DIRECT/1 FP part of 8B for both arg and ret. */
    215       expect_direct_1x_fp(tag, &fi->params[0], 8);
    216       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    217       expect_direct_1x_fp(tag, &fi->ret, 8);
    218       EXPECT(fi->has_sret == 0, "%s/%s: f128 should not set has_sret",
    219              arch_name(arch), os_name(os));
    220     } else if (arch == KIT_ARCH_X86_64) {
    221       /* SysV-x64: long double is x87 (80-bit padded to 16B). kit lacks
    222        * x87 support; classify as INDIRECT (memory) so it routes through
    223        * a stack image consistent with the wide16 CG-layer shortcut. */
    224       expect_indirect(tag, &fi->params[0], /*is_return=*/0);
    225       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    226       expect_indirect(tag, &fi->ret, /*is_return=*/1);
    227       EXPECT(fi->has_sret == 1, "%s/%s: f128 ret should set has_sret",
    228              arch_name(arch), os_name(os));
    229     } else if (arch == KIT_ARCH_ARM_64) {
    230       /* AAPCS64 / Apple ARM64: 128-bit FP scalar passes in a single Q
    231        * register — DIRECT/1 FP part of 16B. */
    232       expect_direct_1x_fp(tag, &fi->params[0], 16);
    233       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    234       expect_direct_1x_fp(tag, &fi->ret, 16);
    235       EXPECT(fi->has_sret == 0, "%s/%s: f128 should not set has_sret",
    236              arch_name(arch), os_name(os));
    237     } else if (arch == KIT_ARCH_RV64) {
    238       /* RV64 LP64D: long double passes like a 2*XLEN scalar — 2 INT parts. */
    239       expect_direct_2x_int8(tag, &fi->params[0]);
    240       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    241       expect_direct_2x_int8(tag, &fi->ret);
    242       EXPECT(fi->has_sret == 0, "%s/%s: f128 should not set has_sret",
    243              arch_name(arch), os_name(os));
    244     }
    245   }
    246 
    247   if (arch == KIT_ARCH_X86_64) {
    248     KitCgTypeId f64_i64 =
    249         record2(c, bi.id[KIT_CG_BUILTIN_F64], bi.id[KIT_CG_BUILTIN_I64]);
    250     KitCgTypeId i64_f64 =
    251         record2(c, bi.id[KIT_CG_BUILTIN_I64], bi.id[KIT_CG_BUILTIN_F64]);
    252     KitCgTypeId f32x2 =
    253         record2(c, bi.id[KIT_CG_BUILTIN_F32], bi.id[KIT_CG_BUILTIN_F32]);
    254     {
    255       const ABIFuncInfo* fi = classify_fn(c, f64_i64, f64_i64);
    256       snprintf(tag, sizeof tag, "%s/%s {double,long} arg", arch_name(arch),
    257                os_name(os));
    258       if (os == KIT_OS_WINDOWS) {
    259         expect_indirect(tag, &fi->params[0], /*is_return=*/0);
    260       } else {
    261         expect_direct_2(tag, &fi->params[0], ABI_CLASS_FP, ABI_CLASS_INT, 8, 8);
    262       }
    263       snprintf(tag, sizeof tag, "%s/%s {double,long} ret", arch_name(arch),
    264                os_name(os));
    265       if (os == KIT_OS_WINDOWS) {
    266         expect_indirect(tag, &fi->ret, /*is_return=*/1);
    267         EXPECT(fi->has_sret == 1, "%s/%s: mixed record should use sret",
    268                arch_name(arch), os_name(os));
    269       } else {
    270         expect_direct_2(tag, &fi->ret, ABI_CLASS_FP, ABI_CLASS_INT, 8, 8);
    271         EXPECT(fi->has_sret == 0, "%s/%s: mixed record should not use sret",
    272                arch_name(arch), os_name(os));
    273       }
    274     }
    275     {
    276       const ABIFuncInfo* fi = classify_fn(c, i64_f64, i64_f64);
    277       snprintf(tag, sizeof tag, "%s/%s {long,double} arg", arch_name(arch),
    278                os_name(os));
    279       if (os == KIT_OS_WINDOWS) {
    280         expect_indirect(tag, &fi->params[0], /*is_return=*/0);
    281       } else {
    282         expect_direct_2(tag, &fi->params[0], ABI_CLASS_INT, ABI_CLASS_FP, 8, 8);
    283       }
    284       snprintf(tag, sizeof tag, "%s/%s {long,double} ret", arch_name(arch),
    285                os_name(os));
    286       if (os == KIT_OS_WINDOWS) {
    287         expect_indirect(tag, &fi->ret, /*is_return=*/1);
    288       } else {
    289         expect_direct_2(tag, &fi->ret, ABI_CLASS_INT, ABI_CLASS_FP, 8, 8);
    290       }
    291     }
    292     {
    293       const ABIFuncInfo* fi = classify_fn(c, f32x2, f32x2);
    294       snprintf(tag, sizeof tag, "%s/%s {float,float} arg", arch_name(arch),
    295                os_name(os));
    296       if (os == KIT_OS_WINDOWS)
    297         expect_direct_1x_int(tag, &fi->params[0], 8);
    298       else
    299         expect_direct_1x_fp(tag, &fi->params[0], 8);
    300       snprintf(tag, sizeof tag, "%s/%s {float,float} ret", arch_name(arch),
    301                os_name(os));
    302       if (os == KIT_OS_WINDOWS)
    303         expect_direct_1x_int(tag, &fi->ret, 8);
    304       else
    305         expect_direct_1x_fp(tag, &fi->ret, 8);
    306     }
    307   }
    308 
    309   kit_compiler_free(c);
    310 }
    311 
    312 /* Build a record with N i8 fields (so size == N and align == 1). */
    313 static KitCgTypeId make_i8_record(KitCompiler* c, const char* tag_name,
    314                                   u32 nfields) {
    315   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    316   KitCgTypeId i8 = bi.id[KIT_CG_BUILTIN_I8];
    317   KitCgField fields[16];
    318   static const char* const names[16] = {"f0", "f1", "f2", "f3", "f4", "f5",
    319                                         "f6", "f7", "f8", "f9", "fa", "fb",
    320                                         "fc", "fd", "fe", "ff"};
    321   if (nfields > 16) exit(2);
    322   memset(fields, 0, sizeof fields);
    323   for (u32 i = 0; i < nfields; ++i) {
    324     fields[i].name = kit_sym_intern(c, kit_slice_cstr(names[i]));
    325     fields[i].type = i8;
    326   }
    327   return kit_cg_type_record(c, kit_sym_intern(c, kit_slice_cstr(tag_name)),
    328                             fields, nfields);
    329 }
    330 
    331 /* Build a record { i64 a; i64 b; } — size 16, align 8. */
    332 static KitCgTypeId make_two_i64_record(KitCompiler* c, const char* tag_n) {
    333   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    334   KitCgTypeId i64 = bi.id[KIT_CG_BUILTIN_I64];
    335   KitCgField fields[2];
    336   memset(fields, 0, sizeof fields);
    337   fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("a"));
    338   fields[0].type = i64;
    339   fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("b"));
    340   fields[1].type = i64;
    341   return kit_cg_type_record(c, kit_sym_intern(c, kit_slice_cstr(tag_n)), fields,
    342                             2);
    343 }
    344 
    345 /* Classify a function `ret_ty fn(p0, p1, ..., pN-1)` and return its info. */
    346 static const ABIFuncInfo* classify_fn_n(KitCompiler* c, KitCgTypeId ret_ty,
    347                                         const KitCgTypeId* arg_types, u32 nargs,
    348                                         int variadic) {
    349   KitCgFuncParam params[8];
    350   KitCgFuncSig sig;
    351   KitCgTypeId fn;
    352   KitCgFuncResult sig_result;
    353   if (nargs > 8) exit(2);
    354   memset(params, 0, sizeof params);
    355   for (u32 i = 0; i < nargs; ++i) params[i].type = arg_types[i];
    356   memset(&sig, 0, sizeof sig);
    357   if (ret_ty != kit_cg_builtin_types(c).id[KIT_CG_BUILTIN_VOID]) {
    358     memset(&sig_result, 0, sizeof sig_result);
    359     sig_result.type = ret_ty;
    360     sig.result = sig_result;
    361   }
    362   sig.params = params;
    363   sig.nparams = nargs;
    364   sig.abi_variadic = variadic ? true : false;
    365   fn = kit_cg_type_func(c, sig);
    366   return abi_cg_func_info(((Compiler*)c)->abi, fn);
    367 }
    368 
    369 /* Expect INDIRECT (memory image) with a specific indirect alignment.
    370  * Win64 preserves the source type's natural alignment in the byval/sret
    371  * copy — for a 3-byte i8 aggregate that's 1, not 8. */
    372 static void expect_indirect_align(const char* tag, const ABIArgInfo* ai,
    373                                   int is_return, u32 want_align) {
    374   EXPECT(ai->kind == ABI_ARG_INDIRECT, "%s: kind=%d want INDIRECT", tag,
    375          (int)ai->kind);
    376   EXPECT(ai->nparts == 0, "%s: nparts=%u want 0", tag, (unsigned)ai->nparts);
    377   EXPECT(ai->indirect_align == want_align, "%s: indirect_align=%u want %u", tag,
    378          (unsigned)ai->indirect_align, want_align);
    379   u32 expected_flag = is_return ? ABI_AF_SRET : ABI_AF_BYVAL;
    380   EXPECT((ai->flags & expected_flag) != 0, "%s: flags=0x%x missing %s", tag,
    381          (unsigned)ai->flags, is_return ? "SRET" : "BYVAL");
    382 }
    383 
    384 /* Expect DIRECT with a single INT part of the given size. */
    385 static void expect_direct_1x_int(const char* tag, const ABIArgInfo* ai,
    386                                  u32 want_size) {
    387   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    388          (int)ai->kind);
    389   EXPECT(ai->nparts == 1, "%s: nparts=%u want 1", tag, (unsigned)ai->nparts);
    390   if (ai->nparts != 1 || !ai->parts) return;
    391   EXPECT(ai->parts[0].cls == ABI_CLASS_INT, "%s: parts[0].cls=%d want INT", tag,
    392          (int)ai->parts[0].cls);
    393   EXPECT(ai->parts[0].size == want_size, "%s: parts[0].size=%u want %u", tag,
    394          (unsigned)ai->parts[0].size, want_size);
    395 }
    396 
    397 /* Win64-specific ABI shape assertions: aggregate rules ({1,2,4,8} by value
    398  * else hidden pointer), va_list = void*, variadic flag wiring, and that
    399  * each scalar arg gets one part of the right class (reg-vs-stack
    400  * placement is codegen, not classifier output). */
    401 static void test_win64_specifics(void) {
    402   KitCompiler* c = new_compiler(KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    403   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    404   KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32];
    405   KitCgTypeId f64 = bi.id[KIT_CG_BUILTIN_F64];
    406   KitCgTypeId voidp = kit_cg_type_ptr(c, bi.id[KIT_CG_BUILTIN_VOID], 0);
    407   KitCgTypeId rec1 = make_i8_record(c, "S1", 1);
    408   KitCgTypeId rec3 = make_i8_record(c, "S3", 3);
    409   KitCgTypeId rec16 = make_two_i64_record(c, "S16");
    410 
    411   /* Case 1: int main(void) — DIRECT/1 INT/4 ret, no params. */
    412   {
    413     const ABIFuncInfo* fi = classify_fn_n(c, i32, NULL, 0, 0);
    414     expect_direct_1x_int("win64 main ret", &fi->ret, 4);
    415     EXPECT(fi->nparams == 0, "win64 main: nparams=%u want 0",
    416            (unsigned)fi->nparams);
    417     EXPECT(fi->has_sret == 0, "win64 main: has_sret set");
    418     EXPECT(fi->variadic == 0, "win64 main: variadic set");
    419   }
    420 
    421   /* Case 2: void f(int,int,int,int,int) — 5 ints, each DIRECT/1 INT/4.
    422    * Reg vs stack placement (4 reg slots) is a codegen concern; the
    423    * classifier emits per-arg parts regardless. */
    424   {
    425     KitCgTypeId args[5] = {i32, i32, i32, i32, i32};
    426     const ABIFuncInfo* fi =
    427         classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 5, 0);
    428     EXPECT(fi->nparams == 5, "win64 5xint: nparams=%u want 5",
    429            (unsigned)fi->nparams);
    430     for (u32 i = 0; i < 5; ++i) {
    431       char t[64];
    432       snprintf(t, sizeof t, "win64 5xint arg[%u]", i);
    433       expect_direct_1x_int(t, &fi->params[i], 4);
    434     }
    435   }
    436 
    437   /* Case 3: void f(double,double,double,double,double) — 5 doubles. */
    438   {
    439     KitCgTypeId args[5] = {f64, f64, f64, f64, f64};
    440     const ABIFuncInfo* fi =
    441         classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 5, 0);
    442     EXPECT(fi->nparams == 5, "win64 5xfp: nparams=%u want 5",
    443            (unsigned)fi->nparams);
    444     for (u32 i = 0; i < 5; ++i) {
    445       char t[64];
    446       snprintf(t, sizeof t, "win64 5xfp arg[%u]", i);
    447       expect_direct_1x_fp(t, &fi->params[i], 8);
    448     }
    449   }
    450 
    451   /* Case 4: void f(int,double,int,double) — slot-shared on Win64.
    452    * The classifier just emits per-arg parts of the right class; slot
    453    * sharing is a codegen call-site concern. */
    454   {
    455     KitCgTypeId args[4] = {i32, f64, i32, f64};
    456     const ABIFuncInfo* fi =
    457         classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 4, 0);
    458     EXPECT(fi->nparams == 4, "win64 mix: nparams=%u want 4",
    459            (unsigned)fi->nparams);
    460     expect_direct_1x_int("win64 mix arg[0]", &fi->params[0], 4);
    461     expect_direct_1x_fp("win64 mix arg[1]", &fi->params[1], 8);
    462     expect_direct_1x_int("win64 mix arg[2]", &fi->params[2], 4);
    463     expect_direct_1x_fp("win64 mix arg[3]", &fi->params[3], 8);
    464   }
    465 
    466   /* Case 5: struct{char a;} foo(void) — size 1, DIRECT/1 INT/1. */
    467   {
    468     const ABIFuncInfo* fi = classify_fn_n(c, rec1, NULL, 0, 0);
    469     expect_direct_1x_int("win64 ret S1", &fi->ret, 1);
    470     EXPECT(fi->has_sret == 0, "win64 S1 ret: has_sret set");
    471   }
    472 
    473   /* Case 6: struct{long a; long b;} foo(void) — size 16, INDIRECT/sret. */
    474   {
    475     const ABIFuncInfo* fi = classify_fn_n(c, rec16, NULL, 0, 0);
    476     expect_indirect("win64 ret S16", &fi->ret, /*is_return=*/1);
    477     EXPECT(fi->has_sret == 1, "win64 S16 ret: has_sret not set");
    478   }
    479 
    480   /* Case 7: struct{char,char,char} foo(void) — size 3, INDIRECT/sret on
    481    * Win64 (only {1,2,4,8} pass by value). Natural align of the 3-byte
    482    * i8 aggregate is 1, which Win64 preserves in indirect_align. */
    483   {
    484     const ABIFuncInfo* fi = classify_fn_n(c, rec3, NULL, 0, 0);
    485     expect_indirect_align("win64 ret S3", &fi->ret, /*is_return=*/1,
    486                           /*want_align=*/1);
    487     EXPECT(fi->has_sret == 1, "win64 S3 ret: has_sret not set");
    488   }
    489 
    490   /* Case 8: void f(struct{char,char,char}) — by-value 3-byte aggregate
    491    * goes by hidden pointer (BYVAL) on Win64. */
    492   {
    493     KitCgTypeId args[1] = {rec3};
    494     const ABIFuncInfo* fi =
    495         classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 0);
    496     EXPECT(fi->nparams == 1, "win64 S3 arg: nparams=%u want 1",
    497            (unsigned)fi->nparams);
    498     expect_indirect_align("win64 S3 arg", &fi->params[0], /*is_return=*/0,
    499                           /*want_align=*/1);
    500   }
    501 
    502   /* Case 9: int printf(const char*, ...) — variadic flag set. */
    503   {
    504     KitCgTypeId args[1] = {voidp};
    505     const ABIFuncInfo* fi = classify_fn_n(c, i32, args, 1, /*variadic=*/1);
    506     EXPECT(fi->variadic == 1, "win64 printf: variadic=%u want 1",
    507            (unsigned)fi->variadic);
    508     EXPECT(fi->vararg_on_stack == 0, "win64 printf: vararg_on_stack=%u want 0",
    509            (unsigned)fi->vararg_on_stack);
    510     expect_direct_1x_int("win64 printf ret", &fi->ret, 4);
    511   }
    512 
    513   /* Case 10: va_list info — Win64 has va_list = void* (8/8/PTR). */
    514   {
    515     ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi);
    516     EXPECT(vi.size == 8, "win64 va_list size=%u want 8", (unsigned)vi.size);
    517     EXPECT(vi.align == 8, "win64 va_list align=%u want 8", (unsigned)vi.align);
    518     EXPECT(vi.scalar_kind == ABI_SC_PTR,
    519            "win64 va_list scalar_kind=%u want ABI_SC_PTR (%u)",
    520            (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR);
    521   }
    522 
    523   kit_compiler_free(c);
    524 }
    525 
    526 /* AArch64-Windows mostly starts from AAPCS64. Deltas: va_list is `void*`,
    527  * and FP parameters to variadic functions are routed through integer slots. */
    528 static void test_aarch64_windows_variadic(void) {
    529   KitCompiler* c = new_compiler(KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    530   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    531   KitCgTypeId f64 = bi.id[KIT_CG_BUILTIN_F64];
    532   KitCgTypeId args[1] = {f64};
    533 
    534   ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi);
    535   EXPECT(vi.size == 8, "aarch64/windows va_list size=%u want 8",
    536          (unsigned)vi.size);
    537   EXPECT(vi.align == 8, "aarch64/windows va_list align=%u want 8",
    538          (unsigned)vi.align);
    539   EXPECT(vi.scalar_kind == ABI_SC_PTR,
    540          "aarch64/windows va_list scalar_kind=%u want ABI_SC_PTR (%u)",
    541          (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR);
    542 
    543   {
    544     const ABIFuncInfo* fi =
    545         classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 0);
    546     expect_direct_1x_fp("aarch64/windows nonvariadic double arg",
    547                         &fi->params[0], 8);
    548   }
    549   {
    550     const ABIFuncInfo* fi =
    551         classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 1);
    552     expect_direct_1x_int("aarch64/windows variadic double arg", &fi->params[0],
    553                          8);
    554     EXPECT(fi->vararg_on_stack == 0,
    555            "aarch64/windows variadic: vararg_on_stack=%u want 0",
    556            (unsigned)fi->vararg_on_stack);
    557   }
    558   kit_compiler_free(c);
    559 }
    560 
    561 static void test_apple_arm64_stack_traits(void) {
    562   KitCompiler* c = new_compiler(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO);
    563   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    564   KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32];
    565   KitCgTypeId args[1] = {i32};
    566   const ABIFuncInfo* fi =
    567       classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 1);
    568 
    569   EXPECT(fi->vararg_on_stack == 1,
    570          "apple arm64 variadic: vararg_on_stack=%u want 1",
    571          (unsigned)fi->vararg_on_stack);
    572   EXPECT(fi->stack_arg_min_align == 4,
    573          "apple arm64 fixed stack min=%u want 4",
    574          (unsigned)fi->stack_arg_min_align);
    575   EXPECT(fi->vararg_stack_arg_min_align == 8,
    576          "apple arm64 vararg stack min=%u want 8",
    577          (unsigned)fi->vararg_stack_arg_min_align);
    578   {
    579     ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi);
    580     EXPECT(vi.size == 8, "apple arm64 va_list size=%u want 8",
    581            (unsigned)vi.size);
    582     EXPECT(vi.scalar_kind == ABI_SC_PTR,
    583            "apple arm64 va_list scalar_kind=%u want ABI_SC_PTR (%u)",
    584            (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR);
    585   }
    586   kit_compiler_free(c);
    587 }
    588 
    589 static void check_scalar_split_lane_target(KitArchKind arch, KitOSKind os,
    590                                            KitObjFmt obj, const char* tag,
    591                                            u32 want_i64, u32 want_f64) {
    592   KitCompiler* c = new_compiler(arch, os, obj);
    593   KitCgBuiltinTypes bi = kit_cg_builtin_types(c);
    594   TargetABI* abi = ((Compiler*)c)->abi;
    595 
    596   EXPECT(abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_I64]) ==
    597              want_i64,
    598          "%s i64 split lane=%u want %u", tag,
    599          (unsigned)abi_cg_scalar_split_lane_size(abi,
    600                                                  bi.id[KIT_CG_BUILTIN_I64]),
    601          (unsigned)want_i64);
    602   EXPECT(abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_F64]) ==
    603              want_f64,
    604          "%s f64 split lane=%u want %u", tag,
    605          (unsigned)abi_cg_scalar_split_lane_size(abi,
    606                                                  bi.id[KIT_CG_BUILTIN_F64]),
    607          (unsigned)want_f64);
    608   EXPECT(abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_I32]) == 0,
    609          "%s i32 should not be split-lane", tag);
    610   EXPECT(abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_F32]) == 0,
    611          "%s f32 should not be split-lane", tag);
    612 
    613   kit_compiler_free(c);
    614 }
    615 
    616 static void test_scalar_split_lane_size(void) {
    617   /* RV32's default profile is ilp32f: 64-bit ints and soft doubles are
    618    * represented as two 4-byte integer lanes. */
    619   check_scalar_split_lane_target(KIT_ARCH_RV32, KIT_OS_LINUX, KIT_OBJ_ELF,
    620                                  "rv32", 4, 4);
    621   check_scalar_split_lane_target(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF,
    622                                  "rv64", 0, 0);
    623   check_scalar_split_lane_target(KIT_ARCH_WASM, KIT_OS_WASI, KIT_OBJ_WASM,
    624                                  "wasm32", 0, 0);
    625 }
    626 
    627 int main(void) {
    628   kit_unit_init(&g_u);
    629   check_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    630   check_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    631   check_target(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO);
    632   check_target(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF);
    633   check_target(KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    634   check_target(KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    635   test_win64_specifics();
    636   test_aarch64_windows_variadic();
    637   test_apple_arm64_stack_traits();
    638   test_scalar_split_lane_size();
    639   kit_unit_summary(&g_u, "abi_classify_test");
    640   return kit_unit_status(&g_u);
    641 }