Summary
LLVM optimizes away a redundant checked integer conversion inside a loop on x86_64, but not on s390x.
This reproduces with the exact reduced shape from Rust’s codegen-llvm test tests/codegen-llvm/range-len-try-from.rs, compiled with optimization enabled. The call to unwrap_failed for the loop-local conversion u32::try_from(i).unwrap() is proven redundant on x86_64, but remains as an extra path on s390x.
The same Rust source produces the same high-level checked-conversion structure before optimization, and the divergence only appears after target-specific LLVM optimization (x86_64 removes the inner unwrap_failed path while s390x does not), which strongly suggests the missed optimization is in LLVM rather than Rust.
Targets
- affected: s390x-unknown-linux-gnu
- optimized as expected: x86_64-unknown-linux-gnu
Observed behavior
With -Copt-level=3:
- x86_64: the function contains only one
unwrap_failed call, corresponding to the expected outer check:
u32::try_from(candidates.len()).unwrap()
- s390x: the function contains two
unwrap_failed calls:
- the expected outer length check
- an extra path from the loop-local
u32::try_from(i).unwrap()
This suggests LLVM is failing on s390x to prove that the loop index is bounded by the already-checked slice length.
Reproducer
use std::convert::TryFrom;
use std::hint::black_box;
#[no_mangle]
pub fn score_round_enumerate(candidates: &[bool]) {
u32::try_from(candidates.len()).unwrap();
for (i, _) in candidates.iter().enumerate() {
u32::try_from(i).unwrap();
black_box(42);
}
}
Compile:
rustc -Copt-level=3 --emit=llvm-ir --crate-type=lib reproduce.rs --target s390x-unknown-linux-gnu
rustc -Copt-level=3 --emit=llvm-ir --crate-type=lib reproduce.rs --target x86_64-unknown-linux-gnu
Expected result
Both targets should retain only the single unwrap_failed associated with: u32::try_from(candidates.len()).unwrap();
The loop-local conversion unwrap_failed call (u32::try_from(i).unwrap();) should be optimized away on both targets.
Actual result
On s390x the function retain an extra unwrap_failed from the loop-local checked conversion. On x86_64, that extra unwrap_failed path is removed.
Attachments:
reproduce.rs.txt
reproduce_s390x_noopt.ll.txt
reproduce_s390x_o3.ll.txt
reproduce_x86_64_noopt.ll.txt
reproduce_x86_64_o3.ll.txt
Summary
LLVM optimizes away a redundant checked integer conversion inside a loop on x86_64, but not on s390x.
This reproduces with the exact reduced shape from Rust’s codegen-llvm test
tests/codegen-llvm/range-len-try-from.rs, compiled with optimization enabled. The call tounwrap_failedfor the loop-local conversionu32::try_from(i).unwrap()is proven redundant on x86_64, but remains as an extra path on s390x.The same Rust source produces the same high-level checked-conversion structure before optimization, and the divergence only appears after target-specific LLVM optimization (x86_64 removes the inner
unwrap_failedpath while s390x does not), which strongly suggests the missed optimization is in LLVM rather than Rust.Targets
Observed behavior
With
-Copt-level=3:unwrap_failedcall, corresponding to the expected outer check:u32::try_from(candidates.len()).unwrap()unwrap_failedcalls:u32::try_from(i).unwrap()This suggests LLVM is failing on s390x to prove that the loop index is bounded by the already-checked slice length.
Reproducer
Compile:
Expected result
Both targets should retain only the single unwrap_failed associated with:
u32::try_from(candidates.len()).unwrap();The loop-local conversion
unwrap_failedcall (u32::try_from(i).unwrap();) should be optimized away on both targets.Actual result
On s390x the function retain an extra unwrap_failed from the loop-local checked conversion. On x86_64, that extra
unwrap_failedpath is removed.Attachments:
reproduce.rs.txt
reproduce_s390x_noopt.ll.txt
reproduce_s390x_o3.ll.txt
reproduce_x86_64_noopt.ll.txt
reproduce_x86_64_o3.ll.txt