zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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:
Msrc/codegen/llvm/FuncGen.zig | 31++++++++++++++++++++++++++++++-
Mtest/c_abi/cfuncs.c | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtest/c_abi/main.zig | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
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 {