Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
55c3dd1
add fixes to overflow analysis in bounds inference
rootjalex Jan 7, 2021
b43eec1
make clang format happy
rootjalex Jan 7, 2021
0e969b7
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 8, 2021
b66a82d
change to C casting and add div_cant_overflow
rootjalex Jan 9, 2021
ae15682
Merge branch 'rootjalex/bounds_overflow_fixes' of https://github.com/…
rootjalex Jan 9, 2021
1be801a
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 13, 2021
0b4244e
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 14, 2021
aa21fba
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 15, 2021
6f478bc
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 15, 2021
b218511
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 19, 2021
0433845
Merge branch 'master' into rootjalex/bounds_overflow_fixes
steven-johnson Jan 21, 2021
f9b207f
Merge branch 'master' of https://github.com/halide/Halide into rootja…
rootjalex Jan 21, 2021
c64a0d0
Merge branch 'rootjalex/bounds_overflow_fixes' of https://github.com/…
rootjalex Jan 21, 2021
6fa0a56
fix TODO to reference issue
rootjalex Feb 1, 2021
87383fb
Merge branch 'rootjalex/bounds_overflow_fixes' of https://github.com/…
rootjalex Feb 1, 2021
59441f1
div_cant_overflow -> div_cannot_overflow
rootjalex Feb 1, 2021
4f0a437
add nits for can_overflow
rootjalex Feb 2, 2021
7ddccb6
Merge branch 'master' of https://github.com/halide/Halide into rootja…
rootjalex Feb 2, 2021
2c07165
Merge branch 'master' of https://github.com/halide/Halide into rootja…
rootjalex Feb 3, 2021
8e9b5b8
Merge branch 'master' of https://github.com/halide/Halide into rootja…
rootjalex Feb 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 95 additions & 29 deletions src/Bounds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,16 +400,20 @@ class Bounds : public IRVisitor {
}

// Assume no overflow for float, int32, and int64
if (!op->type.is_float() && (!op->type.is_int() || op->type.bits() < 32)) {
if (op->type.can_overflow()) {
if (interval.has_upper_bound()) {
Expr no_overflow = (cast<int>(a.max) + cast<int>(b.max) == cast<int>(interval.max));
// TODO(5682): Can't catch overflow of UInt(64) currently.
Type t = op->type.is_uint() ? UInt(64) : Int(32);
Expr no_overflow = (cast(t, a.max) + cast(t, b.max) == cast(t, interval.max));
if (!can_prove(no_overflow)) {
bounds_of_type(op->type);
return;
}
}
if (interval.has_lower_bound()) {
Expr no_overflow = (cast<int>(a.min) + cast<int>(b.min) == cast<int>(interval.min));
// TODO(5682): Can't catch overflow of UInt(64) currently.
Type t = op->type.is_uint() ? UInt(64) : Int(32);
Expr no_overflow = (cast(t, a.min) + cast(t, b.min) == cast(t, interval.min));
if (!can_prove(no_overflow)) {
bounds_of_type(op->type);
return;
Expand Down Expand Up @@ -440,7 +444,7 @@ class Bounds : public IRVisitor {
}

// Assume no overflow for float, int32, and int64
if (!op->type.is_float() && (!op->type.is_int() || op->type.bits() < 32)) {
if (op->type.can_overflow()) {
if (interval.has_upper_bound()) {
Expr no_overflow = (cast<int>(a.max) - cast<int>(b.min) == cast<int>(interval.max));
if (!can_prove(no_overflow)) {
Expand Down Expand Up @@ -518,11 +522,12 @@ class Bounds : public IRVisitor {
}

// Assume no overflow for float, int32, and int64
if (!op->type.is_float() && (!op->type.is_int() || op->type.bits() < 32)) {
if (op->type.can_overflow()) {
if (a.is_bounded() && b.is_bounded()) {
// Try to prove it can't overflow. (Be sure to use uint32 for unsigned
// types so that the case of 65535*65535 won't misleadingly fail.)
Type t = op->type.is_uint() ? UInt(32) : Int(32);
// TODO(5682): Can't catch overflow of UInt(64) currently.
Type t = op->type.is_uint() ? UInt(64) : Int(32);
Expr test1 = (cast(t, a.min) * cast(t, b.min) == cast(t, a.min * b.min));
Expr test2 = (cast(t, a.min) * cast(t, b.max) == cast(t, a.min * b.max));
Expr test3 = (cast(t, a.max) * cast(t, b.min) == cast(t, a.max * b.min));
Expand All @@ -536,6 +541,16 @@ class Bounds : public IRVisitor {
}
}

bool div_cannot_overflow(const Interval &a, const Interval &b, Type t) {
// No overflow if: not an allowed overflow int type, or `a` cannot be t.min() or
// `b` cannot be -1, because t.min() / -1 overflows for int16 and int8.
Expr neg_one = make_const(t, -1);
return !t.can_overflow_int() ||
(a.has_lower_bound() && can_prove(a.min != t.min())) ||
(b.has_upper_bound() && can_prove(b.max < neg_one)) ||
(b.has_lower_bound() && can_prove(b.min > neg_one));
}

void visit(const Div *op) override {
TRACK_BOUNDS_INTERVAL;
op->a.accept(this);
Expand Down Expand Up @@ -576,23 +591,32 @@ class Bounds : public IRVisitor {
} else if (can_prove(b.min == b.max)) {
Expr e1 = a.has_lower_bound() ? a.min / b.min : a.min;
Expr e2 = a.has_upper_bound() ? a.max / b.max : a.max;
// TODO: handle real numbers with can_prove(b.min > 0) and can_prove(b.min < 0) as well - treating floating point as
// reals can be error prone when dealing with division near 0, so for now we only consider integers in the can_prove() path
if (op->type.is_uint() || is_positive_const(b.min) || (op->type.is_int() && can_prove(b.min >= 0))) {
interval = Interval(e1, e2);
} else if (is_negative_const(b.min) || (op->type.is_int() && can_prove(b.min <= 0))) {
if (e1.same_as(Interval::neg_inf())) {
e1 = Interval::pos_inf();
}
if (e2.same_as(Interval::pos_inf())) {
e2 = Interval::neg_inf();

Type t = op->type.element_of();

if (div_cannot_overflow(a, b, t)) {
// TODO: handle real numbers with can_prove(b.min > 0) and can_prove(b.min < 0) as well - treating floating point as
// reals can be error prone when dealing with division near 0, so for now we only consider integers in the can_prove() path
if (op->type.is_uint() || is_positive_const(b.min) || (op->type.is_int() && can_prove(b.min >= 0))) {
interval = Interval(e1, e2);
} else if (is_negative_const(b.min) || (op->type.is_int() && can_prove(b.min <= 0))) {
if (e1.same_as(Interval::neg_inf())) {
e1 = Interval::pos_inf();
}
if (e2.same_as(Interval::pos_inf())) {
e2 = Interval::neg_inf();
}
interval = Interval(e2, e1);
} else if (a.is_bounded()) {
// Sign of b is unknown.
Expr cmp = b.min > make_zero(b.min.type().element_of());
interval = Interval(select(cmp, e1, e2), select(cmp, e2, e1));
} else {
bounds_of_type(op->type);
}
interval = Interval(e2, e1);
} else if (a.is_bounded()) {
// Sign of b is unknown.
Expr cmp = b.min > make_zero(b.min.type().element_of());
interval = Interval(select(cmp, e1, e2), select(cmp, e2, e1));
} else {
// Overflow is possible because a can be min value of type t and
// b can be -1.
bounds_of_type(op->type);
}
} else if (a.is_bounded()) {
Expand Down Expand Up @@ -621,13 +645,21 @@ class Bounds : public IRVisitor {
bounds_of_type(op->type);
}
} else {
// Divisor is either strictly positive or strictly
// negative, so we can just take the extrema.
interval = Interval::nothing();
interval.include(a.min / b.min);
interval.include(a.max / b.min);
interval.include(a.min / b.max);
interval.include(a.max / b.max);
Type t = op->type.element_of();

if (div_cannot_overflow(a, b, t)) {
// Divisor is either strictly positive or strictly
// negative, so we can just take the extrema.
interval = Interval::nothing();
interval.include(a.min / b.min);
interval.include(a.max / b.min);
interval.include(a.min / b.max);
interval.include(a.max / b.max);
} else {
// Overflow is possible because a can be min value of type t and
// b can be -1.
bounds_of_type(op->type);
}
}
} else {
bounds_of_type(op->type);
Expand Down Expand Up @@ -1039,7 +1071,14 @@ class Bounds : public IRVisitor {
string var_name = unique_name('t');
Expr var = Variable::make(op->base.type().element_of(), var_name);
Expr lane = op->base + var * op->stride;
ScopedBinding<Interval> p(scope, var_name, Interval(make_const(var.type(), 0), make_const(var.type(), op->lanes - 1)));
Expr min_value = make_const(var.type(), 0);
Expr max_value = make_const(var.type(), op->lanes - 1);
if (!var.type().can_represent((int64_t)(op->lanes - 1))) {
// max_value will overflow.
min_value = var.type().min();
max_value = var.type().max();
}
ScopedBinding<Interval> p(scope, var_name, Interval(min_value, max_value));
lane.accept(this);
}

Expand Down Expand Up @@ -3207,6 +3246,33 @@ void bounds_test() {
// Cannot change sign, only can decrease magnitude.
check(scope, x << y, 0, Interval::pos_inf());
}
// Overflow testing (for types with defined overflow).
{
Type uint32 = UInt(32);
Expr a = Variable::make(uint32, "a");
Expr b = Variable::make(uint32, "b");
ScopedBinding<Interval> ab(scope, "a", Interval(UIntImm::make(uint32, 0), simplify(uint32.max() / 4 + 2)));
ScopedBinding<Interval> bb(scope, "b", Interval(UIntImm::make(uint32, 0), uint32.max()));
// Overflow should be detected
check(scope, a + b, Interval::neg_inf(), Interval::pos_inf());
check(scope, a * b, Interval::neg_inf(), Interval::pos_inf());
}
{
Type int16 = Int(16);
Expr a = Variable::make(int16, "a");
Expr b = Variable::make(int16, "b");
ScopedBinding<Interval> ab(scope, "a", Interval(int16.min(), int16.max()));
ScopedBinding<Interval> bb(scope, "b", Interval(IntImm::make(int16, -4), IntImm::make(int16, -1)));
check(scope, a * -1, int16.min(), int16.max());
// int16.min() / -1 should be caught as overflow.
check(scope, a / -1, int16.min(), int16.max());
check(scope, a / b, int16.min(), int16.max());
}
{
Expr zero = UIntImm::make(UInt(1), 0);
Expr one = UIntImm::make(UInt(1), 1);
check(scope, Ramp::make(zero, one, 3), zero, one);
}

// If we clamp something unbounded as one type, the bounds should
// propagate through casts whenever the cast can be proved to not
Expand Down
12 changes: 12 additions & 0 deletions src/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,18 @@ struct Type {
return code() == Handle;
}

// Returns true iff type is an integral type where overflow is defined.
HALIDE_ALWAYS_INLINE
bool can_overflow_int() const {
return is_int() && bits() <= 16;
}

// Returns true iff type does have a well defined overflow behavior.
HALIDE_ALWAYS_INLINE
bool can_overflow() const {
return is_uint() || can_overflow_int();
}

/** Check that the type name of two handles matches. */
bool same_handle_type(const Type &other) const;

Expand Down