commit 1f43e049d0d46c14ce5d598f1b6587d2c043b82c (tree)
parent b0608a3bd28b402ab33cb6b74ee97d194d8df229
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 9 Jun 2026 03:48:54 +0200
Merge pull request 'llvm: fix return type lowering of x86 fastcall' (#35671) from teflate/zig:x86-fastcall-sret into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35671
Reviewed-by: Andrew Kelley <andrew@ziglang.org>
Diffstat:
3 files changed, 237 insertions(+), 9 deletions(-)
diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig
@@ -6944,6 +6944,7 @@ pub fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: *cons
.x86_64_win => x86_64_abi.classifyWindows(return_type, zcu, target, .ret) == .memory,
.x86_sysv, .x86_win => isByRef(return_type, zcu),
.x86_stdcall => !isScalar(zcu, return_type),
+ .x86_fastcall => firstParamSRetX86Fastcall(zcu, return_type),
.wasm_mvp => wasm_c_abi.classifyType(return_type, zcu) == .indirect,
.aarch64_aapcs,
.aarch64_aapcs_darwin,
@@ -6963,6 +6964,20 @@ pub fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: *cons
};
}
+fn firstParamSRetX86Fastcall(zcu: *Zcu, ty: Type) bool {
+ if (isScalar(zcu, ty)) {
+ return false;
+ }
+ const tag = ty.zigTypeTag(zcu);
+ if (tag == .@"struct" or tag == .@"union") {
+ const size = ty.abiSize(zcu);
+ if (size == 1 or size == 2 or size == 4 or size == 8) {
+ return false;
+ }
+ }
+ return true;
+}
+
fn firstParamSRetSystemV(ty: Type, zcu: *Zcu, target: *const std.Target) bool {
const class = x86_64_abi.classifySystemV(ty, zcu, target, .ret);
if (class[0] == .memory) return true;
@@ -6984,10 +6999,10 @@ pub fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Erro
switch (fn_info.cc) {
.@"inline" => unreachable,
.auto => return if (returnTypeByRef(zcu, target, return_type)) .void else o.lowerType(return_type),
-
.x86_64_sysv => return lowerSystemVFnRetTy(o, fn_info),
.x86_64_win => return lowerWin64FnRetTy(o, fn_info),
.x86_stdcall => return if (isScalar(zcu, return_type)) o.lowerType(return_type) else .void,
+ .x86_fastcall => return lowerX86FastcallFnRetTy(o, zcu, return_type),
.x86_sysv, .x86_win => return if (isByRef(return_type, zcu)) .void else o.lowerType(return_type),
.aarch64_aapcs, .aarch64_aapcs_darwin, .aarch64_aapcs_win => switch (aarch64_c_abi.classifyType(return_type, zcu)) {
.memory => return .void,
@@ -7038,6 +7053,20 @@ pub fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Erro
}
}
+fn lowerX86FastcallFnRetTy(o: *Object, zcu: *Zcu, ty: Type) Allocator.Error!Builder.Type {
+ if (isScalar(zcu, ty)) {
+ return o.lowerType(ty);
+ }
+ const tag = ty.zigTypeTag(zcu);
+ if (tag == .@"struct" or tag == .@"union") {
+ const size = ty.abiSize(zcu);
+ if (size == 1 or size == 2 or size == 4 or size == 8) {
+ return o.builder.intType(@intCast(size * 8));
+ }
+ }
+ return .void;
+}
+
fn lowerWin64FnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Builder.Type {
const zcu = o.zcu;
const return_type = Type.fromInterned(fn_info.return_type);
diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c
@@ -69,7 +69,7 @@ static void assert_or_panic(bool ok) {
# define ZIG_NO_COMPLEX
#endif
-#ifdef __powerpc__
+#ifdef ZIG_PPC32
# define ZIG_NO_COMPLEX
#endif
@@ -153,6 +153,10 @@ static void assert_or_panic(bool ok) {
#define ZIG_NO_F128
#endif
+#ifdef _MSC_VER
+#define ZIG_NO_F128
+#endif
+
#ifndef ZIG_NO_I128
struct i128 {
__int128 value;
@@ -198,12 +202,14 @@ void zig_ptr(void *);
void zig_bool(bool);
+#ifndef ZIG_NO_COMPLEX
// Note: These two functions match the signature of __mulsc3 and __muldc3 in compiler-rt (and libgcc)
float complex zig_cmultf_comp(float a_r, float a_i, float b_r, float b_i);
double complex zig_cmultd_comp(double a_r, double a_i, double b_r, double b_i);
float complex zig_cmultf(float complex a, float complex b);
double complex zig_cmultd(double complex a, double complex b);
+#endif
struct Struct_u8 {
uint8_t a;
@@ -5312,6 +5318,7 @@ void c_five_floats(float a, float b, float c, float d, float e) {
assert_or_panic(e == 5.0);
}
+#ifndef ZIG_NO_COMPLEX
float complex c_cmultf_comp(float a_r, float a_i, float b_r, float b_i) {
assert_or_panic(a_r == 1.25f);
assert_or_panic(a_i == 2.6f);
@@ -5347,6 +5354,7 @@ double complex c_cmultd(double complex a, double complex b) {
return 1.5 + I * 13.5;
}
+#endif
struct Struct_i32_i32 c_mut_struct_i32_i32(struct Struct_i32_i32 s) {
assert_or_panic(s.a == 1);
@@ -5792,7 +5800,7 @@ f16_struct c_f16_struct(f16_struct a) {
return (f16_struct){34};
}
-#if defined __x86_64__ || defined __i386__
+#if (defined __x86_64__ || defined __i386__) && !defined _MSC_VER
typedef long double f80;
f80 c_f80(f80 a) {
assert_or_panic((double)a == 12.34);
@@ -5907,7 +5915,7 @@ double c_byval_tail_callsite_attr(struct byval_tail_callsite_attr_Rect in) {
return in.size.width;
}
-#ifdef __i386__
+#if defined(__i386__) && defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER)
void __attribute__((fastcall)) zig_fastcall_check(int a, float b, void *c, double d, int e);
void __attribute__((fastcall)) c_fastcall_check(int a, float b, void *c, double d, int e) {
assert_or_panic(a == 1);
@@ -5915,7 +5923,98 @@ void __attribute__((fastcall)) c_fastcall_check(int a, float b, void *c, double
assert_or_panic((uintptr_t)c == 3);
assert_or_panic(d == 4.0);
assert_or_panic(e == 5);
- zig_fastcall_check(a, b, c, d, e);
+}
+
+typedef struct {
+ int a;
+ int b;
+ int c;
+} FastcallSRet;
+FastcallSRet __attribute__((fastcall)) zig_fastcall_sret(void);
+FastcallSRet __attribute__((fastcall)) c_fastcall_sret(void) {
+ return (FastcallSRet){
+ .a = 1,
+ .b = 2,
+ .c = 3
+ };
+}
+
+typedef struct {
+ char a;
+ short b;
+} FastcallNoSRet;
+FastcallNoSRet __attribute__((fastcall)) zig_fastcall_no_sret(void);
+FastcallNoSRet __attribute__((fastcall)) c_fastcall_no_sret(void) {
+ return (FastcallNoSRet){
+ .a = 1,
+ .b = 2
+ };
+}
+
+typedef struct {
+ float a;
+ float b;
+} FastcallNoSRetF32F32;
+FastcallNoSRetF32F32 __attribute__((fastcall)) zig_fastcall_no_sret_f32_f32(void);
+FastcallNoSRetF32F32 __attribute__((fastcall)) c_fastcall_no_sret_f32_f32(void) {
+ return (FastcallNoSRetF32F32){
+ .a = 1,
+ .b = 2
+ };
+}
+
+typedef struct {
+ double a;
+} FastcallNoSRetF64;
+FastcallNoSRetF64 __attribute__((fastcall)) zig_fastcall_no_sret_f64(void);
+FastcallNoSRetF64 __attribute__((fastcall)) c_fastcall_no_sret_f64(void) {
+ return (FastcallNoSRetF64){
+ .a = 1
+ };
+}
+
+float __attribute__((fastcall)) zig_fastcall_ret_f32(void);
+float __attribute__((fastcall)) c_fastcall_ret_f32(void) {
+ return 1;
+}
+
+double __attribute__((fastcall)) zig_fastcall_ret_f64(void);
+double __attribute__((fastcall)) c_fastcall_ret_f64(void) {
+ return 1;
+}
+
+void run_c_fastcall_tests(void) {
+ {
+ zig_fastcall_check(1, 2, (void*)3, 4, 5);
+ }
+ {
+ const FastcallSRet s = zig_fastcall_sret();
+ assert_or_panic(s.a == 1);
+ assert_or_panic(s.b == 2);
+ assert_or_panic(s.c == 3);
+ }
+ {
+ const FastcallNoSRet s = zig_fastcall_no_sret();
+ assert_or_panic(s.a == 1);
+ assert_or_panic(s.b == 2);
+ }
+ {
+ const FastcallNoSRetF32F32 s = zig_fastcall_no_sret_f32_f32();
+ assert_or_panic(s.a == 1);
+ assert_or_panic(s.b == 2);
+ }
+ {
+ const FastcallNoSRetF64 s = zig_fastcall_no_sret_f64();
+ assert_or_panic(s.a == 1);
+ }
+ {
+ const float s = zig_fastcall_ret_f32();
+ assert_or_panic(s == 1);
+ }
+ {
+ const double s = zig_fastcall_ret_f64();
+ assert_or_panic(s == 1);
+ }
}
void __attribute__((vectorcall)) zig_vectorcall_check(int a, float b, double c, void *d, float e, double f, double g, float h, float i, int j);
diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig
@@ -15,8 +15,8 @@ const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and
builtin.cpu.arch != .hexagon and
builtin.cpu.arch != .s390x; // https://github.com/llvm/llvm-project/issues/168460
-const have_f128 = builtin.cpu.arch.isWasm() or (builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin());
-const have_f80 = builtin.cpu.arch.isX86();
+const have_f128 = builtin.cpu.arch.isWasm() or (builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin() and builtin.abi != .msvc);
+const have_f80 = builtin.cpu.arch.isX86() and builtin.abi != .msvc;
extern fn run_c_tests() void;
@@ -6162,21 +6162,121 @@ test "byval tail callsite attribute" {
test "x86 fastcall calling convention" {
if (builtin.cpu.arch != .x86) return error.SkipZigTest;
+ if (builtin.os.tag != .windows) return error.SkipZigTest;
+ if (builtin.abi != .msvc) return error.SkipZigTest;
+
const static = struct {
- extern fn c_fastcall_check(a: c_int, b: f32, c: *anyopaque, d: f64, e: c_int) callconv(.{ .x86_fastcall = .{} }) void;
- export fn zig_fastcall_check(a: c_int, b: f32, c: *anyopaque, d: f64, e: c_int) callconv(.{ .x86_fastcall = .{} }) void {
+ const fastcall: std.builtin.CallingConvention = .{ .x86_fastcall = .{} };
+
+ extern fn c_fastcall_check(a: c_int, b: f32, c: *anyopaque, d: f64, e: c_int) callconv(fastcall) void;
+ export fn zig_fastcall_check(a: c_int, b: f32, c: *anyopaque, d: f64, e: c_int) callconv(fastcall) void {
if (a != 1) @panic("test failure");
if (b != 2.0) @panic("test failure");
if (@intFromPtr(c) != 3) @panic("test failure");
if (d != 4.0) @panic("test failure");
if (e != 5) @panic("test failure");
}
+
+ const SRet = extern struct {
+ a: i32,
+ b: i32,
+ c: i32,
+ };
+ extern fn c_fastcall_sret() callconv(fastcall) SRet;
+ export fn zig_fastcall_sret() callconv(fastcall) SRet {
+ return .{
+ .a = 1,
+ .b = 2,
+ .c = 3,
+ };
+ }
+
+ const NoSRet = extern struct {
+ a: i8,
+ b: i16,
+ };
+ extern fn c_fastcall_no_sret() callconv(fastcall) NoSRet;
+ export fn zig_fastcall_no_sret() callconv(fastcall) NoSRet {
+ return .{
+ .a = 1,
+ .b = 2,
+ };
+ }
+
+ const NoSRetF32F32 = extern struct {
+ a: f32,
+ b: f32,
+ };
+ extern fn c_fastcall_no_sret_f32_f32() callconv(fastcall) NoSRetF32F32;
+ export fn zig_fastcall_no_sret_f32_f32() callconv(fastcall) NoSRetF32F32 {
+ return .{
+ .a = 1,
+ .b = 2,
+ };
+ }
+
+ const NoSRetF64 = extern struct {
+ a: f64,
+ };
+ extern fn c_fastcall_no_sret_f64() callconv(fastcall) NoSRetF64;
+ export fn zig_fastcall_no_sret_f64() callconv(fastcall) NoSRetF64 {
+ return .{
+ .a = 1,
+ };
+ }
+
+ extern fn c_fastcall_ret_f32() callconv(fastcall) f32;
+ export fn zig_fastcall_ret_f32() callconv(fastcall) f32 {
+ return 1;
+ }
+
+ extern fn c_fastcall_ret_f64() callconv(fastcall) f64;
+ export fn zig_fastcall_ret_f64() callconv(fastcall) f64 {
+ return 1;
+ }
+
+ extern fn run_c_fastcall_tests() void;
};
+
static.c_fastcall_check(1, 2.0, @ptrFromInt(3), 4.0, 5);
+
+ {
+ const s = static.c_fastcall_sret();
+ try expect(s.a == 1);
+ try expect(s.b == 2);
+ try expect(s.c == 3);
+ }
+ {
+ const s = static.c_fastcall_no_sret();
+ try expect(s.a == 1);
+ try expect(s.b == 2);
+ }
+ {
+ const s = static.c_fastcall_no_sret_f32_f32();
+ try expect(s.a == 1);
+ try expect(s.b == 2);
+ }
+ {
+ const s = static.c_fastcall_no_sret_f64();
+ try expect(s.a == 1);
+ }
+ {
+ const s = static.c_fastcall_ret_f32();
+ try expect(s == 1);
+ }
+ {
+ const s = static.c_fastcall_ret_f64();
+ try expect(s == 1);
+ }
+
+ static.run_c_fastcall_tests();
}
test "x86 vectorcall calling convention" {
if (builtin.cpu.arch != .x86) return error.SkipZigTest;
+ if (builtin.os.tag != .windows) return error.SkipZigTest;
+ if (builtin.abi != .msvc) return error.SkipZigTest;
+
const static = struct {
extern fn c_vectorcall_check(a: c_int, b: f32, c: f64, d: *anyopaque, e: f32, f: f64, g: f64, h: f32, i: f32, j: c_int) callconv(.{ .x86_vectorcall = .{} }) void;
export fn zig_vectorcall_check(a: c_int, b: f32, c: f64, d: *anyopaque, e: f32, f: f64, g: f64, h: f32, i: f32, j: c_int) callconv(.{ .x86_vectorcall = .{} }) void {