authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-06-30 17:34:05-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-06-30 17:34:05-04:00
log77a334451f1329110d6c1bd07b46813cef10e97c
tree6708ceee35b9c7fc1c358696cd18a7b05d87bbf7
parentbe18459c81f831e455750e62f37dae6aeef62f1d
parentb3b96b5e288ddf3694b4a0d203c684b9b8f6b49f
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11967 from ziglang/runtime-float-negation

stage2: lower float negation explicitly + modify hash/eql logic for floats

26 files changed, 278 insertions(+), 72 deletions(-)

CMakeLists.txt+5-2
...@@ -608,7 +608,6 @@ set(ZIG_STAGE2_SOURCES...@@ -608,7 +608,6 @@ set(ZIG_STAGE2_SOURCES
608 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/multf3.zig"608 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/multf3.zig"
609 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/multi3.zig"609 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/multi3.zig"
610 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/mulxf3.zig"610 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/mulxf3.zig"
611 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXf2.zig"
612 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXi2.zig"611 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXi2.zig"
613 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negv.zig"612 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negv.zig"
614 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/os_version_check.zig"613 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/os_version_check.zig"
...@@ -623,11 +622,15 @@ set(ZIG_STAGE2_SOURCES...@@ -623,11 +622,15 @@ set(ZIG_STAGE2_SOURCES
623 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/sincos.zig"622 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/sincos.zig"
624 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/sqrt.zig"623 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/sqrt.zig"
625 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/stack_probe.zig"624 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/stack_probe.zig"
626 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subdf3.zig"
627 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subo.zig"625 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subo.zig"
628 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subsf3.zig"626 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subsf3.zig"
627 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subdf3.zig"
629 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subtf3.zig"628 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subtf3.zig"
630 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subxf3.zig"629 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/subxf3.zig"
630 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negsf2.zig"
631 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negdf2.zig"
632 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negtf2.zig"
633 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/negxf2.zig"
631 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/tan.zig"634 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/tan.zig"
632 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/trig.zig"635 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/trig.zig"
633 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/trunc.zig"636 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/trunc.zig"
lib/compiler_rt.zig+8-4
...@@ -4,12 +4,13 @@ comptime {...@@ -4,12 +4,13 @@ comptime {
4 _ = @import("compiler_rt/atomics.zig");4 _ = @import("compiler_rt/atomics.zig");
55
6 _ = @import("compiler_rt/addf3.zig");6 _ = @import("compiler_rt/addf3.zig");
7 _ = @import("compiler_rt/adddf3.zig");
8 _ = @import("compiler_rt/addsf3.zig");7 _ = @import("compiler_rt/addsf3.zig");
8 _ = @import("compiler_rt/adddf3.zig");
9 _ = @import("compiler_rt/addtf3.zig");9 _ = @import("compiler_rt/addtf3.zig");
10 _ = @import("compiler_rt/addxf3.zig");10 _ = @import("compiler_rt/addxf3.zig");
11 _ = @import("compiler_rt/subdf3.zig");11
12 _ = @import("compiler_rt/subsf3.zig");12 _ = @import("compiler_rt/subsf3.zig");
13 _ = @import("compiler_rt/subdf3.zig");
13 _ = @import("compiler_rt/subtf3.zig");14 _ = @import("compiler_rt/subtf3.zig");
14 _ = @import("compiler_rt/subxf3.zig");15 _ = @import("compiler_rt/subxf3.zig");
1516
...@@ -19,6 +20,11 @@ comptime {...@@ -19,6 +20,11 @@ comptime {
19 _ = @import("compiler_rt/multf3.zig");20 _ = @import("compiler_rt/multf3.zig");
20 _ = @import("compiler_rt/mulxf3.zig");21 _ = @import("compiler_rt/mulxf3.zig");
2122
23 _ = @import("compiler_rt/negsf2.zig");
24 _ = @import("compiler_rt/negdf2.zig");
25 _ = @import("compiler_rt/negtf2.zig");
26 _ = @import("compiler_rt/negxf2.zig");
27
22 _ = @import("compiler_rt/comparef.zig");28 _ = @import("compiler_rt/comparef.zig");
23 _ = @import("compiler_rt/cmpsf2.zig");29 _ = @import("compiler_rt/cmpsf2.zig");
24 _ = @import("compiler_rt/cmpdf2.zig");30 _ = @import("compiler_rt/cmpdf2.zig");
...@@ -172,8 +178,6 @@ comptime {...@@ -172,8 +178,6 @@ comptime {
172 _ = @import("compiler_rt/mulo.zig");178 _ = @import("compiler_rt/mulo.zig");
173 _ = @import("compiler_rt/cmp.zig");179 _ = @import("compiler_rt/cmp.zig");
174180
175 _ = @import("compiler_rt/negXf2.zig");
176
177 _ = @import("compiler_rt/os_version_check.zig");181 _ = @import("compiler_rt/os_version_check.zig");
178 _ = @import("compiler_rt/emutls.zig");182 _ = @import("compiler_rt/emutls.zig");
179 _ = @import("compiler_rt/arm.zig");183 _ = @import("compiler_rt/arm.zig");
lib/compiler_rt/common.zig+12
...@@ -188,3 +188,15 @@ pub fn normalize(comptime T: type, significand: *std.meta.Int(.unsigned, @typeIn...@@ -188,3 +188,15 @@ pub fn normalize(comptime T: type, significand: *std.meta.Int(.unsigned, @typeIn
188 significand.* <<= @intCast(std.math.Log2Int(Z), shift);188 significand.* <<= @intCast(std.math.Log2Int(Z), shift);
189 return @as(i32, 1) - shift;189 return @as(i32, 1) - shift;
190}190}
191
192pub inline fn fneg(a: anytype) @TypeOf(a) {
193 const F = @TypeOf(a);
194 const bits = @typeInfo(F).Float.bits;
195 const U = @Type(.{ .Int = .{
196 .signedness = .unsigned,
197 .bits = bits,
198 } });
199 const sign_bit_mask = @as(U, 1) << (bits - 1);
200 const negated = @bitCast(U, a) ^ sign_bit_mask;
201 return @bitCast(F, negated);
202}
lib/compiler_rt/negXf2.zig deleted-42
...@@ -1,42 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const common = @import("common.zig");
4
5pub const panic = common.panic;
6
7comptime {
8 if (common.want_aeabi) {
9 @export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage });
10 @export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage });
11 } else {
12 @export(__negsf2, .{ .name = "__negsf2", .linkage = common.linkage });
13 @export(__negdf2, .{ .name = "__negdf2", .linkage = common.linkage });
14 }
15}
16
17pub fn __negsf2(a: f32) callconv(.C) f32 {
18 return negXf2(f32, a);
19}
20
21fn __aeabi_fneg(a: f32) callconv(.AAPCS) f32 {
22 return negXf2(f32, a);
23}
24
25pub fn __negdf2(a: f64) callconv(.C) f64 {
26 return negXf2(f64, a);
27}
28
29fn __aeabi_dneg(a: f64) callconv(.AAPCS) f64 {
30 return negXf2(f64, a);
31}
32
33inline fn negXf2(comptime T: type, a: T) T {
34 const Z = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
35
36 const significandBits = std.math.floatMantissaBits(T);
37 const exponentBits = std.math.floatExponentBits(T);
38
39 const signBit = (@as(Z, 1) << (significandBits + exponentBits));
40
41 return @bitCast(T, @bitCast(Z, a) ^ signBit);
42}
lib/compiler_rt/negdf2.zig created+19
...@@ -0,0 +1,19 @@
1const common = @import("./common.zig");
2
3pub const panic = common.panic;
4
5comptime {
6 if (common.want_aeabi) {
7 @export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage });
8 } else {
9 @export(__negdf2, .{ .name = "__negdf2", .linkage = common.linkage });
10 }
11}
12
13fn __negdf2(a: f64) callconv(.C) f64 {
14 return common.fneg(a);
15}
16
17fn __aeabi_dneg(a: f64) callconv(.AAPCS) f64 {
18 return common.fneg(a);
19}
lib/compiler_rt/negsf2.zig created+19
...@@ -0,0 +1,19 @@
1const common = @import("./common.zig");
2
3pub const panic = common.panic;
4
5comptime {
6 if (common.want_aeabi) {
7 @export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage });
8 } else {
9 @export(__negsf2, .{ .name = "__negsf2", .linkage = common.linkage });
10 }
11}
12
13fn __negsf2(a: f32) callconv(.C) f32 {
14 return common.fneg(a);
15}
16
17fn __aeabi_fneg(a: f32) callconv(.AAPCS) f32 {
18 return common.fneg(a);
19}
lib/compiler_rt/negtf2.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2
3pub const panic = common.panic;
4
5comptime {
6 @export(__negtf2, .{ .name = "__negtf2", .linkage = common.linkage });
7}
8
9fn __negtf2(a: f128) callconv(.C) f128 {
10 return common.fneg(a);
11}
lib/compiler_rt/negxf2.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2
3pub const panic = common.panic;
4
5comptime {
6 @export(__negxf2, .{ .name = "__negxf2", .linkage = common.linkage });
7}
8
9fn __negxf2(a: f80) callconv(.C) f80 {
10 return common.fneg(a);
11}
lib/std/fmt.zig-2
...@@ -2225,7 +2225,6 @@ test "float.scientific.precision" {...@@ -2225,7 +2225,6 @@ test "float.scientific.precision" {
2225}2225}
22262226
2227test "float.special" {2227test "float.special" {
2228 if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
2229 try expectFmt("f64: nan", "f64: {}", .{math.nan_f64});2228 try expectFmt("f64: nan", "f64: {}", .{math.nan_f64});
2230 // negative nan is not defined by IEE 754,2229 // negative nan is not defined by IEE 754,
2231 // and ARM thus normalizes it to positive nan2230 // and ARM thus normalizes it to positive nan
...@@ -2237,7 +2236,6 @@ test "float.special" {...@@ -2237,7 +2236,6 @@ test "float.special" {
2237}2236}
22382237
2239test "float.hexadecimal.special" {2238test "float.hexadecimal.special" {
2240 if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
2241 try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64});2239 try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64});
2242 // negative nan is not defined by IEE 754,2240 // negative nan is not defined by IEE 754,
2243 // and ARM thus normalizes it to positive nan2241 // and ARM thus normalizes it to positive nan
lib/std/math/copysign.zig-1
...@@ -13,7 +13,6 @@ pub fn copysign(magnitude: anytype, sign: @TypeOf(magnitude)) @TypeOf(magnitude)...@@ -13,7 +13,6 @@ pub fn copysign(magnitude: anytype, sign: @TypeOf(magnitude)) @TypeOf(magnitude)
13}13}
1414
15test "math.copysign" {15test "math.copysign" {
16 if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
17 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {16 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
18 try expect(copysign(@as(T, 1.0), @as(T, 1.0)) == 1.0);17 try expect(copysign(@as(T, 1.0), @as(T, 1.0)) == 1.0);
19 try expect(copysign(@as(T, 2.0), @as(T, -2.0)) == -2.0);18 try expect(copysign(@as(T, 2.0), @as(T, -2.0)) == -2.0);
lib/std/math/signbit.zig-1
...@@ -10,7 +10,6 @@ pub fn signbit(x: anytype) bool {...@@ -10,7 +10,6 @@ pub fn signbit(x: anytype) bool {
10}10}
1111
12test "math.signbit" {12test "math.signbit" {
13 if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
14 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {13 inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
15 try expect(!signbit(@as(T, 0.0)));14 try expect(!signbit(@as(T, 0.0)));
16 try expect(!signbit(@as(T, 1.0)));15 try expect(!signbit(@as(T, 1.0)));
src/Air.zig+6
...@@ -288,6 +288,11 @@ pub const Inst = struct {...@@ -288,6 +288,11 @@ pub const Inst = struct {
288 /// Rounds a floating pointer number to the nearest integer towards zero.288 /// Rounds a floating pointer number to the nearest integer towards zero.
289 /// Uses the `un_op` field.289 /// Uses the `un_op` field.
290 trunc_float,290 trunc_float,
291 /// Float negation. This affects the sign of zero, inf, and NaN, which is impossible
292 /// to do with sub. Integers are not allowed and must be represented with sub with
293 /// LHS of zero.
294 /// Uses the `un_op` field.
295 neg,
291296
292 /// `<`. Result type is always bool.297 /// `<`. Result type is always bool.
293 /// Uses the `bin_op` field.298 /// Uses the `bin_op` field.
...@@ -970,6 +975,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {...@@ -970,6 +975,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
970 .ceil,975 .ceil,
971 .round,976 .round,
972 .trunc_float,977 .trunc_float,
978 .neg,
973 => return air.typeOf(datas[inst].un_op),979 => return air.typeOf(datas[inst].un_op),
974980
975 .cmp_lt,981 .cmp_lt,
src/Liveness.zig+2
...@@ -287,6 +287,7 @@ pub fn categorizeOperand(...@@ -287,6 +287,7 @@ pub fn categorizeOperand(
287 .ceil,287 .ceil,
288 .round,288 .round,
289 .trunc_float,289 .trunc_float,
290 .neg,
290 .cmp_lt_errors_len,291 .cmp_lt_errors_len,
291 => {292 => {
292 const o = air_datas[inst].un_op;293 const o = air_datas[inst].un_op;
...@@ -834,6 +835,7 @@ fn analyzeInst(...@@ -834,6 +835,7 @@ fn analyzeInst(
834 .ceil,835 .ceil,
835 .round,836 .round,
836 .trunc_float,837 .trunc_float,
838 .neg,
837 .cmp_lt_errors_len,839 .cmp_lt_errors_len,
838 .set_err_return_trace,840 .set_err_return_trace,
839 => {841 => {
src/Sema.zig+3-1
...@@ -10070,12 +10070,14 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air....@@ -10070,12 +10070,14 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
10070 }10070 }
1007110071
10072 if (rhs_scalar_ty.isAnyFloat()) {10072 if (rhs_scalar_ty.isAnyFloat()) {
10073 // We handle comptime negation here to ensure negative zero is represented in the bits.10073 // We handle float negation here to ensure negative zero is represented in the bits.
10074 if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {10074 if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
10075 if (rhs_val.isUndef()) return sema.addConstUndef(rhs_ty);10075 if (rhs_val.isUndef()) return sema.addConstUndef(rhs_ty);
10076 const target = sema.mod.getTarget();10076 const target = sema.mod.getTarget();
10077 return sema.addConstant(rhs_ty, try rhs_val.floatNeg(rhs_ty, sema.arena, target));10077 return sema.addConstant(rhs_ty, try rhs_val.floatNeg(rhs_ty, sema.arena, target));
10078 }10078 }
10079 try sema.requireRuntimeBlock(block, rhs_src);
10080 return block.addUnOp(.neg, rhs);
10079 }10081 }
1008010082
10081 const lhs = if (rhs_ty.zigTypeTag() == .Vector)10083 const lhs = if (rhs_ty.zigTypeTag() == .Vector)
src/arch/aarch64/CodeGen.zig+2-1
...@@ -582,7 +582,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -582,7 +582,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
582 .floor,582 .floor,
583 .ceil,583 .ceil,
584 .round,584 .round,
585 .trunc_float585 .trunc_float,
586 .neg,
586 => try self.airUnaryMath(inst),587 => try self.airUnaryMath(inst),
587588
588 .add_with_overflow => try self.airOverflow(inst),589 .add_with_overflow => try self.airOverflow(inst),
src/arch/arm/CodeGen.zig+1
...@@ -596,6 +596,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -596,6 +596,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
596 .ceil,596 .ceil,
597 .round,597 .round,
598 .trunc_float,598 .trunc_float,
599 .neg,
599 => try self.airUnaryMath(inst),600 => try self.airUnaryMath(inst),
600601
601 .add_with_overflow => try self.airOverflow(inst),602 .add_with_overflow => try self.airOverflow(inst),
src/arch/riscv64/CodeGen.zig+1
...@@ -516,6 +516,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -516,6 +516,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
516 .ceil,516 .ceil,
517 .round,517 .round,
518 .trunc_float,518 .trunc_float,
519 .neg,
519 => try self.airUnaryMath(inst),520 => try self.airUnaryMath(inst),
520521
521 .add_with_overflow => try self.airAddWithOverflow(inst),522 .add_with_overflow => try self.airAddWithOverflow(inst),
src/arch/sparc64/CodeGen.zig+1
...@@ -529,6 +529,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -529,6 +529,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
529 .ceil,529 .ceil,
530 .round,530 .round,
531 .trunc_float,531 .trunc_float,
532 .neg,
532 => @panic("TODO try self.airUnaryMath(inst)"),533 => @panic("TODO try self.airUnaryMath(inst)"),
533534
534 .add_with_overflow => try self.airAddSubWithOverflow(inst),535 .add_with_overflow => try self.airAddSubWithOverflow(inst),
src/arch/wasm/CodeGen.zig+1
...@@ -1607,6 +1607,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1607,6 +1607,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1607 .log10,1607 .log10,
1608 .fabs,1608 .fabs,
1609 .round,1609 .round,
1610 .neg,
16101611
1611 .cmpxchg_weak,1612 .cmpxchg_weak,
1612 .cmpxchg_strong,1613 .cmpxchg_strong,
src/arch/x86_64/CodeGen.zig+1
...@@ -605,6 +605,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -605,6 +605,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
605 .ceil,605 .ceil,
606 .round,606 .round,
607 .trunc_float,607 .trunc_float,
608 .neg,
608 => try self.airUnaryMath(inst),609 => try self.airUnaryMath(inst),
609610
610 .add_with_overflow => try self.airAddSubShlWithOverflow(inst),611 .add_with_overflow => try self.airAddSubShlWithOverflow(inst),
src/codegen/c.zig+16
...@@ -1755,6 +1755,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO...@@ -1755,6 +1755,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
1755 .mul_sat => try airSatOp(f, inst, "muls_"),1755 .mul_sat => try airSatOp(f, inst, "muls_"),
1756 .shl_sat => try airSatOp(f, inst, "shls_"),1756 .shl_sat => try airSatOp(f, inst, "shls_"),
17571757
1758 .neg => try airNeg(f, inst),
1759
1758 .sqrt,1760 .sqrt,
1759 .sin,1761 .sin,
1760 .cos,1762 .cos,
...@@ -4098,6 +4100,20 @@ fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -4098,6 +4100,20 @@ fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue {
4098 return local;4100 return local;
4099}4101}
41004102
4103fn airNeg(f: *Function, inst: Air.Inst.Index) !CValue {
4104 if (f.liveness.isUnused(inst)) return CValue.none;
4105
4106 const un_op = f.air.instructions.items(.data)[inst].un_op;
4107 const writer = f.object.writer();
4108 const inst_ty = f.air.typeOfIndex(inst);
4109 const operand = try f.resolveInst(un_op);
4110 const local = try f.allocLocal(inst_ty, .Const);
4111 try writer.writeAll("-");
4112 try f.writeCValue(writer, operand);
4113 try writer.writeAll(";\n");
4114 return local;
4115}
4116
4101fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {4117fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {
4102 if (f.liveness.isUnused(inst)) return CValue.none;4118 if (f.liveness.isUnused(inst)) return CValue.none;
4103 const pl_op = f.air.instructions.items(.data)[inst].pl_op;4119 const pl_op = f.air.instructions.items(.data)[inst].pl_op;
src/codegen/llvm.zig+17-3
...@@ -4022,6 +4022,7 @@ pub const FuncGen = struct {...@@ -4022,6 +4022,7 @@ pub const FuncGen = struct {
4022 .ceil => try self.airUnaryOp(inst, .ceil),4022 .ceil => try self.airUnaryOp(inst, .ceil),
4023 .round => try self.airUnaryOp(inst, .round),4023 .round => try self.airUnaryOp(inst, .round),
4024 .trunc_float => try self.airUnaryOp(inst, .trunc),4024 .trunc_float => try self.airUnaryOp(inst, .trunc),
4025 .neg => try self.airUnaryOp(inst, .neg),
40254026
4026 .cmp_eq => try self.airCmp(inst, .eq),4027 .cmp_eq => try self.airCmp(inst, .eq),
4027 .cmp_gt => try self.airCmp(inst, .gt),4028 .cmp_gt => try self.airCmp(inst, .gt),
...@@ -6548,13 +6549,14 @@ pub const FuncGen = struct {...@@ -6548,13 +6549,14 @@ pub const FuncGen = struct {
6548 fabs,6549 fabs,
6549 floor,6550 floor,
6550 fma,6551 fma,
6552 fmax,
6553 fmin,
6554 fmod,
6551 log,6555 log,
6552 log10,6556 log10,
6553 log2,6557 log2,
6554 fmax,
6555 fmin,
6556 mul,6558 mul,
6557 fmod,6559 neg,
6558 round,6560 round,
6559 sin,6561 sin,
6560 sqrt,6562 sqrt,
...@@ -6587,6 +6589,7 @@ pub const FuncGen = struct {...@@ -6587,6 +6589,7 @@ pub const FuncGen = struct {
6587 var fn_name_buf: [64]u8 = undefined;6589 var fn_name_buf: [64]u8 = undefined;
6588 const strat: FloatOpStrat = if (intrinsics_allowed) switch (op) {6590 const strat: FloatOpStrat = if (intrinsics_allowed) switch (op) {
6589 // Some operations are dedicated LLVM instructions, not available as intrinsics6591 // Some operations are dedicated LLVM instructions, not available as intrinsics
6592 .neg => return self.builder.buildFNeg(params[0], ""),
6590 .add => return self.builder.buildFAdd(params[0], params[1], ""),6593 .add => return self.builder.buildFAdd(params[0], params[1], ""),
6591 .sub => return self.builder.buildFSub(params[0], params[1], ""),6594 .sub => return self.builder.buildFSub(params[0], params[1], ""),
6592 .mul => return self.builder.buildFMul(params[0], params[1], ""),6595 .mul => return self.builder.buildFMul(params[0], params[1], ""),
...@@ -6598,6 +6601,17 @@ pub const FuncGen = struct {...@@ -6598,6 +6601,17 @@ pub const FuncGen = struct {
6598 } else b: {6601 } else b: {
6599 const float_bits = scalar_ty.floatBits(target);6602 const float_bits = scalar_ty.floatBits(target);
6600 break :b switch (op) {6603 break :b switch (op) {
6604 .neg => {
6605 // In this case we can generate a softfloat negation by XORing the
6606 // bits with a constant.
6607 const int_llvm_ty = self.dg.context.intType(float_bits);
6608 const one = int_llvm_ty.constInt(1, .False);
6609 const shift_amt = int_llvm_ty.constInt(float_bits - 1, .False);
6610 const sign_mask = one.constShl(shift_amt);
6611 const bitcasted_operand = self.builder.buildBitCast(params[0], int_llvm_ty, "");
6612 const result = self.builder.buildXor(bitcasted_operand, sign_mask, "");
6613 return self.builder.buildBitCast(result, llvm_ty, "");
6614 },
6601 .add, .sub, .div, .mul => FloatOpStrat{6615 .add, .sub, .div, .mul => FloatOpStrat{
6602 .libc = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f3", .{6616 .libc = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f3", .{
6603 @tagName(op), compilerRtFloatAbbrev(float_bits),6617 @tagName(op), compilerRtFloatAbbrev(float_bits),
src/codegen/llvm/bindings.zig+3
...@@ -549,6 +549,9 @@ pub const Builder = opaque {...@@ -549,6 +549,9 @@ pub const Builder = opaque {
549 pub const buildFSub = LLVMBuildFSub;549 pub const buildFSub = LLVMBuildFSub;
550 extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;550 extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
551551
552 pub const buildFNeg = LLVMBuildFNeg;
553 extern fn LLVMBuildFNeg(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value;
554
552 pub const buildSub = LLVMBuildSub;555 pub const buildSub = LLVMBuildSub;
553 extern fn LLVMBuildSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;556 extern fn LLVMBuildSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
554557
src/print_air.zig+1
...@@ -168,6 +168,7 @@ const Writer = struct {...@@ -168,6 +168,7 @@ const Writer = struct {
168 .ceil,168 .ceil,
169 .round,169 .round,
170 .trunc_float,170 .trunc_float,
171 .neg,
171 .cmp_lt_errors_len,172 .cmp_lt_errors_len,
172 .set_err_return_trace,173 .set_err_return_trace,
173 => try w.writeUnOp(s, inst),174 => try w.writeUnOp(s, inst),
src/value.zig+35-15
...@@ -2189,12 +2189,25 @@ pub const Value = extern union {...@@ -2189,12 +2189,25 @@ pub const Value = extern union {
2189 return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0;2189 return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0;
2190 },2190 },
2191 .Float => {2191 .Float => {
2192 const a_nan = a.isNan();2192 switch (ty.floatBits(target)) {
2193 const b_nan = b.isNan();2193 16 => return @bitCast(u16, a.toFloat(f16)) == @bitCast(u16, b.toFloat(f16)),
2194 if (a_nan or b_nan) {2194 32 => return @bitCast(u32, a.toFloat(f32)) == @bitCast(u32, b.toFloat(f32)),
2195 return a_nan and b_nan;2195 64 => return @bitCast(u64, a.toFloat(f64)) == @bitCast(u64, b.toFloat(f64)),
2196 80 => return @bitCast(u80, a.toFloat(f80)) == @bitCast(u80, b.toFloat(f80)),
2197 128 => return @bitCast(u128, a.toFloat(f128)) == @bitCast(u128, b.toFloat(f128)),
2198 else => unreachable,
2196 }2199 }
2197 return order(a, b, target).compare(.eq);2200 },
2201 .ComptimeFloat => {
2202 const a_float = a.toFloat(f128);
2203 const b_float = b.toFloat(f128);
2204
2205 const a_nan = std.math.isNan(a_float);
2206 const b_nan = std.math.isNan(b_float);
2207 if (a_nan != b_nan) return false;
2208 if (std.math.signbit(a_float) != std.math.signbit(b_float)) return false;
2209 if (a_nan) return true;
2210 return a_float == b_float;
2198 },2211 },
2199 .Optional => {2212 .Optional => {
2200 if (a.tag() != .opt_payload and b.tag() == .opt_payload) {2213 if (a.tag() != .opt_payload and b.tag() == .opt_payload) {
...@@ -2231,18 +2244,25 @@ pub const Value = extern union {...@@ -2231,18 +2244,25 @@ pub const Value = extern union {
2231 var buf: ToTypeBuffer = undefined;2244 var buf: ToTypeBuffer = undefined;
2232 return val.toType(&buf).hashWithHasher(hasher, mod);2245 return val.toType(&buf).hashWithHasher(hasher, mod);
2233 },2246 },
2234 .Float, .ComptimeFloat => {2247 .Float => {
2235 // Normalize the float here because this hash must match eql semantics.2248 // For hash/eql purposes, we treat floats as their IEEE integer representation.
2236 // These functions are used for hash maps so we want NaN to equal itself,2249 switch (ty.floatBits(mod.getTarget())) {
2237 // and -0.0 to equal +0.0.2250 16 => std.hash.autoHash(hasher, @bitCast(u16, val.toFloat(f16))),
2251 32 => std.hash.autoHash(hasher, @bitCast(u32, val.toFloat(f32))),
2252 64 => std.hash.autoHash(hasher, @bitCast(u64, val.toFloat(f64))),
2253 80 => std.hash.autoHash(hasher, @bitCast(u80, val.toFloat(f80))),
2254 128 => std.hash.autoHash(hasher, @bitCast(u128, val.toFloat(f128))),
2255 else => unreachable,
2256 }
2257 },
2258 .ComptimeFloat => {
2238 const float = val.toFloat(f128);2259 const float = val.toFloat(f128);
2239 if (std.math.isNan(float)) {2260 const is_nan = std.math.isNan(float);
2240 std.hash.autoHash(hasher, std.math.nan_u128);2261 std.hash.autoHash(hasher, is_nan);
2241 } else if (float == 0.0) {2262 if (!is_nan) {
2242 var normalized_zero: f128 = 0.0;
2243 std.hash.autoHash(hasher, @bitCast(u128, normalized_zero));
2244 } else {
2245 std.hash.autoHash(hasher, @bitCast(u128, float));2263 std.hash.autoHash(hasher, @bitCast(u128, float));
2264 } else {
2265 std.hash.autoHash(hasher, std.math.signbit(float));
2246 }2266 }
2247 },2267 },
2248 .Bool, .Int, .ComptimeInt, .Pointer => switch (val.tag()) {2268 .Bool, .Int, .ComptimeInt, .Pointer => switch (val.tag()) {
test/behavior/floatop.zig+103
...@@ -574,6 +574,7 @@ test "negation f32" {...@@ -574,6 +574,7 @@ test "negation f32" {
574 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO574 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
575 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO575 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
576 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO576 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
577 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
577578
578 const S = struct {579 const S = struct {
579 fn doTheTest() !void {580 fn doTheTest() !void {
...@@ -593,6 +594,8 @@ test "negation f64" {...@@ -593,6 +594,8 @@ test "negation f64" {
593 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO594 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
594 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO595 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
595 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO596 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
597 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
598 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
596599
597 const S = struct {600 const S = struct {
598 fn doTheTest() !void {601 fn doTheTest() !void {
...@@ -707,3 +710,103 @@ test "comptime_float zero divided by zero produces zero" {...@@ -707,3 +710,103 @@ test "comptime_float zero divided by zero produces zero" {
707710
708 try expect((0.0 / 0.0) == 0.0);711 try expect((0.0 / 0.0) == 0.0);
709}712}
713
714test "nan negation f16" {
715 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
716 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
717 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
718 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
719 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
720
721 const nan_comptime = comptime math.nan(f16);
722 const neg_nan_comptime = -nan_comptime;
723
724 var nan_runtime = math.nan(f16);
725 const neg_nan_runtime = -nan_runtime;
726
727 try expect(!math.signbit(nan_runtime));
728 try expect(math.signbit(neg_nan_runtime));
729
730 try expect(!math.signbit(nan_comptime));
731 try expect(math.signbit(neg_nan_comptime));
732}
733
734test "nan negation f32" {
735 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
736 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
737 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
738 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
739 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
740
741 const nan_comptime = comptime math.nan(f32);
742 const neg_nan_comptime = -nan_comptime;
743
744 var nan_runtime = math.nan(f32);
745 const neg_nan_runtime = -nan_runtime;
746
747 try expect(!math.signbit(nan_runtime));
748 try expect(math.signbit(neg_nan_runtime));
749
750 try expect(!math.signbit(nan_comptime));
751 try expect(math.signbit(neg_nan_comptime));
752}
753
754test "nan negation f64" {
755 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
756 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
757 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
758 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
759 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
760
761 const nan_comptime = comptime math.nan(f64);
762 const neg_nan_comptime = -nan_comptime;
763
764 var nan_runtime = math.nan(f64);
765 const neg_nan_runtime = -nan_runtime;
766
767 try expect(!math.signbit(nan_runtime));
768 try expect(math.signbit(neg_nan_runtime));
769
770 try expect(!math.signbit(nan_comptime));
771 try expect(math.signbit(neg_nan_comptime));
772}
773
774test "nan negation f128" {
775 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
776 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
777 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
778 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
779 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
780
781 const nan_comptime = comptime math.nan(f128);
782 const neg_nan_comptime = -nan_comptime;
783
784 var nan_runtime = math.nan(f128);
785 const neg_nan_runtime = -nan_runtime;
786
787 try expect(!math.signbit(nan_runtime));
788 try expect(math.signbit(neg_nan_runtime));
789
790 try expect(!math.signbit(nan_comptime));
791 try expect(math.signbit(neg_nan_comptime));
792}
793
794test "nan negation f80" {
795 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
796 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
797 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
798 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
799 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
800
801 const nan_comptime = comptime math.nan(f80);
802 const neg_nan_comptime = -nan_comptime;
803
804 var nan_runtime = math.nan(f80);
805 const neg_nan_runtime = -nan_runtime;
806
807 try expect(!math.signbit(nan_runtime));
808 try expect(math.signbit(neg_nan_runtime));
809
810 try expect(!math.signbit(nan_comptime));
811 try expect(math.signbit(neg_nan_comptime));
812}