diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 997fe8a925..fa40535018 100644 --- 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 index 9ba98c3e83..1008305f95 100644 --- 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); - - 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(), - ); + try self.convertFloatVarArg(inst, ty, promote_ty, promote_mcv); + break :result promote_mcv; + }, + .x86_64_win => result: { + try self.spillEflagsIfOccupied(); + + 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 index 5b71bcb4c3..a8affddfc4 100644 --- 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); +}