From 981f84157ce9e37cdc7ea96ea736808b1273142e Mon Sep 17 00:00:00 2001 From: kcbanner Date: Wed, 6 Nov 2024 19:33:52 -0500 Subject: [PATCH 1/4] Value: fix comparison of NaN in compareHeteroAdvanaced Sema: fix equality comparison of signed zeroes and NaN in compareScalar tests: add test coverage for vector float comparisons --- src/Sema.zig | 5 +++++ src/Value.zig | 2 ++ test/behavior/floatop.zig | 11 ++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Sema.zig b/src/Sema.zig index 9e729a17ea..5982024d89 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -38137,6 +38137,11 @@ fn compareScalar( const pt = sema.pt; const coerced_lhs = try pt.getCoerced(lhs, ty); const coerced_rhs = try pt.getCoerced(rhs, ty); + + // Equality comparisons of signed zero and NaN need to use floating point semantics + if (coerced_lhs.isFloat(pt.zcu) or coerced_rhs.isFloat(pt.zcu)) + return Value.compareHeteroSema(coerced_lhs, op, coerced_rhs, pt); + switch (op) { .eq => return sema.valuesEqual(coerced_lhs, coerced_rhs, ty), .neq => return !(try sema.valuesEqual(coerced_lhs, coerced_rhs, ty)), diff --git a/src/Value.zig b/src/Value.zig index be2c73c3e9..40e5331c4e 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -1132,6 +1132,8 @@ pub fn compareHeteroAdvanced( else => {}, } } + + if (lhs.isNan(zcu) or rhs.isNan(zcu)) return op == .neq; return (try orderAdvanced(lhs, rhs, strat, zcu, tid)).compare(op); } diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index f2fa2f4b0a..0eb1bd2d93 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -194,7 +194,7 @@ fn testCmp(comptime T: type) !void { try expect(x <= 2.0); } - @setEvalBranchQuota(2_000); + @setEvalBranchQuota(4_000); var edges = [_]T{ -math.inf(T), -math.floatMax(T), @@ -210,6 +210,7 @@ fn testCmp(comptime T: type) !void { }; _ = &edges; for (edges, 0..) |rhs, rhs_i| { + const rhs_v: @Vector(4, T) = @splat(rhs); for (edges, 0..) |lhs, lhs_i| { const no_nan = lhs_i != 5 and rhs_i != 5; const lhs_order = if (lhs_i < 5) lhs_i else lhs_i - 2; @@ -220,6 +221,14 @@ fn testCmp(comptime T: type) !void { try expect((lhs > rhs) == (no_nan and lhs_order > rhs_order)); try expect((lhs <= rhs) == (no_nan and lhs_order <= rhs_order)); try expect((lhs >= rhs) == (no_nan and lhs_order >= rhs_order)); + + const lhs_v: @Vector(4, T) = @splat(lhs); + try expect(@reduce(.And, (lhs_v == rhs_v)) == (no_nan and lhs_order == rhs_order)); + try expect(@reduce(.And, (lhs_v != rhs_v)) == !(no_nan and lhs_order == rhs_order)); + try expect(@reduce(.And, (lhs_v < rhs_v)) == (no_nan and lhs_order < rhs_order)); + try expect(@reduce(.And, (lhs_v > rhs_v)) == (no_nan and lhs_order > rhs_order)); + try expect(@reduce(.And, (lhs_v <= rhs_v)) == (no_nan and lhs_order <= rhs_order)); + try expect(@reduce(.And, (lhs_v >= rhs_v)) == (no_nan and lhs_order >= rhs_order)); } } } From 71d0d4bbfe4cad69afa4d6ca2262f81e6b528018 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Wed, 6 Nov 2024 20:38:20 -0500 Subject: [PATCH 2/4] test: separate out float vector tests and skip them on unsupported backends --- test/behavior/floatop.zig | 107 ++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 11 deletions(-) diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 0eb1bd2d93..de7d3fd127 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -132,13 +132,19 @@ test "cmp f16" { try comptime testCmp(f16); } -test "cmp f32/f64" { +test "cmp f32" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; if (builtin.cpu.arch.isArm() and builtin.target.abi.float() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 try testCmp(f32); try comptime testCmp(f32); +} + +test "cmp f64" { + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; + if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + try testCmp(f64); try comptime testCmp(f64); } @@ -194,6 +200,92 @@ fn testCmp(comptime T: type) !void { try expect(x <= 2.0); } + @setEvalBranchQuota(2_000); + var edges = [_]T{ + -math.inf(T), + -math.floatMax(T), + -math.floatMin(T), + -math.floatTrueMin(T), + -0.0, + math.nan(T), + 0.0, + math.floatTrueMin(T), + math.floatMin(T), + math.floatMax(T), + math.inf(T), + }; + _ = &edges; + for (edges, 0..) |rhs, rhs_i| { + for (edges, 0..) |lhs, lhs_i| { + const no_nan = lhs_i != 5 and rhs_i != 5; + const lhs_order = if (lhs_i < 5) lhs_i else lhs_i - 2; + const rhs_order = if (rhs_i < 5) rhs_i else rhs_i - 2; + try expect((lhs == rhs) == (no_nan and lhs_order == rhs_order)); + try expect((lhs != rhs) == !(no_nan and lhs_order == rhs_order)); + try expect((lhs < rhs) == (no_nan and lhs_order < rhs_order)); + try expect((lhs > rhs) == (no_nan and lhs_order > rhs_order)); + try expect((lhs <= rhs) == (no_nan and lhs_order <= rhs_order)); + try expect((lhs >= rhs) == (no_nan and lhs_order >= rhs_order)); + } + } +} + +test "vector cmp f16" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + + try testCmpVector(f16); + try comptime testCmpVector(f16); +} + +test "vector cmp f32" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + + try testCmpVector(f32); + try comptime testCmpVector(f32); +} + +test "vector cmp f64" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + + try testCmpVector(f64); + try comptime testCmpVector(f64); +} + +test "vector cmp f128" { + if (builtin.zig_backend == .stage2_wasm) 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 and builtin.cpu.arch.isArm()) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + + try testCmpVector(f128); + try comptime testCmpVector(f128); +} + +test "vector cmp f80/c_longdouble" { + if (true) return error.SkipZigTest; + + try testCmpVector(f80); + try comptime testCmpVector(f80); + try testCmpVector(c_longdouble); + try comptime testCmpVector(c_longdouble); +} +fn testCmpVector(comptime T: type) !void { @setEvalBranchQuota(4_000); var edges = [_]T{ -math.inf(T), @@ -210,19 +302,12 @@ fn testCmp(comptime T: type) !void { }; _ = &edges; for (edges, 0..) |rhs, rhs_i| { - const rhs_v: @Vector(4, T) = @splat(rhs); + const rhs_v: @Vector(4, T) = .{ rhs, rhs, rhs, rhs }; for (edges, 0..) |lhs, lhs_i| { const no_nan = lhs_i != 5 and rhs_i != 5; const lhs_order = if (lhs_i < 5) lhs_i else lhs_i - 2; const rhs_order = if (rhs_i < 5) rhs_i else rhs_i - 2; - try expect((lhs == rhs) == (no_nan and lhs_order == rhs_order)); - try expect((lhs != rhs) == !(no_nan and lhs_order == rhs_order)); - try expect((lhs < rhs) == (no_nan and lhs_order < rhs_order)); - try expect((lhs > rhs) == (no_nan and lhs_order > rhs_order)); - try expect((lhs <= rhs) == (no_nan and lhs_order <= rhs_order)); - try expect((lhs >= rhs) == (no_nan and lhs_order >= rhs_order)); - - const lhs_v: @Vector(4, T) = @splat(lhs); + const lhs_v: @Vector(4, T) = .{ lhs, lhs, lhs, lhs }; try expect(@reduce(.And, (lhs_v == rhs_v)) == (no_nan and lhs_order == rhs_order)); try expect(@reduce(.And, (lhs_v != rhs_v)) == !(no_nan and lhs_order == rhs_order)); try expect(@reduce(.And, (lhs_v < rhs_v)) == (no_nan and lhs_order < rhs_order)); From 8eefc4c5c2553648422c6d6e4f35021baf2c9683 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Wed, 6 Nov 2024 23:58:07 -0500 Subject: [PATCH 3/4] test: skip the float vector cmp tests on failing targets --- test/behavior/floatop.zig | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index de7d3fd127..c09c2cc4cd 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -135,6 +135,7 @@ test "cmp f16" { test "cmp f32" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.cpu.arch.isArm() and builtin.target.abi.float() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; try testCmp(f32); try comptime testCmp(f32); @@ -143,7 +144,7 @@ test "cmp f32" { test "cmp f64" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; - if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + if (builtin.cpu.arch.isArm() and builtin.target.abi.float() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 try testCmp(f64); try comptime testCmp(f64); @@ -236,7 +237,8 @@ test "vector cmp f16" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + if (builtin.cpu.arch.isArm()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; try testCmpVector(f16); try comptime testCmpVector(f16); @@ -247,7 +249,8 @@ test "vector cmp f32" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + if (builtin.cpu.arch.isArm()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; try testCmpVector(f32); try comptime testCmpVector(f32); @@ -258,7 +261,8 @@ test "vector cmp f64" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.cpu.arch.isArm() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234 + if (builtin.cpu.arch.isArm()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; try testCmpVector(f64); try comptime testCmpVector(f64); @@ -272,6 +276,8 @@ test "vector cmp f128" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + if (builtin.cpu.arch.isArm()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; try testCmpVector(f128); try comptime testCmpVector(f128); @@ -286,7 +292,6 @@ test "vector cmp f80/c_longdouble" { try comptime testCmpVector(c_longdouble); } fn testCmpVector(comptime T: type) !void { - @setEvalBranchQuota(4_000); var edges = [_]T{ -math.inf(T), -math.floatMax(T), From 144d69b571601612f3ec84f038231e924db10ca1 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 4 Mar 2025 23:28:49 -0500 Subject: [PATCH 4/4] test: add comptime memoization tests for bit-for-bit float equality --- test/behavior/floatop.zig | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index c09c2cc4cd..4b11f61f19 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -1802,3 +1802,33 @@ test "optimized float mode" { try expect(S.optimized(small) == small); try expect(S.strict(small) == tiny); } + +fn MakeType(comptime x: anytype) type { + return struct { + fn get() @TypeOf(x) { + return x; + } + }; +} + +const nan_a: f32 = @bitCast(@as(u32, 0xffc00000)); +const nan_b: f32 = @bitCast(@as(u32, 0xffe00000)); + +fn testMemoization() !void { + try expect(MakeType(nan_a) == MakeType(nan_a)); + try expect(MakeType(nan_b) == MakeType(nan_b)); + try expect(MakeType(nan_a) != MakeType(nan_b)); +} + +fn testVectorMemoization(comptime T: type) !void { + const nan_a_v: T = @splat(nan_a); + const nan_b_v: T = @splat(nan_b); + try expect(MakeType(nan_a_v) == MakeType(nan_a_v)); + try expect(MakeType(nan_b_v) == MakeType(nan_b_v)); + try expect(MakeType(nan_a_v) != MakeType(nan_b_v)); +} + +test "comptime calls are only memoized when float arguments are bit-for-bit equal" { + try comptime testMemoization(); + try comptime testVectorMemoization(@Vector(4, f32)); +}