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

C++: Handle overflow for upperbound #6745

Merged
merged 4 commits into from Sep 27, 2021

Conversation

andersfugmann
Copy link
Contributor

SimpleRangeAnalysis::getGuardedUpperBound may calculate wrong upper bounds when overflows are involved.
This PR change the said predicate to not require that no overflows exists in the guard expression.

@andersfugmann andersfugmann added bug Something isn't working C++ no-change-note-required This PR does not need a change note labels Sep 24, 2021
@andersfugmann andersfugmann requested a review from a team as a code owner September 24, 2021 09:55
@@ -1549,7 +1549,8 @@ private float getGuardedUpperBound(VariableAccess guardedAccess) {
// that there is one predecessor, albeit somewhat conservative.
exists(unique(BasicBlock b | b = def.(BasicBlock).getAPredecessor())) and
guardedAccess = def.getAUse(v) and
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch))
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch)) and
not exists(Expr e | e = guard.getAChild+() | convertedExprMightOverflow(e))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This predicate looks at any child of guard including expressions that does not affect guardVa.

I was considering extending this to:
not exists(Expr e | e = guard.getAChild+() and e = guardVa.getParent*() | convertedExprMightOverflow(e))

but I was unable to construct an expression where this had any effect.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit scary that we have to exclude overflows in any child of the guard. This is not something we've had to do before, I think. I would have hoped that we could fix it inside upperBoundFromGuard somehow. Is the real problem that we are overflowing in linearAccessImpl?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same concerns as @MathiasVP. I'd also like to think a bit more about the root cause, but at least we have this PR if we don't find a better solution before Monday.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree there might be a better solution for this, but I'm unsure on where we should add this.

The predicate getTruncatedUpperBounds does handle the overflows correctly, but the addition of predicate getGuardedUpperBound was add specifically to handle cases where getTruncatedUpperBounds would widen the result inside a loop and hence miss obvious cases.

@andersfugmann andersfugmann changed the title Handle overflow for upperbound C++: Handle overflow for upperbound Sep 24, 2021
@@ -1549,7 +1549,8 @@ private float getGuardedUpperBound(VariableAccess guardedAccess) {
// that there is one predecessor, albeit somewhat conservative.
exists(unique(BasicBlock b | b = def.(BasicBlock).getAPredecessor())) and
guardedAccess = def.getAUse(v) and
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch))
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch)) and
not exists(Expr e | e = guard.getAChild+() | convertedExprMightOverflow(e))
Copy link
Contributor

Choose a reason for hiding this comment

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

Whenever we use transitive closures like this, we should double-check tuple counts and performance. But we might change the whole approach, so we don't need to do that now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Anders and I looked at the tuple counts together on the Asterisk project. The TC gets compiled to a boundedFastTC with a good restriction on the starting points. It produces 34M (compressed) rows in 7MB, which is not alarming, and DCA shows no measurable impact.

@@ -1549,7 +1549,8 @@ private float getGuardedUpperBound(VariableAccess guardedAccess) {
// that there is one predecessor, albeit somewhat conservative.
exists(unique(BasicBlock b | b = def.(BasicBlock).getAPredecessor())) and
guardedAccess = def.getAUse(v) and
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch))
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch)) and
not exists(Expr e | e = guard.getAChild+() | convertedExprMightOverflow(e))
Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same concerns as @MathiasVP. I'd also like to think a bit more about the root cause, but at least we have this PR if we don't find a better solution before Monday.

Co-authored-by: Jonas Jensen <jbj@github.com>
@jbj
Copy link
Contributor

jbj commented Sep 27, 2021

I was able to construct a test case to show that the unsound treatment of overflow was not introduced with #6568 but existed already:

void test_overflow() {
  unsigned int x = 0xffFFffFF;
  if ((x + 256) <= 512) {
    out(x); // range analysis says upper bound is 256
  }
}

The existing problem is much harder to solve than the amplification of it that got introduced with #6568. That's because we haven't calculated overflow information yet at the point where we determine what to do with guard phi nodes. The only general solution I can think of is to make linearAccessImpl much more conservative around unsigned arithmetic.

Copy link
Contributor

@jbj jbj left a comment

Choose a reason for hiding this comment

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

This PR might not be the ideal solution, but I think it's the most low-risk solution we can merge this morning, before branching rc/3.3. It's been validated with DCA and by looking at tuple counts, and all alternatives we've considered would either involve a lot more code or would remove a lot more good results.

@jbj jbj merged commit 06b36f7 into github:main Sep 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working C++ no-change-note-required This PR does not need a change note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants