Skip to content

Commit f98f095

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

File tree

3 files changed

+128
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)