Compiler projects using llvm
; RUN: opt        -lower-global-dtors -S < %s | FileCheck %s --implicit-check-not=llvm.global_dtors
; RUN: opt -passes=lower-global-dtors -S < %s | FileCheck %s --implicit-check-not=llvm.global_dtors

; Test that @llvm.global_dtors is properly lowered into @llvm.global_ctors,
; grouping dtor calls by priority and associated symbol.

declare void @orig_ctor()
declare void @orig_dtor0()
declare void @orig_dtor1a()
declare void @orig_dtor1b()
declare void @orig_dtor1c0()
declare void @orig_dtor1c1a()
declare void @orig_dtor1c1b()
declare void @orig_dtor1c2a()
declare void @orig_dtor1c2b()
declare void @orig_dtor1c3()
declare void @orig_dtor1d()
declare void @orig_dtor65535()
declare void @orig_dtor65535c0()
declare void @after_the_null()

@associatedc0 = external global i8
@associatedc1 = external global i8
@associatedc2 = global i8 42
@associatedc3 = global i8 84

@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [
  { i32, void ()*, i8* } { i32 200, void ()* @orig_ctor, i8* null }
]

@llvm.global_dtors = appending global [14 x { i32, void ()*, i8* }] [
  { i32, void ()*, i8* } { i32 0, void ()* @orig_dtor0, i8* null },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1a, i8* null },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1b, i8* null },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1c0, i8* @associatedc0 },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1c1a, i8* @associatedc1 },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1c1b, i8* @associatedc1 },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1c2a, i8* @associatedc2 },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1c2b, i8* @associatedc2 },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1c3, i8* @associatedc3 },
  { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1d, i8* null },
  { i32, void ()*, i8* } { i32 65535, void ()* @orig_dtor65535c0, i8* @associatedc0 },
  { i32, void ()*, i8* } { i32 65535, void ()* @orig_dtor65535, i8* null },
  { i32, void ()*, i8* } { i32 65535, void ()* null, i8* null },
  { i32, void ()*, i8* } { i32 65535, void ()* @after_the_null, i8* null }
]

; CHECK: @associatedc0 = external global i8
; CHECK: @associatedc1 = external global i8
; CHECK: @associatedc2 = global i8 42
; CHECK: @associatedc3 = global i8 84
; CHECK: @__dso_handle = extern_weak hidden constant i8

; CHECK-LABEL: @llvm.global_ctors = appending global [10 x { i32, void ()*, i8* }] [
; CHECK-SAME:  { i32, void ()*, i8* } { i32 200, void ()* @orig_ctor, i8* null },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 0, void ()* @register_call_dtors.0, i8* null },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 1, void ()* @"register_call_dtors.1$0", i8* null },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 1, void ()* @"register_call_dtors.1$1.associatedc0", i8* @associatedc0 },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 1, void ()* @"register_call_dtors.1$2.associatedc1", i8* @associatedc1 },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 1, void ()* @"register_call_dtors.1$3.associatedc2", i8* @associatedc2 },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 1, void ()* @"register_call_dtors.1$4.associatedc3", i8* @associatedc3 },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 1, void ()* @"register_call_dtors.1$5", i8* null },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 65535, void ()* @"register_call_dtors$0.associatedc0", i8* @associatedc0 },
; CHECK-SAME:  { i32, void ()*, i8* } { i32 65535, void ()* @"register_call_dtors$1", i8* null }]

; CHECK: declare void @orig_ctor()
; CHECK: declare void @orig_dtor0()
; --- other dtors here ---
; CHECK: declare void @after_the_null()

; CHECK: declare i32 @__cxa_atexit(void (i8*)*, i8*, i8*)

; CHECK-LABEL: define private void @call_dtors.0(i8* %0)
; CHECK:       call void @orig_dtor0()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @register_call_dtors.0()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @call_dtors.0, i8* null, i8* @__dso_handle)
; CHECK-NEXT:  %0 = icmp ne i32 %call, 0
; CHECK-NEXT:  br i1 %0, label %fail, label %return
; CHECK-EMPTY:
; CHECK-NEXT:  fail:
; CHECK-NEXT:    call void @llvm.trap()
; CHECK-NEXT:    unreachable
; CHECK-EMPTY:
; CHECK-NEXT:  return:
; CHECK-NEXT:    ret void

; CHECK-LABEL: define private void @"call_dtors.1$0"(i8* %0)
; CHECK:       call void @orig_dtor1b()
; CHECK-NEXT:  call void @orig_dtor1a()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors.1$0"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors.1$0", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors.1$1.associatedc0"(i8* %0)
; CHECK:       call void @orig_dtor1c0()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors.1$1.associatedc0"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors.1$1.associatedc0", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors.1$2.associatedc1"(i8* %0)
; CHECK:       call void @orig_dtor1c1b()
; CHECK-NEXT:  call void @orig_dtor1c1a()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors.1$2.associatedc1"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors.1$2.associatedc1", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors.1$3.associatedc2"(i8* %0)
; CHECK:       call void @orig_dtor1c2b()
; CHECK-NEXT:  call void @orig_dtor1c2a()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors.1$3.associatedc2"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors.1$3.associatedc2", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors.1$4.associatedc3"(i8* %0)
; CHECK:       call void @orig_dtor1c3()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors.1$4.associatedc3"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors.1$4.associatedc3", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors.1$5"(i8* %0)
; CHECK:       call void @orig_dtor1d()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors.1$5"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors.1$5", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors$0.associatedc0"(i8* %0)
; CHECK:       call void @orig_dtor65535c0()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors$0.associatedc0"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors$0.associatedc0", i8* null, i8* @__dso_handle)

; CHECK-LABEL: define private void @"call_dtors$1"(i8* %0)
; CHECK:       call void @orig_dtor65535()
; CHECK-NEXT:  ret void

; CHECK-LABEL: define private void @"register_call_dtors$1"()
; CHECK:       %call = call i32 @__cxa_atexit(void (i8*)* @"call_dtors$1", i8* null, i8* @__dso_handle)


; This function is listed after the null terminator, so it should
; be excluded.

; CHECK-NOT: after_the_null