authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-17 11:00:38+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-17 11:00:38+01:00
log4650e5b9fcaa74b724a51458f5cf8952f3c734de
tree3d3b16bb86bff91c115f6778102995ac1d0e40a7
parenta5c922179f99591d20e5b6b203c7e292692e0c28
signaturelock-open Commit is signed but in an unrecognized format.

Sema: clean up cmpNumeric

There is one minor language change here, which is that comparisons of the form `comptime_inf < runtime_f32` have their results comptime-known. This is consistent with comparisons against comptime NaN for instance, which are always comptime known. A corresponding behavior test is added. This fixes a bug with int comparison elision which my previous commit somehow triggered. `Sema.compareIntsOnlyPossibleResult` is much cleaner now!

4 files changed, 126 insertions(+), 116 deletions(-)

src/Sema.zig+60-108
...@@ -33814,11 +33814,11 @@ fn cmpNumeric(...@@ -33814,11 +33814,11 @@ fn cmpNumeric(
33814 const maybe_lhs_val = try sema.resolveValue(lhs);33814 const maybe_lhs_val = try sema.resolveValue(lhs);
33815 const maybe_rhs_val = try sema.resolveValue(rhs);33815 const maybe_rhs_val = try sema.resolveValue(rhs);
3381633816
33817 // If the LHS is const, check if there is a guaranteed result which does not depend on ths RHS.33817 // If the LHS is const, check if there is a guaranteed result which does not depend on ths RHS value.
33818 if (maybe_lhs_val) |lhs_val| {33818 if (maybe_lhs_val) |lhs_val| {
33819 // Result based on comparison exceeding type bounds33819 // Result based on comparison exceeding type bounds
33820 if (!lhs_val.isUndef(zcu) and (lhs_ty.isInt(zcu) or lhs_ty_tag == .comptime_int) and rhs_ty.isInt(zcu)) {33820 if (!lhs_val.isUndef(zcu) and (lhs_ty_tag == .int or lhs_ty_tag == .comptime_int) and rhs_ty.isInt(zcu)) {
33821 if (try sema.compareIntsOnlyPossibleResult(try sema.resolveLazyValue(lhs_val), op, rhs_ty)) |res| {33821 if (try sema.compareIntsOnlyPossibleResult(lhs_val, op, rhs_ty)) |res| {
33822 return if (res) .bool_true else .bool_false;33822 return if (res) .bool_true else .bool_false;
33823 }33823 }
33824 }33824 }
...@@ -33826,13 +33826,20 @@ fn cmpNumeric(...@@ -33826,13 +33826,20 @@ fn cmpNumeric(
33826 if (lhs_val.isNan(zcu)) {33826 if (lhs_val.isNan(zcu)) {
33827 return if (op == .neq) .bool_true else .bool_false;33827 return if (op == .neq) .bool_true else .bool_false;
33828 }33828 }
33829 // Result based on inf comparison to int
33830 if (lhs_val.isInf(zcu) and rhs_ty_tag == .int) return switch (op) {
33831 .neq => .bool_true,
33832 .eq => .bool_false,
33833 .gt, .gte => if (lhs_val.isNegativeInf(zcu)) .bool_false else .bool_true,
33834 .lt, .lte => if (lhs_val.isNegativeInf(zcu)) .bool_true else .bool_false,
33835 };
33829 }33836 }
3383033837
33831 // If the RHS is const, check if there is a guaranteed result which does not depend on ths LHS.33838 // If the RHS is const, check if there is a guaranteed result which does not depend on ths LHS value.
33832 if (maybe_rhs_val) |rhs_val| {33839 if (maybe_rhs_val) |rhs_val| {
33833 // Result based on comparison exceeding type bounds33840 // Result based on comparison exceeding type bounds
33834 if (!rhs_val.isUndef(zcu) and (rhs_ty.isInt(zcu) or rhs_ty_tag == .comptime_int) and lhs_ty.isInt(zcu)) {33841 if (!rhs_val.isUndef(zcu) and (rhs_ty_tag == .int or rhs_ty_tag == .comptime_int) and lhs_ty.isInt(zcu)) {
33835 if (try sema.compareIntsOnlyPossibleResult(try sema.resolveLazyValue(rhs_val), op.reverse(), lhs_ty)) |res| {33842 if (try sema.compareIntsOnlyPossibleResult(rhs_val, op.reverse(), lhs_ty)) |res| {
33836 return if (res) .bool_true else .bool_false;33843 return if (res) .bool_true else .bool_false;
33837 }33844 }
33838 }33845 }
...@@ -33840,6 +33847,13 @@ fn cmpNumeric(...@@ -33840,6 +33847,13 @@ fn cmpNumeric(
33840 if (rhs_val.isNan(zcu)) {33847 if (rhs_val.isNan(zcu)) {
33841 return if (op == .neq) .bool_true else .bool_false;33848 return if (op == .neq) .bool_true else .bool_false;
33842 }33849 }
33850 // Result based on inf comparison to int
33851 if (rhs_val.isInf(zcu) and lhs_ty_tag == .int) return switch (op) {
33852 .neq => .bool_true,
33853 .eq => .bool_false,
33854 .gt, .gte => if (rhs_val.isNegativeInf(zcu)) .bool_true else .bool_false,
33855 .lt, .lte => if (rhs_val.isNegativeInf(zcu)) .bool_false else .bool_true,
33856 };
33843 }33857 }
3384433858
33845 // Any other comparison depends on both values, so the result is undef if either is undef.33859 // Any other comparison depends on both values, so the result is undef if either is undef.
...@@ -33889,17 +33903,18 @@ fn cmpNumeric(...@@ -33889,17 +33903,18 @@ fn cmpNumeric(
33889 const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src);33903 const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src);
33890 return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .optimized), casted_lhs, casted_rhs);33904 return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .optimized), casted_lhs, casted_rhs);
33891 }33905 }
33906
33892 // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.33907 // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
33893 // For mixed signed and unsigned integers, implicit cast both operands to a signed33908 // For mixed signed and unsigned integers, implicit cast both operands to a signed
33894 // integer with + 1 bit.33909 // integer with + 1 bit.
33895 // For mixed floats and integers, extract the integer part from the float, cast that to33910 // For mixed floats and integers, extract the integer part from the float, cast that to
33896 // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,33911 // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
33897 // add/subtract 1.33912 // add/subtract 1.
33898 const lhs_is_signed = if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val|33913 const lhs_is_signed = if (maybe_lhs_val) |lhs_val|
33899 !(try lhs_val.compareAllWithZeroSema(.gte, pt))33914 !(try lhs_val.compareAllWithZeroSema(.gte, pt))
33900 else33915 else
33901 (lhs_ty.isRuntimeFloat() or lhs_ty.isSignedInt(zcu));33916 (lhs_ty.isRuntimeFloat() or lhs_ty.isSignedInt(zcu));
33902 const rhs_is_signed = if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val|33917 const rhs_is_signed = if (maybe_rhs_val) |rhs_val|
33903 !(try rhs_val.compareAllWithZeroSema(.gte, pt))33918 !(try rhs_val.compareAllWithZeroSema(.gte, pt))
33904 else33919 else
33905 (rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt(zcu));33920 (rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt(zcu));
...@@ -33908,19 +33923,8 @@ fn cmpNumeric(...@@ -33908,19 +33923,8 @@ fn cmpNumeric(
33908 var dest_float_type: ?Type = null;33923 var dest_float_type: ?Type = null;
3390933924
33910 var lhs_bits: usize = undefined;33925 var lhs_bits: usize = undefined;
33911 if (try sema.resolveValueResolveLazy(lhs)) |lhs_val| {33926 if (maybe_lhs_val) |unresolved_lhs_val| {
33912 if (lhs_val.isUndef(zcu))33927 const lhs_val = try sema.resolveLazyValue(unresolved_lhs_val);
33913 return pt.undefRef(Type.bool);
33914 if (lhs_val.isNan(zcu)) switch (op) {
33915 .neq => return .bool_true,
33916 else => return .bool_false,
33917 };
33918 if (lhs_val.isInf(zcu)) switch (op) {
33919 .neq => return .bool_true,
33920 .eq => return .bool_false,
33921 .gt, .gte => return if (lhs_val.isNegativeInf(zcu)) .bool_false else .bool_true,
33922 .lt, .lte => return if (lhs_val.isNegativeInf(zcu)) .bool_true else .bool_false,
33923 };
33924 if (!rhs_is_signed) {33928 if (!rhs_is_signed) {
33925 switch (lhs_val.orderAgainstZero(zcu)) {33929 switch (lhs_val.orderAgainstZero(zcu)) {
33926 .gt => {},33930 .gt => {},
...@@ -33966,19 +33970,8 @@ fn cmpNumeric(...@@ -33966,19 +33970,8 @@ fn cmpNumeric(
33966 }33970 }
3396733971
33968 var rhs_bits: usize = undefined;33972 var rhs_bits: usize = undefined;
33969 if (try sema.resolveValueResolveLazy(rhs)) |rhs_val| {33973 if (maybe_rhs_val) |unresolved_rhs_val| {
33970 if (rhs_val.isUndef(zcu))33974 const rhs_val = try sema.resolveLazyValue(unresolved_rhs_val);
33971 return pt.undefRef(Type.bool);
33972 if (rhs_val.isNan(zcu)) switch (op) {
33973 .neq => return .bool_true,
33974 else => return .bool_false,
33975 };
33976 if (rhs_val.isInf(zcu)) switch (op) {
33977 .neq => return .bool_true,
33978 .eq => return .bool_false,
33979 .gt, .gte => return if (rhs_val.isNegativeInf(zcu)) .bool_true else .bool_false,
33980 .lt, .lte => return if (rhs_val.isNegativeInf(zcu)) .bool_false else .bool_true,
33981 };
33982 if (!lhs_is_signed) {33975 if (!lhs_is_signed) {
33983 switch (rhs_val.orderAgainstZero(zcu)) {33976 switch (rhs_val.orderAgainstZero(zcu)) {
33984 .gt => {},33977 .gt => {},
...@@ -34045,90 +34038,49 @@ fn compareIntsOnlyPossibleResult(...@@ -34045,90 +34038,49 @@ fn compareIntsOnlyPossibleResult(
34045 lhs_val: Value,34038 lhs_val: Value,
34046 op: std.math.CompareOperator,34039 op: std.math.CompareOperator,
34047 rhs_ty: Type,34040 rhs_ty: Type,
34048) Allocator.Error!?bool {34041) SemaError!?bool {
34049 const pt = sema.pt;34042 const pt = sema.pt;
34050 const zcu = pt.zcu;34043 const zcu = pt.zcu;
34051 const rhs_info = rhs_ty.intInfo(zcu);
34052 const vs_zero = lhs_val.orderAgainstZeroSema(pt) catch unreachable;
34053 const is_zero = vs_zero == .eq;
34054 const is_negative = vs_zero == .lt;
34055 const is_positive = vs_zero == .gt;
3405634044
34057 // Anything vs. zero-sized type has guaranteed outcome.34045 const min_rhs = try rhs_ty.minInt(pt, rhs_ty);
34058 if (rhs_info.bits == 0) return switch (op) {34046 const max_rhs = try rhs_ty.maxInt(pt, rhs_ty);
34059 .eq, .lte, .gte => is_zero,
34060 .neq, .lt, .gt => !is_zero,
34061 };
3406234047
34063 // Special case for i1, which can only be 0 or -1.34048 if (min_rhs.toIntern() == max_rhs.toIntern()) {
34064 // Zero and positive ints have guaranteed outcome.34049 // RHS is effectively comptime-known.
34065 if (rhs_info.bits == 1 and rhs_info.signedness == .signed) {34050 return try Value.compareHeteroSema(lhs_val, op, min_rhs, pt);
34066 if (is_positive) return switch (op) {
34067 .gt, .gte, .neq => true,
34068 .lt, .lte, .eq => false,
34069 };
34070 if (is_zero) return switch (op) {
34071 .gte => true,
34072 .lt => false,
34073 .gt, .lte, .eq, .neq => null,
34074 };
34075 }34051 }
3407634052
34077 // Negative vs. unsigned has guaranteed outcome.34053 const against_min = try lhs_val.orderAdvanced(min_rhs, .sema, zcu, pt.tid);
34078 if (rhs_info.signedness == .unsigned and is_negative) return switch (op) {34054 const against_max = try lhs_val.orderAdvanced(max_rhs, .sema, zcu, pt.tid);
34079 .eq, .gt, .gte => false,
34080 .neq, .lt, .lte => true,
34081 };
34082
34083 const sign_adj = @intFromBool(!is_negative and rhs_info.signedness == .signed);
34084 const req_bits = lhs_val.intBitCountTwosComp(zcu) + sign_adj;
34085
34086 // No sized type can have more than 65535 bits.
34087 // The RHS type operand is either a runtime value or sized (but undefined) constant.
34088 if (req_bits > 65535) return switch (op) {
34089 .lt, .lte => is_negative,
34090 .gt, .gte => is_positive,
34091 .eq => false,
34092 .neq => true,
34093 };
34094 const fits = req_bits <= rhs_info.bits;
3409534055
34096 // Oversized int has guaranteed outcome.
34097 switch (op) {34056 switch (op) {
34098 .eq => return if (!fits) false else null,34057 .eq => {
34099 .neq => return if (!fits) true else null,34058 if (against_min.compare(.lt)) return false;
34100 .lt, .lte => if (!fits) return is_negative,34059 if (against_max.compare(.gt)) return false;
34101 .gt, .gte => if (!fits) return !is_negative,34060 },
34061 .neq => {
34062 if (against_min.compare(.lt)) return true;
34063 if (against_max.compare(.gt)) return true;
34064 },
34065 .lt => {
34066 if (against_min.compare(.lt)) return true;
34067 if (against_max.compare(.gte)) return false;
34068 },
34069 .gt => {
34070 if (against_max.compare(.gt)) return true;
34071 if (against_min.compare(.lte)) return false;
34072 },
34073 .lte => {
34074 if (against_min.compare(.lte)) return true;
34075 if (against_max.compare(.gt)) return false;
34076 },
34077 .gte => {
34078 if (against_max.compare(.gte)) return true;
34079 if (against_min.compare(.lt)) return false;
34080 },
34102 }34081 }
3410334082
34104 // For any other comparison, we need to know if the LHS value is34083 return null;
34105 // equal to the maximum or minimum possible value of the RHS type.
34106 const is_min, const is_max = edge: {
34107 if (is_zero and rhs_info.signedness == .unsigned) break :edge .{ true, false };
34108
34109 if (req_bits != rhs_info.bits) break :edge .{ false, false };
34110
34111 const ty = try pt.intType(
34112 if (is_negative) .signed else .unsigned,
34113 @intCast(req_bits),
34114 );
34115 const pop_count = lhs_val.popCount(ty, zcu);
34116
34117 if (is_negative) {
34118 break :edge .{ pop_count == 1, false };
34119 } else {
34120 break :edge .{ false, pop_count == req_bits - sign_adj };
34121 }
34122 };
34123
34124 assert(fits);
34125 return switch (op) {
34126 .lt => if (is_max) false else null,
34127 .lte => if (is_min) true else null,
34128 .gt => if (is_min) false else null,
34129 .gte => if (is_max) true else null,
34130 .eq, .neq => unreachable,
34131 };
34132}34084}
3413334085
34134/// Asserts that lhs and rhs types are both vectors.34086/// Asserts that lhs and rhs types are both vectors.
src/Type.zig+2-6
...@@ -3040,8 +3040,7 @@ pub fn minInt(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {...@@ -3040,8 +3040,7 @@ pub fn minInt(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {
3040pub fn minIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {3040pub fn minIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {
3041 const zcu = pt.zcu;3041 const zcu = pt.zcu;
3042 const info = ty.intInfo(zcu);3042 const info = ty.intInfo(zcu);
3043 if (info.signedness == .unsigned) return pt.intValue(dest_ty, 0);3043 if (info.signedness == .unsigned or info.bits == 0) return pt.intValue(dest_ty, 0);
3044 if (info.bits == 0) return pt.intValue(dest_ty, -1);
30453044
3046 if (std.math.cast(u6, info.bits - 1)) |shift| {3045 if (std.math.cast(u6, info.bits - 1)) |shift| {
3047 const n = @as(i64, std.math.minInt(i64)) >> (63 - shift);3046 const n = @as(i64, std.math.minInt(i64)) >> (63 - shift);
...@@ -3072,10 +3071,7 @@ pub fn maxIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {...@@ -3072,10 +3071,7 @@ pub fn maxIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {
3072 const info = ty.intInfo(pt.zcu);3071 const info = ty.intInfo(pt.zcu);
30733072
3074 switch (info.bits) {3073 switch (info.bits) {
3075 0 => return switch (info.signedness) {3074 0 => return pt.intValue(dest_ty, 0),
3076 .signed => try pt.intValue(dest_ty, -1),
3077 .unsigned => try pt.intValue(dest_ty, 0),
3078 },
3079 1 => return switch (info.signedness) {3075 1 => return switch (info.signedness) {
3080 .signed => try pt.intValue(dest_ty, 0),3076 .signed => try pt.intValue(dest_ty, 0),
3081 .unsigned => try pt.intValue(dest_ty, 1),3077 .unsigned => try pt.intValue(dest_ty, 1),
src/Value.zig+2-2
...@@ -191,7 +191,7 @@ pub fn toBigIntAdvanced(...@@ -191,7 +191,7 @@ pub fn toBigIntAdvanced(
191 comptime strat: ResolveStrat,191 comptime strat: ResolveStrat,
192 zcu: *Zcu,192 zcu: *Zcu,
193 tid: strat.Tid(),193 tid: strat.Tid(),
194) Zcu.CompileError!BigIntConst {194) Zcu.SemaError!BigIntConst {
195 const ip = &zcu.intern_pool;195 const ip = &zcu.intern_pool;
196 return switch (val.toIntern()) {196 return switch (val.toIntern()) {
197 .bool_false => BigIntMutable.init(&space.limbs, 0).toConst(),197 .bool_false => BigIntMutable.init(&space.limbs, 0).toConst(),
...@@ -1038,7 +1038,7 @@ pub fn orderAgainstZeroInner(...@@ -1038,7 +1038,7 @@ pub fn orderAgainstZeroInner(
1038 comptime strat: ResolveStrat,1038 comptime strat: ResolveStrat,
1039 zcu: *Zcu,1039 zcu: *Zcu,
1040 tid: strat.Tid(),1040 tid: strat.Tid(),
1041) Zcu.CompileError!std.math.Order {1041) Zcu.SemaError!std.math.Order {
1042 return switch (lhs.toIntern()) {1042 return switch (lhs.toIntern()) {
1043 .bool_false => .eq,1043 .bool_false => .eq,
1044 .bool_true => .gt,1044 .bool_true => .gt,
test/behavior/math.zig+62
...@@ -1729,3 +1729,65 @@ test "@clz works on both vector and scalar inputs" {...@@ -1729,3 +1729,65 @@ test "@clz works on both vector and scalar inputs" {
1729 try std.testing.expectEqual(@as(u6, 31), a);1729 try std.testing.expectEqual(@as(u6, 31), a);
1730 try std.testing.expectEqual([_]u6{ 31, 31, 31, 31 }, b);1730 try std.testing.expectEqual([_]u6{ 31, 31, 31, 31 }, b);
1731}1731}
1732
1733test "runtime comparison to NaN is comptime-known" {
1734 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1735 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1736 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1737 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1738 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1739 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1740 if (builtin.cpu.arch.isArmOrThumb() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234
1741
1742 const S = struct {
1743 fn doTheTest(comptime F: type, x: F) void {
1744 const nan = math.nan(F);
1745 if (!(nan != x)) comptime unreachable;
1746 if (nan == x) comptime unreachable;
1747 if (nan > x) comptime unreachable;
1748 if (nan < x) comptime unreachable;
1749 if (nan >= x) comptime unreachable;
1750 if (nan <= x) comptime unreachable;
1751 }
1752 };
1753
1754 S.doTheTest(f16, 123.0);
1755 S.doTheTest(f32, 123.0);
1756 S.doTheTest(f64, 123.0);
1757 S.doTheTest(f128, 123.0);
1758 comptime S.doTheTest(f16, 123.0);
1759 comptime S.doTheTest(f32, 123.0);
1760 comptime S.doTheTest(f64, 123.0);
1761 comptime S.doTheTest(f128, 123.0);
1762}
1763
1764test "runtime int comparison to inf is comptime-known" {
1765 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1766 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1767 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1768 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1769 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1770 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1771 if (builtin.cpu.arch.isArmOrThumb() and builtin.target.floatAbi() == .soft) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21234
1772
1773 const S = struct {
1774 fn doTheTest(comptime F: type, x: u32) void {
1775 const inf = math.inf(F);
1776 if (!(inf != x)) comptime unreachable;
1777 if (inf == x) comptime unreachable;
1778 if (x > inf) comptime unreachable;
1779 if (x >= inf) comptime unreachable;
1780 if (!(x < inf)) comptime unreachable;
1781 if (!(x <= inf)) comptime unreachable;
1782 }
1783 };
1784
1785 S.doTheTest(f16, 123);
1786 S.doTheTest(f32, 123);
1787 S.doTheTest(f64, 123);
1788 S.doTheTest(f128, 123);
1789 comptime S.doTheTest(f16, 123);
1790 comptime S.doTheTest(f32, 123);
1791 comptime S.doTheTest(f64, 123);
1792 comptime S.doTheTest(f128, 123);
1793}