kit

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

abi_classify_test.c (33409B)


      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_ARM_32 || arch == KIT_ARCH_RV32 ||
     36       arch == KIT_ARCH_WASM) {
     37     t.ptr_size = 4;
     38     t.ptr_align = 4;
     39   }
     40   return t;
     41 }
     42 
     43 static KitCompiler* new_compiler(KitArchKind arch, KitOSKind os,
     44                                  KitObjFmt obj) {
     45   KitTargetSpec t = test_target_spec(arch, os, obj);
     46   KitCompiler* c = NULL;
     47   if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) {
     48     fprintf(stderr, "compiler_new failed for arch=%d os=%d\n", (int)arch,
     49             (int)os);
     50     exit(2);
     51   }
     52   return c;
     53 }
     54 
     55 static KitCgTypeId builtin(KitCompiler* c, KitCgBuiltinType which) {
     56   return kit_cg_type_builtin(c, which);
     57 }
     58 
     59 /* Build a function type `ret_ty fn(arg_ty)` and return its ABIFuncInfo. */
     60 static const ABIFuncInfo* classify_fn(KitCompiler* c, KitCgTypeId ret_ty,
     61                                       KitCgTypeId arg_ty) {
     62   KitCgFuncParam param;
     63   KitCgFuncSig sig;
     64   KitCgTypeId fn;
     65   KitCgFuncResult sig_result;
     66   memset(&param, 0, sizeof param);
     67   param.type = arg_ty;
     68   memset(&sig, 0, sizeof sig);
     69   memset(&sig_result, 0, sizeof sig_result);
     70   sig_result.type = ret_ty;
     71   sig.result = sig_result;
     72   sig.params = &param;
     73   sig.nparams = 1;
     74   fn = kit_cg_type_func(c, sig);
     75   return abi_cg_func_info(((Compiler*)c)->abi, fn);
     76 }
     77 
     78 static const char* arch_name(KitArchKind a) {
     79   switch (a) {
     80     case KIT_ARCH_X86_64:
     81       return "x64";
     82     case KIT_ARCH_ARM_64:
     83       return "aarch64";
     84     case KIT_ARCH_ARM_32:
     85       return "arm32";
     86     case KIT_ARCH_RV32:
     87       return "rv32";
     88     case KIT_ARCH_RV64:
     89       return "rv64";
     90     case KIT_ARCH_WASM:
     91       return "wasm";
     92     default:
     93       return "?";
     94   }
     95 }
     96 static const char* os_name(KitOSKind o) {
     97   switch (o) {
     98     case KIT_OS_LINUX:
     99       return "linux";
    100     case KIT_OS_MACOS:
    101       return "macos";
    102     case KIT_OS_IOS:
    103       return "ios";
    104     case KIT_OS_IOS_SIMULATOR:
    105       return "ios-simulator";
    106     case KIT_OS_WINDOWS:
    107       return "windows";
    108     case KIT_OS_FREESTANDING:
    109       return "freestanding";
    110     case KIT_OS_WASI:
    111       return "wasi";
    112     default:
    113       return "?";
    114   }
    115 }
    116 
    117 /* Assert: arg/ret classify as DIRECT with two 8-byte INT parts at offsets 0/8.
    118  * This is the shape every native ABI uses for i128 (and for RV64, also f128).
    119  * Used as both the green case (RV64) and the post-fix expectation (SysV-x64).
    120  */
    121 static void expect_direct_2x_int8(const char* tag, const ABIArgInfo* ai) {
    122   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    123          (int)ai->kind);
    124   EXPECT(ai->nparts == 2, "%s: nparts=%u want 2", tag, (unsigned)ai->nparts);
    125   if (ai->nparts != 2 || !ai->parts) return;
    126   for (u32 i = 0; i < 2; ++i) {
    127     EXPECT(ai->parts[i].cls == ABI_CLASS_INT, "%s: parts[%u].cls=%d want INT",
    128            tag, i, (int)ai->parts[i].cls);
    129     EXPECT(ai->parts[i].size == 8, "%s: parts[%u].size=%u want 8", tag, i,
    130            (unsigned)ai->parts[i].size);
    131     EXPECT(ai->parts[i].src_offset == i * 8u,
    132            "%s: parts[%u].src_offset=%u want %u", tag, i,
    133            (unsigned)ai->parts[i].src_offset, (unsigned)(i * 8u));
    134   }
    135 }
    136 
    137 /* Assert: classifies as INDIRECT (memory image). */
    138 static void expect_indirect(const char* tag, const ABIArgInfo* ai,
    139                             int is_return) {
    140   EXPECT(ai->kind == ABI_ARG_INDIRECT, "%s: kind=%d want INDIRECT", tag,
    141          (int)ai->kind);
    142   EXPECT(ai->nparts == 0, "%s: nparts=%u want 0", tag, (unsigned)ai->nparts);
    143   EXPECT(ai->indirect_align >= 8, "%s: indirect_align=%u want >=8", tag,
    144          (unsigned)ai->indirect_align);
    145   u32 expected_flag = is_return ? ABI_AF_SRET : ABI_AF_BYVAL;
    146   EXPECT((ai->flags & expected_flag) != 0, "%s: flags=0x%x missing %s", tag,
    147          (unsigned)ai->flags, is_return ? "SRET" : "BYVAL");
    148 }
    149 
    150 /* Assert: DIRECT with a single FP part covering the full type. */
    151 static void expect_direct_1x_fp(const char* tag, const ABIArgInfo* ai,
    152                                 u32 want_size) {
    153   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    154          (int)ai->kind);
    155   EXPECT(ai->nparts == 1, "%s: nparts=%u want 1", tag, (unsigned)ai->nparts);
    156   if (ai->nparts != 1 || !ai->parts) return;
    157   EXPECT(ai->parts[0].cls == ABI_CLASS_FP, "%s: parts[0].cls=%d want FP", tag,
    158          (int)ai->parts[0].cls);
    159   EXPECT(ai->parts[0].size == want_size, "%s: parts[0].size=%u want %u", tag,
    160          (unsigned)ai->parts[0].size, want_size);
    161   EXPECT(ai->parts[0].src_offset == 0, "%s: parts[0].src_offset=%u want 0", tag,
    162          (unsigned)ai->parts[0].src_offset);
    163 }
    164 
    165 static void expect_direct_2(const char* tag, const ABIArgInfo* ai, u8 c0, u8 c1,
    166                             u32 s0, u32 s1) {
    167   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    168          (int)ai->kind);
    169   EXPECT(ai->nparts == 2, "%s: nparts=%u want 2", tag, (unsigned)ai->nparts);
    170   if (ai->nparts != 2 || !ai->parts) return;
    171   EXPECT(ai->parts[0].cls == c0, "%s: parts[0].cls=%d want %d", tag,
    172          (int)ai->parts[0].cls, (int)c0);
    173   EXPECT(ai->parts[1].cls == c1, "%s: parts[1].cls=%d want %d", tag,
    174          (int)ai->parts[1].cls, (int)c1);
    175   EXPECT(ai->parts[0].size == s0, "%s: parts[0].size=%u want %u", tag,
    176          (unsigned)ai->parts[0].size, s0);
    177   EXPECT(ai->parts[1].size == s1, "%s: parts[1].size=%u want %u", tag,
    178          (unsigned)ai->parts[1].size, s1);
    179   EXPECT(ai->parts[0].src_offset == 0, "%s: parts[0].src_offset=%u want 0", tag,
    180          (unsigned)ai->parts[0].src_offset);
    181   EXPECT(ai->parts[1].src_offset == 8, "%s: parts[1].src_offset=%u want 8", tag,
    182          (unsigned)ai->parts[1].src_offset);
    183 }
    184 
    185 static KitCgTypeId record2(KitCompiler* c, KitCgTypeId a, KitCgTypeId b) {
    186   KitCgFieldDesc f[2];
    187   KitCgRecordDesc desc;
    188   memset(f, 0, sizeof f);
    189   f[0].type = a;
    190   f[1].type = b;
    191   memset(&desc, 0, sizeof desc);
    192   desc.fields = f;
    193   desc.nfields = 2;
    194   return kit_cg_type_record(c, &desc);
    195 }
    196 
    197 static KitCgTypeId record_i32s(KitCompiler* c, u32 n) {
    198   KitCgFieldDesc fields[8];
    199   KitCgRecordDesc desc;
    200   KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32);
    201   if (n == 0u || n > sizeof fields / sizeof fields[0]) return KIT_CG_TYPE_NONE;
    202   memset(fields, 0, sizeof fields);
    203   for (u32 i = 0; i < n; ++i) fields[i].type = i32;
    204   memset(&desc, 0, sizeof desc);
    205   desc.fields = fields;
    206   desc.nfields = n;
    207   return kit_cg_type_record(c, &desc);
    208 }
    209 
    210 static void expect_arm32_word_parts(const char* tag, const ABIArgInfo* ai,
    211                                     u32 nwords) {
    212   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    213          (int)ai->kind);
    214   EXPECT(ai->nparts == nwords, "%s: nparts=%u want %u", tag,
    215          (unsigned)ai->nparts, (unsigned)nwords);
    216   if (ai->nparts != nwords || !ai->parts) return;
    217   for (u32 i = 0; i < nwords; ++i) {
    218     EXPECT(ai->parts[i].cls == ABI_CLASS_INT,
    219            "%s: parts[%u].cls=%d want INT", tag, (unsigned)i,
    220            (int)ai->parts[i].cls);
    221     EXPECT(ai->parts[i].size == 4u,
    222            "%s: parts[%u].size=%u want 4", tag, (unsigned)i,
    223            (unsigned)ai->parts[i].size);
    224     EXPECT(ai->parts[i].src_offset == i * 4u,
    225            "%s: parts[%u].src_offset=%u want %u", tag, (unsigned)i,
    226            (unsigned)ai->parts[i].src_offset, (unsigned)(i * 4u));
    227   }
    228 }
    229 
    230 static void test_aapcs32_specifics(void) {
    231   KitCompiler* c =
    232       new_compiler(KIT_ARCH_ARM_32, KIT_OS_FREESTANDING, KIT_OBJ_ELF);
    233   static const u32 words[] = {1u, 2u, 4u, 8u};
    234   char tag[64];
    235 
    236   for (u32 i = 0; i < sizeof words / sizeof words[0]; ++i) {
    237     KitCgTypeId rec = record_i32s(c, words[i]);
    238     const ABIFuncInfo* fi = classify_fn(c, rec, rec);
    239     snprintf(tag, sizeof tag, "arm32 record%u arg", (unsigned)(words[i] * 4u));
    240     expect_arm32_word_parts(tag, &fi->params[0], words[i]);
    241     snprintf(tag, sizeof tag, "arm32 record%u ret", (unsigned)(words[i] * 4u));
    242     if (words[i] == 1u) {
    243       expect_arm32_word_parts(tag, &fi->ret, 1u);
    244       EXPECT(fi->has_sret == 0, "%s: one-word result should not use sret", tag);
    245       EXPECT(fi->sret_consumes_int_arg == 0,
    246              "%s: one-word result should not consume an argument register",
    247              tag);
    248     } else {
    249       EXPECT(fi->ret.kind == ABI_ARG_INDIRECT,
    250              "%s: kind=%d want INDIRECT", tag, (int)fi->ret.kind);
    251       EXPECT(fi->ret.nparts == 0, "%s: nparts=%u want 0", tag,
    252              (unsigned)fi->ret.nparts);
    253       EXPECT((fi->ret.flags & ABI_AF_SRET) != 0,
    254              "%s: flags=0x%x missing SRET", tag, (unsigned)fi->ret.flags);
    255       EXPECT(fi->ret.indirect_align == 4u,
    256              "%s: indirect_align=%u want 4", tag,
    257              (unsigned)fi->ret.indirect_align);
    258       EXPECT(fi->has_sret == 1, "%s: composite result should use sret", tag);
    259       EXPECT(fi->sret_consumes_int_arg == 1,
    260              "%s: sret pointer should consume r0", tag);
    261     }
    262   }
    263 
    264   {
    265     KitCgFieldDesc fields[3];
    266     KitCgRecordDesc desc;
    267     KitCgTypeId i8 = builtin(c, KIT_CG_BUILTIN_I8);
    268     const ABIFuncInfo* fi;
    269     memset(fields, 0, sizeof fields);
    270     for (u32 i = 0; i < 3u; ++i) fields[i].type = i8;
    271     memset(&desc, 0, sizeof desc);
    272     desc.fields = fields;
    273     desc.nfields = 3u;
    274     fi = classify_fn(c, kit_cg_type_record(c, &desc),
    275                      kit_cg_type_record(c, &desc));
    276     EXPECT(fi->params[0].kind == ABI_ARG_DIRECT,
    277            "arm32 record3 arg: want DIRECT");
    278     EXPECT(fi->params[0].nparts == 1u,
    279            "arm32 record3 arg: nparts=%u want 1",
    280            (unsigned)fi->params[0].nparts);
    281     EXPECT(fi->params[0].parts && fi->params[0].parts[0].size == 3u,
    282            "arm32 record3 arg: tail size should be exact");
    283     EXPECT(fi->ret.kind == ABI_ARG_DIRECT,
    284            "arm32 record3 ret: want DIRECT");
    285     EXPECT(fi->ret.nparts == 1u,
    286            "arm32 record3 ret: nparts=%u want 1",
    287            (unsigned)fi->ret.nparts);
    288     EXPECT(fi->ret.parts && fi->ret.parts[0].size == 3u,
    289            "arm32 record3 ret: tail size should be exact");
    290     EXPECT(fi->has_sret == 0,
    291            "arm32 record3 ret: one-word result should not use sret");
    292   }
    293 
    294   {
    295     KitCgTypeId i64 = builtin(c, KIT_CG_BUILTIN_I64);
    296     KitCgTypeId rec = record2(c, i64, i64);
    297     const ABIFuncInfo* fi = classify_fn(c, rec, rec);
    298     expect_arm32_word_parts("arm32 aligned record16 arg", &fi->params[0],
    299                             4u);
    300     EXPECT(fi->params[0].parts && fi->params[0].parts[0].align == 8u,
    301            "arm32 aligned record16 arg: first-part align=%u want 8",
    302            fi->params[0].parts
    303                ? (unsigned)fi->params[0].parts[0].align
    304                : 0u);
    305     EXPECT(fi->ret.kind == ABI_ARG_INDIRECT,
    306            "arm32 aligned record16 ret: want INDIRECT");
    307     EXPECT(fi->ret.indirect_align == 8u,
    308            "arm32 aligned record16 ret: indirect_align=%u want 8",
    309            (unsigned)fi->ret.indirect_align);
    310     EXPECT((fi->ret.flags & ABI_AF_SRET) != 0,
    311            "arm32 aligned record16 ret: missing SRET");
    312   }
    313 
    314   {
    315     static const u32 nwords = 65537u;
    316     KitCgFieldDesc field;
    317     KitCgRecordDesc desc;
    318     KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32);
    319     KitCgTypeId array = kit_cg_type_array(c, i32, nwords);
    320     const ABIFuncInfo* fi;
    321     memset(&field, 0, sizeof field);
    322     field.type = array;
    323     memset(&desc, 0, sizeof desc);
    324     desc.fields = &field;
    325     desc.nfields = 1u;
    326     fi = classify_fn(c, kit_cg_type_record(c, &desc),
    327                      kit_cg_type_record(c, &desc));
    328     EXPECT(fi->params[0].kind == ABI_ARG_DIRECT,
    329            "arm32 huge record arg: want DIRECT");
    330     EXPECT(fi->params[0].nparts == nwords,
    331            "arm32 huge record arg: nparts=%u want %u",
    332            (unsigned)fi->params[0].nparts, (unsigned)nwords);
    333     EXPECT(fi->params[0].parts &&
    334                fi->params[0].parts[nwords - 1u].src_offset ==
    335                    (nwords - 1u) * 4u &&
    336                fi->params[0].parts[nwords - 1u].size == 4u,
    337            "arm32 huge record arg: last carrier lane was truncated");
    338     EXPECT(fi->ret.kind == ABI_ARG_INDIRECT &&
    339                (fi->ret.flags & ABI_AF_SRET) != 0,
    340            "arm32 huge record ret: want INDIRECT SRET");
    341   }
    342 
    343   kit_compiler_free(c);
    344 }
    345 
    346 static void check_target(KitArchKind arch, KitOSKind os, KitObjFmt obj) {
    347   KitCompiler* c = new_compiler(arch, os, obj);
    348   KitCgTypeId i128_ty = builtin(c, KIT_CG_BUILTIN_I128);
    349   KitCgTypeId f128_ty = builtin(c, KIT_CG_BUILTIN_F128);
    350   EXPECT(i128_ty != KIT_CG_TYPE_NONE, "%s/%s: missing i128 builtin",
    351          arch_name(arch), os_name(os));
    352   EXPECT(f128_ty != KIT_CG_TYPE_NONE, "%s/%s: missing f128 builtin",
    353          arch_name(arch), os_name(os));
    354 
    355   char tag[64];
    356 
    357   /* i128 — every native ABI: DIRECT/2 INT parts of 8B. */
    358   {
    359     const ABIFuncInfo* fi = classify_fn(c, i128_ty, i128_ty);
    360     snprintf(tag, sizeof tag, "%s/%s i128 arg", arch_name(arch), os_name(os));
    361     expect_direct_2x_int8(tag, &fi->params[0]);
    362     snprintf(tag, sizeof tag, "%s/%s i128 ret", arch_name(arch), os_name(os));
    363     expect_direct_2x_int8(tag, &fi->ret);
    364     EXPECT(fi->has_sret == 0, "%s/%s: i128 should not set has_sret",
    365            arch_name(arch), os_name(os));
    366   }
    367 
    368   /* f128 (long double / __float128). Per-target expectations differ. */
    369   {
    370     const ABIFuncInfo* fi = classify_fn(c, f128_ty, f128_ty);
    371     snprintf(tag, sizeof tag, "%s/%s f128 arg", arch_name(arch), os_name(os));
    372     if (arch == KIT_ARCH_X86_64 && os == KIT_OS_WINDOWS) {
    373       /* Win64: long double is 64-bit double. Front end normally lowers
    374        * f128 before classification; defensive path treats size-16 FP as
    375        * a size-8 double — DIRECT/1 FP part of 8B for both arg and ret. */
    376       expect_direct_1x_fp(tag, &fi->params[0], 8);
    377       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    378       expect_direct_1x_fp(tag, &fi->ret, 8);
    379       EXPECT(fi->has_sret == 0, "%s/%s: f128 should not set has_sret",
    380              arch_name(arch), os_name(os));
    381     } else if (arch == KIT_ARCH_X86_64) {
    382       /* SysV-x64: long double is x87 (80-bit padded to 16B). kit lacks
    383        * x87 support; classify as INDIRECT (memory) so it routes through
    384        * a stack image consistent with the wide16 CG-layer shortcut. */
    385       expect_indirect(tag, &fi->params[0], /*is_return=*/0);
    386       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    387       expect_indirect(tag, &fi->ret, /*is_return=*/1);
    388       EXPECT(fi->has_sret == 1, "%s/%s: f128 ret should set has_sret",
    389              arch_name(arch), os_name(os));
    390     } else if (arch == KIT_ARCH_ARM_64) {
    391       /* AAPCS64 / Apple ARM64: 128-bit FP scalar passes in a single Q
    392        * register — DIRECT/1 FP part of 16B. */
    393       expect_direct_1x_fp(tag, &fi->params[0], 16);
    394       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    395       expect_direct_1x_fp(tag, &fi->ret, 16);
    396       EXPECT(fi->has_sret == 0, "%s/%s: f128 should not set has_sret",
    397              arch_name(arch), os_name(os));
    398     } else if (arch == KIT_ARCH_RV64) {
    399       /* RV64 LP64D: long double passes like a 2*XLEN scalar — 2 INT parts. */
    400       expect_direct_2x_int8(tag, &fi->params[0]);
    401       snprintf(tag, sizeof tag, "%s/%s f128 ret", arch_name(arch), os_name(os));
    402       expect_direct_2x_int8(tag, &fi->ret);
    403       EXPECT(fi->has_sret == 0, "%s/%s: f128 should not set has_sret",
    404              arch_name(arch), os_name(os));
    405     }
    406   }
    407 
    408   if (arch == KIT_ARCH_X86_64) {
    409     KitCgTypeId f64_i64 = record2(c, builtin(c, KIT_CG_BUILTIN_F64),
    410                                   builtin(c, KIT_CG_BUILTIN_I64));
    411     KitCgTypeId i64_f64 = record2(c, builtin(c, KIT_CG_BUILTIN_I64),
    412                                   builtin(c, KIT_CG_BUILTIN_F64));
    413     KitCgTypeId f32x2 = record2(c, builtin(c, KIT_CG_BUILTIN_F32),
    414                                 builtin(c, KIT_CG_BUILTIN_F32));
    415     {
    416       const ABIFuncInfo* fi = classify_fn(c, f64_i64, f64_i64);
    417       snprintf(tag, sizeof tag, "%s/%s {double,long} arg", arch_name(arch),
    418                os_name(os));
    419       if (os == KIT_OS_WINDOWS) {
    420         expect_indirect(tag, &fi->params[0], /*is_return=*/0);
    421       } else {
    422         expect_direct_2(tag, &fi->params[0], ABI_CLASS_FP, ABI_CLASS_INT, 8, 8);
    423       }
    424       snprintf(tag, sizeof tag, "%s/%s {double,long} ret", arch_name(arch),
    425                os_name(os));
    426       if (os == KIT_OS_WINDOWS) {
    427         expect_indirect(tag, &fi->ret, /*is_return=*/1);
    428         EXPECT(fi->has_sret == 1, "%s/%s: mixed record should use sret",
    429                arch_name(arch), os_name(os));
    430       } else {
    431         expect_direct_2(tag, &fi->ret, ABI_CLASS_FP, ABI_CLASS_INT, 8, 8);
    432         EXPECT(fi->has_sret == 0, "%s/%s: mixed record should not use sret",
    433                arch_name(arch), os_name(os));
    434       }
    435     }
    436     {
    437       const ABIFuncInfo* fi = classify_fn(c, i64_f64, i64_f64);
    438       snprintf(tag, sizeof tag, "%s/%s {long,double} arg", arch_name(arch),
    439                os_name(os));
    440       if (os == KIT_OS_WINDOWS) {
    441         expect_indirect(tag, &fi->params[0], /*is_return=*/0);
    442       } else {
    443         expect_direct_2(tag, &fi->params[0], ABI_CLASS_INT, ABI_CLASS_FP, 8, 8);
    444       }
    445       snprintf(tag, sizeof tag, "%s/%s {long,double} ret", arch_name(arch),
    446                os_name(os));
    447       if (os == KIT_OS_WINDOWS) {
    448         expect_indirect(tag, &fi->ret, /*is_return=*/1);
    449       } else {
    450         expect_direct_2(tag, &fi->ret, ABI_CLASS_INT, ABI_CLASS_FP, 8, 8);
    451       }
    452     }
    453     {
    454       const ABIFuncInfo* fi = classify_fn(c, f32x2, f32x2);
    455       snprintf(tag, sizeof tag, "%s/%s {float,float} arg", arch_name(arch),
    456                os_name(os));
    457       if (os == KIT_OS_WINDOWS)
    458         expect_direct_1x_int(tag, &fi->params[0], 8);
    459       else
    460         expect_direct_1x_fp(tag, &fi->params[0], 8);
    461       snprintf(tag, sizeof tag, "%s/%s {float,float} ret", arch_name(arch),
    462                os_name(os));
    463       if (os == KIT_OS_WINDOWS)
    464         expect_direct_1x_int(tag, &fi->ret, 8);
    465       else
    466         expect_direct_1x_fp(tag, &fi->ret, 8);
    467     }
    468   }
    469 
    470   kit_compiler_free(c);
    471 }
    472 
    473 /* Build a record with N i8 fields (so size == N and align == 1). */
    474 static KitCgTypeId make_i8_record(KitCompiler* c, const char* tag_name,
    475                                   u32 nfields) {
    476   KitCgTypeId i8 = builtin(c, KIT_CG_BUILTIN_I8);
    477   KitCgFieldDesc fields[16];
    478   KitCgRecordDesc desc;
    479   static const char* const names[16] = {"f0", "f1", "f2", "f3", "f4", "f5",
    480                                         "f6", "f7", "f8", "f9", "fa", "fb",
    481                                         "fc", "fd", "fe", "ff"};
    482   if (nfields > 16) exit(2);
    483   memset(fields, 0, sizeof fields);
    484   for (u32 i = 0; i < nfields; ++i) {
    485     fields[i].name = kit_sym_intern(c, kit_slice_cstr(names[i]));
    486     fields[i].type = i8;
    487   }
    488   memset(&desc, 0, sizeof desc);
    489   desc.tag = kit_sym_intern(c, kit_slice_cstr(tag_name));
    490   desc.fields = fields;
    491   desc.nfields = nfields;
    492   return kit_cg_type_record(c, &desc);
    493 }
    494 
    495 /* Build a record { i64 a; i64 b; } — size 16, align 8. */
    496 static KitCgTypeId make_two_i64_record(KitCompiler* c, const char* tag_n) {
    497   KitCgTypeId i64 = builtin(c, KIT_CG_BUILTIN_I64);
    498   KitCgFieldDesc fields[2];
    499   KitCgRecordDesc desc;
    500   memset(fields, 0, sizeof fields);
    501   fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("a"));
    502   fields[0].type = i64;
    503   fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("b"));
    504   fields[1].type = i64;
    505   memset(&desc, 0, sizeof desc);
    506   desc.tag = kit_sym_intern(c, kit_slice_cstr(tag_n));
    507   desc.fields = fields;
    508   desc.nfields = 2;
    509   return kit_cg_type_record(c, &desc);
    510 }
    511 
    512 /* Classify a function `ret_ty fn(p0, p1, ..., pN-1)` and return its info. */
    513 static const ABIFuncInfo* classify_fn_n(KitCompiler* c, KitCgTypeId ret_ty,
    514                                         const KitCgTypeId* arg_types, u32 nargs,
    515                                         int variadic) {
    516   KitCgFuncParam params[8];
    517   KitCgFuncSig sig;
    518   KitCgTypeId fn;
    519   KitCgFuncResult sig_result;
    520   if (nargs > 8) exit(2);
    521   memset(params, 0, sizeof params);
    522   for (u32 i = 0; i < nargs; ++i) params[i].type = arg_types[i];
    523   memset(&sig, 0, sizeof sig);
    524   memset(&sig_result, 0, sizeof sig_result);
    525   sig_result.type = ret_ty;
    526   sig.result = sig_result;
    527   sig.params = params;
    528   sig.nparams = nargs;
    529   sig.abi_variadic = variadic ? true : false;
    530   fn = kit_cg_type_func(c, sig);
    531   return abi_cg_func_info(((Compiler*)c)->abi, fn);
    532 }
    533 
    534 /* Expect INDIRECT (memory image) with a specific indirect alignment.
    535  * Win64 preserves the source type's natural alignment in the byval/sret
    536  * copy — for a 3-byte i8 aggregate that's 1, not 8. */
    537 static void expect_indirect_align(const char* tag, const ABIArgInfo* ai,
    538                                   int is_return, u32 want_align) {
    539   EXPECT(ai->kind == ABI_ARG_INDIRECT, "%s: kind=%d want INDIRECT", tag,
    540          (int)ai->kind);
    541   EXPECT(ai->nparts == 0, "%s: nparts=%u want 0", tag, (unsigned)ai->nparts);
    542   EXPECT(ai->indirect_align == want_align, "%s: indirect_align=%u want %u", tag,
    543          (unsigned)ai->indirect_align, want_align);
    544   u32 expected_flag = is_return ? ABI_AF_SRET : ABI_AF_BYVAL;
    545   EXPECT((ai->flags & expected_flag) != 0, "%s: flags=0x%x missing %s", tag,
    546          (unsigned)ai->flags, is_return ? "SRET" : "BYVAL");
    547 }
    548 
    549 /* Expect DIRECT with a single INT part of the given size. */
    550 static void expect_direct_1x_int(const char* tag, const ABIArgInfo* ai,
    551                                  u32 want_size) {
    552   EXPECT(ai->kind == ABI_ARG_DIRECT, "%s: kind=%d want DIRECT", tag,
    553          (int)ai->kind);
    554   EXPECT(ai->nparts == 1, "%s: nparts=%u want 1", tag, (unsigned)ai->nparts);
    555   if (ai->nparts != 1 || !ai->parts) return;
    556   EXPECT(ai->parts[0].cls == ABI_CLASS_INT, "%s: parts[0].cls=%d want INT", tag,
    557          (int)ai->parts[0].cls);
    558   EXPECT(ai->parts[0].size == want_size, "%s: parts[0].size=%u want %u", tag,
    559          (unsigned)ai->parts[0].size, want_size);
    560 }
    561 
    562 /* Win64-specific ABI shape assertions: aggregate rules ({1,2,4,8} by value
    563  * else hidden pointer), va_list = void*, variadic flag wiring, and that
    564  * each scalar arg gets one part of the right class (reg-vs-stack
    565  * placement is codegen, not classifier output). */
    566 static void test_win64_specifics(void) {
    567   KitCompiler* c = new_compiler(KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    568   KitCgTypeId void_ty = builtin(c, KIT_CG_BUILTIN_VOID);
    569   KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32);
    570   KitCgTypeId f64 = builtin(c, KIT_CG_BUILTIN_F64);
    571   KitCgTypeId voidp = kit_cg_type_ptr(c, void_ty, 0);
    572   KitCgTypeId rec1 = make_i8_record(c, "S1", 1);
    573   KitCgTypeId rec3 = make_i8_record(c, "S3", 3);
    574   KitCgTypeId rec16 = make_two_i64_record(c, "S16");
    575 
    576   /* Case 1: int main(void) — DIRECT/1 INT/4 ret, no params. */
    577   {
    578     const ABIFuncInfo* fi = classify_fn_n(c, i32, NULL, 0, 0);
    579     expect_direct_1x_int("win64 main ret", &fi->ret, 4);
    580     EXPECT(fi->nparams == 0, "win64 main: nparams=%u want 0",
    581            (unsigned)fi->nparams);
    582     EXPECT(fi->has_sret == 0, "win64 main: has_sret set");
    583     EXPECT(fi->variadic == 0, "win64 main: variadic set");
    584   }
    585 
    586   /* Case 2: void f(int,int,int,int,int) — 5 ints, each DIRECT/1 INT/4.
    587    * Reg vs stack placement (4 reg slots) is a codegen concern; the
    588    * classifier emits per-arg parts regardless. */
    589   {
    590     KitCgTypeId args[5] = {i32, i32, i32, i32, i32};
    591     const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 5, 0);
    592     EXPECT(fi->nparams == 5, "win64 5xint: nparams=%u want 5",
    593            (unsigned)fi->nparams);
    594     for (u32 i = 0; i < 5; ++i) {
    595       char t[64];
    596       snprintf(t, sizeof t, "win64 5xint arg[%u]", i);
    597       expect_direct_1x_int(t, &fi->params[i], 4);
    598     }
    599   }
    600 
    601   /* Case 3: void f(double,double,double,double,double) — 5 doubles. */
    602   {
    603     KitCgTypeId args[5] = {f64, f64, f64, f64, f64};
    604     const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 5, 0);
    605     EXPECT(fi->nparams == 5, "win64 5xfp: nparams=%u want 5",
    606            (unsigned)fi->nparams);
    607     for (u32 i = 0; i < 5; ++i) {
    608       char t[64];
    609       snprintf(t, sizeof t, "win64 5xfp arg[%u]", i);
    610       expect_direct_1x_fp(t, &fi->params[i], 8);
    611     }
    612   }
    613 
    614   /* Case 4: void f(int,double,int,double) — slot-shared on Win64.
    615    * The classifier just emits per-arg parts of the right class; slot
    616    * sharing is a codegen call-site concern. */
    617   {
    618     KitCgTypeId args[4] = {i32, f64, i32, f64};
    619     const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 4, 0);
    620     EXPECT(fi->nparams == 4, "win64 mix: nparams=%u want 4",
    621            (unsigned)fi->nparams);
    622     expect_direct_1x_int("win64 mix arg[0]", &fi->params[0], 4);
    623     expect_direct_1x_fp("win64 mix arg[1]", &fi->params[1], 8);
    624     expect_direct_1x_int("win64 mix arg[2]", &fi->params[2], 4);
    625     expect_direct_1x_fp("win64 mix arg[3]", &fi->params[3], 8);
    626   }
    627 
    628   /* Case 5: struct{char a;} foo(void) — size 1, DIRECT/1 INT/1. */
    629   {
    630     const ABIFuncInfo* fi = classify_fn_n(c, rec1, NULL, 0, 0);
    631     expect_direct_1x_int("win64 ret S1", &fi->ret, 1);
    632     EXPECT(fi->has_sret == 0, "win64 S1 ret: has_sret set");
    633   }
    634 
    635   /* Case 6: struct{long a; long b;} foo(void) — size 16, INDIRECT/sret. */
    636   {
    637     const ABIFuncInfo* fi = classify_fn_n(c, rec16, NULL, 0, 0);
    638     expect_indirect("win64 ret S16", &fi->ret, /*is_return=*/1);
    639     EXPECT(fi->has_sret == 1, "win64 S16 ret: has_sret not set");
    640   }
    641 
    642   /* Case 7: struct{char,char,char} foo(void) — size 3, INDIRECT/sret on
    643    * Win64 (only {1,2,4,8} pass by value). Natural align of the 3-byte
    644    * i8 aggregate is 1, which Win64 preserves in indirect_align. */
    645   {
    646     const ABIFuncInfo* fi = classify_fn_n(c, rec3, NULL, 0, 0);
    647     expect_indirect_align("win64 ret S3", &fi->ret, /*is_return=*/1,
    648                           /*want_align=*/1);
    649     EXPECT(fi->has_sret == 1, "win64 S3 ret: has_sret not set");
    650   }
    651 
    652   /* Case 8: void f(struct{char,char,char}) — by-value 3-byte aggregate
    653    * goes by hidden pointer (BYVAL) on Win64. */
    654   {
    655     KitCgTypeId args[1] = {rec3};
    656     const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 0);
    657     EXPECT(fi->nparams == 1, "win64 S3 arg: nparams=%u want 1",
    658            (unsigned)fi->nparams);
    659     expect_indirect_align("win64 S3 arg", &fi->params[0], /*is_return=*/0,
    660                           /*want_align=*/1);
    661   }
    662 
    663   /* Case 9: int printf(const char*, ...) — variadic flag set. */
    664   {
    665     KitCgTypeId args[1] = {voidp};
    666     const ABIFuncInfo* fi = classify_fn_n(c, i32, args, 1, /*variadic=*/1);
    667     EXPECT(fi->variadic == 1, "win64 printf: variadic=%u want 1",
    668            (unsigned)fi->variadic);
    669     EXPECT(fi->vararg_on_stack == 0, "win64 printf: vararg_on_stack=%u want 0",
    670            (unsigned)fi->vararg_on_stack);
    671     expect_direct_1x_int("win64 printf ret", &fi->ret, 4);
    672   }
    673 
    674   /* Case 10: va_list info — Win64 has va_list = void* (8/8/PTR). */
    675   {
    676     ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi);
    677     EXPECT(vi.size == 8, "win64 va_list size=%u want 8", (unsigned)vi.size);
    678     EXPECT(vi.align == 8, "win64 va_list align=%u want 8", (unsigned)vi.align);
    679     EXPECT(vi.scalar_kind == ABI_SC_PTR,
    680            "win64 va_list scalar_kind=%u want ABI_SC_PTR (%u)",
    681            (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR);
    682   }
    683 
    684   kit_compiler_free(c);
    685 }
    686 
    687 /* AArch64-Windows mostly starts from AAPCS64. Deltas: va_list is `void*`,
    688  * and FP parameters to variadic functions are routed through integer slots. */
    689 static void test_aarch64_windows_variadic(void) {
    690   KitCompiler* c = new_compiler(KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    691   KitCgTypeId void_ty = builtin(c, KIT_CG_BUILTIN_VOID);
    692   KitCgTypeId f64 = builtin(c, KIT_CG_BUILTIN_F64);
    693   KitCgTypeId args[1] = {f64};
    694 
    695   ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi);
    696   EXPECT(vi.size == 8, "aarch64/windows va_list size=%u want 8",
    697          (unsigned)vi.size);
    698   EXPECT(vi.align == 8, "aarch64/windows va_list align=%u want 8",
    699          (unsigned)vi.align);
    700   EXPECT(vi.scalar_kind == ABI_SC_PTR,
    701          "aarch64/windows va_list scalar_kind=%u want ABI_SC_PTR (%u)",
    702          (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR);
    703 
    704   {
    705     const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 0);
    706     expect_direct_1x_fp("aarch64/windows nonvariadic double arg",
    707                         &fi->params[0], 8);
    708   }
    709   {
    710     const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 1);
    711     expect_direct_1x_int("aarch64/windows variadic double arg", &fi->params[0],
    712                          8);
    713     EXPECT(fi->vararg_on_stack == 0,
    714            "aarch64/windows variadic: vararg_on_stack=%u want 0",
    715            (unsigned)fi->vararg_on_stack);
    716   }
    717   kit_compiler_free(c);
    718 }
    719 
    720 static void test_apple_arm64_stack_traits(void) {
    721   KitCompiler* c = new_compiler(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO);
    722   KitCgTypeId void_ty = builtin(c, KIT_CG_BUILTIN_VOID);
    723   KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32);
    724   KitCgTypeId args[1] = {i32};
    725   const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 1);
    726 
    727   EXPECT(fi->vararg_on_stack == 1,
    728          "apple arm64 variadic: vararg_on_stack=%u want 1",
    729          (unsigned)fi->vararg_on_stack);
    730   EXPECT(fi->stack_arg_min_align == 4, "apple arm64 fixed stack min=%u want 4",
    731          (unsigned)fi->stack_arg_min_align);
    732   EXPECT(fi->vararg_stack_arg_min_align == 8,
    733          "apple arm64 vararg stack min=%u want 8",
    734          (unsigned)fi->vararg_stack_arg_min_align);
    735   {
    736     ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi);
    737     EXPECT(vi.size == 8, "apple arm64 va_list size=%u want 8",
    738            (unsigned)vi.size);
    739     EXPECT(vi.scalar_kind == ABI_SC_PTR,
    740            "apple arm64 va_list scalar_kind=%u want ABI_SC_PTR (%u)",
    741            (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR);
    742   }
    743   kit_compiler_free(c);
    744 }
    745 
    746 static void check_scalar_split_lane_target(KitArchKind arch, KitOSKind os,
    747                                            KitObjFmt obj, const char* tag,
    748                                            u32 want_i64, u32 want_f64) {
    749   KitCompiler* c = new_compiler(arch, os, obj);
    750   TargetABI* abi = ((Compiler*)c)->abi;
    751   KitCgTypeId i64 = builtin(c, KIT_CG_BUILTIN_I64);
    752   KitCgTypeId f64 = builtin(c, KIT_CG_BUILTIN_F64);
    753   KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32);
    754   KitCgTypeId f32 = builtin(c, KIT_CG_BUILTIN_F32);
    755 
    756   EXPECT(abi_cg_scalar_split_lane_size(abi, i64) == want_i64,
    757          "%s i64 split lane=%u want %u", tag,
    758          (unsigned)abi_cg_scalar_split_lane_size(abi, i64), (unsigned)want_i64);
    759   EXPECT(abi_cg_scalar_split_lane_size(abi, f64) == want_f64,
    760          "%s f64 split lane=%u want %u", tag,
    761          (unsigned)abi_cg_scalar_split_lane_size(abi, f64), (unsigned)want_f64);
    762   EXPECT(abi_cg_scalar_split_lane_size(abi, i32) == 0,
    763          "%s i32 should not be split-lane", tag);
    764   EXPECT(abi_cg_scalar_split_lane_size(abi, f32) == 0,
    765          "%s f32 should not be split-lane", tag);
    766 
    767   kit_compiler_free(c);
    768 }
    769 
    770 static void test_scalar_split_lane_size(void) {
    771   /* RV32's default profile is ilp32 (soft float): 64-bit ints and (always-soft)
    772    * doubles are represented as two 4-byte integer lanes. The split-lane sizes
    773    * are the same under ilp32f, since rv32 has no D and passes doubles in
    774    * integer-register pairs regardless of the float ABI. */
    775   check_scalar_split_lane_target(KIT_ARCH_RV32, KIT_OS_LINUX, KIT_OBJ_ELF,
    776                                  "rv32", 4, 4);
    777   check_scalar_split_lane_target(KIT_ARCH_ARM_32, KIT_OS_FREESTANDING,
    778                                  KIT_OBJ_ELF, "arm32", 4, 4);
    779   check_scalar_split_lane_target(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF,
    780                                  "rv64", 0, 0);
    781   check_scalar_split_lane_target(KIT_ARCH_WASM, KIT_OS_WASI, KIT_OBJ_WASM,
    782                                  "wasm32", 0, 0);
    783 }
    784 
    785 int main(void) {
    786   kit_unit_init(&g_u);
    787   check_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    788   check_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    789   check_target(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO);
    790   check_target(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF);
    791   check_target(KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    792   check_target(KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF);
    793   test_win64_specifics();
    794   test_aarch64_windows_variadic();
    795   test_apple_arm64_stack_traits();
    796   test_aapcs32_specifics();
    797   test_scalar_split_lane_size();
    798   kit_unit_summary(&g_u, "abi_classify_test");
    799   return kit_unit_status(&g_u);
    800 }