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(
3381433814 const maybe_lhs_val = try sema.resolveValue(lhs);
3381533815 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.
3381833818 if (maybe_lhs_val) |lhs_val| {
3381933819 // 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)) {
33821 if (try sema.compareIntsOnlyPossibleResult(try sema.resolveLazyValue(lhs_val), op, rhs_ty)) |res| {
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(lhs_val, op, rhs_ty)) |res| {
3382233822 return if (res) .bool_true else .bool_false;
3382333823 }
3382433824 }
......@@ -33826,13 +33826,20 @@ fn cmpNumeric(
3382633826 if (lhs_val.isNan(zcu)) {
3382733827 return if (op == .neq) .bool_true else .bool_false;
3382833828 }
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 };
3382933836 }
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.
3383233839 if (maybe_rhs_val) |rhs_val| {
3383333840 // 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)) {
33835 if (try sema.compareIntsOnlyPossibleResult(try sema.resolveLazyValue(rhs_val), op.reverse(), lhs_ty)) |res| {
33841 if (!rhs_val.isUndef(zcu) and (rhs_ty_tag == .int or rhs_ty_tag == .comptime_int) and lhs_ty.isInt(zcu)) {
33842 if (try sema.compareIntsOnlyPossibleResult(rhs_val, op.reverse(), lhs_ty)) |res| {
3383633843 return if (res) .bool_true else .bool_false;
3383733844 }
3383833845 }
......@@ -33840,6 +33847,13 @@ fn cmpNumeric(
3384033847 if (rhs_val.isNan(zcu)) {
3384133848 return if (op == .neq) .bool_true else .bool_false;
3384233849 }
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 };
3384333857 }
3384433858
3384533859 // Any other comparison depends on both values, so the result is undef if either is undef.
......@@ -33889,17 +33903,18 @@ fn cmpNumeric(
3388933903 const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src);
3389033904 return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .optimized), casted_lhs, casted_rhs);
3389133905 }
33906
3389233907 // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
3389333908 // For mixed signed and unsigned integers, implicit cast both operands to a signed
3389433909 // integer with + 1 bit.
3389533910 // For mixed floats and integers, extract the integer part from the float, cast that to
3389633911 // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
3389733912 // 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|
3389933914 !(try lhs_val.compareAllWithZeroSema(.gte, pt))
3390033915 else
3390133916 (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|
3390333918 !(try rhs_val.compareAllWithZeroSema(.gte, pt))
3390433919 else
3390533920 (rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt(zcu));
......@@ -33908,19 +33923,8 @@ fn cmpNumeric(
3390833923 var dest_float_type: ?Type = null;
3390933924
3391033925 var lhs_bits: usize = undefined;
33911 if (try sema.resolveValueResolveLazy(lhs)) |lhs_val| {
33912 if (lhs_val.isUndef(zcu))
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 };
33926 if (maybe_lhs_val) |unresolved_lhs_val| {
33927 const lhs_val = try sema.resolveLazyValue(unresolved_lhs_val);
3392433928 if (!rhs_is_signed) {
3392533929 switch (lhs_val.orderAgainstZero(zcu)) {
3392633930 .gt => {},
......@@ -33966,19 +33970,8 @@ fn cmpNumeric(
3396633970 }
3396733971
3396833972 var rhs_bits: usize = undefined;
33969 if (try sema.resolveValueResolveLazy(rhs)) |rhs_val| {
33970 if (rhs_val.isUndef(zcu))
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 };
33973 if (maybe_rhs_val) |unresolved_rhs_val| {
33974 const rhs_val = try sema.resolveLazyValue(unresolved_rhs_val);
3398233975 if (!lhs_is_signed) {
3398333976 switch (rhs_val.orderAgainstZero(zcu)) {
3398433977 .gt => {},
......@@ -34045,90 +34038,49 @@ fn compareIntsOnlyPossibleResult(
3404534038 lhs_val: Value,
3404634039 op: std.math.CompareOperator,
3404734040 rhs_ty: Type,
34048) Allocator.Error!?bool {
34041) SemaError!?bool {
3404934042 const pt = sema.pt;
3405034043 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.
34058 if (rhs_info.bits == 0) return switch (op) {
34059 .eq, .lte, .gte => is_zero,
34060 .neq, .lt, .gt => !is_zero,
34061 };
34045 const min_rhs = try rhs_ty.minInt(pt, rhs_ty);
34046 const max_rhs = try rhs_ty.maxInt(pt, rhs_ty);
3406234047
34063 // Special case for i1, which can only be 0 or -1.
34064 // Zero and positive ints have guaranteed outcome.
34065 if (rhs_info.bits == 1 and rhs_info.signedness == .signed) {
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 };
34048 if (min_rhs.toIntern() == max_rhs.toIntern()) {
34049 // RHS is effectively comptime-known.
34050 return try Value.compareHeteroSema(lhs_val, op, min_rhs, pt);
3407534051 }
3407634052
34077 // Negative vs. unsigned has guaranteed outcome.
34078 if (rhs_info.signedness == .unsigned and is_negative) return switch (op) {
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;
34053 const against_min = try lhs_val.orderAdvanced(min_rhs, .sema, zcu, pt.tid);
34054 const against_max = try lhs_val.orderAdvanced(max_rhs, .sema, zcu, pt.tid);
3409534055
34096 // Oversized int has guaranteed outcome.
3409734056 switch (op) {
34098 .eq => return if (!fits) false else null,
34099 .neq => return if (!fits) true else null,
34100 .lt, .lte => if (!fits) return is_negative,
34101 .gt, .gte => if (!fits) return !is_negative,
34057 .eq => {
34058 if (against_min.compare(.lt)) return false;
34059 if (against_max.compare(.gt)) return false;
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 },
3410234081 }
3410334082
34104 // For any other comparison, we need to know if the LHS value is
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 };
34083 return null;
3413234084}
3413334085
3413434086/// 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 {
30403040pub fn minIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value {
30413041 const zcu = pt.zcu;
30423042 const info = ty.intInfo(zcu);
3043 if (info.signedness == .unsigned) return pt.intValue(dest_ty, 0);
3044 if (info.bits == 0) return pt.intValue(dest_ty, -1);
3043 if (info.signedness == .unsigned or info.bits == 0) return pt.intValue(dest_ty, 0);
30453044
30463045 if (std.math.cast(u6, info.bits - 1)) |shift| {
30473046 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 {
30723071 const info = ty.intInfo(pt.zcu);
30733072
30743073 switch (info.bits) {
3075 0 => return switch (info.signedness) {
3076 .signed => try pt.intValue(dest_ty, -1),
3077 .unsigned => try pt.intValue(dest_ty, 0),
3078 },
3074 0 => return pt.intValue(dest_ty, 0),
30793075 1 => return switch (info.signedness) {
30803076 .signed => try pt.intValue(dest_ty, 0),
30813077 .unsigned => try pt.intValue(dest_ty, 1),
src/Value.zig+2-2
......@@ -191,7 +191,7 @@ pub fn toBigIntAdvanced(
191191 comptime strat: ResolveStrat,
192192 zcu: *Zcu,
193193 tid: strat.Tid(),
194) Zcu.CompileError!BigIntConst {
194) Zcu.SemaError!BigIntConst {
195195 const ip = &zcu.intern_pool;
196196 return switch (val.toIntern()) {
197197 .bool_false => BigIntMutable.init(&space.limbs, 0).toConst(),
......@@ -1038,7 +1038,7 @@ pub fn orderAgainstZeroInner(
10381038 comptime strat: ResolveStrat,
10391039 zcu: *Zcu,
10401040 tid: strat.Tid(),
1041) Zcu.CompileError!std.math.Order {
1041) Zcu.SemaError!std.math.Order {
10421042 return switch (lhs.toIntern()) {
10431043 .bool_false => .eq,
10441044 .bool_true => .gt,
test/behavior/math.zig+62
......@@ -1729,3 +1729,65 @@ test "@clz works on both vector and scalar inputs" {
17291729 try std.testing.expectEqual(@as(u6, 31), a);
17301730 try std.testing.expectEqual([_]u6{ 31, 31, 31, 31 }, b);
17311731}
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}