-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[analyzer] Workaround for slowdown spikes (unintended scope increase) #136720
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
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
011008b
[analyzer] Workaround for unintended slowdown (scope increase)
NagyDonat d57946b
Use clang_analyzer_dump in tests
NagyDonat ba6a2ce
Correct docs and comments
NagyDonat 4140f53
Rename the new test file
NagyDonat 6c238f8
Document loop handling limitations that are required by the inlining …
NagyDonat 32399ee
Rename and flip the config option
NagyDonat e8991c8
Fix "the the" typo
NagyDonat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify=expected,default %s | ||
| // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config inline-functions-with-ambiguous-loops=true -verify=expected,enabled %s | ||
|
|
||
| // This file tests some heuristics in the engine that put functions on a | ||
| // "do not inline" list if their analyisis reaches the `analyzer-max-loop` | ||
| // limit (by default 4 iterations) in a loop. This was almost surely intended | ||
| // as memoization optimization for the "retry without inlining" fallback (if we | ||
| // had to retry once, next time don't even try inlining), but aggressively | ||
| // oversteps the "natural" scope: reaching 4 iterations on _one particular_ | ||
| // execution path does not imply that each path would need "retry without | ||
| // inlining" especially if a different call receives different arguments. | ||
| // | ||
| // This heuristic significantly affects the scope/depth of the analysis (and | ||
| // therefore the execution time) because without this limitation on the | ||
| // inlining significantly more entry points would be able to exhaust their | ||
| // `max-nodes` quota. (Trivial thin wrappers around big complex functions are | ||
| // common in many projects.) | ||
| // | ||
| // Unfortunately, this arbitrary heuristic strongly relies on the current loop | ||
| // handling model and its many limitations, so improvements in loop handling | ||
| // can cause surprising slowdowns by reducing the "do not inline" blacklist. | ||
| // In the tests "FIXME-BUT-NEEDED" comments mark "problematic" (aka buggy) | ||
| // analyzer behavior which cannot be fixed without also improving the | ||
| // heuristics for (not) inlining large functions. | ||
|
|
||
| int getNum(void); // Get an unknown symbolic number. | ||
|
|
||
| void clang_analyzer_dump(int arg); | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Simple case: inlined function never reaches `analyzer-max-loop`, so it is | ||
| // always inlined. | ||
|
|
||
| int inner_simple(int callIdx) { | ||
| clang_analyzer_dump(callIdx); // expected-warning {{1 S32}} | ||
| // expected-warning@-1 {{2 S32}} | ||
| return 42; | ||
| } | ||
|
|
||
| int outer_simple(void) { | ||
| int x = inner_simple(1); | ||
| int y = inner_simple(2); | ||
| return 53 / (x - y); // expected-warning {{Division by zero}} | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Inlined function always reaches `analyzer-max-loop`, which stops the | ||
| // analysis on that path and puts the function on the "do not inline" list. | ||
|
|
||
| int inner_fixed_loop_1(int callIdx) { | ||
| int i; | ||
| clang_analyzer_dump(callIdx); // expected-warning {{1 S32}} | ||
| for (i = 0; i < 10; i++); // FIXME-BUT-NEEDED: This stops the analysis. | ||
| clang_analyzer_dump(callIdx); // no-warning | ||
| return 42; | ||
| } | ||
|
|
||
| int outer_fixed_loop_1(void) { | ||
| int x = inner_fixed_loop_1(1); | ||
| int y = inner_fixed_loop_1(2); | ||
|
|
||
| // FIXME-BUT-NEEDED: The analysis doesn't reach this zero division. | ||
| return 53 / (x - y); // no-warning | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Inlined function always reaches `analyzer-max-loop`; inlining is prevented | ||
| // even for different entry points. | ||
| // NOTE: the analyzer happens to analyze the entry points in a reversed order, | ||
| // so `outer_2_fixed_loop_2` is analyzed first and it will be the one which is | ||
| // able to inline the inner function. | ||
|
|
||
| int inner_fixed_loop_2(int callIdx) { | ||
| // Identical copy of inner_fixed_loop_1. | ||
| int i; | ||
| clang_analyzer_dump(callIdx); // expected-warning {{2 S32}} | ||
| for (i = 0; i < 10; i++); // FIXME-BUT-NEEDED: This stops the analysis. | ||
| clang_analyzer_dump(callIdx); // no-warning | ||
| return 42; | ||
| } | ||
|
|
||
| int outer_1_fixed_loop_2(void) { | ||
| return inner_fixed_loop_2(1); | ||
| } | ||
|
|
||
| int outer_2_fixed_loop_2(void) { | ||
| return inner_fixed_loop_2(2); | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Inlined function reaches `analyzer-max-loop` only in its second call. The | ||
| // function is inlined twice but the second call doesn't finish and ends up | ||
| // being conservatively evaluated. | ||
|
|
||
| int inner_parametrized_loop_1(int count) { | ||
| int i; | ||
| clang_analyzer_dump(count); // expected-warning {{2 S32}} | ||
| // expected-warning@-1 {{10 S32}} | ||
| for (i = 0; i < count; i++); | ||
| // FIXME-BUT-NEEDED: This loop stops the analysis when count >=4. | ||
| clang_analyzer_dump(count); // expected-warning {{2 S32}} | ||
| return 42; | ||
| } | ||
|
|
||
| int outer_parametrized_loop_1(void) { | ||
| int x = inner_parametrized_loop_1(2); | ||
| int y = inner_parametrized_loop_1(10); | ||
|
|
||
| // FIXME-BUT-NEEDED: The analysis doesn't reach this zero division. | ||
| return 53 / (x - y); // no-warning | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Inlined function reaches `analyzer-max-loop` on its first call, so the | ||
| // second call isn't inlined (although it could be fully evaluated). | ||
|
|
||
| int inner_parametrized_loop_2(int count) { | ||
| // Identical copy of inner_parametrized_loop_1. | ||
| int i; | ||
| clang_analyzer_dump(count); // expected-warning {{10 S32}} | ||
| for (i = 0; i < count; i++); | ||
| // FIXME-BUT-NEEDED: This loop stops the analysis when count >=4. | ||
| clang_analyzer_dump(count); // no-warning | ||
| return 42; | ||
| } | ||
|
|
||
| int outer_parametrized_loop_2(void) { | ||
| int y = inner_parametrized_loop_2(10); | ||
| int x = inner_parametrized_loop_2(2); | ||
|
|
||
| // FIXME-BUT-NEEDED: The analysis doesn't reach this zero division. | ||
| return 53 / (x - y); // no-warning | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Inlined function may or may not reach `analyzer-max-loop` depending on an | ||
| // ambiguous check before the loop. This is very similar to the "fixed loop" | ||
| // cases: the function is placed on the "don't inline" list when any execution | ||
| // path reaches `analyzer-max-loop` (even if other execution paths reach the | ||
| // end of the function). | ||
| // NOTE: This is tested with two separate entry points to ensure that one | ||
| // inlined call is fully evaluated before we try to inline the other call. | ||
| // NOTE: the analyzer happens to analyze the entry points in a reversed order, | ||
| // so `outer_2_conditional_loop` is analyzed first and it will be the one which | ||
| // is able to inline the inner function. | ||
|
|
||
| int inner_conditional_loop(int callIdx) { | ||
| int i; | ||
| clang_analyzer_dump(callIdx); // expected-warning {{2 S32}} | ||
| if (getNum() == 777) { | ||
| for (i = 0; i < 10; i++); | ||
| } | ||
| clang_analyzer_dump(callIdx); // expected-warning {{2 S32}} | ||
| return 42; | ||
| } | ||
|
|
||
| int outer_1_conditional_loop(void) { | ||
| return inner_conditional_loop(1); | ||
| } | ||
|
|
||
| int outer_2_conditional_loop(void) { | ||
| return inner_conditional_loop(2); | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Inlined function executes an ambiguous loop that may or may not reach | ||
| // `analyzer-max-loop`. Historically, before the "don't assume third iteration" | ||
| // commit (bb27d5e5c6b194a1440b8ac4e5ace68d0ee2a849) this worked like the | ||
| // `conditional_loop` cases: the analyzer was able to find a path reaching | ||
| // `analyzer-max-loop` so inlining was disabled. After that commit the analyzer | ||
| // does not _assume_ a third (or later) iteration (i.e. does not enter those | ||
| // iterations if the loop condition is an unknown value), so e.g. this test | ||
| // function does not reach `analyzer-max-loop` iterations and the inlining is | ||
| // not disabled. | ||
| // Unfortunately this change significantly increased the workload and | ||
| // runtime of the analyzer (more entry points used up their budget), so the | ||
| // option `inline-functions-with-ambiguous-loops` was introduced and disabled | ||
| // by default to suppress the inlining in situations where the "don't assume | ||
| // third iteration" logic activates. | ||
| // NOTE: This is tested with two separate entry points to ensure that one | ||
| // inlined call is fully evaluated before we try to inline the other call. | ||
| // NOTE: the analyzer happens to analyze the entry points in a reversed order, | ||
| // so `outer_2_ambiguous_loop` is analyzed first and it will be the one which | ||
| // is able to inline the inner function. | ||
|
|
||
| int inner_ambiguous_loop(int callIdx) { | ||
| int i; | ||
| clang_analyzer_dump(callIdx); // default-warning {{2 S32}} | ||
| // enabled-warning@-1 {{1 S32}} | ||
| // enabled-warning@-2 {{2 S32}} | ||
| for (i = 0; i < getNum(); i++); | ||
| return i; | ||
| } | ||
|
|
||
| int outer_1_ambiguous_loop(void) { | ||
| return inner_ambiguous_loop(1); | ||
| } | ||
| int outer_2_ambiguous_loop(void) { | ||
| return inner_ambiguous_loop(2); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.