Skip to content

Commit 07044e2

Browse files
alexreinkingAndrew Adams
andauthored
Move existing fuzzers to test/fuzz (#9053)
* Move lossless_cast fuzzing to test/fuzz * Move widening_lerp to test/fuzz * Move random_expr_generator.h and simplify fuzzing to test/fuzz * Integrate simplify and random_expr_generator into fuzzing framework * Replace LimitDepth visitor with simplify_at_depth * A little bounds.cpp modernization -------- This PR also fixes several simplifier bugs that were found during development: * Propagate info in Simplify_Cast case. * Fix accidentally wraparound in cast constant folding * Fix idempotence issues for uint64 simplifications * Infer more aggressive bounds in UIntImm visitor * Logic fixes for division and cast bounds * Respect old min_defined when preserving min Co-authored-by: Andrew Adams <anadams@adobe.com>
1 parent db9fcd8 commit 07044e2

22 files changed

Lines changed: 882 additions & 970 deletions

src/Simplify_Add.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,16 @@ Expr Simplify::visit(const Add *op, ExprInfo *info) {
2525

2626
if (rewrite(IRMatcher::Overflow() + x, a) ||
2727
rewrite(x + IRMatcher::Overflow(), b) ||
28-
rewrite(x + 0, x) ||
29-
rewrite(0 + x, x)) {
28+
rewrite(x + 0, a) ||
29+
rewrite(0 + x, b)) {
30+
if (info) {
31+
if (rewrite.result.same_as(a)) {
32+
info->intersect(a_info);
33+
} else {
34+
internal_assert(rewrite.result.same_as(b));
35+
info->intersect(b_info);
36+
}
37+
}
3038
return rewrite.result;
3139
}
3240

src/Simplify_Call.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,11 @@ Expr Simplify::visit(const Call *op, ExprInfo *info) {
282282
// pattern minus x. We get more information that way than just
283283
// counting the leading zeros or ones.
284284
Expr e = mutate(make_const(op->type, (int64_t)(-1), nullptr) - a, info);
285-
// If the result of this happens to be a constant, we may as well
286-
// return it. This is redundant with the constant folding below, but
287-
// the constant folding below still needs to happen when info is
288-
// nullptr.
285+
// If the result of this happens to fold to a constant, we may as
286+
// well return it immediately. This only happens if a is a constant
287+
// uint or int, in which case the logic below would produce the
288+
// exact same constant Expr with the exact same info as we're
289+
// already holding.
289290
if (info->bounds.is_single_point()) {
290291
return e;
291292
}

src/Simplify_Cast.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,23 @@ Expr Simplify::visit(const Cast *op, ExprInfo *info) {
1717
// know.
1818
*info = ExprInfo{};
1919
} else {
20+
int64_t old_min = value_info.bounds.min;
21+
bool old_min_defined = value_info.bounds.min_defined;
2022
value_info.cast_to(op->type);
23+
if (op->type.is_uint() && op->type.bits() == 64 && old_min_defined && old_min > 0) {
24+
// It's impossible for a cast *to* a uint64 in Halide to lower the
25+
// min. Casts to uint64_t don't overflow for any source type.
26+
value_info.bounds.min_defined = true;
27+
value_info.bounds.min = old_min;
28+
}
2129
value_info.trim_bounds_using_alignment();
2230
if (info) {
2331
*info = value_info;
2432
}
2533
// It's possible we just reduced to a constant. E.g. if we cast an
2634
// even number to uint1 we get zero.
2735
if (value_info.bounds.is_single_point()) {
28-
return make_const(op->type, value_info.bounds.min, nullptr);
36+
return make_const(op->type, value_info.bounds.min, info);
2937
}
3038
}
3139

src/Simplify_Div.cpp

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,44 @@ Expr Simplify::visit(const Div *op, ExprInfo *info) {
88
Expr a = mutate(op->a, &a_info);
99
Expr b = mutate(op->b, &b_info);
1010

11-
if (info) {
12-
if (op->type.is_int_or_uint()) {
13-
// ConstantInterval division is integer division, so we can't use
14-
// this code path for floats.
15-
info->bounds = a_info.bounds / b_info.bounds;
16-
info->alignment = a_info.alignment / b_info.alignment;
17-
info->cast_to(op->type);
18-
info->trim_bounds_using_alignment();
19-
20-
// Bounded numerator divided by constantish bounded denominator can
21-
// sometimes collapse things to a constant at this point. This
22-
// mostly happens when the denominator is a constant and the
23-
// numerator span is small (e.g. [23, 29]/10 = 2), but there are
24-
// also cases with a bounded denominator (e.g. [5, 7]/[4, 5] = 1).
25-
if (info->bounds.is_single_point()) {
26-
if (op->type.can_represent(info->bounds.min)) {
27-
return make_const(op->type, info->bounds.min, nullptr);
28-
} else {
29-
// Even though this is 'no-overflow-int', if the result
30-
// we calculate can't fit into the destination type,
31-
// we're better off returning an overflow condition than
32-
// a known-wrong value. (Note that no_overflow_int() should
33-
// only be true for signed integers.)
34-
internal_assert(no_overflow_int(op->type)) << op->type << " " << info->bounds;
35-
clear_expr_info(info);
36-
return make_signed_integer_overflow(op->type);
37-
}
11+
ExprInfo div_info;
12+
13+
if (op->type.is_int_or_uint()) {
14+
// ConstantInterval division is integer division, so we can't use
15+
// this code path for floats.
16+
div_info.bounds = a_info.bounds / b_info.bounds;
17+
div_info.alignment = a_info.alignment / b_info.alignment;
18+
div_info.cast_to(op->type);
19+
div_info.trim_bounds_using_alignment();
20+
21+
// Bounded numerator divided by constantish bounded denominator can
22+
// sometimes collapse things to a constant at this point. This
23+
// mostly happens when the denominator is a constant and the
24+
// numerator span is small (e.g. [23, 29]/10 = 2), but there are
25+
// also cases with a bounded denominator (e.g. [5, 7]/[4, 5] = 1).
26+
if (div_info.bounds.is_single_point()) {
27+
if (op->type.can_represent(div_info.bounds.min)) {
28+
return make_const(op->type, div_info.bounds.min, info);
29+
} else {
30+
// Even though this is 'no-overflow-int', if the result
31+
// we calculate can't fit into the destination type,
32+
// we're better off returning an overflow condition than
33+
// a known-wrong value. (Note that no_overflow_int() should
34+
// only be true for signed integers.)
35+
internal_assert(no_overflow_int(op->type)) << op->type << " " << div_info.bounds;
36+
clear_expr_info(info);
37+
return make_signed_integer_overflow(op->type);
3838
}
39-
} else {
40-
// TODO: Tracking constant integer bounds of floating point values
41-
// isn't so useful right now, but if we want integer bounds for
42-
// floating point division later, here's the place to put it.
43-
clear_expr_info(info);
4439
}
40+
} else {
41+
// TODO: Tracking constant integer bounds of floating point values isn't
42+
// so useful right now, but if we want integer bounds for floating point
43+
// division later, here's the place to put it. Just leave div_info empty
44+
// for now (i.e. nothing is known).
45+
}
46+
47+
if (info) {
48+
*info = div_info;
4549
}
4650

4751
bool denominator_non_zero =
@@ -55,9 +59,17 @@ Expr Simplify::visit(const Div *op, ExprInfo *info) {
5559

5660
if (rewrite(IRMatcher::Overflow() / x, a) ||
5761
rewrite(x / IRMatcher::Overflow(), b) ||
58-
rewrite(x / 1, x) ||
59-
rewrite(0 / x, 0) ||
62+
rewrite(x / 1, a) ||
63+
rewrite(0 / x, a) ||
6064
false) {
65+
if (info) {
66+
if (rewrite.result.same_as(a)) {
67+
info->intersect(a_info);
68+
} else {
69+
internal_assert(rewrite.result.same_as(b));
70+
info->intersect(b_info);
71+
}
72+
}
6173
return rewrite.result;
6274
}
6375

src/Simplify_Exprs.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ Expr Simplify::visit(const IntImm *op, ExprInfo *info) {
1919
}
2020

2121
Expr Simplify::visit(const UIntImm *op, ExprInfo *info) {
22-
if (info && Int(64).can_represent(op->value)) {
22+
if (info) {
23+
// Pretend it's an int constant that has been cast to uint.
2324
int64_t v = (int64_t)(op->value);
2425
info->bounds = ConstantInterval::single_point(v);
2526
info->alignment = ModulusRemainder(0, v);
27+
// If it's not representable as an int64, this will wrap the alignment appropriately:
2628
info->cast_to(op->type);
29+
// Be as informative as we can with bounds for out-of-range uint64s
30+
if ((int64_t)op->value < 0) {
31+
info->bounds = ConstantInterval::bounded_below(INT64_MAX);
32+
}
2733
} else {
2834
clear_expr_info(info);
2935
}

src/Simplify_Internal.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,15 @@ class Simplify : public VariadicVisitor<Simplify, Expr, Stmt> {
124124
}
125125
}
126126

127-
// Truncate the bounds to the new type.
127+
// For UInt64 constants, the remainder might not be representable as an int64
128+
if (t.bits() == 64 && t.is_uint() &&
129+
alignment.modulus == 0 && alignment.remainder < 0) {
130+
// Forget the leading two bits to get a representable modulus
131+
// and remainder.
132+
alignment.modulus = (int64_t)1 << 62;
133+
alignment.remainder = alignment.remainder & (alignment.modulus - 1);
134+
}
135+
128136
bounds.cast_to(t);
129137
}
130138

@@ -241,6 +249,10 @@ class Simplify : public VariadicVisitor<Simplify, Expr, Stmt> {
241249
// We never want to return make_const anything in the simplifier without
242250
// also setting the ExprInfo, so shadow the global make_const.
243251
Expr make_const(const Type &t, int64_t c, ExprInfo *info) {
252+
if (t.is_uint() && c < 0) {
253+
// Wrap it around
254+
return make_const(t, (uint64_t)c, info);
255+
}
244256
c = normalize_constant(t, c);
245257
set_expr_info_to_constant(info, c);
246258
return Halide::Internal::make_const(t, c);

src/Simplify_Max.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ Expr Simplify::visit(const Max *op, ExprInfo *info) {
2121
if (max_info.bounds.is_single_point()) {
2222
// This is possible when, for example, the largest number in the type
2323
// that satisfies the alignment of the left-hand-side is smaller than
24-
// the min value of the right-hand-side.
25-
return make_const(op->type, max_info.bounds.min, nullptr);
24+
// the min value of the right-hand-side. Reinferring the info can
25+
// potentially give us something tighter than what was computed above if
26+
// it's a large uint64.
27+
return make_const(op->type, max_info.bounds.min, info);
2628
}
2729

2830
auto strip_likely = [](const Expr &e) {
@@ -65,10 +67,10 @@ Expr Simplify::visit(const Max *op, ExprInfo *info) {
6567
return rewrite.result;
6668
}
6769

70+
// Cases where one side dominates. All of these must reduce to a or b in the
71+
// RHS for ExprInfo to update correctly.
6872
if (EVAL_IN_LAMBDA //
6973
(rewrite(max(x, x), a) ||
70-
rewrite(max(c0, c1), fold(max(c0, c1))) ||
71-
// Cases where one side dominates:
7274
rewrite(max(x, c0), b, is_max_value(c0)) ||
7375
rewrite(max(x, c0), a, is_min_value(c0)) ||
7476
rewrite(max((x / c0) * c0, x), b, c0 > 0) ||
@@ -148,16 +150,17 @@ Expr Simplify::visit(const Max *op, ExprInfo *info) {
148150
// than just applying max to two constant intervals.
149151
if (rewrite.result.same_as(a)) {
150152
info->intersect(a_info);
151-
} else if (rewrite.result.same_as(b)) {
153+
} else {
154+
internal_assert(rewrite.result.same_as(b));
152155
info->intersect(b_info);
153156
}
154157
}
155-
156158
return rewrite.result;
157159
}
158160

159161
if (EVAL_IN_LAMBDA //
160-
(rewrite(max(max(x, c0), c1), max(x, fold(max(c0, c1)))) ||
162+
(rewrite(max(c0, c1), fold(max(c0, c1))) ||
163+
rewrite(max(max(x, c0), c1), max(x, fold(max(c0, c1)))) ||
161164
rewrite(max(max(x, c0), y), max(max(x, y), c0)) ||
162165
rewrite(max(max(x, y), max(x, z)), max(max(y, z), x)) ||
163166
rewrite(max(max(y, x), max(x, z)), max(max(y, z), x)) ||

src/Simplify_Min.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Expr Simplify::visit(const Min *op, ExprInfo *info) {
2222
// This is possible when, for example, the smallest number in the type
2323
// that satisfies the alignment of the left-hand-side is greater than
2424
// the max value of the right-hand-side.
25-
return make_const(op->type, min_info.bounds.min, nullptr);
25+
return make_const(op->type, min_info.bounds.min, info);
2626
}
2727

2828
// Early out when the bounds tells us one side or the other is smaller
@@ -66,10 +66,10 @@ Expr Simplify::visit(const Min *op, ExprInfo *info) {
6666
return rewrite.result;
6767
}
6868

69+
// Cases where one side dominates. All of these must reduce to a or b in the
70+
// RHS for ExprInfo to update correctly.
6971
if (EVAL_IN_LAMBDA //
7072
(rewrite(min(x, x), a) ||
71-
rewrite(min(c0, c1), fold(min(c0, c1))) ||
72-
// Cases where one side dominates:
7373
rewrite(min(x, c0), b, is_min_value(c0)) ||
7474
rewrite(min(x, c0), a, is_max_value(c0)) ||
7575
rewrite(min((x / c0) * c0, x), a, c0 > 0) ||
@@ -148,15 +148,17 @@ Expr Simplify::visit(const Min *op, ExprInfo *info) {
148148
if (info) {
149149
if (rewrite.result.same_as(a)) {
150150
info->intersect(a_info);
151-
} else if (rewrite.result.same_as(b)) {
151+
} else {
152+
internal_assert(rewrite.result.same_as(b));
152153
info->intersect(b_info);
153154
}
154155
}
155156
return rewrite.result;
156157
}
157158

158159
if (EVAL_IN_LAMBDA //
159-
(rewrite(min(min(x, c0), c1), min(x, fold(min(c0, c1)))) ||
160+
(rewrite(min(c0, c1), fold(min(c0, c1))) ||
161+
rewrite(min(min(x, c0), c1), min(x, fold(min(c0, c1)))) ||
160162
rewrite(min(min(x, c0), y), min(min(x, y), c0)) ||
161163
rewrite(min(min(x, y), min(x, z)), min(min(y, z), x)) ||
162164
rewrite(min(min(y, x), min(x, z)), min(min(y, z), x)) ||

src/Simplify_Mul.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,18 @@ Expr Simplify::visit(const Mul *op, ExprInfo *info) {
4141
return rewrite.result;
4242
}
4343

44-
if (rewrite(0 * x, 0) ||
45-
rewrite(1 * x, x) ||
46-
rewrite(x * 0, 0) ||
47-
rewrite(x * 1, x)) {
44+
if (rewrite(0 * x, a) ||
45+
rewrite(1 * x, b) ||
46+
rewrite(x * 0, b) ||
47+
rewrite(x * 1, a)) {
48+
if (info) {
49+
if (rewrite.result.same_as(a)) {
50+
info->intersect(a_info);
51+
} else {
52+
internal_assert(rewrite.result.same_as(b));
53+
info->intersect(b_info);
54+
}
55+
}
4856
return rewrite.result;
4957
}
5058

src/Simplify_Select.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Expr Simplify::visit(const Select *op, ExprInfo *info) {
3434
if (info) {
3535
if (rewrite.result.same_as(true_value)) {
3636
*info = t_info;
37-
} else if (rewrite.result.same_as(false_value)) {
37+
} else {
38+
internal_assert(rewrite.result.same_as(false_value));
3839
*info = f_info;
3940
}
4041
}

0 commit comments

Comments
 (0)