Skip to content

Functions with uninhabited return values codegen trap instead of unreachable #59793

Open
@RalfJung

Description

@RalfJung

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-codegenArea: Code generationT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions