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,...@@ -219,6 +219,8 @@ generated_docs: ?*GeneratedFile,
219generated_asm: ?*GeneratedFile,219generated_asm: ?*GeneratedFile,
220generated_bin: ?*GeneratedFile,220generated_bin: ?*GeneratedFile,
221generated_pdb: ?*GeneratedFile,221generated_pdb: ?*GeneratedFile,
222// hack for stage2_x86_64 + coff
223generated_compiler_rt_dyn_lib: ?*GeneratedFile,
222generated_implib: ?*GeneratedFile,224generated_implib: ?*GeneratedFile,
223generated_llvm_bc: ?*GeneratedFile,225generated_llvm_bc: ?*GeneratedFile,
224generated_llvm_ir: ?*GeneratedFile,226generated_llvm_ir: ?*GeneratedFile,
...@@ -441,6 +443,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {...@@ -441,6 +443,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
441 .generated_asm = null,443 .generated_asm = null,
442 .generated_bin = null,444 .generated_bin = null,
443 .generated_pdb = null,445 .generated_pdb = null,
446 .generated_compiler_rt_dyn_lib = null,
444 .generated_implib = null,447 .generated_implib = null,
445 .generated_llvm_bc = null,448 .generated_llvm_bc = null,
446 .generated_llvm_ir = null,449 .generated_llvm_ir = null,
...@@ -691,6 +694,13 @@ pub fn producesPdbFile(compile: *Compile) bool {...@@ -691,6 +694,13 @@ pub fn producesPdbFile(compile: *Compile) bool {
691 return compile.isDynamicLibrary() or compile.kind == .exe or compile.kind == .@"test";694 return compile.isDynamicLibrary() or compile.kind == .exe or compile.kind == .@"test";
692}695}
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
694pub fn producesImplib(compile: *Compile) bool {704pub fn producesImplib(compile: *Compile) bool {
695 return compile.isDll();705 return compile.isDll();
696}706}
...@@ -869,6 +879,12 @@ pub fn getEmittedPdb(compile: *Compile) LazyPath {...@@ -869,6 +879,12 @@ pub fn getEmittedPdb(compile: *Compile) LazyPath {
869 return compile.getEmittedFileGeneric(&compile.generated_pdb);879 return compile.getEmittedFileGeneric(&compile.generated_pdb);
870}880}
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
872/// Returns the path to the generated documentation directory.888/// Returns the path to the generated documentation directory.
873pub fn getEmittedDocs(compile: *Compile) LazyPath {889pub fn getEmittedDocs(compile: *Compile) LazyPath {
874 return compile.getEmittedFileGeneric(&compile.generated_docs);890 return compile.getEmittedFileGeneric(&compile.generated_docs);
...@@ -1794,6 +1810,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1794,6 +1810,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1794 // zig fmt: off1810 // zig fmt: off
1795 if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin);1811 if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin);
1796 if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb);1812 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);
1797 if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib);1815 if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib);
1798 if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h);1816 if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h);
1799 if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs);1817 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,...@@ -17,6 +17,10 @@ emitted_implib: ?LazyPath,
17pdb_dir: ?InstallDir,17pdb_dir: ?InstallDir,
18emitted_pdb: ?LazyPath,18emitted_pdb: ?LazyPath,
1919
20// hack for stage2_x86_64 + coff
21compiler_rt_dyn_lib_dir: ?InstallDir,
22emitted_compiler_rt_dyn_lib: ?LazyPath,
23
20h_dir: ?InstallDir,24h_dir: ?InstallDir,
21emitted_h: ?LazyPath,25emitted_h: ?LazyPath,
2226
...@@ -35,6 +39,7 @@ pub const Options = struct {...@@ -35,6 +39,7 @@ pub const Options = struct {
35 /// Which installation directory to put the main output file into.39 /// Which installation directory to put the main output file into.
36 dest_dir: Dir = .default,40 dest_dir: Dir = .default,
37 pdb_dir: Dir = .default,41 pdb_dir: Dir = .default,
42 compiler_rt_dyn_lib_dir: Dir = .default,
38 h_dir: Dir = .default,43 h_dir: Dir = .default,
39 implib_dir: Dir = .default,44 implib_dir: Dir = .default,
4045
...@@ -75,6 +80,11 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins...@@ -75,6 +80,11 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
75 .default => if (artifact.producesPdbFile()) dest_dir else null,80 .default => if (artifact.producesPdbFile()) dest_dir else null,
76 .override => |o| o,81 .override => |o| o,
77 },82 },
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 },
78 .h_dir = switch (options.h_dir) {88 .h_dir = switch (options.h_dir) {
79 .disabled => null,89 .disabled => null,
80 .default => if (artifact.kind == .lib) .header else null,90 .default => if (artifact.kind == .lib) .header else null,
...@@ -98,6 +108,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins...@@ -98,6 +108,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
98108
99 .emitted_bin = null,109 .emitted_bin = null,
100 .emitted_pdb = null,110 .emitted_pdb = null,
111 .emitted_compiler_rt_dyn_lib = null,
101 .emitted_h = null,112 .emitted_h = null,
102 .emitted_implib = null,113 .emitted_implib = null,
103114
...@@ -107,6 +118,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins...@@ -107,6 +118,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
107 install_artifact.step.dependOn(&artifact.step);118 install_artifact.step.dependOn(&artifact.step);
108119
109 if (install_artifact.dest_dir != null) install_artifact.emitted_bin = artifact.getEmittedBin();120 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();
110 if (install_artifact.pdb_dir != null) install_artifact.emitted_pdb = artifact.getEmittedPdb();122 if (install_artifact.pdb_dir != null) install_artifact.emitted_pdb = artifact.getEmittedPdb();
111 // https://github.com/ziglang/zig/issues/9698123 // https://github.com/ziglang/zig/issues/9698
112 //if (install_artifact.h_dir != null) install_artifact.emitted_h = artifact.getEmittedH();124 //if (install_artifact.h_dir != null) install_artifact.emitted_h = artifact.getEmittedH();
...@@ -135,6 +147,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -135,6 +147,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
135 install_artifact.artifact.installed_path = full_dest_path;147 install_artifact.artifact.installed_path = full_dest_path;
136 }148 }
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
138 if (install_artifact.implib_dir) |implib_dir| {156 if (install_artifact.implib_dir) |implib_dir| {
139 const full_implib_path = b.getInstallPath(implib_dir, install_artifact.emitted_implib.?.basename(b, step));157 const full_implib_path = b.getInstallPath(implib_dir, install_artifact.emitted_implib.?.basename(b, step));
140 const p = try step.installFile(install_artifact.emitted_implib.?, full_implib_path);158 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 {...@@ -986,11 +986,14 @@ pub const EmitArtifact = enum {
986 docs,986 docs,
987 pdb,987 pdb,
988 h,988 h,
989 compiler_rt_dyn_lib,
989990
990 /// If using `Server` to communicate with the compiler, it will place requested artifacts in991 /// If using `Server` to communicate with the compiler, it will place requested artifacts in
991 /// paths under the output directory, where those paths are named according to this function.992 /// 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.993 /// 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 {994 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";
994 const suffix: []const u8 = switch (ea) {997 const suffix: []const u8 = switch (ea) {
995 .bin => return binNameAlloc(gpa, opts),998 .bin => return binNameAlloc(gpa, opts),
996 .@"asm" => ".s",999 .@"asm" => ".s",
...@@ -1000,6 +1003,7 @@ pub const EmitArtifact = enum {...@@ -1000,6 +1003,7 @@ pub const EmitArtifact = enum {
1000 .docs => "-docs",1003 .docs => "-docs",
1001 .pdb => ".pdb",1004 .pdb => ".pdb",
1002 .h => ".h",1005 .h => ".h",
1006 .compiler_rt_dyn_lib => unreachable,
1003 };1007 };
1004 return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });1008 return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
1005 }1009 }
src/codegen/x86_64/CodeGen.zig+135-39
...@@ -2049,7 +2049,16 @@ fn gen(...@@ -2049,7 +2049,16 @@ fn gen(
20492049
2050 self.performReloc(skip_sse_reloc);2050 self.performReloc(skip_sse_reloc);
2051 },2051 },
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 },
2053 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),2062 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
2054 };2063 };
20552064
...@@ -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.
...@@ -180792,7 +180822,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {...@@ -180792,7 +180822,18 @@ fn airVaStart(self: *CodeGen, inst: Air.Inst.Index) !void {
180792 field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));180822 field_off += @intCast(ptr_anyopaque_ty.abiSize(zcu));
180793 break :result .{ .load_frame = .{ .index = dst_fi } };180823 break :result .{ .load_frame = .{ .index = dst_fi } };
180794 },180824 },
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 },
180796 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),180837 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180797 };180838 };
180798 return self.finishAir(inst, result, .{ .none, .none, .none });180839 return self.finishAir(inst, result, .{ .none, .none, .none });
...@@ -180931,48 +180972,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {...@@ -180931,48 +180972,103 @@ fn airVaArg(self: *CodeGen, inst: Air.Inst.Index) !void {
180931 break :result dst_mcv;180972 break :result dst_mcv;
180932 }180973 }
180933180974
180934 assert(ty.toIntern() == .f32_type and promote_ty.toIntern() == .f64_type);180975 try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv);
180935 const dst_mcv = if (promote_mcv.isRegister())180976 break :result promote_mcv;
180936 promote_mcv180977 },
180937 else180978 .x86_64_win => result: {
180938 try self.copyToRegisterWithInstTracking(inst, ty, promote_mcv);180979 try self.spillEflagsIfOccupied();
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);
180942180980
180943 if (self.hasFeature(.avx)) if (promote_mcv.isBase()) try self.asmRegisterRegisterMemory(180981 const promote_mcv = try self.allocTempRegOrMem(promote_ty, true);
180944 .{ .v_ss, .cvtsd2 },180982 const promote_lock = switch (promote_mcv) {
180945 dst_reg,180983 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
180946 dst_reg,180984 else => null,
180947 try promote_mcv.mem(self, .{ .size = .qword }),180985 };
180948 ) else try self.asmRegisterRegisterRegister(180986 defer if (promote_lock) |lock| self.register_manager.unlockReg(lock);
180949 .{ .v_ss, .cvtsd2 },180987
180950 dst_reg,180988 const ptr_arg_list_reg =
180951 dst_reg,180989 try self.copyToTmpRegister(self.typeOf(ty_op.operand), .{ .air_ref = ty_op.operand });
180952 (if (promote_mcv.isRegister())180990 const ptr_arg_list_lock = self.register_manager.lockRegAssumeUnused(ptr_arg_list_reg);
180953 promote_mcv.getReg().?180991 defer self.register_manager.unlockReg(ptr_arg_list_lock);
180954 else180992
180955 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),180993 const next_arg_ptr: MCValue = .{ .indirect = .{ .reg = ptr_arg_list_reg } };
180956 ) else if (promote_mcv.isBase()) try self.asmRegisterMemory(180994
180957 .{ ._ss, .cvtsd2 },180995 const class = abi.classifyWindows(promote_ty, zcu, self.target, .arg);
180958 dst_reg,180996 switch (class) {
180959 try promote_mcv.mem(self, .{ .size = .qword }),180997 .integer, .sse => {
180960 ) else try self.asmRegisterRegister(180998 const next_arg_ptr_reg = try self.copyToTmpRegister(.usize, next_arg_ptr);
180961 .{ ._ss, .cvtsd2 },180999 if (!unused) try self.genCopy(promote_ty, promote_mcv, .{
180962 dst_reg,181000 .indirect = .{ .reg = next_arg_ptr_reg },
180963 (if (promote_mcv.isRegister())181001 }, .{});
180964 promote_mcv.getReg().?181002 try self.asmRegisterMemory(.{ ._, .lea }, next_arg_ptr_reg, .{
180965 else181003 .base = .{ .reg = next_arg_ptr_reg.to64() },
180966 try self.copyToTmpRegister(promote_ty, promote_mcv)).to128(),181004 .mod = .{ .rm = .{ .disp = 8 } },
180967 );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);
180968 break :result promote_mcv;181022 break :result promote_mcv;
180969 },181023 },
180970 .x86_64_win => return self.fail("TODO implement c_va_arg for Win64", .{}),
180971 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),181024 else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}),
180972 };181025 };
180973 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });181026 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
180974}181027}
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
180976fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {181072fn airVaCopy(self: *CodeGen, inst: Air.Inst.Index) !void {
180977 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;181073 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
180978 const ptr_va_list_ty = self.typeOf(ty_op.operand);181074 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" {...@@ -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_llvm) 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_llvm) 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_llvm) 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_llvm) 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_llvm) {
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,51 @@ test "unused VaList arg" {...@@ -305,3 +305,51 @@ 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) 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}