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