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(¶m, 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 = ¶m; 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 }