Skip to content

Commit f8d5a87

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

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 {
23+
0
24+
} else {
25+
s.len() - 4
26+
};
27+
&s[start..]
28+
}
29+
30+
// asm-label: last_four_optimized:
31+
#[no_mangle]
32+
pub fn last_four_optimized(s: &[u8]) -> &[u8] {
33+
// This implementation has always been optimized correctly
34+
// asm-not: slice_start_index_len_fail
35+
if s.len() <= 4 {
36+
&s[0..]
37+
} else {
38+
&s[s.len() - 4..]
39+
}
40+
}
41+
42+
// For comparison, this should still produce the branch
43+
// asm-label: test_bounds_check_happens:
44+
#[no_mangle]
45+
pub fn test_bounds_check_happens(s: &[u8], i: usize) -> &[u8] {
46+
// asm: slice_start_index_len_fail
47+
&s[i..]
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 {
21+
0
22+
} else {
23+
s.len() - 4
24+
};
25+
&s[start..]
26+
}
27+
28+
// CHECK-LABEL: @last_four_optimized(
29+
#[no_mangle]
30+
pub fn last_four_optimized(s: &[u8]) -> &[u8] {
31+
// This implementation avoids the issue by moving the slice operation
32+
// inside the conditional.
33+
// CHECK-NOT: slice_start_index_len_fail
34+
// CHECK-NOT: unreachable
35+
if s.len() <= 4 {
36+
&s[0..]
37+
} else {
38+
&s[s.len() - 4..]
39+
}
40+
}
41+
42+
// Just to verify we're correctly checking for the right thing
43+
// CHECK-LABEL: @test_bounds_check_happens(
44+
#[no_mangle]
45+
pub fn test_bounds_check_happens(s: &[u8], i: usize) -> &[u8] {
46+
// CHECK: slice_start_index_len_fail
47+
&s[i..]
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 {
13+
0
14+
} else {
15+
s.len() - 4
16+
};
17+
&s[start..] // This should not generate an unreachable bounds check failure
18+
}
19+
20+
fn last_four_optimized(s: &[u8]) -> &[u8] {
21+
if s.len() <= 4 {
22+
&s[0..]
23+
} else {
24+
&s[s.len() - 4..]
25+
}
26+
}
27+
28+
fn generic_last_n<T>(s: &[T], n: usize) -> &[T] {
29+
let start = if s.len() <= n {
30+
0
31+
} else {
32+
s.len() - n
33+
};
34+
&s[start..] // This should not generate an unreachable bounds check failure
35+
}
36+
37+
fn main() {
38+
// Test with different slice sizes to exercise all code paths
39+
let empty: [u8; 0] = [];
40+
let small = [1, 2, 3];
41+
let large = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
42+
43+
// Test the first implementation
44+
assert_eq!(last_four(&empty), &[]);
45+
assert_eq!(last_four(&small), &[1, 2, 3]);
46+
assert_eq!(last_four(&large), &[7, 8, 9, 10]);
47+
48+
// Test the optimized implementation
49+
assert_eq!(last_four_optimized(&empty), &[]);
50+
assert_eq!(last_four_optimized(&small), &[1, 2, 3]);
51+
assert_eq!(last_four_optimized(&large), &[7, 8, 9, 10]);
52+
53+
// Test the generic implementation
54+
assert_eq!(generic_last_n(&empty, 2), &[]);
55+
assert_eq!(generic_last_n(&small, 2), &[2, 3]);
56+
assert_eq!(generic_last_n(&large, 5), &[6, 7, 8, 9, 10]);
57+
}

0 commit comments

Comments
 (0)