Skip to content
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

is_positive_const and is_negative_const broken for (some) casts #5615

Merged
merged 10 commits into from
Jan 21, 2021

Conversation

rootjalex
Copy link
Member

@rootjalex rootjalex commented Jan 5, 2021

is_positive_const and is_negative_const don't work for some casts that have defined overflow behavior.

For example:

simplify(int8(uint8(Expr((uint32_t)200543487)))) -> -1

but

is_positive_const(int8(uint8(Expr((uint32_t)200543487)))) -> true
is_negative_const(int8(uint8(Expr((uint32_t)200543487)))) -> false

This PR fixes these bugs, and adds a fuzzing test that validates that the sign of a simplified cast chain is the sign of the original expression.

Leaving as a draft because there are some unfinished pieces:

  • Currently, there is not support for checking is_( positive | negative )_const for a cast on a Ramp - is this ever necessary?
  • Is it worth making a generic fuzzing header in tests/correctness? I'm also working on a fuzzer for interval arithmetic (that work actually detected the bug in is_positive_const), and many pieces across that fuzzer, this fuzzer, and fuzz_simplify.cpp are close enough that it might be worth merging them.
  • In propagating a casted value, what should be done if a float is casted into a non-finite value? (see // TODO: check std::isfinite? in the code added to IROperator.cpp).

@steven-johnson
Copy link
Contributor

Is this ready for review? It's still marked as draft.

@rootjalex
Copy link
Member Author

It is ready for review, I was planning to leave it as a draft until I got answers to the three questions (and applied fixes accordingly).

@rootjalex rootjalex marked this pull request as ready for review January 7, 2021 19:23
@steven-johnson
Copy link
Contributor

  • Currently, there is not support for checking is_( positive | negative )_const for a cast on a Ramp - is this ever necessary?

I don't know, deferring to @abadams here.

  • Is it worth making a generic fuzzing header in tests/correctness? I'm also working on a fuzzer for interval arithmetic (that work actually detected the bug in is_positive_const), and many pieces across that fuzzer, this fuzzer, and fuzz_simplify.cpp are close enough that it might be worth merging them.

Yes, that sounds useful, but as a separate PR (not part of this one)

  • In propagating a casted value, what should be done if a float is casted into a non-finite value? (see // TODO: check std::isfinite? in the code added to IROperator.cpp).

I don't know, deferring to @abadams here.

@rootjalex rootjalex added the bug label Jan 8, 2021
Copy link
Contributor

@steven-johnson steven-johnson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but probably want @abadams to address the TODO items before landing.

return is_positive_const(c->value);
Type to = c->type;
if (!to.is_int_or_uint() ||
lossless_cast(to, c->value).defined()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lossless_cast is quite expensive, and is_positive_const is used in places where it needs to be very cheap. I think we might need to come up with an alternative strategy for handling cast chains. Maybe this condition should just be to.can_represent(c->value.type()) to check for widening casts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these expectations of "expected to be cheap", "known to be expensive" etc documented for each of these?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the responsibilities of these things tends to creep over time (e.g. lossless_cast is becoming more and more heavy-duty), so I'm trying to push back. I just know in the past I've called is_positive_const assuming it'll be cheap. The simplifier used to be littered with it, but that was all stripped out. Now it seems to be used for special cases in bounds, is_monotonic, the solver, and some more bounds stuff in vectorization. I think in those contexts the simplifier has already run. If that's the case we should just enshrine that in the comments on is_positive_const (i.e. that it does not do constant folding).

// non-integral cast, so no overflow behavior to worry about.
return is_positive_const(c->value);
} else {
// May need to deal with overflow behaviors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually now I'm wondering if this function should handle casts at all. That seems like eager simplification, but in the contexts where is_positive_const is used I think the simplifier has already run. Perhaps this function shouldn't do anything that constant folding would do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. I wouldn't expect is_positive_const to return true on select(false, 4, 5);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable to me, in which case I can remove all cast codes (and the fuzzing test). I just want it to be correct, seeing as it produced incorrect bounds for bounds inference in one of my test cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, one thing to note: not all calls to is_positive_const (and the negative version) are called on simplified expressions, notably the calls in bounds inference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. Then having more smarts here does have a positive effect. Is it feasible to insert simplification into the places that might be unsimplified in bounds? Alternatively, maybe we should just have a cheap constant-folding routine that we can call first as an alternative to full simplification. Your propagate functions just handle casts. Yet another alternative would be is_bounded_const, which would take a min and max. This would be nice because it has a natural recursive thing to do when you hit a narrowing cast: reduce the max to the maximum value of the narrower type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think cheap constant folding as an alternative would be better, as I'm pretty sure no simplification is done in bounds, but constant folding would be very helpful (I often see very complicated expressions produced that simplify down with just constant folding).

I'm not as sure as to the usefulness of is_bounded_const - where would that be used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, no simplification other than in can_prove, which actually might be called a lot..

@alexreinking alexreinking added this to the v12.0.0 milestone Jan 8, 2021
@steven-johnson
Copy link
Contributor

buildbots are green. ready to merge?

@rootjalex
Copy link
Member Author

buildbots are green. ready to merge?

@abadams and I discussed this yesterday - for now, is_positive_const and is_negative_const should only recurse on widening casts. As far as we can tell, the only times that these are being called on non-widening casts of constants are in bounds inference, and we're discussing a different solution to that problem.

I'll push the discussed fix soon, hopefully tonight.

@steven-johnson
Copy link
Contributor

Looks clean and ready to land.

@rootjalex rootjalex merged commit be7a6a3 into master Jan 21, 2021
@rootjalex rootjalex deleted the rootjalex/fix_signed_consts branch January 21, 2021 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants