From 6bc6e47b1582a4538078c8f4e9b3dae386854d07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 30 Jun 2022 00:02:00 -0700 Subject: [PATCH 1/3] stage2: lower float negation explicitly Rather than lowering float negation as `0.0 - x`. * Add AIR instruction for float negation. * Add compiler-rt functions for f128, f80 negation closes #11853 --- CMakeLists.txt | 7 ++- lib/compiler_rt.zig | 12 ++-- lib/compiler_rt/common.zig | 12 ++++ lib/compiler_rt/negXf2.zig | 42 -------------- lib/compiler_rt/negdf2.zig | 19 +++++++ lib/compiler_rt/negsf2.zig | 19 +++++++ lib/compiler_rt/negtf2.zig | 11 ++++ lib/compiler_rt/negxf2.zig | 11 ++++ lib/std/fmt.zig | 2 - lib/std/math/copysign.zig | 1 - lib/std/math/signbit.zig | 1 - src/Air.zig | 6 ++ src/Liveness.zig | 2 + src/Sema.zig | 4 +- src/arch/aarch64/CodeGen.zig | 3 +- src/arch/arm/CodeGen.zig | 1 + src/arch/riscv64/CodeGen.zig | 1 + src/arch/sparc64/CodeGen.zig | 1 + src/arch/wasm/CodeGen.zig | 1 + src/arch/x86_64/CodeGen.zig | 1 + src/codegen/c.zig | 16 ++++++ src/codegen/llvm.zig | 14 ++++- src/codegen/llvm/bindings.zig | 3 + src/print_air.zig | 1 + test/behavior/floatop.zig | 103 ++++++++++++++++++++++++++++++++++ 25 files changed, 237 insertions(+), 57 deletions(-) delete mode 100644 lib/compiler_rt/negXf2.zig create mode 100644 lib/compiler_rt/negdf2.zig create mode 100644 lib/compiler_rt/negsf2.zig create mode 100644 lib/compiler_rt/negtf2.zig create mode 100644 lib/compiler_rt/negxf2.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 29f521c789..b28748fba4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -608,7 +608,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/compiler_rt/multf3.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/multi3.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/mulxf3.zig" - "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXf2.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXi2.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negv.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/os_version_check.zig" @@ -623,11 +622,15 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/compiler_rt/sincos.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/sqrt.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/stack_probe.zig" - "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subdf3.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subo.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subsf3.zig" + "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subdf3.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subtf3.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subxf3.zig" + "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negsf2.zig" + "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negdf2.zig" + "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negtf2.zig" + "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negxf2.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/tan.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/trig.zig" "${CMAKE_SOURCE_DIR}/lib/compiler_rt/trunc.zig" diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index 3216fcc357..105c5ed7ad 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -4,12 +4,13 @@ comptime { _ = @import("compiler_rt/atomics.zig"); _ = @import("compiler_rt/addf3.zig"); - _ = @import("compiler_rt/adddf3.zig"); _ = @import("compiler_rt/addsf3.zig"); + _ = @import("compiler_rt/adddf3.zig"); _ = @import("compiler_rt/addtf3.zig"); _ = @import("compiler_rt/addxf3.zig"); - _ = @import("compiler_rt/subdf3.zig"); + _ = @import("compiler_rt/subsf3.zig"); + _ = @import("compiler_rt/subdf3.zig"); _ = @import("compiler_rt/subtf3.zig"); _ = @import("compiler_rt/subxf3.zig"); @@ -19,6 +20,11 @@ comptime { _ = @import("compiler_rt/multf3.zig"); _ = @import("compiler_rt/mulxf3.zig"); + _ = @import("compiler_rt/negsf2.zig"); + _ = @import("compiler_rt/negdf2.zig"); + _ = @import("compiler_rt/negtf2.zig"); + _ = @import("compiler_rt/negxf2.zig"); + _ = @import("compiler_rt/comparef.zig"); _ = @import("compiler_rt/cmpsf2.zig"); _ = @import("compiler_rt/cmpdf2.zig"); @@ -172,8 +178,6 @@ comptime { _ = @import("compiler_rt/mulo.zig"); _ = @import("compiler_rt/cmp.zig"); - _ = @import("compiler_rt/negXf2.zig"); - _ = @import("compiler_rt/os_version_check.zig"); _ = @import("compiler_rt/emutls.zig"); _ = @import("compiler_rt/arm.zig"); diff --git a/lib/compiler_rt/common.zig b/lib/compiler_rt/common.zig index b6e4a5d311..538b237e5e 100644 --- a/lib/compiler_rt/common.zig +++ b/lib/compiler_rt/common.zig @@ -188,3 +188,15 @@ pub fn normalize(comptime T: type, significand: *std.meta.Int(.unsigned, @typeIn significand.* <<= @intCast(std.math.Log2Int(Z), shift); return @as(i32, 1) - shift; } + +pub inline fn fneg(a: anytype) @TypeOf(a) { + const F = @TypeOf(a); + const bits = @typeInfo(F).Float.bits; + const U = @Type(.{ .Int = .{ + .signedness = .unsigned, + .bits = bits, + } }); + const sign_bit_mask = @as(U, 1) << (bits - 1); + const negated = @bitCast(U, a) ^ sign_bit_mask; + return @bitCast(F, negated); +} diff --git a/lib/compiler_rt/negXf2.zig b/lib/compiler_rt/negXf2.zig deleted file mode 100644 index bcff3660f4..0000000000 --- a/lib/compiler_rt/negXf2.zig +++ /dev/null @@ -1,42 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const common = @import("common.zig"); - -pub const panic = common.panic; - -comptime { - if (common.want_aeabi) { - @export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage }); - @export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage }); - } else { - @export(__negsf2, .{ .name = "__negsf2", .linkage = common.linkage }); - @export(__negdf2, .{ .name = "__negdf2", .linkage = common.linkage }); - } -} - -pub fn __negsf2(a: f32) callconv(.C) f32 { - return negXf2(f32, a); -} - -fn __aeabi_fneg(a: f32) callconv(.AAPCS) f32 { - return negXf2(f32, a); -} - -pub fn __negdf2(a: f64) callconv(.C) f64 { - return negXf2(f64, a); -} - -fn __aeabi_dneg(a: f64) callconv(.AAPCS) f64 { - return negXf2(f64, a); -} - -inline fn negXf2(comptime T: type, a: T) T { - const Z = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); - - const significandBits = std.math.floatMantissaBits(T); - const exponentBits = std.math.floatExponentBits(T); - - const signBit = (@as(Z, 1) << (significandBits + exponentBits)); - - return @bitCast(T, @bitCast(Z, a) ^ signBit); -} diff --git a/lib/compiler_rt/negdf2.zig b/lib/compiler_rt/negdf2.zig new file mode 100644 index 0000000000..c730ada7e0 --- /dev/null +++ b/lib/compiler_rt/negdf2.zig @@ -0,0 +1,19 @@ +const common = @import("./common.zig"); + +pub const panic = common.panic; + +comptime { + if (common.want_aeabi) { + @export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage }); + } else { + @export(__negdf2, .{ .name = "__negdf2", .linkage = common.linkage }); + } +} + +fn __negdf2(a: f64) callconv(.C) f64 { + return common.fneg(a); +} + +fn __aeabi_dneg(a: f64) callconv(.AAPCS) f64 { + return common.fneg(a); +} diff --git a/lib/compiler_rt/negsf2.zig b/lib/compiler_rt/negsf2.zig new file mode 100644 index 0000000000..4cb32097ba --- /dev/null +++ b/lib/compiler_rt/negsf2.zig @@ -0,0 +1,19 @@ +const common = @import("./common.zig"); + +pub const panic = common.panic; + +comptime { + if (common.want_aeabi) { + @export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage }); + } else { + @export(__negsf2, .{ .name = "__negsf2", .linkage = common.linkage }); + } +} + +fn __negsf2(a: f32) callconv(.C) f32 { + return common.fneg(a); +} + +fn __aeabi_fneg(a: f32) callconv(.AAPCS) f32 { + return common.fneg(a); +} diff --git a/lib/compiler_rt/negtf2.zig b/lib/compiler_rt/negtf2.zig new file mode 100644 index 0000000000..c1c1e97802 --- /dev/null +++ b/lib/compiler_rt/negtf2.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); + +pub const panic = common.panic; + +comptime { + @export(__negtf2, .{ .name = "__negtf2", .linkage = common.linkage }); +} + +fn __negtf2(a: f128) callconv(.C) f128 { + return common.fneg(a); +} diff --git a/lib/compiler_rt/negxf2.zig b/lib/compiler_rt/negxf2.zig new file mode 100644 index 0000000000..4e8258453b --- /dev/null +++ b/lib/compiler_rt/negxf2.zig @@ -0,0 +1,11 @@ +const common = @import("./common.zig"); + +pub const panic = common.panic; + +comptime { + @export(__negxf2, .{ .name = "__negxf2", .linkage = common.linkage }); +} + +fn __negxf2(a: f80) callconv(.C) f80 { + return common.fneg(a); +} diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index b49a954800..fb826f4562 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -2225,7 +2225,6 @@ test "float.scientific.precision" { } test "float.special" { - if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO try expectFmt("f64: nan", "f64: {}", .{math.nan_f64}); // negative nan is not defined by IEE 754, // and ARM thus normalizes it to positive nan @@ -2237,7 +2236,6 @@ test "float.special" { } test "float.hexadecimal.special" { - if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64}); // negative nan is not defined by IEE 754, // and ARM thus normalizes it to positive nan diff --git a/lib/std/math/copysign.zig b/lib/std/math/copysign.zig index 521724a998..b5fd6d4d9a 100644 --- a/lib/std/math/copysign.zig +++ b/lib/std/math/copysign.zig @@ -13,7 +13,6 @@ pub fn copysign(magnitude: anytype, sign: @TypeOf(magnitude)) @TypeOf(magnitude) } test "math.copysign" { - if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { try expect(copysign(@as(T, 1.0), @as(T, 1.0)) == 1.0); try expect(copysign(@as(T, 2.0), @as(T, -2.0)) == -2.0); diff --git a/lib/std/math/signbit.zig b/lib/std/math/signbit.zig index cb19212b5b..9aab487d37 100644 --- a/lib/std/math/signbit.zig +++ b/lib/std/math/signbit.zig @@ -10,7 +10,6 @@ pub fn signbit(x: anytype) bool { } test "math.signbit" { - if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { try expect(!signbit(@as(T, 0.0))); try expect(!signbit(@as(T, 1.0))); diff --git a/src/Air.zig b/src/Air.zig index 53421b6475..2b1c718140 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -288,6 +288,11 @@ pub const Inst = struct { /// Rounds a floating pointer number to the nearest integer towards zero. /// Uses the `un_op` field. trunc_float, + /// Float negation. This affects the sign of zero, inf, and NaN, which is impossible + /// to do with sub. Integers are not allowed and must be represented with sub with + /// LHS of zero. + /// Uses the `un_op` field. + neg, /// `<`. Result type is always bool. /// Uses the `bin_op` field. @@ -970,6 +975,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .ceil, .round, .trunc_float, + .neg, => return air.typeOf(datas[inst].un_op), .cmp_lt, diff --git a/src/Liveness.zig b/src/Liveness.zig index ecb755ae0a..e0a60b50fa 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -287,6 +287,7 @@ pub fn categorizeOperand( .ceil, .round, .trunc_float, + .neg, .cmp_lt_errors_len, => { const o = air_datas[inst].un_op; @@ -834,6 +835,7 @@ fn analyzeInst( .ceil, .round, .trunc_float, + .neg, .cmp_lt_errors_len, .set_err_return_trace, => { diff --git a/src/Sema.zig b/src/Sema.zig index fdf3810d4a..a071c97df3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10070,12 +10070,14 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. } if (rhs_scalar_ty.isAnyFloat()) { - // We handle comptime negation here to ensure negative zero is represented in the bits. + // We handle float negation here to ensure negative zero is represented in the bits. if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { if (rhs_val.isUndef()) return sema.addConstUndef(rhs_ty); const target = sema.mod.getTarget(); return sema.addConstant(rhs_ty, try rhs_val.floatNeg(rhs_ty, sema.arena, target)); } + try sema.requireRuntimeBlock(block, rhs_src); + return block.addUnOp(.neg, rhs); } const lhs = if (rhs_ty.zigTypeTag() == .Vector) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 47d7d1282f..64d49f2508 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -582,7 +582,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .floor, .ceil, .round, - .trunc_float + .trunc_float, + .neg, => try self.airUnaryMath(inst), .add_with_overflow => try self.airOverflow(inst), diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 9a93969209..b1b5c0fcb3 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -596,6 +596,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ceil, .round, .trunc_float, + .neg, => try self.airUnaryMath(inst), .add_with_overflow => try self.airOverflow(inst), diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 94d019781e..1d4108a77e 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -516,6 +516,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ceil, .round, .trunc_float, + .neg, => try self.airUnaryMath(inst), .add_with_overflow => try self.airAddWithOverflow(inst), diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index a28cb0002c..75260156f8 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -529,6 +529,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ceil, .round, .trunc_float, + .neg, => @panic("TODO try self.airUnaryMath(inst)"), .add_with_overflow => try self.airAddSubWithOverflow(inst), diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index c614f7eb48..d2d21925b2 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1607,6 +1607,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .log10, .fabs, .round, + .neg, .cmpxchg_weak, .cmpxchg_strong, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 5c30d495c7..8616c4ac5c 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -605,6 +605,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .ceil, .round, .trunc_float, + .neg, => try self.airUnaryMath(inst), .add_with_overflow => try self.airAddSubShlWithOverflow(inst), diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 54c0dfb3c7..15eb917fda 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1755,6 +1755,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .mul_sat => try airSatOp(f, inst, "muls_"), .shl_sat => try airSatOp(f, inst, "shls_"), + .neg => try airNeg(f, inst), + .sqrt, .sin, .cos, @@ -4098,6 +4100,20 @@ fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue { return local; } +fn airNeg(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const operand = try f.resolveInst(un_op); + const local = try f.allocLocal(inst_ty, .Const); + try writer.writeAll("-"); + try f.writeCValue(writer, operand); + try writer.writeAll(";\n"); + return local; +} + fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const pl_op = f.air.instructions.items(.data)[inst].pl_op; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2b320923ad..d5096e3fe5 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4019,6 +4019,7 @@ pub const FuncGen = struct { .ceil => try self.airUnaryOp(inst, .ceil), .round => try self.airUnaryOp(inst, .round), .trunc_float => try self.airUnaryOp(inst, .trunc), + .neg => try self.airUnaryOp(inst, .neg), .cmp_eq => try self.airCmp(inst, .eq), .cmp_gt => try self.airCmp(inst, .gt), @@ -6545,13 +6546,14 @@ pub const FuncGen = struct { fabs, floor, fma, + fmax, + fmin, + fmod, log, log10, log2, - fmax, - fmin, mul, - fmod, + neg, round, sin, sqrt, @@ -6584,6 +6586,7 @@ pub const FuncGen = struct { var fn_name_buf: [64]u8 = undefined; const strat: FloatOpStrat = if (intrinsics_allowed) switch (op) { // Some operations are dedicated LLVM instructions, not available as intrinsics + .neg => return self.builder.buildFNeg(params[0], ""), .add => return self.builder.buildFAdd(params[0], params[1], ""), .sub => return self.builder.buildFSub(params[0], params[1], ""), .mul => return self.builder.buildFMul(params[0], params[1], ""), @@ -6595,6 +6598,11 @@ pub const FuncGen = struct { } else b: { const float_bits = scalar_ty.floatBits(target); break :b switch (op) { + .neg => FloatOpStrat{ + .libc = std.fmt.bufPrintZ(&fn_name_buf, "__neg{s}f2", .{ + compilerRtFloatAbbrev(float_bits), + }) catch unreachable, + }, .add, .sub, .div, .mul => FloatOpStrat{ .libc = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f3", .{ @tagName(op), compilerRtFloatAbbrev(float_bits), diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 671014ba3b..8aa8e5c6f2 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -549,6 +549,9 @@ pub const Builder = opaque { pub const buildFSub = LLVMBuildFSub; extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildFNeg = LLVMBuildFNeg; + extern fn LLVMBuildFNeg(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value; + pub const buildSub = LLVMBuildSub; extern fn LLVMBuildSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; diff --git a/src/print_air.zig b/src/print_air.zig index d6db7ca75f..a58b27fe2f 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -168,6 +168,7 @@ const Writer = struct { .ceil, .round, .trunc_float, + .neg, .cmp_lt_errors_len, .set_err_return_trace, => try w.writeUnOp(s, inst), diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index ac35834928..c057f7a842 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -574,6 +574,7 @@ test "negation f32" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { fn doTheTest() !void { @@ -593,6 +594,8 @@ test "negation f64" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO const S = struct { fn doTheTest() !void { @@ -707,3 +710,103 @@ test "comptime_float zero divided by zero produces zero" { try expect((0.0 / 0.0) == 0.0); } + +test "nan negation f16" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + + const nan_comptime = comptime math.nan(f16); + const neg_nan_comptime = -nan_comptime; + + var nan_runtime = math.nan(f16); + const neg_nan_runtime = -nan_runtime; + + try expect(!math.signbit(nan_runtime)); + try expect(math.signbit(neg_nan_runtime)); + + try expect(!math.signbit(nan_comptime)); + try expect(math.signbit(neg_nan_comptime)); +} + +test "nan negation f32" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + + const nan_comptime = comptime math.nan(f32); + const neg_nan_comptime = -nan_comptime; + + var nan_runtime = math.nan(f32); + const neg_nan_runtime = -nan_runtime; + + try expect(!math.signbit(nan_runtime)); + try expect(math.signbit(neg_nan_runtime)); + + try expect(!math.signbit(nan_comptime)); + try expect(math.signbit(neg_nan_comptime)); +} + +test "nan negation f64" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + + const nan_comptime = comptime math.nan(f64); + const neg_nan_comptime = -nan_comptime; + + var nan_runtime = math.nan(f64); + const neg_nan_runtime = -nan_runtime; + + try expect(!math.signbit(nan_runtime)); + try expect(math.signbit(neg_nan_runtime)); + + try expect(!math.signbit(nan_comptime)); + try expect(math.signbit(neg_nan_comptime)); +} + +test "nan negation f128" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + + const nan_comptime = comptime math.nan(f128); + const neg_nan_comptime = -nan_comptime; + + var nan_runtime = math.nan(f128); + const neg_nan_runtime = -nan_runtime; + + try expect(!math.signbit(nan_runtime)); + try expect(math.signbit(neg_nan_runtime)); + + try expect(!math.signbit(nan_comptime)); + try expect(math.signbit(neg_nan_comptime)); +} + +test "nan negation f80" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + + const nan_comptime = comptime math.nan(f80); + const neg_nan_comptime = -nan_comptime; + + var nan_runtime = math.nan(f80); + const neg_nan_runtime = -nan_runtime; + + try expect(!math.signbit(nan_runtime)); + try expect(math.signbit(neg_nan_runtime)); + + try expect(!math.signbit(nan_comptime)); + try expect(math.signbit(neg_nan_comptime)); +} From c8531faaf56612773ef860d341f8eebb36af4bf3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 30 Jun 2022 00:54:03 -0700 Subject: [PATCH 2/3] stage2: hash/eql of fixed-size floats use bit pattern Zig guarantees the memory layout of f16, f32, f64, f80, and f128 which means for generic function purposes, values of these types need to be compared on the basis of their bits in memory. This means nan-packing can be used with generic functions, for example. For comptime_float, the sign is observable, whether it is nan is observable, but not any more kinds of bit patterns are observable. This fixes the std.fmt tests that check printing "-nan". --- src/value.zig | 50 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/value.zig b/src/value.zig index e5332a72e7..90cdf82834 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2189,12 +2189,25 @@ pub const Value = extern union { return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0; }, .Float => { - const a_nan = a.isNan(); - const b_nan = b.isNan(); - if (a_nan or b_nan) { - return a_nan and b_nan; + switch (ty.floatBits(target)) { + 16 => return @bitCast(u16, a.toFloat(f16)) == @bitCast(u16, b.toFloat(f16)), + 32 => return @bitCast(u32, a.toFloat(f32)) == @bitCast(u32, b.toFloat(f32)), + 64 => return @bitCast(u64, a.toFloat(f64)) == @bitCast(u64, b.toFloat(f64)), + 80 => return @bitCast(u80, a.toFloat(f80)) == @bitCast(u80, b.toFloat(f80)), + 128 => return @bitCast(u128, a.toFloat(f128)) == @bitCast(u128, b.toFloat(f128)), + else => unreachable, } - return order(a, b, target).compare(.eq); + }, + .ComptimeFloat => { + const a_float = a.toFloat(f128); + const b_float = b.toFloat(f128); + + const a_nan = std.math.isNan(a_float); + const b_nan = std.math.isNan(b_float); + if (a_nan != b_nan) return false; + if (std.math.signbit(a_float) != std.math.signbit(b_float)) return false; + if (a_nan) return true; + return a_float == b_float; }, .Optional => { if (a.tag() != .opt_payload and b.tag() == .opt_payload) { @@ -2231,18 +2244,25 @@ pub const Value = extern union { var buf: ToTypeBuffer = undefined; return val.toType(&buf).hashWithHasher(hasher, mod); }, - .Float, .ComptimeFloat => { - // Normalize the float here because this hash must match eql semantics. - // These functions are used for hash maps so we want NaN to equal itself, - // and -0.0 to equal +0.0. + .Float => { + // For hash/eql purposes, we treat floats as their IEEE integer representation. + switch (ty.floatBits(mod.getTarget())) { + 16 => std.hash.autoHash(hasher, @bitCast(u16, val.toFloat(f16))), + 32 => std.hash.autoHash(hasher, @bitCast(u32, val.toFloat(f32))), + 64 => std.hash.autoHash(hasher, @bitCast(u64, val.toFloat(f64))), + 80 => std.hash.autoHash(hasher, @bitCast(u80, val.toFloat(f80))), + 128 => std.hash.autoHash(hasher, @bitCast(u128, val.toFloat(f128))), + else => unreachable, + } + }, + .ComptimeFloat => { const float = val.toFloat(f128); - if (std.math.isNan(float)) { - std.hash.autoHash(hasher, std.math.nan_u128); - } else if (float == 0.0) { - var normalized_zero: f128 = 0.0; - std.hash.autoHash(hasher, @bitCast(u128, normalized_zero)); - } else { + const is_nan = std.math.isNan(float); + std.hash.autoHash(hasher, is_nan); + if (!is_nan) { std.hash.autoHash(hasher, @bitCast(u128, float)); + } else { + std.hash.autoHash(hasher, std.math.signbit(float)); } }, .Bool, .Int, .ComptimeInt, .Pointer => switch (val.tag()) { From b3b96b5e288ddf3694b4a0d203c684b9b8f6b49f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 30 Jun 2022 11:48:37 -0700 Subject: [PATCH 3/3] LLVM: lower float negation with xor a constant Rather than a compiler-rt call in the case that LLVM does not support lowering the fneg instruction. --- src/codegen/llvm.zig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d5096e3fe5..37d6d2c52b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -6598,10 +6598,16 @@ pub const FuncGen = struct { } else b: { const float_bits = scalar_ty.floatBits(target); break :b switch (op) { - .neg => FloatOpStrat{ - .libc = std.fmt.bufPrintZ(&fn_name_buf, "__neg{s}f2", .{ - compilerRtFloatAbbrev(float_bits), - }) catch unreachable, + .neg => { + // In this case we can generate a softfloat negation by XORing the + // bits with a constant. + const int_llvm_ty = self.dg.context.intType(float_bits); + const one = int_llvm_ty.constInt(1, .False); + const shift_amt = int_llvm_ty.constInt(float_bits - 1, .False); + const sign_mask = one.constShl(shift_amt); + const bitcasted_operand = self.builder.buildBitCast(params[0], int_llvm_ty, ""); + const result = self.builder.buildXor(bitcasted_operand, sign_mask, ""); + return self.builder.buildBitCast(result, llvm_ty, ""); }, .add, .sub, .div, .mul => FloatOpStrat{ .libc = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f3", .{