Keep bounds-check temporaries that are still read after slice index reconstruction - #320
Conversation
…econstruction With optimizations enabled, rustc reuses a length the program computes itself (`let n = a.len()`, or `a[a.len() - 1]`) as the bounds check's length operand. remove_bounds_check_setup NOP'd every assignment to the bounds check's condition and length locals in the assert block, so the program's own `len()` result lost its definition while still being read, and the analysis panicked in FunctionType::remove_param. Remove a temporary only when nothing in the body reads it any more, after the assert terminator is replaced. This subsumes the special case for the receiver, which the reconstructed call reads. Fixes #319 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171U9qC8QWRU95brd4pMBLX
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
The This PR doesn't cause that failure. I generated the CHC system for this test with No fix exists for this timeout yet. I've re-run the failed job once. Generated by Claude Code |
Fixes #319.
The problem, in MIR
Take this function:
At
-C opt-level=0, rustc computes the length twice: once for the user'slen()(_3) and once more for the bounds check (_4):At
-C opt-level=1and above, GVN merges the two, so the bounds check reuses the user's_3:reconstruct_slice_indexingreplaces theassertwith anIndex::indexcall. Thenremove_bounds_check_setupturns intoNopevery assignment inbb0to the assert's condition (_4) and length (_3). Onmain, the result is:_3is still read, but its definition is gone. The analysis then panics inFunctionType::remove_param:At
-C opt-level=0none of this happens, because the deleted_4and_5are read only by the bounds check.A user-written
let n = a.len()whosenis used later is reused the same way. Inlet n = a.len(); let x = a[i]; x + n as i64, the assert reads_3, and so does_6 = copy _3 as i64in the next block.The fix
remove_bounds_check_setupnow runs after theasserthas been replaced by the call. It looks at each candidate local in turn: the condition, then the length, then the operand ofPtrMetadata. A candidate is removed only if nothing in the body still reads it.For
lastat-C opt-level=1, the result is:At
-C opt-level=0,_5(Lt) and_4(PtrMetadata) are removed exactly as before. The receiver_1is read by the new call, so it is always kept. That makes the old receiver special case unnecessary, and this PR removes it.These decisions are logged at trace level (
RUST_LOG=thrust::analyze::reconstruct_slice_indexing=trace):-C opt-level=0-C opt-level=1_5removed_4removed_4removed_3kept (still read)PtrMetadataoperand_1kept (receiver)_1kept (receiver)Test
tests/ui/{pass,fail}/slice_index_len.rscheckslice[slice.len() - 1]at-C opt-level=1. Onmain, both panic as shown above. With this PR, the pass test issafeand the fail test isUnsat.Validation
cargo fmt --all -- --checkandcargo clippy -- -D warningsare clean.cargo testpasses: 392 UI tests plus the unit tests.🤖 Generated with Claude Code
https://claude.ai/code/session_0171U9qC8QWRU95brd4pMBLX