JIT: Guard loop cloning against unsigned decreasing IV wraparound - #133915
AndyAyersMS wants to merge 3 commits into
Conversation
Loop cloning was not doing propre checks for cloning a decreasing loop with a non-unit stride and an unsigned control variable. The IV might wrap around leading to out of bounds accesses. Only clone such loops when the init and limit are jit-time constants that provably can't lead to wraparound. Fixes dotnet#133824. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 5 pipeline(s). 11 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
🟡 Changes recommended
A critical wraparound issue remains, and the tests do not exercise the constant-proof path.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Guards JIT loop cloning against unsigned decreasing-IV wraparound and adds array/span regression coverage.
Changes:
- Adds constant-bound safety checks for unsigned decreasing loops.
- Adds underflow and safe-iteration tests.
File summaries
| File | Summary | Review findings |
|---|---|---|
src/tests/JIT/opt/Cloning/DownCounted.cs |
Adds array and span regression tests. | Nit (2 votes): Tests do not exercise the constant-proof path; add literal-bound no-inlining helpers. |
src/coreclr/jit/loopcloning.cpp |
Adds unsigned countdown safety checks. | Critical (3 votes): GT_GE is treated like GT_GT, allowing inclusive loops to wrap around. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
Constant-bound proof allowed cloning for unsigned decreasing loops using >= tests when the const limit was below the stride, since it only checked stride divisibility, not the actual last visited value. Compare that value against the stride instead. Also add regression tests with literal bounds to exercise the constant-proof path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for catching this. Fixed both points:
Rebuilt the JIT and re-validated with the standalone repro harness Note This comment was generated with the help of GitHub Copilot. |
There was a problem hiding this comment.
🟡 Changes recommended
A critical cloning guard still permits unsafe unsigned countdown loops that can wrap around.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
Unit-stride countdown loops using an inclusive >= test were exempt from the wraparound check, but >= 0 still underflows on the next decrement. Drop the stride!=1 exemption so the same const-bound proof covers unit stride too. Adds a regression test for this case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Good catch, thanks. The Dropped the Rebuilt the JIT and re-validated with the standalone harness (14 Note This comment was generated with the help of GitHub Copilot. |
There was a problem hiding this comment.
🟡 Changes recommended
Address the GT_NE handling issue and expand arithmetic-proof test coverage.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
src/tests/JIT/opt/Cloning/DownCounted.cs:118
- The new literal tests cover only zero-remainder cases (
(init - limit) % 3 == 0for every stride-3 helper), so they never execute ther != 0branch of the proof atloopcloning.cpp:1362. The parameterized helpers cannot cover it becauseHasConstInit/HasConstLimitare false there; add representative constant safe and unsafe non-zero-remainder cases so this arithmetic is validated.
// init=7, limit=1, stride=3: remainder r=0, so a GT test never
// visits "limit" itself -- always safe to clone.
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
| // actually visits "constLimit" (the smallest in-range value | ||
| // is "constLimit + stride"), so it's safe regardless. | ||
| const unsigned r = ((unsigned)constInit - (unsigned)constLimit) % (unsigned)stride; | ||
| if (((r == 0) && (iterInfo->TestOper() == GT_GT)) || ((unsigned)constLimit + r >= (unsigned)stride)) |
Loop cloning was not doing proper checks for cloning a decreasing loop
with a non-unit stride and an unsigned control variable. The IV might
wrap around leading to out of bounds accesses.
Only clone such loops when the init and limit are jit-time constants
that provably can't lead to wraparound.
Fixes #133824.
Note
This pull request description was created with GitHub Copilot.