llvm: fix aarch64 c abi HFA detection

Aggregate types do not count as Homogeneous Aggregates if they have
padding gaps between fields or at the end due to field alignments.
This commit is contained in:
Jacob Young
2026-04-25 09:11:18 -04:00
parent 23bcb8148f
commit cb1c7319b5
4 changed files with 227 additions and 64 deletions
+49 -43
View File
@@ -1,5 +1,6 @@
const assert = @import("std").debug.assert;
const std = @import("std");
const InternPool = @import("../../InternPool.zig");
const Type = @import("../../Type.zig");
const Zcu = @import("../../Zcu.zig");
@@ -15,12 +16,10 @@ pub const Class = union(enum) {
pub fn classifyType(ty: Type, zcu: *Zcu) Class {
assert(ty.hasRuntimeBits(zcu));
var maybe_float_bits: ?u16 = null;
switch (ty.zigTypeTag(zcu)) {
.@"struct" => {
if (ty.containerLayout(zcu) == .@"packed") return .byval;
const float_count = countFloats(ty, zcu, &maybe_float_bits);
if (float_count <= sret_float_count) return .{ .float_array = float_count };
if (countFloats(ty, zcu)) |float| return .{ .float_array = float.count };
const bit_size = ty.bitSize(zcu);
if (bit_size > 128) return .memory;
@@ -29,8 +28,7 @@ pub fn classifyType(ty: Type, zcu: *Zcu) Class {
},
.@"union" => {
if (ty.containerLayout(zcu) == .@"packed") return .byval;
const float_count = countFloats(ty, zcu, &maybe_float_bits);
if (float_count <= sret_float_count) return .{ .float_array = float_count };
if (countFloats(ty, zcu)) |float| return .{ .float_array = float.count };
const bit_size = ty.bitSize(zcu);
if (bit_size > 128) return .memory;
@@ -70,46 +68,52 @@ pub fn classifyType(ty: Type, zcu: *Zcu) Class {
}
}
const sret_float_count = 4;
fn countFloats(ty: Type, zcu: *Zcu, maybe_float_bits: *?u16) u8 {
const CountFloatsResult = struct {
ty: Type,
count: std.math.IntFittingRange(0, max_count),
const none: CountFloatsResult = .{ .ty = .void, .count = 0 };
const max_count = 4;
};
fn countFloats(ty: Type, zcu: *Zcu) ?CountFloatsResult {
const ip = &zcu.intern_pool;
const target = zcu.getTarget();
const invalid = std.math.maxInt(u8);
if (!ty.hasRuntimeBits(zcu)) return .none;
switch (ty.zigTypeTag(zcu)) {
.@"union" => {
const union_obj = zcu.typeToUnion(ty).?;
var max_count: u8 = 0;
for (union_obj.field_types.get(ip)) |field_ty| {
const field_count = countFloats(Type.fromInterned(field_ty), zcu, maybe_float_bits);
if (field_count == invalid) return invalid;
if (field_count > max_count) max_count = field_count;
if (max_count > sret_float_count) return invalid;
const loaded_union = zcu.typeToUnion(ty).?;
var result: CountFloatsResult = .none;
for (loaded_union.field_types.get(ip)) |field_ty| {
const float = countFloats(Type.fromInterned(field_ty), zcu) orelse return null;
if (result.ty.toIntern() == .void_type) {
result.ty = float.ty;
} else if (result.ty.bitSize(zcu) != float.ty.bitSize(zcu)) return null;
result.count = @max(result.count, float.count);
}
return max_count;
if (ty.abiSize(zcu) != result.ty.abiSize(zcu) * result.count) return null;
return result;
},
.@"struct" => {
const fields_len = ty.structFieldCount(zcu);
var count: u8 = 0;
var i: u32 = 0;
while (i < fields_len) : (i += 1) {
const field_ty = ty.fieldType(i, zcu);
const field_count = countFloats(field_ty, zcu, maybe_float_bits);
if (field_count == invalid) return invalid;
count += field_count;
if (count > sret_float_count) return invalid;
var result: CountFloatsResult = .none;
var field_it: InternPool.LoadedStructType.RuntimeOrderIterator = if (zcu.typeToStruct(ty)) |loaded_struct|
loaded_struct.iterateRuntimeOrder(ip)
else
.{ .runtime_order = null, .fields_len = ty.structFieldCount(zcu), .next_index = 0 };
while (field_it.next()) |field_index| {
if (ty.structFieldOffset(field_index, zcu) != result.ty.abiSize(zcu) * result.count) return null;
const field_ty = ty.fieldType(field_index, zcu);
const float = countFloats(field_ty, zcu) orelse return null;
if (result.ty.toIntern() == .void_type) {
result.ty = float.ty;
} else if (result.ty.bitSize(zcu) != float.ty.bitSize(zcu)) return null;
if (float.count > CountFloatsResult.max_count - result.count) return null;
result.count += float.count;
}
return count;
if (ty.abiSize(zcu) != result.ty.abiSize(zcu) * result.count) return null;
return result;
},
.float => {
const float_bits = maybe_float_bits.* orelse {
maybe_float_bits.* = ty.floatBits(target);
return 1;
};
if (ty.floatBits(target) == float_bits) return 1;
return invalid;
},
.void => return 0,
else => return invalid,
.float => return .{ .ty = ty, .count = 1 },
else => return null,
}
}
@@ -117,17 +121,19 @@ pub fn getFloatArrayType(ty: Type, zcu: *Zcu) ?Type {
const ip = &zcu.intern_pool;
switch (ty.zigTypeTag(zcu)) {
.@"union" => {
const union_obj = zcu.typeToUnion(ty).?;
for (union_obj.field_types.get(ip)) |field_ty| {
const loaded_union = zcu.typeToUnion(ty).?;
for (loaded_union.field_types.get(ip)) |field_ty| {
if (getFloatArrayType(Type.fromInterned(field_ty), zcu)) |some| return some;
}
return null;
},
.@"struct" => {
const fields_len = ty.structFieldCount(zcu);
var i: u32 = 0;
while (i < fields_len) : (i += 1) {
const field_ty = ty.fieldType(i, zcu);
var field_it: InternPool.LoadedStructType.RuntimeOrderIterator = if (zcu.typeToStruct(ty)) |loaded_struct|
loaded_struct.iterateRuntimeOrder(ip)
else
.{ .runtime_order = null, .fields_len = ty.structFieldCount(zcu), .next_index = 0 };
while (field_it.next()) |field_index| {
const field_ty = ty.fieldType(field_index, zcu);
if (getFloatArrayType(field_ty, zcu)) |some| return some;
}
return null;
+7 -6
View File
@@ -6707,17 +6707,18 @@ const ParamTypeIterator = struct {
.double_integer => return Lowering{ .i64_array = 2 },
.fields => {
it.types_len = 0;
var offset: u64 = 0;
for (0..ty.structFieldCount(zcu)) |field_index| {
var field_it: InternPool.LoadedStructType.RuntimeOrderIterator = if (zcu.typeToStruct(ty)) |loaded_struct|
loaded_struct.iterateRuntimeOrder(&zcu.intern_pool)
else
.{ .runtime_order = null, .fields_len = ty.structFieldCount(zcu), .next_index = 0 };
while (field_it.next()) |field_index| {
const field_ty = ty.fieldType(field_index, zcu);
if (!field_ty.hasRuntimeBits(zcu)) continue;
offset = field_ty.abiAlignment(zcu).forward(offset);
it.types_buffer[it.types_len] = try it.object.lowerType(field_ty);
it.offsets_buffer[it.types_len] = offset;
it.offsets_buffer[it.types_len] = ty.structFieldOffset(field_index, zcu);
it.types_len += 1;
offset += field_ty.abiSize(zcu);
}
it.offsets_buffer[it.types_len] = offset;
it.offsets_buffer[it.types_len] = ty.abiSize(zcu);
it.llvm_index += it.types_len - 1;
return .multiple_llvm_types;
},
+95 -13
View File
@@ -1,5 +1,6 @@
#include <complex.h>
#include <inttypes.h>
#include <stdalign.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
@@ -209,7 +210,7 @@ struct Struct_u8 zig_ret_struct_u8(void);
void zig_struct_u8(struct Struct_u8, size_t);
struct Struct_u8 c_ret_struct_u8(void) {
return (struct Struct_u8){ 4 };
return (struct Struct_u8){ .a = 4 };
}
void c_struct_u8(struct Struct_u8 s, size_t i) {
@@ -226,7 +227,7 @@ struct Struct_u16 zig_ret_struct_u16(void);
void zig_struct_u16(struct Struct_u16, size_t);
struct Struct_u16 c_ret_struct_u16(void) {
return (struct Struct_u16){ 10 };
return (struct Struct_u16){ .a = 10 };
}
void c_struct_u16(struct Struct_u16 s, size_t i) {
@@ -243,7 +244,7 @@ struct Struct_u32 zig_ret_struct_u32(void);
void zig_struct_u32(struct Struct_u32, size_t);
struct Struct_u32 c_ret_struct_u32(void) {
return (struct Struct_u32){ 16 };
return (struct Struct_u32){ .a = 16 };
}
void c_struct_u32(struct Struct_u32 s, size_t i) {
@@ -260,7 +261,7 @@ struct Struct_u64 zig_ret_struct_u64(void);
void zig_struct_u64(struct Struct_u64, size_t);
struct Struct_u64 c_ret_struct_u64(void) {
return (struct Struct_u64){ 22 };
return (struct Struct_u64){ .a = 22 };
}
void c_struct_u64(struct Struct_u64 s, size_t i) {
@@ -286,7 +287,7 @@ void zig_struct_u64_u64_7(size_t, size_t, size_t, size_t, size_t, size_t, size_t
void zig_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64, size_t);
struct Struct_u64_u64 c_ret_struct_u64_u64(void) {
return (struct Struct_u64_u64){ 21, 22 };
return (struct Struct_u64_u64){ .a = 21, .b = 22 };
}
void c_struct_u64_u64_0(struct Struct_u64_u64 s, size_t i) {
@@ -344,7 +345,7 @@ struct Struct_f32 zig_ret_struct_f32(void);
void zig_struct_f32(struct Struct_f32);
struct Struct_f32 c_ret_struct_f32(void) {
return (struct Struct_f32){ 2.5f };
return (struct Struct_f32){ .a = 2.5f };
}
void c_struct_f32(struct Struct_f32 s) {
@@ -360,13 +361,49 @@ struct Struct_f64 zig_ret_struct_f64(void);
void zig_struct_f64(struct Struct_f64);
struct Struct_f64 c_ret_struct_f64(void) {
return (struct Struct_f64){ 2.5 };
return (struct Struct_f64){ .a = 2.5 };
}
void c_struct_f64(struct Struct_f64 s) {
assert_or_panic(s.a == 2.5);
}
struct Struct_f32a8 {
alignas(8) float a;
};
struct Struct_f32a8 zig_ret_struct_f32a8(void);
void zig_struct_f32a8(struct Struct_f32a8, float);
struct Struct_f32a8 c_ret_struct_f32a8(void) {
return (struct Struct_f32a8){ .a = 4.125f };
}
void c_struct_f32a8(struct Struct_f32a8 s, float f) {
assert_or_panic(s.a == 5.375f);
assert_or_panic(f == 6.5f);
}
struct Struct_f32a8_f32a8 {
alignas(8) float a;
alignas(8) float b;
};
struct Struct_f32a8_f32a8 zig_ret_struct_f32a8_f32a8(void);
void zig_struct_f32a8_f32a8(struct Struct_f32a8_f32a8, float);
struct Struct_f32a8_f32a8 c_ret_struct_f32a8_f32a8(void) {
return (struct Struct_f32a8_f32a8){ .a = 6.625f, .b = 7.875f };
}
void c_struct_f32a8_f32a8(struct Struct_f32a8_f32a8 s, float f) {
assert_or_panic(s.a == 8.0625f);
assert_or_panic(s.b == 9.1875f);
assert_or_panic(f == 10.5f);
}
struct Struct_f32f32_f32 {
struct {
float b, c;
@@ -379,7 +416,7 @@ struct Struct_f32f32_f32 zig_ret_struct_f32f32_f32(void);
void zig_struct_f32f32_f32(struct Struct_f32f32_f32);
struct Struct_f32f32_f32 c_ret_struct_f32f32_f32(void) {
return (struct Struct_f32f32_f32){ { 1.0f, 2.0f }, 3.0f };
return (struct Struct_f32f32_f32){ .a = { .b = 1.0f, .c = 2.0f }, .d = 3.0f };
}
void c_struct_f32f32_f32(struct Struct_f32f32_f32 s) {
@@ -400,7 +437,7 @@ struct Struct_f32_f32f32 zig_ret_struct_f32_f32f32(void);
void zig_struct_f32_f32f32(struct Struct_f32_f32f32);
struct Struct_f32_f32f32 c_ret_struct_f32_f32f32(void) {
return (struct Struct_f32_f32f32){ 1.0f, { 2.0f, 3.0f } };
return (struct Struct_f32_f32f32){ .a = 1.0f, .b = { .c = 2.0f, .d = 3.0f } };
}
void c_struct_f32_f32f32(struct Struct_f32_f32f32 s) {
@@ -2870,7 +2907,7 @@ void run_c_tests(void) {
{
struct Struct_f32 s = zig_ret_struct_f32();
assert_or_panic(s.a == 2.5f);
zig_struct_f32((struct Struct_f32){ 2.5f });
zig_struct_f32((struct Struct_f32){ .a = 2.5f });
}
#endif
@@ -2879,17 +2916,62 @@ void run_c_tests(void) {
{
struct Struct_f64 s = zig_ret_struct_f64();
assert_or_panic(s.a == 2.5);
zig_struct_f64((struct Struct_f64){ 2.5 });
zig_struct_f64((struct Struct_f64){ .a = 2.5 });
}
#endif
#endif
#if !defined(__arm__)
#if !defined(__loongarch__)
#if !defined(__mips64__)
#if !defined(__powerpc__)
#if !defined(ZIG_RISCV32)
#if !defined(__s390x__)
#if !defined(__i386__)
{
struct Struct_f32a8 s = zig_ret_struct_f32a8();
assert_or_panic(s.a == 1.25f);
zig_struct_f32a8((struct Struct_f32a8){ .a = 2.75f }, 3.5f);
}
#endif
#endif
#endif
#endif
#endif
#endif
#endif
#if !defined(__arm__)
#if !defined(__loongarch__)
#if !defined(__mips64__)
#if !defined(__powerpc__)
#if !defined(__riscv)
#if !defined(__s390x__)
#if !defined(__i386__)
#if !defined(__x86_64__)
{
struct Struct_f32a8_f32a8 s = zig_ret_struct_f32a8_f32a8();
assert_or_panic(s.a == 1.25f);
assert_or_panic(s.b == 2.75f);
zig_struct_f32a8_f32a8((struct Struct_f32a8_f32a8){ .a = 3.125f, .b = 4.375f }, 5.5f);
}
#endif
#endif
#endif
#endif
#endif
#endif
#endif
#endif
#if !(defined(__arm__) && defined(__SOFTFP__))
#if !defined(__loongarch__) && !defined(__mips64__)
{
struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32();
assert_or_panic(s.a.b == 1.0f);
assert_or_panic(s.a.c == 2.0f);
assert_or_panic(s.d == 3.0f);
zig_struct_f32f32_f32((struct Struct_f32f32_f32){ { 1.0f, 2.0f }, 3.0f });
zig_struct_f32f32_f32((struct Struct_f32f32_f32){ .a = { .b = 1.0f, .c = 2.0f }, .d = 3.0f });
}
{
@@ -2897,7 +2979,7 @@ void run_c_tests(void) {
assert_or_panic(s.a == 1.0f);
assert_or_panic(s.b.c == 2.0f);
assert_or_panic(s.b.d == 3.0f);
zig_struct_f32_f32f32((struct Struct_f32_f32f32){ 1.0f, { 2.0f, 3.0f } });
zig_struct_f32_f32f32((struct Struct_f32_f32f32){ .a = 1.0f, .b = { .c = 2.0f, .d = 3.0f } });
}
#endif
#endif
+76 -2
View File
@@ -444,7 +444,7 @@ extern fn c_struct_u64_u64_6(usize, usize, usize, usize, usize, usize, Struct_u6
extern fn c_struct_u64_u64_7(usize, usize, usize, usize, usize, usize, usize, Struct_u64_u64, usize) void;
extern fn c_struct_u64_u64_8(usize, usize, usize, usize, usize, usize, usize, usize, Struct_u64_u64, usize) void;
test "C ABI struct u64 u64" {
test "C ABI struct u64, u64" {
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
if (builtin.cpu.arch == .hexagon) return error.SkipZigTest;
@@ -518,6 +518,80 @@ test "C ABI struct f64" {
c_struct_f64(.{ .a = 2.5 });
}
const Struct_f32a8 = extern struct {
a: f32 align(8),
};
export fn zig_ret_struct_f32a8() Struct_f32a8 {
return .{ .a = 1.25 };
}
export fn zig_struct_f32a8(s: Struct_f32a8, f: f32) void {
expect(s.a == 2.75) catch @panic("test failure");
expect(f == 3.5) catch @panic("test failure");
}
extern fn c_ret_struct_f32a8() Struct_f32a8;
extern fn c_struct_f32a8(Struct_f32a8, f32) void;
test "C ABI struct f32 align(8)" {
if (builtin.cpu.arch.isArm()) return error.SkipZigTest;
if (builtin.cpu.arch.isLoongArch()) return error.SkipZigTest;
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest;
if (builtin.cpu.arch == .riscv32) return error.SkipZigTest;
if (builtin.cpu.arch == .s390x) return error.SkipZigTest;
if (builtin.cpu.arch == .x86) return error.SkipZigTest;
const s = c_ret_struct_f32a8();
try expect(s.a == 4.125);
c_struct_f32a8(.{ .a = 5.375 }, 6.5);
}
const Struct_f32a8_f32a8 = extern struct {
a: f32 align(8),
b: f32 align(8),
};
comptime {
skip: {
if (builtin.zig_backend == .stage2_x86_64) break :skip;
_ = struct {
export fn zig_ret_struct_f32a8_f32a8() Struct_f32a8_f32a8 {
return .{ .a = 1.25, .b = 2.75 };
}
export fn zig_struct_f32a8_f32a8(s: Struct_f32a8_f32a8, f: f32) void {
expect(s.a == 3.125) catch @panic("test failure");
expect(s.b == 4.375) catch @panic("test failure");
expect(f == 5.5) catch @panic("test failure");
}
};
}
}
extern fn c_ret_struct_f32a8_f32a8() Struct_f32a8_f32a8;
extern fn c_struct_f32a8_f32a8(Struct_f32a8_f32a8, f32) void;
test "C ABI struct f32 align(8), f32 align(8)" {
if (builtin.cpu.arch.isArm()) return error.SkipZigTest;
if (builtin.cpu.arch.isLoongArch()) return error.SkipZigTest;
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest;
if (builtin.cpu.arch.isRISCV()) return error.SkipZigTest;
if (builtin.cpu.arch == .s390x) return error.SkipZigTest;
if (builtin.cpu.arch == .x86) return error.SkipZigTest;
if (builtin.cpu.arch == .x86_64) return error.SkipZigTest;
const s = c_ret_struct_f32a8_f32a8();
try expect(s.a == 6.625);
try expect(s.b == 7.875);
c_struct_f32a8_f32a8(.{ .a = 8.0625, .b = 9.1875 }, 10.5);
}
const Struct_f32f32_f32 = extern struct {
a: extern struct { b: f32, c: f32 },
d: f32,
@@ -537,7 +611,7 @@ extern fn c_ret_struct_f32f32_f32() Struct_f32f32_f32;
extern fn c_struct_f32f32_f32(Struct_f32f32_f32) void;
test "C ABI struct {f32,f32} f32" {
test "C ABI struct {f32, f32}, f32" {
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
if (builtin.cpu.arch.isArm() and builtin.abi.float() == .soft) return error.SkipZigTest;