commit ca99896d7383f02812d6cea549ad4af276558f64 (tree)
parent 4e3fcbea84de17afdf391293dc143a1fcc7b2ef7
Author: kcbanner <kcbanner@gmail.com>
Date: Wed, 25 Mar 2026 02:54:38 -0400
- 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
Diffstat:
3 files changed, 197 insertions(+), 45 deletions(-)
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
@@ -991,7 +991,7 @@ pub const EmitArtifact = enum {
/// paths under the output directory, where those paths are named according to this function.
/// Returned string is allocated with `gpa` and owned by the caller.
pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
- // hack for stage2_x86_64 + coff
+ // hack for stage2_x86_64 + coff. See Coff.flush.
if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";
const suffix: []const u8 = switch (ea) {
.bin => return binNameAlloc(gpa, opts),
diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig
@@ -2050,7 +2050,16 @@ fn gen(
self.performReloc(skip_sse_reloc);
},
- .x86_64_win => return self.fail("TODO implement gen var arg function for Win64", .{}),
+ .x86_64_win => {
+ for (abi.Win64.c_abi_int_param_regs[0..], 0..) |reg, reg_i|
+ try self.genSetMem(
+ .{ .frame = .args_frame },
+ @intCast(reg_i * 8),
+ .usize,
+ .{ .register = reg },
+ .{},
+ );
+ },
else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
};
@@ -176020,12 +176029,22 @@ fn genCall(self: *CodeGen, info: union(enum) {
.indirect => |reg_off| try self.register_manager.getReg(reg_off.reg, null),
else => unreachable,
}
- for (call_info.args, arg_types, args, frame_indices) |dst_arg, arg_ty, src_arg, *frame_index|
+ for (call_info.args, arg_types, args, frame_indices, 0..) |dst_arg, arg_ty, src_arg, *frame_index, arg_i|
switch (dst_arg) {
.none => {},
.register => |reg| {
try self.register_manager.getReg(reg, null);
try reg_locks.append(self.register_manager.lockReg(reg));
+
+ if (fn_info.is_var_args and
+ fn_info.cc == .x86_64_win and
+ reg.class() == .sse and
+ arg_i < abi.Win64.c_abi_int_param_regs.len)
+ {
+ // Floating point arguments must be duplicated into the equivalent integer registers on this ABI
+ const int_reg = abi.Win64.c_abi_int_param_regs[arg_i];
+ try reg_locks.append(self.register_manager.lockReg(int_reg));
+ }
},
.register_pair => |regs| {
for (regs) |reg| try self.register_manager.getReg(reg, null);
@@ -176129,7 +176148,7 @@ fn genCall(self: *CodeGen, info: union(enum) {
else => unreachable,
}
- for (call_info.args, arg_types, args, frame_indices) |dst_arg, arg_ty, src_arg, frame_index|
+ for (call_info.args, arg_types, args, frame_indices, 0..) |dst_arg, arg_ty, src_arg, frame_index, arg_i|
switch (dst_arg) {
.none, .load_frame => {},
.register => |dst_reg| switch (fn_info.cc) {
@@ -176144,6 +176163,16 @@ fn genCall(self: *CodeGen, info: union(enum) {
try self.genSetReg(dst_alias, promoted_ty, src_arg, opts);
if (promoted_ty.toIntern() != arg_ty.toIntern())
try self.truncateRegister(arg_ty, dst_alias);
+
+ if (fn_info.is_var_args and
+ fn_info.cc == .x86_64_win and
+ dst_reg.class() == .sse and
+ arg_i < abi.Win64.c_abi_int_param_regs.len)
+ {
+ const int_dst_reg = abi.Win64.c_abi_int_param_regs[arg_i];
+ const int_dst_alias = registerAlias(int_dst_reg, promoted_abi_size);
+ try self.genSetReg(int_dst_alias, promoted_ty, .{ .register = dst_alias }, opts);
+ }
},
},
.register_pair => try self.genCopy(arg_ty, dst_arg, src_arg, opts),
@@ -176180,7 +176209,8 @@ fn genCall(self: *CodeGen, info: union(enum) {
else => unreachable,
};
- if (fn_info.is_var_args) try self.asmRegisterImmediate(.{ ._, .mov }, .al, .u(call_info.fp_count));
+ if (fn_info.is_var_args and fn_info.cc == .x86_64_sysv)
+ try self.asmRegisterImmediate(.{ ._, .mov }, .al, .u(call_info.fp_count));
// Due to incremental compilation, how function calls are generated depends
// on linking.
@@ -180782,7 +180812,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {
field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));
break :result .{ .load_frame = .{ .index = dst_fi } };
},
- .x86_64_win => return self.fail("TODO implement c_va_start for Win64", .{}),
+ .x86_64_win => result: {
+ const dst_fi = try self.allocFrameIndex(.initSpill(va_list_ty, zcu));
+ const fn_info = zcu.typeToFunc(self.fn_type).?;
+ try self.genSetMem(
+ .{ .frame = dst_fi },
+ 0,
+ ptr_anyopaque_ty,
+ .{ .lea_frame = .{ .index = .args_frame, .off = @intCast(fn_info.param_types.len * 8) } },
+ .{},
+ );
+ break :result .{ .load_frame = .{ .index = dst_fi } };
+ },
else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
};
return self.finishAir(inst, result, .{ .none, .none, .none });
@@ -180921,48 +180962,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {
break :result dst_mcv;
}
- assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);
- const dst_mcv = if (promote_mcv.isRegister())
- promote_mcv
- else
- try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);
- const dst_reg = dst_mcv.getReg().?.to128();
- const dst_lock = self.register_manager.lockReg(dst_reg);
- defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
+ try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
+ break :result promote_mcv;
+ },
+ .x86_64_win => result: {
+ try self.spillEflagsIfOccupied();
- if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(
- .{ .v_ss, .cvtsd2 },
- dst_reg,
- dst_reg,
- try promote_mcv.mem(self, .{ .size = .qword }),
- ) else try self.asmRegisterRegisterRegister(
- .{ .v_ss, .cvtsd2 },
- dst_reg,
- dst_reg,
- (if (promote_mcv.isRegister())
- promote_mcv.getReg().?
- else
- try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
- ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(
- .{ ._ss, .cvtsd2 },
- dst_reg,
- try promote_mcv.mem(self, .{ .size = .qword }),
- ) else try self.asmRegisterRegister(
- .{ ._ss, .cvtsd2 },
- dst_reg,
- (if (promote_mcv.isRegister())
- promote_mcv.getReg().?
- else
- try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
- );
+ const promote_mcv = try self.allocTempRegOrMem(promote_ty, true);
+ const promote_lock = switch (promote_mcv) {
+ .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
+ else => null,
+ };
+ defer if (promote_lock) |lock| self.register_manager.unlockReg(lock);
+
+ const ptr_arg_list_reg =
+ try self.copyToTmpRegister(self.typeOf(ty_op.operand), .{ .air_ref = ty_op.operand });
+ const ptr_arg_list_lock = self.register_manager.lockRegAssumeUnused(ptr_arg_list_reg);
+ defer self.register_manager.unlockReg(ptr_arg_list_lock);
+
+ const next_arg_ptr: MCValue = .{ .indirect = .{ .reg = ptr_arg_list_reg } };
+
+ const class = abi.classifyWindows(promote_ty, zcu, self.target, .arg);
+ switch (class) {
+ .integer, .sse => {
+ const next_arg_ptr_reg = try self.copyToTmpRegister(.usize, next_arg_ptr);
+ if (!unused) try self.genCopy(promote_ty, promote_mcv, .{
+ .indirect = .{ .reg = next_arg_ptr_reg },
+ }, .{});
+ try self.asmRegisterMemory(.{ ._, .lea }, next_arg_ptr_reg, .{
+ .base = .{ .reg = next_arg_ptr_reg.to64() },
+ .mod = .{ .rm = .{ .disp = 8 } },
+ });
+ try self.genCopy(.usize, next_arg_ptr, .{ .register = next_arg_ptr_reg }, .{});
+ },
+ .memory => unreachable,
+ else => return self.fail("TODO implement c_va_arg for {f} on Win64", .{promote_ty.fmt(pt)}),
+ }
+
+ if (unused) break :result .unreach;
+ if (ty.toIntern() == promote_ty.toIntern()) break :result promote_mcv;
+
+ if (!promote_ty.isRuntimeFloat()) {
+ const dst_mcv = try self.allocRegOrMem(inst, true);
+ try self.genCopy(ty, dst_mcv, promote_mcv, .{});
+ break :result dst_mcv;
+ }
+
+ try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
break :result promote_mcv;
},
- .x86_64_win => return self.fail("TODO implement c_va_arg for Win64", .{}),
else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
};
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
+fn convertFloatVarArg(
+ self: *CodeGen,
+ inst: Air.Inst.Index,
+ ty: Type,
+ promote_ty: Type,
+ promote_mcv: MCValue,
+) !void {
+ assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);
+ const dst_mcv = if (promote_mcv.isRegister())
+ promote_mcv
+ else
+ try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);
+ const dst_reg = dst_mcv.getReg().?.to128();
+ const dst_lock = self.register_manager.lockReg(dst_reg);
+ defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
+
+ if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(
+ .{ .v_ss, .cvtsd2 },
+ dst_reg,
+ dst_reg,
+ try promote_mcv.mem(self, .{ .size = .qword }),
+ ) else try self.asmRegisterRegisterRegister(
+ .{ .v_ss, .cvtsd2 },
+ dst_reg,
+ dst_reg,
+ (if (promote_mcv.isRegister())
+ promote_mcv.getReg().?
+ else
+ try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
+ ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(
+ .{ ._ss, .cvtsd2 },
+ dst_reg,
+ try promote_mcv.mem(self, .{ .size = .qword }),
+ ) else try self.asmRegisterRegister(
+ .{ ._ss, .cvtsd2 },
+ dst_reg,
+ (if (promote_mcv.isRegister())
+ promote_mcv.getReg().?
+ else
+ try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
+ );
+}
+
fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const ptr_va_list_ty = self.typeOf(ty_op.operand);
diff --git a/test/behavior/var_args.zig b/test/behavior/var_args.zig
@@ -101,7 +101,7 @@ test "simple variadic function" {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
- if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
+ if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
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" {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
- if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
+ if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
const S = struct {
@@ -195,7 +195,7 @@ test "variadic functions" {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
- if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
+ if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
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" {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
- if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
+ if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
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" {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
- if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) {
+ if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) {
// https://github.com/ziglang/zig/issues/16961
return error.SkipZigTest; // TODO
}
@@ -305,3 +305,59 @@ test "unused VaList arg" {
const x = S.thirdArg(0, @as(c_int, 1), @as(c_int, 2));
try std.testing.expectEqual(@as(c_int, 2), x);
}
+
+test "floating point VaList args" {
+ if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+ if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+ if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
+ // https://github.com/ziglang/zig/issues/14096
+ return error.SkipZigTest;
+ }
+ if (builtin.cpu.arch == .x86_64 and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
+ if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows and builtin.zig_backend != .stage2_x86_64) {
+ // https://github.com/ziglang/zig/issues/16961
+ return error.SkipZigTest; // TODO
+ }
+ if (builtin.cpu.arch == .s390x and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21350
+ if (builtin.cpu.arch.isSPARC() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23718
+ if (builtin.cpu.arch.isRISCV() and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25064
+
+ // Float register arguments are handled specially on cc == .x86_64_win, so it's important that we test all 4 slots
+ const S = struct {
+ fn proxy(...) callconv(.c) void {
+ var ap = @cVaStart();
+ defer @cVaEnd(&ap);
+
+ var out_f32: [3]f32 = undefined;
+ var out_f64: [3]f64 = undefined;
+ out_f32[0] = @cVaArg(&ap, f32);
+ out_f64[0] = @cVaArg(&ap, f64);
+ out_f32[1] = @cVaArg(&ap, f32);
+ out_f64[1] = @cVaArg(&ap, f64);
+ out_f32[2] = @cVaArg(&ap, f32);
+ out_f64[2] = @cVaArg(&ap, f64);
+ @cVaArg(&ap, *[3]f32).* = out_f32;
+ @cVaArg(&ap, *[3]f64).* = out_f64;
+ }
+ };
+
+ const expected_f32: []const f32 = &.{ 1000, std.math.floatMax(f32), std.math.floatMin(f32) };
+ const expected_f64: []const f64 = &.{ 2000, std.math.floatMax(f64), std.math.floatMin(f64) };
+ var actual_f32: [3]f32 = undefined;
+ var actual_f64: [3]f64 = undefined;
+ S.proxy(
+ expected_f32[0],
+ expected_f64[0],
+ expected_f32[1],
+ expected_f64[1],
+ expected_f32[2],
+ expected_f64[2],
+ &actual_f32,
+ &actual_f64,
+ );
+
+ try std.testing.expectEqualSlices(f32, expected_f32, &actual_f32);
+ try std.testing.expectEqualSlices(f64, expected_f64, &actual_f64);
+}