authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-03-25 02:54:38-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-03-25 20:05:51-04:00
logca99896d7383f02812d6cea549ad4af276558f64
treeea1bb024cb4b7acf9d952cff35c0ec2df8603642
parent4e3fcbea84de17afdf391293dc143a1fcc7b2ef7

- x86_64: Copy arguments into the shadow store when generating a variadic function on Win64

- x86_64: Implement @cVaStart for Win64 - x86_64: Implement @cVaArg for Win64 - x86_64: Duplicate floating point register args equivalent integer registers when calling variadic functions on Win64 - tests: Enable var_args tests for the self-hosted backend on windows - tests: Add var_args test for floating point arguments

3 files changed, 197 insertions(+), 45 deletions(-)

lib/std/zig.zig+1-1
...@@ -991,7 +991,7 @@ pub const EmitArtifact = enum {...@@ -991,7 +991,7 @@ pub const EmitArtifact = enum {
991 /// paths under the output directory, where those paths are named according to this function.991 /// paths under the output directory, where those paths are named according to this function.
992 /// Returned string is allocated with `gpa` and owned by the caller.992 /// Returned string is allocated with `gpa` and owned by the caller.
993 pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {993 pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
994 // hack for stage2_x86_64 + coff994 // hack for stage2_x86_64 + coff. See Coff.flush.
995 if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";995 if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";
996 const suffix: []const u8 = switch (ea) {996 const suffix: []const u8 = switch (ea) {
997 .bin => return binNameAlloc(gpa, opts),997 .bin => return binNameAlloc(gpa, opts),
src/codegen/x86_64/CodeGen.zig+135-39
...@@ -2050,7 +2050,16 @@ fn gen(...@@ -2050,7 +2050,16 @@ fn gen(
20502050
2051 self.performReloc(skip_sse_reloc);2051 self.performReloc(skip_sse_reloc);
2052 },2052 },
2053 .x86_64_win => return self.fail("TODO implement gen var arg function for Win64", .{}),2053 .x86_64_win => {
2054 for (abi.Win64.c_abi_int_param_regs[0..], 0..) |reg, reg_i|
2055 try self.genSetMem(
2056 .{ .frame = .args_frame },
2057 @intCast(reg_i * 8),
2058 .usize,
2059 .{ .register = reg },
2060 .{},
2061 );
2062 },
2054 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),2063 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
2055 };2064 };
20562065
...@@ -176020,12 +176029,22 @@ fn genCall(self: *CodeGen, info: union(enum) {...@@ -176020,12 +176029,22 @@ fn genCall(self: *CodeGen, info: union(enum) {
176020 .indirect => |reg_off| try self.register_manager.getReg(reg_off.reg, null),176029 .indirect => |reg_off| try self.register_manager.getReg(reg_off.reg, null),
176021 else => unreachable,176030 else => unreachable,
176022 }176031 }
176023 for (call_info.args, arg_types, args, frame_indices) |dst_arg, arg_ty, src_arg, *frame_index|176032 for (call_info.args, arg_types, args, frame_indices, 0..) |dst_arg, arg_ty, src_arg, *frame_index, arg_i|
176024 switch (dst_arg) {176033 switch (dst_arg) {
176025 .none => {},176034 .none => {},
176026 .register => |reg| {176035 .register => |reg| {
176027 try self.register_manager.getReg(reg, null);176036 try self.register_manager.getReg(reg, null);
176028 try reg_locks.append(self.register_manager.lockReg(reg));176037 try reg_locks.append(self.register_manager.lockReg(reg));
176038
176039 if (fn_info.is_var_args and
176040 fn_info.cc == .x86_64_win and
176041 reg.class() == .sse and
176042 arg_i < abi.Win64.c_abi_int_param_regs.len)
176043 {
176044 // Floating point arguments must be duplicated into the equivalent integer registers on this ABI
176045 const int_reg = abi.Win64.c_abi_int_param_regs[arg_i];
176046 try reg_locks.append(self.register_manager.lockReg(int_reg));
176047 }
176029 },176048 },
176030 .register_pair => |regs| {176049 .register_pair => |regs| {
176031 for (regs) |reg| try self.register_manager.getReg(reg, null);176050 for (regs) |reg| try self.register_manager.getReg(reg, null);
...@@ -176129,7 +176148,7 @@ fn genCall(self: *CodeGen, info: union(enum) {...@@ -176129,7 +176148,7 @@ fn genCall(self: *CodeGen, info: union(enum) {
176129 else => unreachable,176148 else => unreachable,
176130 }176149 }
176131176150
176132 for (call_info.args, arg_types, args, frame_indices) |dst_arg, arg_ty, src_arg, frame_index|176151 for (call_info.args, arg_types, args, frame_indices, 0..) |dst_arg, arg_ty, src_arg, frame_index, arg_i|
176133 switch (dst_arg) {176152 switch (dst_arg) {
176134 .none, .load_frame => {},176153 .none, .load_frame => {},
176135 .register => |dst_reg| switch (fn_info.cc) {176154 .register => |dst_reg| switch (fn_info.cc) {
...@@ -176144,6 +176163,16 @@ fn genCall(self: *CodeGen, info: union(enum) {...@@ -176144,6 +176163,16 @@ fn genCall(self: *CodeGen, info: union(enum) {
176144 try self.genSetReg(dst_alias, promoted_ty, src_arg, opts);176163 try self.genSetReg(dst_alias, promoted_ty, src_arg, opts);
176145 if (promoted_ty.toIntern() != arg_ty.toIntern())176164 if (promoted_ty.toIntern() != arg_ty.toIntern())
176146 try self.truncateRegister(arg_ty, dst_alias);176165 try self.truncateRegister(arg_ty, dst_alias);
176166
176167 if (fn_info.is_var_args and
176168 fn_info.cc == .x86_64_win and
176169 dst_reg.class() == .sse and
176170 arg_i < abi.Win64.c_abi_int_param_regs.len)
176171 {
176172 const int_dst_reg = abi.Win64.c_abi_int_param_regs[arg_i];
176173 const int_dst_alias = registerAlias(int_dst_reg, promoted_abi_size);
176174 try self.genSetReg(int_dst_alias, promoted_ty, .{ .register = dst_alias }, opts);
176175 }
176147 },176176 },
176148 },176177 },
176149 .register_pair => try self.genCopy(arg_ty, dst_arg, src_arg, opts),176178 .register_pair => try self.genCopy(arg_ty, dst_arg, src_arg, opts),
...@@ -176180,7 +176209,8 @@ fn genCall(self: *CodeGen, info: union(enum) {...@@ -176180,7 +176209,8 @@ fn genCall(self: *CodeGen, info: union(enum) {
176180 else => unreachable,176209 else => unreachable,
176181 };176210 };
176182176211
176183 if (fn_info.is_var_args) try self.asmRegisterImmediate(.{ ._, .mov }, .al, .u(call_info.fp_count));176212 if (fn_info.is_var_args and fn_info.cc == .x86_64_sysv)
176213 try self.asmRegisterImmediate(.{ ._, .mov }, .al, .u(call_info.fp_count));
176184176214
176185 // Due to incremental compilation, how function calls are generated depends176215 // Due to incremental compilation, how function calls are generated depends
176186 // on linking.176216 // on linking.
...@@ -180782,7 +180812,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {...@@ -180782,7 +180812,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {
180782 field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));180812 field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));
180783 break :result .{ .load_frame = .{ .index = dst_fi } };180813 break :result .{ .load_frame = .{ .index = dst_fi } };
180784 },180814 },
180785 .x86_64_win => return self.fail("TODO implement c_va_start for Win64", .{}),180815 .x86_64_win => result: {
180816 const dst_fi = try self.allocFrameIndex(.initSpill(va_list_ty, zcu));
180817 const fn_info = zcu.typeToFunc(self.fn_type).?;
180818 try self.genSetMem(
180819 .{ .frame = dst_fi },
180820 0,
180821 ptr_anyopaque_ty,
180822 .{ .lea_frame = .{ .index = .args_frame, .off = @intCast(fn_info.param_types.len * 8) } },
180823 .{},
180824 );
180825 break :result .{ .load_frame = .{ .index = dst_fi } };
180826 },
180786 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),180827 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180787 };180828 };
180788 return self.finishAir(inst, result, .{ .none, .none, .none });180829 return self.finishAir(inst, result, .{ .none, .none, .none });
...@@ -180921,48 +180962,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {...@@ -180921,48 +180962,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {
180921 break :result dst_mcv;180962 break :result dst_mcv;
180922 }180963 }
180923180964
180924 assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);180965 try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
180925 const dst_mcv = if (promote_mcv.isRegister())180966 break :result promote_mcv;
180926 promote_mcv180967 },
180927 else180968 .x86_64_win => result: {
180928 try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);180969 try self.spillEflagsIfOccupied();
180929 const dst_reg = dst_mcv.getReg().?.to128();
180930 const dst_lock = self.register_manager.lockReg(dst_reg);
180931 defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
180932180970
180933 if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(180971 const promote_mcv = try self.allocTempRegOrMem(promote_ty, true);
180934 .{ .v_ss, .cvtsd2 },180972 const promote_lock = switch (promote_mcv) {
180935 dst_reg,180973 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
180936 dst_reg,180974 else => null,
180937 try promote_mcv.mem(self, .{ .size = .qword }),180975 };
180938 ) else try self.asmRegisterRegisterRegister(180976 defer if (promote_lock) |lock| self.register_manager.unlockReg(lock);
180939 .{ .v_ss, .cvtsd2 },180977
180940 dst_reg,180978 const ptr_arg_list_reg =
180941 dst_reg,180979 try self.copyToTmpRegister(self.typeOf(ty_op.operand), .{ .air_ref = ty_op.operand });
180942 (if (promote_mcv.isRegister())180980 const ptr_arg_list_lock = self.register_manager.lockRegAssumeUnused(ptr_arg_list_reg);
180943 promote_mcv.getReg().?180981 defer self.register_manager.unlockReg(ptr_arg_list_lock);
180944 else180982
180945 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),180983 const next_arg_ptr: MCValue = .{ .indirect = .{ .reg = ptr_arg_list_reg } };
180946 ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(180984
180947 .{ ._ss, .cvtsd2 },180985 const class = abi.classifyWindows(promote_ty, zcu, self.target, .arg);
180948 dst_reg,180986 switch (class) {
180949 try promote_mcv.mem(self, .{ .size = .qword }),180987 .integer, .sse => {
180950 ) else try self.asmRegisterRegister(180988 const next_arg_ptr_reg = try self.copyToTmpRegister(.usize, next_arg_ptr);
180951 .{ ._ss, .cvtsd2 },180989 if (!unused) try self.genCopy(promote_ty, promote_mcv, .{
180952 dst_reg,180990 .indirect = .{ .reg = next_arg_ptr_reg },
180953 (if (promote_mcv.isRegister())180991 }, .{});
180954 promote_mcv.getReg().?180992 try self.asmRegisterMemory(.{ ._, .lea }, next_arg_ptr_reg, .{
180955 else180993 .base = .{ .reg = next_arg_ptr_reg.to64() },
180956 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),180994 .mod = .{ .rm = .{ .disp = 8 } },
180957 );180995 });
180996 try self.genCopy(.usize, next_arg_ptr, .{ .register = next_arg_ptr_reg }, .{});
180997 },
180998 .memory => unreachable,
180999 else => return self.fail("TODO implement c_va_arg for {f} on Win64", .{promote_ty.fmt(pt)}),
181000 }
181001
181002 if (unused) break :result .unreach;
181003 if (ty.toIntern() == promote_ty.toIntern()) break :result promote_mcv;
181004
181005 if (!promote_ty.isRuntimeFloat()) {
181006 const dst_mcv = try self.allocRegOrMem(inst, true);
181007 try self.genCopy(ty, dst_mcv, promote_mcv, .{});
181008 break :result dst_mcv;
181009 }
181010
181011 try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
180958 break :result promote_mcv;181012 break :result promote_mcv;
180959 },181013 },
180960 .x86_64_win => return self.fail("TODO implement c_va_arg for Win64", .{}),
180961 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),181014 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180962 };181015 };
180963 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });181016 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
180964}181017}
180965181018
181019fn convertFloatVarArg(
181020 self: *CodeGen,
181021 inst: Air.Inst.Index,
181022 ty: Type,
181023 promote_ty: Type,
181024 promote_mcv: MCValue,
181025) !void {
181026 assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);
181027 const dst_mcv = if (promote_mcv.isRegister())
181028 promote_mcv
181029 else
181030 try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);
181031 const dst_reg = dst_mcv.getReg().?.to128();
181032 const dst_lock = self.register_manager.lockReg(dst_reg);
181033 defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
181034
181035 if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(
181036 .{ .v_ss, .cvtsd2 },
181037 dst_reg,
181038 dst_reg,
181039 try promote_mcv.mem(self, .{ .size = .qword }),
181040 ) else try self.asmRegisterRegisterRegister(
181041 .{ .v_ss, .cvtsd2 },
181042 dst_reg,
181043 dst_reg,
181044 (if (promote_mcv.isRegister())
181045 promote_mcv.getReg().?
181046 else
181047 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
181048 ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(
181049 .{ ._ss, .cvtsd2 },
181050 dst_reg,
181051 try promote_mcv.mem(self, .{ .size = .qword }),
181052 ) else try self.asmRegisterRegister(
181053 .{ ._ss, .cvtsd2 },
181054 dst_reg,
181055 (if (promote_mcv.isRegister())
181056 promote_mcv.getReg().?
181057 else
181058 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
181059 );
181060}
181061
180966fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {181062fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {
180967 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;181063 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
180968 const ptr_va_list_ty = self.typeOf(ty_op.operand);181064 const ptr_va_list_ty = self.typeOf(ty_op.operand);
test/behavior/var_args.zig+61-5
...@@ -101,7 +101,7 @@ test "simple variadic function" {...@@ -101,7 +101,7 @@ test "simple variadic function" {
101 // https://github.com/ziglang/zig/issues/14096101 // https://github.com/ziglang/zig/issues/14096
102 return error.SkipZigTest;102 return error.SkipZigTest;
103 }103 }
104 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO104 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
105 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350105 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
106 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718106 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
107 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064107 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064
...@@ -163,7 +163,7 @@ test "coerce reference to var arg" {...@@ -163,7 +163,7 @@ test "coerce reference to var arg" {
163 // https://github.com/ziglang/zig/issues/14096163 // https://github.com/ziglang/zig/issues/14096
164 return error.SkipZigTest;164 return error.SkipZigTest;
165 }165 }
166 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO166 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
167 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350167 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
168168
169 const S = struct {169 const S = struct {
...@@ -195,7 +195,7 @@ test "variadic functions" {...@@ -195,7 +195,7 @@ test "variadic functions" {
195 // https://github.com/ziglang/zig/issues/14096195 // https://github.com/ziglang/zig/issues/14096
196 return error.SkipZigTest;196 return error.SkipZigTest;
197 }197 }
198 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO198 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
199 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350199 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
200 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718200 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
201 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064201 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064
...@@ -248,7 +248,7 @@ test "copy VaList" {...@@ -248,7 +248,7 @@ test "copy VaList" {
248 // https://github.com/ziglang/zig/issues/14096248 // https://github.com/ziglang/zig/issues/14096
249 return error.SkipZigTest;249 return error.SkipZigTest;
250 }250 }
251 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO251 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
252 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350252 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
253 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718253 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
254 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064254 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064
...@@ -283,7 +283,7 @@ test "unused VaList arg" {...@@ -283,7 +283,7 @@ test "unused VaList arg" {
283 // https://github.com/ziglang/zig/issues/14096283 // https://github.com/ziglang/zig/issues/14096
284 return error.SkipZigTest;284 return error.SkipZigTest;
285 }285 }
286 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) {286 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) {
287 // https://github.com/ziglang/zig/issues/16961287 // https://github.com/ziglang/zig/issues/16961
288 return error.SkipZigTest; // TODO288 return error.SkipZigTest; // TODO
289 }289 }
...@@ -305,3 +305,59 @@ test "unused VaList arg" {...@@ -305,3 +305,59 @@ test "unused VaList arg" {
305 const x = S.thirdArg(0, @as(c_int, 1), @as(c_int, 2));305 const x = S.thirdArg(0, @as(c_int, 1), @as(c_int, 2));
306 try std.testing.expectEqual(@as(c_int, 2), x);306 try std.testing.expectEqual(@as(c_int, 2), x);
307}307}
308
309test "floating point VaList args" {
310 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
311 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
312 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
313 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
314 if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
315 // https://github.com/ziglang/zig/issues/14096
316 return error.SkipZigTest;
317 }
318 if (builtin.cpu.arch == .x86_64 and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
319 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) {
320 // https://github.com/ziglang/zig/issues/16961
321 return error.SkipZigTest; // TODO
322 }
323 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
324 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
325 if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064
326
327 // Float register arguments are handled specially on cc == .x86_64_win, so it's important that we test all 4 slots
328 const S = struct {
329 fn proxy(...) callconv(.c) void {
330 var ap = @cVaStart();
331 defer @cVaEnd(&ap);
332
333 var out_f32: [3]f32 = undefined;
334 var out_f64: [3]f64 = undefined;
335 out_f32[0] = @cVaArg(&ap, f32);
336 out_f64[0] = @cVaArg(&ap, f64);
337 out_f32[1] = @cVaArg(&ap, f32);
338 out_f64[1] = @cVaArg(&ap, f64);
339 out_f32[2] = @cVaArg(&ap, f32);
340 out_f64[2] = @cVaArg(&ap, f64);
341 @cVaArg(&ap, *[3]f32).* = out_f32;
342 @cVaArg(&ap, *[3]f64).* = out_f64;
343 }
344 };
345
346 const expected_f32: []const f32 = &.{ 1000, std.math.floatMax(f32), std.math.floatMin(f32) };
347 const expected_f64: []const f64 = &.{ 2000, std.math.floatMax(f64), std.math.floatMin(f64) };
348 var actual_f32: [3]f32 = undefined;
349 var actual_f64: [3]f64 = undefined;
350 S.proxy(
351 expected_f32[0],
352 expected_f64[0],
353 expected_f32[1],
354 expected_f64[1],
355 expected_f32[2],
356 expected_f64[2],
357 &actual_f32,
358 &actual_f64,
359 );
360
361 try std.testing.expectEqualSlices(f32, expected_f32, &actual_f32);
362 try std.testing.expectEqualSlices(f64, expected_f64, &actual_f64);
363}