authorgravatar for codroid@gmail.comStevie Hryciw <codroid@gmail.com> 2022-11-14 15:56:59-08:00
committergravatar for codroid@gmail.comStevie Hryciw <codroid@gmail.com> 2022-12-15 00:56:27-08:00
loge57e835904d54b236cbaf0eedf5b8cea90a94542
tree4201bf0672b2d0e54d8905a26c2a7366772abc1b
parent477038abe9b1c1a2e5074a865bd915d29906a72f

Sema: elide integer comparisons with guaranteed outcomes


4 files changed, 237 insertions(+), 12 deletions(-)

src/Sema.zig+126
......@@ -28460,6 +28460,17 @@ fn cmpNumeric(
2846028460 const runtime_src: LazySrcLoc = src: {
2846128461 if (try sema.resolveMaybeUndefVal(lhs)) |lhs_val| {
2846228462 if (try sema.resolveMaybeUndefVal(rhs)) |rhs_val| {
28463 // Compare ints: const vs. undefined (or vice versa)
28464 if (!lhs_val.isUndef() and (lhs_ty.isInt() or lhs_ty_tag == .ComptimeInt) and rhs_ty.isInt() and rhs_val.isUndef()) {
28465 if (sema.compareIntsOnlyPossibleResult(target, lhs_val, op, rhs_ty)) |res| {
28466 return if (res) Air.Inst.Ref.bool_true else Air.Inst.Ref.bool_false;
28467 }
28468 } else if (!rhs_val.isUndef() and (rhs_ty.isInt() or rhs_ty_tag == .ComptimeInt) and lhs_ty.isInt() and lhs_val.isUndef()) {
28469 if (sema.compareIntsOnlyPossibleResult(target, rhs_val, op.reverse(), lhs_ty)) |res| {
28470 return if (res) Air.Inst.Ref.bool_true else Air.Inst.Ref.bool_false;
28471 }
28472 }
28473
2846328474 if (lhs_val.isUndef() or rhs_val.isUndef()) {
2846428475 return sema.addConstUndef(Type.bool);
2846528476 }
......@@ -28476,9 +28487,23 @@ fn cmpNumeric(
2847628487 return Air.Inst.Ref.bool_false;
2847728488 }
2847828489 } else {
28490 if (!lhs_val.isUndef() and (lhs_ty.isInt() or lhs_ty_tag == .ComptimeInt) and rhs_ty.isInt()) {
28491 // Compare ints: const vs. var
28492 if (sema.compareIntsOnlyPossibleResult(target, lhs_val, op, rhs_ty)) |res| {
28493 return if (res) Air.Inst.Ref.bool_true else Air.Inst.Ref.bool_false;
28494 }
28495 }
2847928496 break :src rhs_src;
2848028497 }
2848128498 } else {
28499 if (try sema.resolveMaybeUndefVal(rhs)) |rhs_val| {
28500 if (!rhs_val.isUndef() and (rhs_ty.isInt() or rhs_ty_tag == .ComptimeInt) and lhs_ty.isInt()) {
28501 // Compare ints: var vs. const
28502 if (sema.compareIntsOnlyPossibleResult(target, rhs_val, op.reverse(), lhs_ty)) |res| {
28503 return if (res) Air.Inst.Ref.bool_true else Air.Inst.Ref.bool_false;
28504 }
28505 }
28506 }
2848228507 break :src lhs_src;
2848328508 }
2848428509 };
......@@ -28667,6 +28692,107 @@ fn cmpNumeric(
2866728692 return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized), casted_lhs, casted_rhs);
2866828693}
2866928694
28695/// Asserts that LHS value is an int or comptime int and not undefined, and that RHS type is an int.
28696/// Given a const LHS and an unknown RHS, attempt to determine whether `op` has a guaranteed result.
28697/// If it cannot be determined, returns null.
28698/// Otherwise returns a bool for the guaranteed comparison operation.
28699fn compareIntsOnlyPossibleResult(sema: *Sema, target: std.Target, lhs_val: Value, op: std.math.CompareOperator, rhs_ty: Type) ?bool {
28700 const rhs_info = rhs_ty.intInfo(target);
28701 const vs_zero = lhs_val.orderAgainstZeroAdvanced(sema) catch unreachable;
28702 const is_zero = vs_zero == .eq;
28703 const is_negative = vs_zero == .lt;
28704 const is_positive = vs_zero == .gt;
28705
28706 // Anything vs. zero-sized type has guaranteed outcome.
28707 if (rhs_info.bits == 0) return switch (op) {
28708 .eq, .lte, .gte => is_zero,
28709 .neq, .lt, .gt => !is_zero,
28710 };
28711
28712 // Special case for i1, which can only be 0 or -1.
28713 // Zero and positive ints have guaranteed outcome.
28714 if (rhs_info.bits == 1 and rhs_info.signedness == .signed) {
28715 if (is_positive) return switch (op) {
28716 .gt, .gte, .neq => true,
28717 .lt, .lte, .eq => false,
28718 };
28719 if (is_zero) return switch (op) {
28720 .gte => true,
28721 .lt => false,
28722 .gt, .lte, .eq, .neq => null,
28723 };
28724 }
28725
28726 // Negative vs. unsigned has guaranteed outcome.
28727 if (rhs_info.signedness == .unsigned and is_negative) return switch (op) {
28728 .eq, .gt, .gte => false,
28729 .neq, .lt, .lte => true,
28730 };
28731
28732 const sign_adj = @boolToInt(!is_negative and rhs_info.signedness == .signed);
28733 const req_bits = lhs_val.intBitCountTwosComp(target) + sign_adj;
28734
28735 // No sized type can have more than 65535 bits.
28736 // The RHS type operand is either a runtime value or sized (but undefined) constant.
28737 if (req_bits > 65535) return switch (op) {
28738 .lt, .lte => is_negative,
28739 .gt, .gte => is_positive,
28740 .eq => false,
28741 .neq => true,
28742 };
28743 const fits = req_bits <= rhs_info.bits;
28744
28745 // Oversized int has guaranteed outcome.
28746 switch (op) {
28747 .eq => return if (!fits) false else null,
28748 .neq => return if (!fits) true else null,
28749 .lt, .lte => if (!fits) return is_negative,
28750 .gt, .gte => if (!fits) return !is_negative,
28751 }
28752
28753 // For any other comparison, we need to know if the LHS value is
28754 // equal to the maximum or minimum possible value of the RHS type.
28755 const edge: struct { min: bool, max: bool } = edge: {
28756 if (is_zero and rhs_info.signedness == .unsigned) break :edge .{
28757 .min = true,
28758 .max = false,
28759 };
28760
28761 if (req_bits != rhs_info.bits) break :edge .{
28762 .min = false,
28763 .max = false,
28764 };
28765
28766 var ty_buffer: Type.Payload.Bits = .{
28767 .base = .{ .tag = if (is_negative) .int_signed else .int_unsigned },
28768 .data = @intCast(u16, req_bits),
28769 };
28770 const ty = Type.initPayload(&ty_buffer.base);
28771 const pop_count = lhs_val.popCount(ty, target);
28772
28773 if (is_negative) {
28774 break :edge .{
28775 .min = pop_count == 1,
28776 .max = false,
28777 };
28778 } else {
28779 break :edge .{
28780 .min = false,
28781 .max = pop_count == req_bits - sign_adj,
28782 };
28783 }
28784 };
28785
28786 assert(fits);
28787 return switch (op) {
28788 .lt => if (edge.max) false else null,
28789 .lte => if (edge.min) true else null,
28790 .gt => if (edge.min) false else null,
28791 .gte => if (edge.max) true else null,
28792 .eq, .neq => unreachable,
28793 };
28794}
28795
2867028796/// Asserts that lhs and rhs types are both vectors.
2867128797fn cmpVector(
2867228798 sema: *Sema,
src/value.zig+2-11
......@@ -1756,17 +1756,8 @@ pub const Value = extern union {
17561756 const info = ty.intInfo(target);
17571757
17581758 var buffer: Value.BigIntSpace = undefined;
1759 const operand_bigint = val.toBigInt(&buffer, target);
1760
1761 var limbs_buffer: [4]std.math.big.Limb = undefined;
1762 var result_bigint = BigIntMutable{
1763 .limbs = &limbs_buffer,
1764 .positive = undefined,
1765 .len = undefined,
1766 };
1767 result_bigint.popCount(operand_bigint, info.bits);
1768
1769 return result_bigint.toConst().to(u64) catch unreachable;
1759 const int = val.toBigInt(&buffer, target);
1760 return @intCast(u64, int.popCount(info.bits));
17701761 },
17711762 }
17721763 }
test/behavior.zig+1-1
......@@ -158,7 +158,7 @@ test {
158158 _ = @import("behavior/incomplete_struct_param_tld.zig");
159159 _ = @import("behavior/inline_switch.zig");
160160 _ = @import("behavior/int128.zig");
161 _ = @import("behavior/int_div.zig");
161 _ = @import("behavior/int_comparison_elision.zig");
162162 _ = @import("behavior/inttoptr.zig");
163163 _ = @import("behavior/ir_block_deps.zig");
164164 _ = @import("behavior/math.zig");
test/behavior/int_comparison_elision.zig created+108
......@@ -0,0 +1,108 @@
1const std = @import("std");
2const minInt = std.math.minInt;
3const maxInt = std.math.maxInt;
4const builtin = @import("builtin");
5
6test "int comparison elision" {
7 testIntEdges(u0);
8 testIntEdges(i0);
9 testIntEdges(u1);
10 testIntEdges(i1);
11 testIntEdges(u4);
12 testIntEdges(i4);
13
14 // TODO: support int types > 128 bits wide in other backends
15 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
16 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
17 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
18 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
19 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
20
21 // TODO: panic: integer overflow with int types > 65528 bits wide
22 // TODO: LLVM generates too many parameters for wasmtime when splitting up int > 64000 bits wide
23 testIntEdges(u64000);
24 testIntEdges(i64000);
25}
26
27// All comparisons in this test have a guaranteed result,
28// so one branch of each 'if' should never be analyzed.
29fn testIntEdges(comptime T: type) void {
30 const min = minInt(T);
31 const max = maxInt(T);
32
33 var runtime_val: T = undefined;
34
35 if (min > runtime_val) @compileError("analyzed impossible branch");
36 if (min <= runtime_val) {} else @compileError("analyzed impossible branch");
37 if (runtime_val < min) @compileError("analyzed impossible branch");
38 if (runtime_val >= min) {} else @compileError("analyzed impossible branch");
39
40 if (min - 1 > runtime_val) @compileError("analyzed impossible branch");
41 if (min - 1 >= runtime_val) @compileError("analyzed impossible branch");
42 if (min - 1 < runtime_val) {} else @compileError("analyzed impossible branch");
43 if (min - 1 <= runtime_val) {} else @compileError("analyzed impossible branch");
44 if (min - 1 == runtime_val) @compileError("analyzed impossible branch");
45 if (min - 1 != runtime_val) {} else @compileError("analyzed impossible branch");
46 if (runtime_val < min - 1) @compileError("analyzed impossible branch");
47 if (runtime_val <= min - 1) @compileError("analyzed impossible branch");
48 if (runtime_val > min - 1) {} else @compileError("analyzed impossible branch");
49 if (runtime_val >= min - 1) {} else @compileError("analyzed impossible branch");
50 if (runtime_val == min - 1) @compileError("analyzed impossible branch");
51 if (runtime_val != min - 1) {} else @compileError("analyzed impossible branch");
52
53 if (max >= runtime_val) {} else @compileError("analyzed impossible branch");
54 if (max < runtime_val) @compileError("analyzed impossible branch");
55 if (runtime_val <= max) {} else @compileError("analyzed impossible branch");
56 if (runtime_val > max) @compileError("analyzed impossible branch");
57
58 if (max + 1 > runtime_val) {} else @compileError("analyzed impossible branch");
59 if (max + 1 >= runtime_val) {} else @compileError("analyzed impossible branch");
60 if (max + 1 < runtime_val) @compileError("analyzed impossible branch");
61 if (max + 1 <= runtime_val) @compileError("analyzed impossible branch");
62 if (max + 1 == runtime_val) @compileError("analyzed impossible branch");
63 if (max + 1 != runtime_val) {} else @compileError("analyzed impossible branch");
64 if (runtime_val < max + 1) {} else @compileError("analyzed impossible branch");
65 if (runtime_val <= max + 1) {} else @compileError("analyzed impossible branch");
66 if (runtime_val > max + 1) @compileError("analyzed impossible branch");
67 if (runtime_val >= max + 1) @compileError("analyzed impossible branch");
68 if (runtime_val == max + 1) @compileError("analyzed impossible branch");
69 if (runtime_val != max + 1) {} else @compileError("analyzed impossible branch");
70
71 const undef_const: T = undefined;
72
73 if (min > undef_const) @compileError("analyzed impossible branch");
74 if (min <= undef_const) {} else @compileError("analyzed impossible branch");
75 if (undef_const < min) @compileError("analyzed impossible branch");
76 if (undef_const >= min) {} else @compileError("analyzed impossible branch");
77
78 if (min - 1 > undef_const) @compileError("analyzed impossible branch");
79 if (min - 1 >= undef_const) @compileError("analyzed impossible branch");
80 if (min - 1 < undef_const) {} else @compileError("analyzed impossible branch");
81 if (min - 1 <= undef_const) {} else @compileError("analyzed impossible branch");
82 if (min - 1 == undef_const) @compileError("analyzed impossible branch");
83 if (min - 1 != undef_const) {} else @compileError("analyzed impossible branch");
84 if (undef_const < min - 1) @compileError("analyzed impossible branch");
85 if (undef_const <= min - 1) @compileError("analyzed impossible branch");
86 if (undef_const > min - 1) {} else @compileError("analyzed impossible branch");
87 if (undef_const >= min - 1) {} else @compileError("analyzed impossible branch");
88 if (undef_const == min - 1) @compileError("analyzed impossible branch");
89 if (undef_const != min - 1) {} else @compileError("analyzed impossible branch");
90
91 if (max >= undef_const) {} else @compileError("analyzed impossible branch");
92 if (max < undef_const) @compileError("analyzed impossible branch");
93 if (undef_const <= max) {} else @compileError("analyzed impossible branch");
94 if (undef_const > max) @compileError("analyzed impossible branch");
95
96 if (max + 1 > undef_const) {} else @compileError("analyzed impossible branch");
97 if (max + 1 >= undef_const) {} else @compileError("analyzed impossible branch");
98 if (max + 1 < undef_const) @compileError("analyzed impossible branch");
99 if (max + 1 <= undef_const) @compileError("analyzed impossible branch");
100 if (max + 1 == undef_const) @compileError("analyzed impossible branch");
101 if (max + 1 != undef_const) {} else @compileError("analyzed impossible branch");
102 if (undef_const < max + 1) {} else @compileError("analyzed impossible branch");
103 if (undef_const <= max + 1) {} else @compileError("analyzed impossible branch");
104 if (undef_const > max + 1) @compileError("analyzed impossible branch");
105 if (undef_const >= max + 1) @compileError("analyzed impossible branch");
106 if (undef_const == max + 1) @compileError("analyzed impossible branch");
107 if (undef_const != max + 1) {} else @compileError("analyzed impossible branch");
108}