-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle many more intrinsics in Bounds.cpp #7823
Conversation
This addresses many (but not all) of the `signed integer overflow` issues we're seeing in Google due to #7814 -- a lot of the issues seems to be in code that uses intrinsics that had no handling in value bounds checking, so the bounds were naively large and overflowed. - Most of the intrinsics from FindIntrinsics.h weren't handled; now they all are (most by lowering to other IR, though the halving_add variants were modeled directly because the bitwise ops don't mesh well) - strict_float() is just a pass-through - round() is a best guess (basically, if bounds exist, expand by one as a worst-case) There are definitely others we should handle here... trunc/floor/ceil probably?
src/Bounds.cpp
Outdated
} else if (op->is_intrinsic(Call::strict_float)) { | ||
internal_assert(op->args.size() == 1); | ||
interval = arg_bounds.get(0); | ||
} else if (op->is_intrinsic(Call::round)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Round was already handled below more tightly than this (you can just round the min and max). strict_float should probably be handled in the same place. i.e. we should probably preserve the strict_float wrapper around the min and max so that floating point optimizations don't happen to the min and max in a way that makes them no longer contain the original expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed the Call::round
handling below -- I think the case I saw didn't pass the (interval = arg_bounds.get(0)).is_bounded()
test and that's why I thought it was missing.
@@ -1468,6 +1480,7 @@ class Bounds : public IRVisitor { | |||
} | |||
} else if (op->args.size() == 1 && | |||
(op->is_intrinsic(Call::round) || | |||
op->is_intrinsic(Call::strict_float) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's going to be a merge conflict here because Call::saturating_cast is in the same category. Probably should add it in this PR in case the other one doesn't go in and we revert the u32 -> i32 cast change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take it back! saturating_cast doesn't belong here.
src/Bounds.cpp
Outdated
} else if (op->is_intrinsic(Call::halving_add)) { | ||
// lower_halving_add() uses bitwise tricks that are hard to reason | ||
// about; let's do this instead: | ||
Expr e = narrow((widen(op->args[0]) + widen(op->args[1])) / 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the bot failure, I suspect this is trying to widen a 64-bit input
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, well, ok, but this is literally the fallback implementation for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I don't know how to make the bitwise op handling robust enough to handle this correctly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the right way to handle this is to special-case 64-bit and use the min/max possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a branch that (among other things) tacked on bounds inference for many of these intrinsics - I did have to special-case any intrinsic that semantically widens if the arguments are 64 bit, and there were a few that would produce a double-widening so had to be even further special-cases (I think rounding_mul_shift_right lowers to a widening mul followed by a rounding shift right that lowers to a widening add or something like that, so it would double-widen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a branch
please share! These changes make for much better bounds inference in some cases (esp pipelines with fixed-point math); if your fixes are better than these we should take yours.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to severely clean it up - that's part of why I never opened a PR. I can try to clean it up and share, might take me a few days unfortunately - I am about to be traveling for a funding thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it's ugly, feel free to put it somewhere I can look at it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the change in Bounds.cpp (note the TODOs I have there are out-of-date): https://github.com/halide/Halide/blob/7a497fd4369f278c16abb9790beabf40514ae22f/src/Bounds.cpp#L1522-#L1546
Here is the corresponding lowering code:
https://github.com/halide/Halide/blob/7a497fd4369f278c16abb9790beabf40514ae22f/src/FindIntrinsics.cpp#L1793-#L1908
// about; let's do this instead: | ||
if (op->type.bits() == 64) { | ||
bounds_of_type(t); | ||
} else if (op->is_intrinsic(Call::widen_right_add)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this many safety checks for the widening operations. Any Expr
in a widening op needs to be able to be widened - we can't lift to widening_mul
unless a user widened the inputs. We only need to be careful with operations that we can lift to without widening operations, but that the "simple" lowering pattern involves widening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, yes and no -- it's true a that the Exprs in a widening op need to be widened, and well-formed code shouldn't pass us cases that don't fit; that said, we absolutely will get misuse in that way, so what should we do when that happens? IMHO we are better off checking for it an explicitly devolving to bounds-of-type, rather than risking that the bounds-calc code makes a mistake and calculates an inappropriate bound due to inadvertent overflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I think I misunderstood the use case. Is this for when users write code that produces a LUT index, and uses intermediate 64 bit types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for when users write code that produces a LUT index
Yes? I mean, we have no control of what the user is doing with these functions; they could pass it insane nonsense, so we need to be somewhat defensive here. We'd prefer to avoid a too-loose bounds, but we absolutely cannot risk getting too-tight bounds.
@rootjalex -- obviously this didn't get landed and I was out all last week; when you get a chance, we should put our heads together to figure out how to combine our approaches. |
I am absolutely swamped this week, and have a 9/19 deadline. Happy to discuss at the dev meeting (I should be able to attend this week, I hope...), or sometime after 9/19. Sorry about that |
Hey @rootjalex, I'm not going to have time to sync up with you on this for a bit -- you are welcome to take over this PR and combine it with your own as you see fit (or ignore it entirely if yours looks better); otherwise this will likely sit unfinished until sometime in November |
This has been sitting here a while. Where does it stand? Does it need more work? |
I think this one is a partial fix for problems identified in #7814 |
This PR is definitely not a complete fix, but I think it is worthy of landing as a partial fix (pending checking in Google) -- WDYT? |
src/Bounds.cpp
Outdated
@@ -41,6 +41,36 @@ using std::string; | |||
using std::vector; | |||
|
|||
namespace { | |||
|
|||
bool can_widen(const Expr &e) { | |||
return e.type().bits() < 64; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be <= 32
. I'm thinking of the 48 bit types in the xtensa backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
I think that's fine. Sorry for going AWOL - I had a big deadline recently that took all of my time. |
Hmm, this injects a lot of signed-integer-overflow failures in google3... I'll need to do some debugging. |
My implementation of |
* Handle many more intrinsics in Bounds.cpp This addresses many (but not all) of the `signed integer overflow` issues we're seeing in Google due to halide#7814 -- a lot of the issues seems to be in code that uses intrinsics that had no handling in value bounds checking, so the bounds were naively large and overflowed. - Most of the intrinsics from FindIntrinsics.h weren't handled; now they all are (most by lowering to other IR, though the halving_add variants were modeled directly because the bitwise ops don't mesh well) - strict_float() is just a pass-through - round() is a best guess (basically, if bounds exist, expand by one as a worst-case) There are definitely others we should handle here... trunc/floor/ceil probably? * Fix round() and strict_float() handling * Update Bounds.cpp * Fixes? * trigger buildbots * Revert saturating_cast handling * Update Bounds.cpp --------- Co-authored-by: Andrew Adams <andrew.b.adams@gmail.com>
This addresses many (but not all) of the
signed integer overflow
issues we're seeing in Google due to #7814 -- a lot of the issues seems to be in code that uses intrinsics that had no handling in value bounds checking, so the bounds were naively large and overflowed.There are definitely others we should handle here... trunc/floor/ceil probably?