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 29f521c78933ee174138444ad79dc5615ee12c12..b28748fba42da20a1a74a69692083e010784f7b2 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 3216fcc357d0be073bf0744bc9a70b6fc4f34fd5..105c5ed7adcef21945dd98755819fb996a01aa07 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 b6e4a5d3113586d220e6ada6470c135aed403c54..538b237e5ec3761151269e313319753ed8dffe49 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 bcff3660f4df82929dfc15023b86eef5e882759f..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..c730ada7e048f1261d63a8d558f063d7b7b41f46 --- /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 0000000000000000000000000000000000000000..4cb32097ba9ed15bd9d5f06d62e31e00acc5c24b --- /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 0000000000000000000000000000000000000000..c1c1e9780251c8527a5fe1fa93c87c38ba160126 --- /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 0000000000000000000000000000000000000000..4e8258453bc29943ca35b2a04137e1f70df9234b --- /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 b49a954800107bc2abead563961431f6eefff9cc..fb826f4562526892575f6284993af44b083316eb 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 521724a9980640a1d040c2261e4a3cd92e32a1f5..b5fd6d4d9aed59078fe93a6411af26c11967de77 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 cb19212b5bab444480d1b533ff63fef99ade4973..9aab487d37e420cfa5dec1fd6ec6210b6e839b98 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 53421b647534740ae7c67f240cd699ed47d5beb5..2b1c7181409ccbaaad0a728cbb40bf9ef06f6ebd 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 ecb755ae0a20cd055b93e5b4110680021a45f143..e0a60b50fa1e98ed73ca8ca4f5494f4a422f595e 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 fdf3810d4ac91ab82b9f8586bfdfd9f670f172d6..a071c97df31ed50452856abcd1d14a0063b77fa6 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 47d7d1282f3e45e0d38c7364d211489610653d75..64d49f25089e7d8a7c2d66e0fe9ffbc55e6a7a84 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 9a93969209c69c4a407f882881c3643eca19ea8c..b1b5c0fcb3b8e0aa7b05f62609556f838f00ca05 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 94d019781e878eb526f955289895e62bd5182ef1..1d4108a77ea36bfd1a94888691c03e7676ed2660 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 a28cb0002ca3802b82c814ab25e7cd4ceb236d2b..75260156f8210fd7118489d5495a2131362025ee 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 c614f7eb481683985de19c66ef03baa11b190939..d2d21925b2b8445721fc5d52a3d22a0b2444bdff 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 5c30d495c7a52509985cbfccf690452f049e2f50..8616c4ac5c2c33a88cca4230ee669c0326725f41 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 54c0dfb3c71383c7f7946b67c81bf6a965703078..15eb917fda440beaa4c7cca7f1957a536a617b70 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 2b320923ad177dc789456b3a74a70cd503170f54..d5096e3fe52b6d75e1bb79cb46bc18108547b4d9 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 671014ba3b107ff3bf601ac39be09ad39e70be0c..8aa8e5c6f27f2a45db01bd2fd87f984ec337fcf9 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 d6db7ca75fa6b83356d4bac57c7a9e7f82041ed9..a58b27fe2f93400213bdac297f3ca86f15b92ed4 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 ac35834928a4c595fcd3f19362541f2127496d59..c057f7a8426bd1049b34a05e2b0a52b614ef79cd 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)); +} -- 2.54.0 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 e5332a72e7924bd0353848a299b28ab4b8818f38..90cdf828348778c2cd15a8af0ee3398dc3e6a2df 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()) { -- 2.54.0 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 d5096e3fe52b6d75e1bb79cb46bc18108547b4d9..37d6d2c52bb50be0dae061a527de6b521942c5da 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", .{ -- 2.54.0