Change:
Consider following functions The first pattern is what we often write and the second one is the most efficient manually unrolled version. Surprisingly rustc fails to optimize the former one into the latter as you can see in : P.S. Yes, I'm aware of
It looks like Rust's niche-filling optimization is defeating LLVM's optimizations. In , the temporary uses the niche in for the discriminant, resulting in the following layout: LLVM ends up inlining the calls to and , but is unable to optimize the particular combination of and that ends up in the LLVM IR. This can be seen by changing the type to a tuple of , which inhibits Rust's niche-filling optimization: (): which generates the following ASM: LLVM has all of the information it needs to optimize the original IR - however, it doesn't seem to have an instcombine special-case that would allow it to do so. Unfortunately, neither nor gets MIR-inlined with , due to both of them having too high of a computed cost: The fact that LLVM decides to inline these functions suggets that we might be overly conservative in how we calculating inlining cost. Hopefully, this situation will be improved by , which specifically calls out as having improved MIR generation.
I can confirm that, with , becomes eligible for MIR inlining in , and the two functions compile to the same assembly with .
Should this issue be closed now? Since it's been solved by . Or maybe since it's still under , and therefore unstable, it's still worth left open? :)
This won't be fixed until MIR inlining becomes more usable and should remain open. I would like to see an "I-slow-fixed-by-MIR-inlining" tag so issues like this and can be triaged more efficiently.
That makes a lot of sense :slightlysmilingface:
It looks like the code is now optimized properly in recent : , maybe because of enabled MIR inlining. Maybe we could add a codegen test for this?

Discussion:
<ins> //@ assembly-output: emit-asm //@ compile-flags:-Copt-level=3 //@ only-x86_64 #![crate_type = "lib"] #[no_mangle] type T = u8; type T1 = (T, T, T, T, T, T, T, T); type T2 = [T; 8]; #[no_mangle] // CHECK-LABEL: foo1a // CHECK: cmp // CHECK-NEXT: set // CHECK-NEXT: ret pub fn foo1a(a: T1, b: T1) -> bool { a == b } #[no_mangle] // CHECK-LABEL: foo1b // CHECK: mov // CHECK-NEXT: cmp // CHECK-NEXT: set // CHECK-NEXT: ret pub fn foo1b(a: &T1, b: &T1) -> bool { a == b } </ins>

<ins> //@ compile-flags: -O //@ min-llvm-version: 17 #![crate_type = "lib"] #[no_mangle] // CHECK-LABEL: @foo // CHECK: getelementptr inbounds // CHECK-NEXT: load i64 // CHECK-NEXT: icmp eq i64 // CHECK-NEXT: br i1 #[no_mangle] pub fn foo(input: &mut &[u64]) -> Option<u64> { let (first, rest) = input.split_first()?; *input = rest; Some(*first) } </ins>

<ins> //@ compile-flags: -O // XXX: The x86-64 assembly get optimized correclty. But llvm-ir output is not until llvm 18? //@ min-llvm-version: 18 #![crate_type = "lib"] pub enum K{ A(Box<[i32]>), B(Box<[u8]>), C(Box<[String]>), D(Box<[u16]>), } #[no_mangle] // CHECK-LABEL: @get_len // CHECK: getelementptr inbounds // CHECK-NEXT: load // CHECK-NEXT: ret i64 // CHECK-NOT: switch pub fn get_len(arg: &K)->usize{ match arg { K::A(ref lst)=>lst.len(), K::B(ref lst)=>lst.len(), K::C(ref lst)=>lst.len(), K::D(ref lst)=>lst.len(), } } </ins>

<ins> //@ compile-flags: -O // This regress since Rust version 1.72. //@ min-llvm-version: 18.1.4 #![crate_type = "lib"] use std::convert::TryInto; const N: usize = 24; #[no_mangle] // CHECK-LABEL: @example // CHECK-NOT: unwrap_failed pub fn example(a: Vec<u8>) -> u8 { if a.len() != 32 { return 0; } let a: [u8; 32] = a.try_into().unwrap(); a[15] + a[N] } </ins>

<ins> //@ compile-flags: -O //@ min-llvm-version: 17 #![crate_type = "lib"] // CHECK-LABEL: @write_u8_variant_a // CHECK: getelementptr // CHECK-NEXT: icmp ugt #[no_mangle] pub fn write_u8_variant_a( bytes: &mut [u8], buf: u8, offset: usize, ) -> Option<&mut [u8]> { let buf = buf.to_le_bytes(); bytes .get_mut(offset..).and_then(|bytes| bytes.get_mut(..buf.len())) } </ins>

<ins> // in Rust 1.73, -O and opt-level=3 optimizes differently //@ compile-flags: -C opt-level=3 //@ min-llvm-version: 17 #![crate_type = "lib"] use std::cmp::max; #[no_mangle] // CHECK-LABEL: @foo // CHECK-NOT: slice_start_index_len_fail // CHECK-NOT: unreachable pub fn foo(v: &mut Vec<u8>, size: usize)-> Option<&mut [u8]> { if v.len() > max(1, size) { let start = v.len() - size; Some(&mut v[start..]) } else { None } } </ins>

<ins> //@ compile-flags: -O //@ min-llvm-version: 18 #![crate_type = "lib"] // CHECK-LABEL: @div2 // CHECK: ashr i32 %a, 1 // CHECK-NEXT: ret i32 #[no_mangle] pub fn div2(a: i32) -> i32 { a.div_euclid(2) } </ins>

<ins> //@ compile-flags: -O #![crate_type = "lib"] const N: usize = 3; pub type T = u8; #[no_mangle] // CHECK-LABEL: @split_mutiple // CHECK-NOT: unreachable pub fn split_mutiple(slice: &[T]) -> (&[T], &[T]) { let len = slice.len() / N; slice.split_at(len * N) } </ins>

<ins> //@ compile-flags: -O //@ min-llvm-version: 17 #![crate_type = "lib"] #[no_mangle] // CHECK-LABEL: @foo // CHECK: {{.*}}: // CHECK: ret // CHECK-NOT: unreachable pub fn foo(arr: &mut [u32]) { for i in 0..arr.len() { for j in 0..i { assert!(j < arr.len()); } } } </ins>

<ins> //@ compile-flags: -O //@ min-llvm-version: 18 #![crate_type = "lib"] use std::ptr::NonNull; // CHECK-LABEL: @slice_ptr_len_1 // CHECK: {{.*}}: // CHECK-NEXT: ret i64 %ptr.1 #[no_mangle] pub fn slice_ptr_len_1(ptr: *const [u8]) -> usize { let ptr = ptr.cast_mut(); if let Some(ptr) = NonNull::new(ptr) { ptr.len() } else { // We know ptr is null, so we know ptr.wrapping_byte_add(1) is not null. NonNull::new(ptr.wrapping_byte_add(1)).unwrap().len() } } </ins>