Skip to content

Commit 599a94d

Browse files
committed
Add tests for LLVM 20 slice bounds check optimization
1 parent 85f518e commit 599a94d

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ assembly-output: emit-asm
2+
//@ compile-flags: -Copt-level=3
3+
//@ only-x86_64
4+
5+
#![crate_type = "lib"]
6+
7+
// This test verifies that LLVM 20 properly optimizes the assembly for
8+
// code that accesses the last few elements of a slice.
9+
// The issue fixed in LLVM 20 was that the assembly would include an unreachable
10+
// branch to slice_start_index_len_fail even when the bounds check was provably safe.
11+
12+
// Check for proper assembly generation in this function:
13+
// asm-label: last_four_initial:
14+
#[no_mangle]
15+
pub fn last_four_initial(s: &[u8]) -> &[u8] {
16+
// Previously this would generate a branch to slice_start_index_len_fail
17+
// that is unreachable. The LLVM 20 fix should eliminate this branch.
18+
//
19+
// We should see no ".LBB" label with a call to slice_start_index_len_fail
20+
//
21+
// asm-not: slice_start_index_len_fail
22+
let start = if s.len() <= 4 { 0 } else { s.len() - 4 };
23+
&s[start..]
24+
}
25+
26+
// asm-label: last_four_optimized:
27+
#[no_mangle]
28+
pub fn last_four_optimized(s: &[u8]) -> &[u8] {
29+
// This implementation has always been optimized correctly
30+
// asm-not: slice_start_index_len_fail
31+
if s.len() <= 4 { &s[0..] } else { &s[s.len() - 4..] }
32+
}
33+
34+
// For comparison, this should still produce the branch
35+
// asm-label: test_bounds_check_happens:
36+
#[no_mangle]
37+
pub fn test_bounds_check_happens(s: &[u8], i: usize) -> &[u8] {
38+
// asm: slice_start_index_len_fail
39+
&s[i..]
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ compile-flags: -Copt-level=3
2+
//@ only-x86_64
3+
#![crate_type = "lib"]
4+
5+
// This test verifies that LLVM 20 properly optimizes the bounds check
6+
// when accessing the last few elements of a slice with proper conditions.
7+
// Previously, this would generate an unreachable branch to
8+
// slice_start_index_len_fail even when the bounds check was provably safe.
9+
10+
// CHECK-LABEL: @last_four_initial(
11+
#[no_mangle]
12+
pub fn last_four_initial(s: &[u8]) -> &[u8] {
13+
// In a suboptimal implementation this would contain a call to
14+
// slice_start_index_len_fail that is unreachable.
15+
16+
// The fix exists in LLVM 20: We use this implementation with
17+
// bounds check outside of slice operation.
18+
// CHECK-NOT: slice_start_index_len_fail
19+
// CHECK-NOT: unreachable
20+
let start = if s.len() <= 4 { 0 } else { s.len() - 4 };
21+
&s[start..]
22+
}
23+
24+
// CHECK-LABEL: @last_four_optimized(
25+
#[no_mangle]
26+
pub fn last_four_optimized(s: &[u8]) -> &[u8] {
27+
// This implementation avoids the issue by moving the slice operation
28+
// inside the conditional.
29+
// CHECK-NOT: slice_start_index_len_fail
30+
// CHECK-NOT: unreachable
31+
if s.len() <= 4 { &s[0..] } else { &s[s.len() - 4..] }
32+
}
33+
34+
// Just to verify we're correctly checking for the right thing
35+
// CHECK-LABEL: @test_bounds_check_happens(
36+
#[no_mangle]
37+
pub fn test_bounds_check_happens(s: &[u8], i: usize) -> &[u8] {
38+
// CHECK: slice_start_index_len_fail
39+
&s[i..]
40+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//@ run-pass
2+
//@ compile-flags: -Copt-level=3
3+
4+
// This test verifies that the LLVM 20 fix for slice bounds check optimization
5+
// correctly identifies that the bounds checks are unnecessary in patterns
6+
// where we access the last few elements of a slice.
7+
//
8+
// Before the fix, this code would generate an unreachable bounds check
9+
// that could lead to suboptimal code generation.
10+
11+
fn last_four(s: &[u8]) -> &[u8] {
12+
let start = if s.len() <= 4 { 0 } else { s.len() - 4 };
13+
&s[start..] // This should not generate an unreachable bounds check failure
14+
}
15+
16+
fn last_four_optimized(s: &[u8]) -> &[u8] {
17+
if s.len() <= 4 { &s[0..] } else { &s[s.len() - 4..] }
18+
}
19+
20+
fn generic_last_n<T>(s: &[T], n: usize) -> &[T] {
21+
let start = if s.len() <= n { 0 } else { s.len() - n };
22+
&s[start..] // This should not generate an unreachable bounds check failure
23+
}
24+
25+
fn main() {
26+
// Test with different slice sizes to exercise all code paths
27+
let empty: [u8; 0] = [];
28+
let small = [1, 2, 3];
29+
let large = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
30+
31+
// Test the first implementation
32+
assert_eq!(last_four(&empty), &[]);
33+
assert_eq!(last_four(&small), &[1, 2, 3]);
34+
assert_eq!(last_four(&large), &[7, 8, 9, 10]);
35+
36+
// Test the optimized implementation
37+
assert_eq!(last_four_optimized(&empty), &[]);
38+
assert_eq!(last_four_optimized(&small), &[1, 2, 3]);
39+
assert_eq!(last_four_optimized(&large), &[7, 8, 9, 10]);
40+
41+
// Test the generic implementation
42+
assert_eq!(generic_last_n(&empty, 2), &[]);
43+
assert_eq!(generic_last_n(&small, 2), &[2, 3]);
44+
assert_eq!(generic_last_n(&large, 5), &[6, 7, 8, 9, 10]);
45+
}

0 commit comments

Comments
 (0)