Skip to content

Commit 602a5c0

Browse files
Make the simplifier idempotent (#8840)
* Add a fuzz test for simplifier idempotency * Remove double simplify in TrimNoOps.cpp * Add remutation around broadcast simplification * Improve visual alignment for idempotency error * Simplify fuzz_simplify.cpp a bit * Fix fuzz_var_count * Simplifier changes to try to achieve idempotence Everywhere we something without remutating, we need to ensure we also return the ExprInfo that would have resulted from remutation Also, redid Simplify_EQ to not rely on Simplify_Sub. New rules are formally verified for infinite integers except for the ones with broadcast and ramps * Missed a non-remutating return in Cast * Don't treat remaining casts as const * Add missing remutate in abs * Replace janky retry loop * Properly initialize RNG (and widen to 64 bit) * abs sometimes folds selects and ramps to constants * Fix ramp(ramp()) folding when there is intentional overflow * Propagate info through promise_clamped * Add missing EQ rule * Handle selects on bools in Solve.cpp Also turn off the infinite loop in the test * Attempt to dodge inscrutable compiler errors with seed_seq on some platforms * Overload resolution is hard. --------- Co-authored-by: Andrew Adams <andrew.b.adams@gmail.com>
1 parent 7c2144e commit 602a5c0

24 files changed

Lines changed: 829 additions & 356 deletions

src/ConstantInterval.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ ConstantInterval operator%(const ConstantInterval &a, const ConstantInterval &b)
456456
result.min_defined = true;
457457
result.min = 0;
458458

459+
if (a.is_single_point() && b.is_single_point()) {
460+
return ConstantInterval::single_point(mod_imp(a.min, b.min));
461+
}
462+
459463
// Mod by produces a result between 0
460464
// and max(0, abs(modulus) - 1). However, if b is unbounded in
461465
// either direction, abs(modulus) could be arbitrarily

src/IRMatch.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ std::ostream &operator<<(std::ostream &s, const WildConstFloat<i> &c) {
391391
return s;
392392
}
393393

394-
// Matches and binds to any constant Expr. Does not support constant-folding.
394+
// Matches and binds to any constant Expr.
395395
template<int i>
396396
struct WildConst {
397397
struct pattern_tag {};
@@ -777,6 +777,7 @@ struct CmpOp {
777777
b.make_folded_const(val_b, ty, state);
778778
ty.lanes |= l;
779779
}
780+
780781
switch (ty.code) {
781782
case halide_type_int:
782783
val.u.u64 = constant_fold_cmp_op<Op>(val_a.u.i64, val_b.u.i64);

src/IRPrinter.cpp

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,18 +1027,46 @@ void IRPrinter::visit(const Broadcast *op) {
10271027
}
10281028

10291029
void IRPrinter::visit(const Call *op) {
1030-
// TODO: Print indication of C vs C++?
1031-
if (!known_type.contains(op->name) &&
1032-
(op->type != Int(32))) {
1033-
if (op->type.is_handle()) {
1034-
stream << type(op->type); // Already has parens
1035-
} else {
1036-
stream << typep(op->type);
1030+
1031+
if (op->is_intrinsic(Call::bitwise_or)) {
1032+
open();
1033+
print(op->args[0]);
1034+
stream << paren(" | ");
1035+
print(op->args[1]);
1036+
close();
1037+
} else if (op->is_intrinsic(Call::bitwise_and)) {
1038+
open();
1039+
print(op->args[0]);
1040+
stream << paren(" & ");
1041+
print(op->args[1]);
1042+
close();
1043+
} else if (op->is_intrinsic(Call::bitwise_xor)) {
1044+
open();
1045+
print(op->args[0]);
1046+
stream << paren(" ^ ");
1047+
print(op->args[1]);
1048+
close();
1049+
} else if (op->is_intrinsic(Call::bitwise_not)) {
1050+
stream << ansi_kw << "~(" << ansi_reset;
1051+
paren_depth++;
1052+
print_no_parens(op->args[0]);
1053+
paren_depth--;
1054+
stream << kw(")");
1055+
} else {
1056+
1057+
// TODO: Print indication of C vs C++?
1058+
if (!known_type.contains(op->name) &&
1059+
(op->type != Int(32))) {
1060+
if (op->type.is_handle()) {
1061+
stream << type(op->type); // Already has parens
1062+
} else {
1063+
stream << typep(op->type);
1064+
}
10371065
}
1066+
openf(op->name.c_str());
1067+
print_list(op->args);
1068+
closef();
10381069
}
1039-
openf(op->name.c_str());
1040-
print_list(op->args);
1041-
closef();
10421070
}
10431071

10441072
void IRPrinter::visit(const Let *op) {

src/ModulusRemainder.cpp

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,27 @@ void modulus_remainder_test() {
365365
}
366366

367367
int64_t gcd(int64_t a, int64_t b) {
368-
if (a < b) {
369-
std::swap(a, b);
368+
// We don't care about factors of -1, so we're going to do this unsigned so
369+
// that we can take an absolute value without worrying about INT64_MIN.
370+
uint64_t ua = (uint64_t)a;
371+
uint64_t ub = (uint64_t)b;
372+
373+
if (a < 0) {
374+
ua = -ua;
375+
}
376+
if (b < 0) {
377+
ub = -ub;
378+
}
379+
380+
if (ua < ub) {
381+
std::swap(ua, ub);
370382
}
371-
while (b != 0) {
372-
int64_t tmp = b;
373-
b = a % b;
374-
a = tmp;
383+
while (ub != 0) {
384+
uint64_t tmp = ub;
385+
ub = ua % ub;
386+
ua = tmp;
375387
}
376-
return a;
388+
return (int64_t)(ua);
377389
}
378390

379391
int64_t lcm(int64_t a, int64_t b) {
@@ -410,13 +422,17 @@ ModulusRemainder operator*(const ModulusRemainder &a, const ModulusRemainder &b)
410422
if (a.modulus == 0) {
411423
// a is constant
412424
if (mul_with_overflow(64, a.remainder, b.modulus, &m) &&
413-
mul_with_overflow(64, a.remainder, b.remainder, &r)) {
425+
mul_with_overflow(64, a.remainder, b.remainder, &r) &&
426+
// a.remainder may have been negative
427+
(m > 0 || mul_with_overflow(64, m, -1, &m))) {
414428
return {m, r};
415429
}
416430
} else if (b.modulus == 0) {
417431
// b is constant
418432
if (mul_with_overflow(64, a.modulus, b.remainder, &m) &&
419-
mul_with_overflow(64, a.remainder, b.remainder, &r)) {
433+
mul_with_overflow(64, a.remainder, b.remainder, &r) &&
434+
// b.remainder may have been negative
435+
(m > 0 || mul_with_overflow(64, m, -1, &m))) {
420436
return {m, r};
421437
}
422438
} else if (a.remainder == 0 && b.remainder == 0) {
@@ -447,6 +463,7 @@ ModulusRemainder operator*(const ModulusRemainder &a, const ModulusRemainder &b)
447463
}
448464

449465
ModulusRemainder operator/(const ModulusRemainder &a, const ModulusRemainder &b) {
466+
450467
// What can we say about:
451468
// floor((m1 * x + r1) / (m2 * y + r2))
452469

@@ -456,8 +473,13 @@ ModulusRemainder operator/(const ModulusRemainder &a, const ModulusRemainder &b)
456473
// (m1 / r2) * x + floor(r1 / r2)
457474
// E.g. (8x + 3) / 2 -> (4x + 1)
458475

459-
if (b.modulus == 0 && b.remainder != 0) {
460-
if (mod(a.modulus, b.remainder) == 0) {
476+
if (b.modulus == 0) {
477+
if (b.remainder == 0 || b.remainder == INT64_MIN) {
478+
return {0, 0};
479+
} else if (b.remainder < 0) {
480+
// In Euclidean division, a / -b == -(a / b)
481+
return ModulusRemainder{0, 0} - (a / -b.remainder);
482+
} else if (mod(a.modulus, b.remainder) == 0) {
461483
int64_t m = a.modulus / b.remainder;
462484
int64_t r = mod(div_imp(a.remainder, b.remainder), m);
463485
return {m, r};
@@ -552,6 +574,7 @@ ModulusRemainder operator%(const ModulusRemainder &a, const ModulusRemainder &b)
552574
// 2w + 1
553575
int64_t modulus = gcd(a.modulus, b.modulus);
554576
modulus = gcd(modulus, b.remainder);
577+
555578
int64_t remainder = mod(a.remainder, modulus);
556579

557580
if (b.remainder == 0 && remainder != 0) {

src/ModulusRemainder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ HALIDE_MUST_USE_RESULT bool reduce_expr_modulo(const Expr &e, int64_t modulus, i
9191

9292
void modulus_remainder_test();
9393

94-
/** The greatest common divisor of two integers */
94+
/** The greatest common divisor of two integers. Returns a positive result,
95+
* unless both args are INT64_MIN. */
9596
int64_t gcd(int64_t, int64_t);
9697

9798
/** The least common multiple of two integers */

src/Simplify.cpp

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -526,53 +526,49 @@ Simplify::ExprInfo::BitsKnown Simplify::ExprInfo::to_bits_known(const Type &type
526526
// the alignment analysis catches constants at the same time as
527527
// bounds analysis does.
528528
return (uint64_t)-1;
529+
} else if ((int64_t)x < 0) {
530+
// There are no leading zeros, but we can't shift left by 64
531+
return (uint64_t)0;
529532
}
530533
return (uint64_t)(-1) << (64 - clz64(x));
531534
};
532535

533-
auto leading_ones_mask = [=](uint64_t x) {
534-
return leading_zeros_mask(~x);
535-
};
536-
537-
// The bounds and the type tell us a bunch of high bits are zero or one
538-
if (type.is_uint()) {
539-
// Narrow uints are always zero-extended.
540-
if (type.bits() < 64) {
541-
result.mask |= (uint64_t)(-1) << type.bits();
542-
}
543-
// A lower bound might tell us that there are some leading ones, and an
544-
// upper bound might tell us that there are some leading
545-
// zeros. Unfortunately we'll never learn about leading ones, because to
546-
// know that there's a leading one from the bounds would require knowing
547-
// that the min is at least 2^63, and ConstantInterval can't represent
548-
// mins that large.
549-
if (bounds.max_defined) {
550-
result.mask |= leading_zeros_mask(bounds.max);
551-
}
536+
if (bounds.min_defined && bounds.max_defined) {
537+
// Any leading bits in common between the min and the max are known.
538+
result.mask |= leading_zeros_mask(bounds.min ^ bounds.max);
539+
result.value |= bounds.min & result.mask;
552540
} else {
553-
internal_assert(type.is_int());
554-
// A mask which is 1 for the sign bit and above.
555-
uint64_t sign_bit_and_above = (uint64_t)(-1) << (type.bits() - 1);
556-
if (bounds >= 0) {
557-
// We know this int is positive, so the sign bit and above are zero.
558-
result.mask |= sign_bit_and_above;
541+
// If we only have a bound on one side, we may still be able to infer
542+
// something about high bits.
543+
544+
// The bounds and the type tell us a bunch of high bits are zero or one
545+
if (type.is_uint()) {
546+
// Narrow uints are always zero-extended.
547+
if (type.bits() < 64) {
548+
result.mask |= (uint64_t)(-1) << type.bits();
549+
}
550+
551+
// A lower bound might tell us that there are some leading ones, and an
552+
// upper bound might tell us that there are some leading
553+
// zeros. Unfortunately we'll never learn about leading ones, because to
554+
// know that there's a leading one from the bounds would require knowing
555+
// that the min is at least 2^63, and ConstantInterval can't represent
556+
// mins that large.
559557
if (bounds.max_defined) {
560-
// We also have an upper bound, so there may be more zero bits,
561-
// depending on how many leading zeros there are in the upper
562-
// bound.
563558
result.mask |= leading_zeros_mask(bounds.max);
564559
}
565-
} else if (bounds < 0) {
566-
// This int is negative, so the sign bit and above are one.
567-
result.mask |= sign_bit_and_above;
568-
result.value |= sign_bit_and_above;
569-
if (bounds.min_defined) {
570-
// We have a lower bound, so there may be more leading one bits,
571-
// depending on how many leading ones there are in the lower
572-
// bound.
573-
uint64_t leading_ones = leading_ones_mask(bounds.min);
574-
result.mask |= leading_ones;
575-
result.value |= leading_ones;
560+
561+
} else {
562+
internal_assert(type.is_int());
563+
// A mask which is 1 for the sign bit and above.
564+
uint64_t sign_bit_and_above = (uint64_t)(-1) << (type.bits() - 1);
565+
if (bounds >= 0) {
566+
// We know this int is positive, so the sign bit and above are zero.
567+
result.mask |= sign_bit_and_above;
568+
} else if (bounds < 0) {
569+
// This int is negative, so the sign bit and above are one.
570+
result.mask |= sign_bit_and_above;
571+
result.value |= sign_bit_and_above;
576572
}
577573
}
578574
}

src/Simplify_Add.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Expr Simplify::visit(const Add *op, ExprInfo *info) {
1111
if (info) {
1212
info->bounds = a_info.bounds + b_info.bounds;
1313
info->alignment = a_info.alignment + b_info.alignment;
14-
info->trim_bounds_using_alignment();
1514
info->cast_to(op->type);
15+
info->trim_bounds_using_alignment();
1616
}
1717

1818
// Order commutative operations by node type

src/Simplify_And.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Expr Simplify::visit(const And *op, ExprInfo *info) {
1111
Expr a = mutate(op->a, nullptr);
1212
Expr b = mutate(op->b, nullptr);
1313

14+
if (info && is_const_one(a) && is_const_one(b)) {
15+
return const_true(op->type.lanes(), info);
16+
}
17+
1418
// Order commutative operations by node type
1519
if (should_commute(a, b)) {
1620
std::swap(a, b);
@@ -27,7 +31,7 @@ Expr Simplify::visit(const And *op, ExprInfo *info) {
2731
// Cases that fold to a constant
2832
if (EVAL_IN_LAMBDA
2933
(rewrite(x && false, false) ||
30-
34+
rewrite(false && x, false) ||
3135
rewrite(x && neg(x), false) ||
3236
rewrite(x && (neg(x) && y), false) ||
3337
rewrite(x && (y && neg(x)), false) ||
@@ -80,7 +84,6 @@ Expr Simplify::visit(const And *op, ExprInfo *info) {
8084
// Cases that fold to one of the args
8185
if (EVAL_IN_LAMBDA
8286
(rewrite(x && true, a) ||
83-
8487
rewrite(x && x, a) ||
8588
rewrite(x && (x && y), b) ||
8689
rewrite(x && (y && x), b) ||

0 commit comments

Comments
 (0)