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 {
991991 /// paths under the output directory, where those paths are named according to this function.
992992 /// Returned string is allocated with `gpa` and owned by the caller.
993993 pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
994 // hack for stage2_x86_64 + coff
994 // hack for stage2_x86_64 + coff. See Coff.flush.
995995 if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";
996996 const suffix: []const u8 = switch (ea) {
997997 .bin => return binNameAlloc(gpa, opts),
src/codegen/x86_64/CodeGen.zig+135-39
......@@ -2050,7 +2050,16 @@ fn gen(
20502050
20512051 self.performReloc(skip_sse_reloc);
20522052 },
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 },
20542063 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
20552064 };
20562065
......@@ -176020,12 +176029,22 @@ fn genCall(self: *CodeGen, info: union(enum) {
176020176029 .indirect => |reg_off| try self.register_manager.getReg(reg_off.reg, null),
176021176030 else => unreachable,
176022176031 }
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|
176024176033 switch (dst_arg) {
176025176034 .none => {},
176026176035 .register => |reg| {
176027176036 try self.register_manager.getReg(reg, null);
176028176037 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 }
176029176048 },
176030176049 .register_pair => |regs| {
176031176050 for (regs) |reg| try self.register_manager.getReg(reg, null);
......@@ -176129,7 +176148,7 @@ fn genCall(self: *CodeGen, info: union(enum) {
176129176148 else => unreachable,
176130176149 }
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|
176133176152 switch (dst_arg) {
176134176153 .none, .load_frame => {},
176135176154 .register => |dst_reg| switch (fn_info.cc) {
......@@ -176144,6 +176163,16 @@ fn genCall(self: *CodeGen, info: union(enum) {
176144176163 try self.genSetReg(dst_alias, promoted_ty, src_arg, opts);
176145176164 if (promoted_ty.toIntern() != arg_ty.toIntern())
176146176165 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 }
176147176176 },
176148176177 },
176149176178 .register_pair => try self.genCopy(arg_ty, dst_arg, src_arg, opts),
......@@ -176180,7 +176209,8 @@ fn genCall(self: *CodeGen, info: union(enum) {
176180176209 else => unreachable,
176181176210 };
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
176185176215 // Due to incremental compilation, how function calls are generated depends
176186176216 // on linking.
......@@ -180782,7 +180812,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {
180782180812 field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));
180783180813 break :result .{ .load_frame = .{ .index = dst_fi } };
180784180814 },
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 },
180786180827 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180787180828 };
180788180829 return self.finishAir(inst, result, .{ .none, .none, .none });
......@@ -180921,48 +180962,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {
180921180962 break :result dst_mcv;
180922180963 }
180923180964
180924 assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);
180925 const dst_mcv = if (promote_mcv.isRegister())
180926 promote_mcv
180927 else
180928 try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);
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);
180965 try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
180966 break :result promote_mcv;
180967 },
180968 .x86_64_win => result: {
180969 try self.spillEflagsIfOccupied();
180932180970
180933 if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(
180934 .{ .v_ss, .cvtsd2 },
180935 dst_reg,
180936 dst_reg,
180937 try promote_mcv.mem(self, .{ .size = .qword }),
180938 ) else try self.asmRegisterRegisterRegister(
180939 .{ .v_ss, .cvtsd2 },
180940 dst_reg,
180941 dst_reg,
180942 (if (promote_mcv.isRegister())
180943 promote_mcv.getReg().?
180944 else
180945 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
180946 ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(
180947 .{ ._ss, .cvtsd2 },
180948 dst_reg,
180949 try promote_mcv.mem(self, .{ .size = .qword }),
180950 ) else try self.asmRegisterRegister(
180951 .{ ._ss, .cvtsd2 },
180952 dst_reg,
180953 (if (promote_mcv.isRegister())
180954 promote_mcv.getReg().?
180955 else
180956 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
180957 );
180971 const promote_mcv = try self.allocTempRegOrMem(promote_ty, true);
180972 const promote_lock = switch (promote_mcv) {
180973 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
180974 else => null,
180975 };
180976 defer if (promote_lock) |lock| self.register_manager.unlockReg(lock);
180977
180978 const ptr_arg_list_reg =
180979 try self.copyToTmpRegister(self.typeOf(ty_op.operand), .{ .air_ref = ty_op.operand });
180980 const ptr_arg_list_lock = self.register_manager.lockRegAssumeUnused(ptr_arg_list_reg);
180981 defer self.register_manager.unlockReg(ptr_arg_list_lock);
180982
180983 const next_arg_ptr: MCValue = .{ .indirect = .{ .reg = ptr_arg_list_reg } };
180984
180985 const class = abi.classifyWindows(promote_ty, zcu, self.target, .arg);
180986 switch (class) {
180987 .integer, .sse => {
180988 const next_arg_ptr_reg = try self.copyToTmpRegister(.usize, next_arg_ptr);
180989 if (!unused) try self.genCopy(promote_ty, promote_mcv, .{
180990 .indirect = .{ .reg = next_arg_ptr_reg },
180991 }, .{});
180992 try self.asmRegisterMemory(.{ ._, .lea }, next_arg_ptr_reg, .{
180993 .base = .{ .reg = next_arg_ptr_reg.to64() },
180994 .mod = .{ .rm = .{ .disp = 8 } },
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);
180958181012 break :result promote_mcv;
180959181013 },
180960 .x86_64_win => return self.fail("TODO implement c_va_arg for Win64", .{}),
180961181014 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180962181015 };
180963181016 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
180964181017}
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
180966181062fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {
180967181063 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
180968181064 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" {
101101 // https://github.com/ziglang/zig/issues/14096
102102 return error.SkipZigTest;
103103 }
104 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
104 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
105105 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
106106 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
107107 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" {
163163 // https://github.com/ziglang/zig/issues/14096
164164 return error.SkipZigTest;
165165 }
166 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
166 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
167167 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
168168
169169 const S = struct {
......@@ -195,7 +195,7 @@ test "variadic functions" {
195195 // https://github.com/ziglang/zig/issues/14096
196196 return error.SkipZigTest;
197197 }
198 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
198 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
199199 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
200200 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
201201 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" {
248248 // https://github.com/ziglang/zig/issues/14096
249249 return error.SkipZigTest;
250250 }
251 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
251 if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
252252 if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
253253 if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
254254 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" {
283283 // https://github.com/ziglang/zig/issues/14096
284284 return error.SkipZigTest;
285285 }
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) {
287287 // https://github.com/ziglang/zig/issues/16961
288288 return error.SkipZigTest; // TODO
289289 }
......@@ -305,3 +305,59 @@ test "unused VaList arg" {
305305 const x = S.thirdArg(0, @as(c_int, 1), @as(c_int, 2));
306306 try std.testing.expectEqual(@as(c_int, 2), x);
307307}
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}