authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-12 01:34:16+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-12 01:34:16+02:00
log2322d45d80a88730b356b6a2b0101bcffb5a0afd
treef35786acf74d59974d4acd6303b08875f5bb649c
parent07f05426fc0c28ad19f87002d5d4d0366e54b7c9
parent91dce64d10ec0da057c1bdceb586e56f7d265880

Merge pull request 'Implement variadic functions for Win64 in the x86_64 backend' (#31672) from kcbanner/zig:win64_varargs into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31672 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

5 files changed, 228 insertions(+), 44 deletions(-)

lib/std/Build/Step/Compile.zig+18
......@@ -219,6 +219,8 @@ generated_docs: ?*GeneratedFile,
219219generated_asm: ?*GeneratedFile,
220220generated_bin: ?*GeneratedFile,
221221generated_pdb: ?*GeneratedFile,
222// hack for stage2_x86_64 + coff
223generated_compiler_rt_dyn_lib: ?*GeneratedFile,
222224generated_implib: ?*GeneratedFile,
223225generated_llvm_bc: ?*GeneratedFile,
224226generated_llvm_ir: ?*GeneratedFile,
......@@ -441,6 +443,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
441443 .generated_asm = null,
442444 .generated_bin = null,
443445 .generated_pdb = null,
446 .generated_compiler_rt_dyn_lib = null,
444447 .generated_implib = null,
445448 .generated_llvm_bc = null,
446449 .generated_llvm_ir = null,
......@@ -691,6 +694,13 @@ pub fn producesPdbFile(compile: *Compile) bool {
691694 return compile.isDynamicLibrary() or compile.kind == .exe or compile.kind == .@"test";
692695}
693696
697pub fn producesCompilerRtDynLib(compile: *Compile) bool {
698 if (compile.rootModuleTarget().ofmt != .coff) return false;
699 if (compile.bundle_compiler_rt orelse (compile.kind == .exe or compile.isDynamicLibrary()))
700 return compile.use_llvm == false;
701 return false;
702}
703
694704pub fn producesImplib(compile: *Compile) bool {
695705 return compile.isDll();
696706}
......@@ -869,6 +879,12 @@ pub fn getEmittedPdb(compile: *Compile) LazyPath {
869879 return compile.getEmittedFileGeneric(&compile.generated_pdb);
870880}
871881
882/// Returns the generated compiler_rt dynamic library.
883/// This is a hack for stage2_x86_64 + coff.
884pub fn getEmittedCompilerRtDynLib(compile: *Compile) ?LazyPath {
885 return compile.getEmittedFileGeneric(&compile.generated_compiler_rt_dyn_lib);
886}
887
872888/// Returns the path to the generated documentation directory.
873889pub fn getEmittedDocs(compile: *Compile) LazyPath {
874890 return compile.getEmittedFileGeneric(&compile.generated_docs);
......@@ -1794,6 +1810,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
17941810 // zig fmt: off
17951811 if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin);
17961812 if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb);
1813 // hack for stage2_x86_64 + coff
1814 if (compile.generated_compiler_rt_dyn_lib) |lp| lp.path = compile.outputPath(output_dir, .compiler_rt_dyn_lib);
17971815 if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib);
17981816 if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h);
17991817 if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs);
lib/std/Build/Step/InstallArtifact.zig+18
......@@ -17,6 +17,10 @@ emitted_implib: ?LazyPath,
1717pdb_dir: ?InstallDir,
1818emitted_pdb: ?LazyPath,
1919
20// hack for stage2_x86_64 + coff
21compiler_rt_dyn_lib_dir: ?InstallDir,
22emitted_compiler_rt_dyn_lib: ?LazyPath,
23
2024h_dir: ?InstallDir,
2125emitted_h: ?LazyPath,
2226
......@@ -35,6 +39,7 @@ pub const Options = struct {
3539 /// Which installation directory to put the main output file into.
3640 dest_dir: Dir = .default,
3741 pdb_dir: Dir = .default,
42 compiler_rt_dyn_lib_dir: Dir = .default,
3843 h_dir: Dir = .default,
3944 implib_dir: Dir = .default,
4045
......@@ -75,6 +80,11 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
7580 .default => if (artifact.producesPdbFile()) dest_dir else null,
7681 .override => |o| o,
7782 },
83 .compiler_rt_dyn_lib_dir = switch (options.compiler_rt_dyn_lib_dir) {
84 .disabled => null,
85 .default => if (artifact.producesCompilerRtDynLib()) dest_dir else null,
86 .override => |o| o,
87 },
7888 .h_dir = switch (options.h_dir) {
7989 .disabled => null,
8090 .default => if (artifact.kind == .lib) .header else null,
......@@ -98,6 +108,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
98108
99109 .emitted_bin = null,
100110 .emitted_pdb = null,
111 .emitted_compiler_rt_dyn_lib = null,
101112 .emitted_h = null,
102113 .emitted_implib = null,
103114
......@@ -107,6 +118,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
107118 install_artifact.step.dependOn(&artifact.step);
108119
109120 if (install_artifact.dest_dir != null) install_artifact.emitted_bin = artifact.getEmittedBin();
121 if (install_artifact.compiler_rt_dyn_lib_dir != null) install_artifact.emitted_compiler_rt_dyn_lib = artifact.getEmittedCompilerRtDynLib();
110122 if (install_artifact.pdb_dir != null) install_artifact.emitted_pdb = artifact.getEmittedPdb();
111123 // https://github.com/ziglang/zig/issues/9698
112124 //if (install_artifact.h_dir != null) install_artifact.emitted_h = artifact.getEmittedH();
......@@ -135,6 +147,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
135147 install_artifact.artifact.installed_path = full_dest_path;
136148 }
137149
150 if (install_artifact.compiler_rt_dyn_lib_dir) |compiler_rt_dir| {
151 const full_compiler_rt_path = b.getInstallPath(compiler_rt_dir, install_artifact.emitted_compiler_rt_dyn_lib.?.basename(b, step));
152 const p = try step.installFile(install_artifact.emitted_compiler_rt_dyn_lib.?, full_compiler_rt_path);
153 all_cached = all_cached and p == .fresh;
154 }
155
138156 if (install_artifact.implib_dir) |implib_dir| {
139157 const full_implib_path = b.getInstallPath(implib_dir, install_artifact.emitted_implib.?.basename(b, step));
140158 const p = try step.installFile(install_artifact.emitted_implib.?, full_implib_path);
lib/std/zig.zig+4
......@@ -986,11 +986,14 @@ pub const EmitArtifact = enum {
986986 docs,
987987 pdb,
988988 h,
989 compiler_rt_dyn_lib,
989990
990991 /// If using `Server` to communicate with the compiler, it will place requested artifacts in
991992 /// paths under the output directory, where those paths are named according to this function.
992993 /// Returned string is allocated with `gpa` and owned by the caller.
993994 pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
995 // hack for stage2_x86_64 + coff. See Coff.flush.
996 if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";
994997 const suffix: []const u8 = switch (ea) {
995998 .bin => return binNameAlloc(gpa, opts),
996999 .@"asm" => ".s",
......@@ -1000,6 +1003,7 @@ pub const EmitArtifact = enum {
10001003 .docs => "-docs",
10011004 .pdb => ".pdb",
10021005 .h => ".h",
1006 .compiler_rt_dyn_lib => unreachable,
10031007 };
10041008 return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
10051009 }
src/codegen/x86_64/CodeGen.zig+135-39
......@@ -2049,7 +2049,16 @@ fn gen(
20492049
20502050 self.performReloc(skip_sse_reloc);
20512051 },
2052 .x86_64_win => return self.fail("TODO implement gen var arg function for Win64", .{}),
2052 .x86_64_win => {
2053 for (abi.Win64.c_abi_int_param_regs[0..], 0..) |reg, reg_i|
2054 try self.genSetMem(
2055 .{ .frame = .args_frame },
2056 @intCast(reg_i * 8),
2057 .usize,
2058 .{ .register = reg },
2059 .{},
2060 );
2061 },
20532062 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
20542063 };
20552064
......@@ -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.
......@@ -180792,7 +180822,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {
180792180822 field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));
180793180823 break :result .{ .load_frame = .{ .index = dst_fi } };
180794180824 },
180795 .x86_64_win => return self.fail("TODO implement c_va_start for Win64", .{}),
180825 .x86_64_win => result: {
180826 const dst_fi = try self.allocFrameIndex(.initSpill(va_list_ty, zcu));
180827 const fn_info = zcu.typeToFunc(self.fn_type).?;
180828 try self.genSetMem(
180829 .{ .frame = dst_fi },
180830 0,
180831 ptr_anyopaque_ty,
180832 .{ .lea_frame = .{ .index = .args_frame, .off = @intCast(fn_info.param_types.len * 8) } },
180833 .{},
180834 );
180835 break :result .{ .load_frame = .{ .index = dst_fi } };
180836 },
180796180837 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180797180838 };
180798180839 return self.finishAir(inst, result, .{ .none, .none, .none });
......@@ -180931,48 +180972,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {
180931180972 break :result dst_mcv;
180932180973 }
180933180974
180934 assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);
180935 const dst_mcv = if (promote_mcv.isRegister())
180936 promote_mcv
180937 else
180938 try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);
180939 const dst_reg = dst_mcv.getReg().?.to128();
180940 const dst_lock = self.register_manager.lockReg(dst_reg);
180941 defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
180975 try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
180976 break :result promote_mcv;
180977 },
180978 .x86_64_win => result: {
180979 try self.spillEflagsIfOccupied();
180942180980
180943 if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(
180944 .{ .v_ss, .cvtsd2 },
180945 dst_reg,
180946 dst_reg,
180947 try promote_mcv.mem(self, .{ .size = .qword }),
180948 ) else try self.asmRegisterRegisterRegister(
180949 .{ .v_ss, .cvtsd2 },
180950 dst_reg,
180951 dst_reg,
180952 (if (promote_mcv.isRegister())
180953 promote_mcv.getReg().?
180954 else
180955 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
180956 ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(
180957 .{ ._ss, .cvtsd2 },
180958 dst_reg,
180959 try promote_mcv.mem(self, .{ .size = .qword }),
180960 ) else try self.asmRegisterRegister(
180961 .{ ._ss, .cvtsd2 },
180962 dst_reg,
180963 (if (promote_mcv.isRegister())
180964 promote_mcv.getReg().?
180965 else
180966 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
180967 );
180981 const promote_mcv = try self.allocTempRegOrMem(promote_ty, true);
180982 const promote_lock = switch (promote_mcv) {
180983 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
180984 else => null,
180985 };
180986 defer if (promote_lock) |lock| self.register_manager.unlockReg(lock);
180987
180988 const ptr_arg_list_reg =
180989 try self.copyToTmpRegister(self.typeOf(ty_op.operand), .{ .air_ref = ty_op.operand });
180990 const ptr_arg_list_lock = self.register_manager.lockRegAssumeUnused(ptr_arg_list_reg);
180991 defer self.register_manager.unlockReg(ptr_arg_list_lock);
180992
180993 const next_arg_ptr: MCValue = .{ .indirect = .{ .reg = ptr_arg_list_reg } };
180994
180995 const class = abi.classifyWindows(promote_ty, zcu, self.target, .arg);
180996 switch (class) {
180997 .integer, .sse => {
180998 const next_arg_ptr_reg = try self.copyToTmpRegister(.usize, next_arg_ptr);
180999 if (!unused) try self.genCopy(promote_ty, promote_mcv, .{
181000 .indirect = .{ .reg = next_arg_ptr_reg },
181001 }, .{});
181002 try self.asmRegisterMemory(.{ ._, .lea }, next_arg_ptr_reg, .{
181003 .base = .{ .reg = next_arg_ptr_reg.to64() },
181004 .mod = .{ .rm = .{ .disp = 8 } },
181005 });
181006 try self.genCopy(.usize, next_arg_ptr, .{ .register = next_arg_ptr_reg }, .{});
181007 },
181008 .memory => unreachable,
181009 else => return self.fail("TODO implement c_va_arg for {f} on Win64", .{promote_ty.fmt(pt)}),
181010 }
181011
181012 if (unused) break :result .unreach;
181013 if (ty.toIntern() == promote_ty.toIntern()) break :result promote_mcv;
181014
181015 if (!promote_ty.isRuntimeFloat()) {
181016 const dst_mcv = try self.allocRegOrMem(inst, true);
181017 try self.genCopy(ty, dst_mcv, promote_mcv, .{});
181018 break :result dst_mcv;
181019 }
181020
181021 try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
180968181022 break :result promote_mcv;
180969181023 },
180970 .x86_64_win => return self.fail("TODO implement c_va_arg for Win64", .{}),
180971181024 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180972181025 };
180973181026 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
180974181027}
180975181028
181029fn convertFloatVarArg(
181030 self: *CodeGen,
181031 inst: Air.Inst.Index,
181032 ty: Type,
181033 promote_ty: Type,
181034 promote_mcv: MCValue,
181035) !void {
181036 assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);
181037 const dst_mcv = if (promote_mcv.isRegister())
181038 promote_mcv
181039 else
181040 try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);
181041 const dst_reg = dst_mcv.getReg().?.to128();
181042 const dst_lock = self.register_manager.lockReg(dst_reg);
181043 defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
181044
181045 if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(
181046 .{ .v_ss, .cvtsd2 },
181047 dst_reg,
181048 dst_reg,
181049 try promote_mcv.mem(self, .{ .size = .qword }),
181050 ) else try self.asmRegisterRegisterRegister(
181051 .{ .v_ss, .cvtsd2 },
181052 dst_reg,
181053 dst_reg,
181054 (if (promote_mcv.isRegister())
181055 promote_mcv.getReg().?
181056 else
181057 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
181058 ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(
181059 .{ ._ss, .cvtsd2 },
181060 dst_reg,
181061 try promote_mcv.mem(self, .{ .size = .qword }),
181062 ) else try self.asmRegisterRegister(
181063 .{ ._ss, .cvtsd2 },
181064 dst_reg,
181065 (if (promote_mcv.isRegister())
181066 promote_mcv.getReg().?
181067 else
181068 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),
181069 );
181070}
181071
180976181072fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {
180977181073 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
180978181074 const ptr_va_list_ty = self.typeOf(ty_op.operand);
test/behavior/var_args.zig+53-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_llvm) 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_llvm) 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_llvm) 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_llvm) 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_llvm) {
287287 // https://github.com/ziglang/zig/issues/16961
288288 return error.SkipZigTest; // TODO
289289 }
......@@ -305,3 +305,51 @@ 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) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/16961
315
316 // Float register arguments are handled specially on cc == .x86_64_win, so it's important that we test all 4 slots,
317 // and pre-C23 doesn't allow a variadic function without at least one non-variadic argument.
318 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
319
320 const S = struct {
321 fn proxy(...) callconv(.c) void {
322 var ap = @cVaStart();
323 defer @cVaEnd(&ap);
324
325 var out_f32: [3]f32 = undefined;
326 var out_f64: [3]f64 = undefined;
327 out_f32[0] = @cVaArg(&ap, f32);
328 out_f64[0] = @cVaArg(&ap, f64);
329 out_f32[1] = @cVaArg(&ap, f32);
330 out_f64[1] = @cVaArg(&ap, f64);
331 out_f32[2] = @cVaArg(&ap, f32);
332 out_f64[2] = @cVaArg(&ap, f64);
333 @cVaArg(&ap, *[3]f32).* = out_f32;
334 @cVaArg(&ap, *[3]f64).* = out_f64;
335 }
336 };
337
338 const expected_f32: []const f32 = &.{ 1000, std.math.floatMax(f32), std.math.floatMin(f32) };
339 const expected_f64: []const f64 = &.{ 2000, std.math.floatMax(f64), std.math.floatMin(f64) };
340 var actual_f32: [3]f32 = undefined;
341 var actual_f64: [3]f64 = undefined;
342 S.proxy(
343 expected_f32[0],
344 expected_f64[0],
345 expected_f32[1],
346 expected_f64[1],
347 expected_f32[2],
348 expected_f64[2],
349 &actual_f32,
350 &actual_f64,
351 );
352
353 try std.testing.expectEqualSlices(f32, expected_f32, &actual_f32);
354 try std.testing.expectEqualSlices(f64, expected_f64, &actual_f64);
355}