Functions with uninhabited return values codegen trap instead of unreachable · Issue #59793 · rust-lang/rust (original) (raw)

With #59639, I would expect the following two functions to generate the same LLVM IR:

#[derive(Clone, Copy)] pub enum EmptyEnum {}

#[no_mangle] pub fn empty(x: &EmptyEnum) -> EmptyEnum { *x }

#[no_mangle] pub fn unreach(x: &EmptyEnum) -> EmptyEnum { unsafe { std::hint::unreachable_unchecked() } }

However, they do not:

; playground::empty
; Function Attrs: noreturn nounwind nonlazybind uwtable
define void @_ZN10playground5empty17he3f416ff39576b93E(%EmptyEnum* noalias nocapture nonnull readonly align 1 %x) unnamed_addr #0 {
start:
  tail call void @llvm.trap()
  unreachable
}

; playground::unreach
; Function Attrs: norecurse noreturn nounwind nonlazybind readnone uwtable
define void @_ZN10playground7unreach17h416ea08edc51ffc9E(%EmptyEnum* noalias nocapture nonnull readonly align 1 %x) unnamed_addr #1 {
start:
  unreachable
}

Namely, empty triggers a well-defined trap while unreach causes UB.

That this makes a difference can be seen when adding:

pub fn test_unreach(x: bool) -> i32 { if x { 42 } else { unsafe { unreach(&*(8 as *const EmptyEnum)) }; 13 } }

pub fn test_empty(x: bool) -> i32 { if x { 42 } else { unsafe { empty(&*(8 as *const EmptyEnum)) }; 13 } }

The first becomes

; playground::test_unreach
; Function Attrs: norecurse nounwind nonlazybind readnone uwtable
define i32 @_ZN10playground12test_unreach17he4cbbc8d69597b0eE(i1 zeroext %x) unnamed_addr #2 {
start:
  ret i32 42
}

but the second becomes

; playground::test_empty
; Function Attrs: nounwind nonlazybind uwtable
define i32 @_ZN10playground10test_empty17hcb53970308f4f532E(i1 zeroext %x) unnamed_addr #3 {
start:
  br i1 %x, label %bb1, label %bb2

bb1:                                              ; preds = %start
  ret i32 42

bb2:                                              ; preds = %start
  tail call void @llvm.trap() #5
  unreachable
}

because the second branch does not actually cause UB.

Cc @eddyb @cuviper