Reland "Lower `@llvm.global_dtors` using `__cxa_atexit` on MachO"

For MachO, lower `@llvm.global_dtors` into `@llvm_global_ctors` with
`__cxa_atexit` calls to avoid emitting the deprecated `__mod_term_func`.

Reuse the existing `WebAssemblyLowerGlobalDtors.cpp` to accomplish this.

Enable fallback to the old behavior via Clang driver flag
(`-fregister-global-dtors-with-atexit`) or llc / code generation flag
(`-lower-global-dtors-via-cxa-atexit`).  This escape hatch will be
removed in the future.

Differential Revision: https://reviews.llvm.org/D121736
This commit is contained in:
Julian Lettner 2022-03-23 17:37:07 -07:00
parent 39aa202aff
commit 64902d335c
26 changed files with 312 additions and 46 deletions

View File

@ -546,6 +546,8 @@ static bool initTargetOptions(DiagnosticsEngine &Diags,
Options.BinutilsVersion =
llvm::TargetMachine::parseBinutilsVersion(CodeGenOpts.BinutilsVersion);
Options.UseInitArray = CodeGenOpts.UseInitArray;
Options.LowerGlobalDtorsViaCxaAtExit =
CodeGenOpts.RegisterGlobalDtorsWithAtExit;
Options.DisableIntegratedAS = CodeGenOpts.DisableIntegratedAS;
Options.CompressDebugSections = CodeGenOpts.getCompressDebugSections();
Options.RelaxELFRelocations = CodeGenOpts.RelaxELFRelocations;

View File

@ -876,6 +876,14 @@ This pass expects :ref:`LICM <passes-licm>` to be run before it to hoist
invariant conditions out of the loop, to make the unswitching opportunity
obvious.
``-lower-global-dtors``: Lower global destructors
------------------------------------------------------------
This pass lowers global module destructors (``llvm.global_dtors``) by creating
wrapper functions that are registered as global constructors in
``llvm.global_ctors`` and which contain a call to ``__cxa_atexit`` to register
their destructor functions.
``-loweratomic``: Lower atomic intrinsics to non-atomic form
------------------------------------------------------------

View File

@ -93,6 +93,8 @@ std::string getTrapFuncName();
bool getUseCtors();
bool getLowerGlobalDtorsViaCxaAtExit();
bool getRelaxELFRelocations();
bool getDataSections();

View File

@ -119,6 +119,9 @@ public:
void Initialize(MCContext &Ctx, const TargetMachine &TM) override;
MCSection *getStaticDtorSection(unsigned Priority,
const MCSymbol *KeySym) const override;
/// Emit the module flags that specify the garbage collection information.
void emitModuleMetadata(MCStreamer &Streamer, Module &M) const override;

View File

@ -274,6 +274,7 @@ void initializeLowerAtomicLegacyPassPass(PassRegistry&);
void initializeLowerConstantIntrinsicsPass(PassRegistry&);
void initializeLowerEmuTLSPass(PassRegistry&);
void initializeLowerExpectIntrinsicPass(PassRegistry&);
void initializeLowerGlobalDtorsLegacyPassPass(PassRegistry &);
void initializeLowerGuardIntrinsicLegacyPassPass(PassRegistry&);
void initializeLowerWidenableConditionLegacyPassPass(PassRegistry&);
void initializeLowerIntrinsicsPass(PassRegistry&);

View File

@ -145,6 +145,7 @@ namespace {
(void) llvm::createLoopRotatePass();
(void) llvm::createLowerConstantIntrinsicsPass();
(void) llvm::createLowerExpectIntrinsicPass();
(void) llvm::createLowerGlobalDtorsLegacyPass();
(void) llvm::createLowerInvokePass();
(void) llvm::createLowerSwitchPass();
(void) llvm::createNaryReassociatePass();

View File

@ -130,12 +130,13 @@ namespace llvm {
HonorSignDependentRoundingFPMathOption(false), NoZerosInBSS(false),
GuaranteedTailCallOpt(false), StackSymbolOrdering(true),
EnableFastISel(false), EnableGlobalISel(false), UseInitArray(false),
DisableIntegratedAS(false), RelaxELFRelocations(false),
FunctionSections(false), DataSections(false),
IgnoreXCOFFVisibility(false), XCOFFTracebackTable(true),
UniqueSectionNames(true), UniqueBasicBlockSectionNames(false),
TrapUnreachable(false), NoTrapAfterNoreturn(false), TLSSize(0),
EmulatedTLS(false), ExplicitEmulatedTLS(false), EnableIPRA(false),
LowerGlobalDtorsViaCxaAtExit(false), DisableIntegratedAS(false),
RelaxELFRelocations(false), FunctionSections(false),
DataSections(false), IgnoreXCOFFVisibility(false),
XCOFFTracebackTable(true), UniqueSectionNames(true),
UniqueBasicBlockSectionNames(false), TrapUnreachable(false),
NoTrapAfterNoreturn(false), TLSSize(0), EmulatedTLS(false),
ExplicitEmulatedTLS(false), EnableIPRA(false),
EmitStackSizeSection(false), EnableMachineOutliner(false),
EnableMachineFunctionSplitter(false), SupportsDefaultOutlining(false),
EmitAddrsig(false), EmitCallSiteInfo(false),
@ -245,6 +246,10 @@ namespace llvm {
/// constructors.
unsigned UseInitArray : 1;
/// Use __cxa_atexit to register global destructors; determines how
/// llvm.global_dtors is lowered.
unsigned LowerGlobalDtorsViaCxaAtExit : 1;
/// Disable the integrated assembler.
unsigned DisableIntegratedAS : 1;

View File

@ -17,7 +17,6 @@ enum class AsanDtorKind {
None, ///< Do not emit any destructors for ASan
Global, ///< Append to llvm.global_dtors
Invalid, ///< Not a valid destructor Kind.
// TODO(dliew): Add more more kinds.
};
/// Mode of ASan detect stack use after return

View File

@ -155,6 +155,12 @@ FunctionPass *createAssumeSimplifyPass();
// don't block SCEV.
//
Pass *createCanonicalizeFreezeInLoopsPass();
//===----------------------------------------------------------------------===//
// LowerGlobalDtorsLegacy - Lower @llvm.global_dtors by creating wrapper
// functions that are registered in @llvm.global_ctors and which contain a call
// to `__cxa_atexit` to register their destructor functions.
ModulePass *createLowerGlobalDtorsLegacyPass();
} // namespace llvm
#endif

View File

@ -0,0 +1,28 @@
//===- LowerGlobalDtors.h - Lower @llvm.global_dtors ----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This pass lowers @llvm.global_dtors by creating wrapper functions that are
// registered in @llvm.global_ctors and which contain a call to `__cxa_atexit`
// to register their destructor functions.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_TRANSFORMS_UTILS_LOWERGLOBALDTORS_H
#define LLVM_TRANSFORMS_UTILS_LOWERGLOBALDTORS_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class LowerGlobalDtorsPass : public PassInfoMixin<LowerGlobalDtorsPass> {
public:
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};
} // namespace llvm
#endif // LLVM_TRANSFORMS_UTILS_LOWERGLOBALDTORS_H

View File

@ -58,6 +58,7 @@ void llvm::initializeCodeGen(PassRegistry &Registry) {
initializeLiveStacksPass(Registry);
initializeLiveVariablesPass(Registry);
initializeLocalStackSlotPassPass(Registry);
initializeLowerGlobalDtorsLegacyPassPass(Registry);
initializeLowerIntrinsicsPass(Registry);
initializeMIRAddFSDiscriminatorsPass(Registry);
initializeMIRCanonicalizerPass(Registry);

View File

@ -79,6 +79,7 @@ CGOPT(bool, StackSymbolOrdering)
CGOPT(bool, StackRealign)
CGOPT(std::string, TrapFuncName)
CGOPT(bool, UseCtors)
CGOPT(bool, LowerGlobalDtorsViaCxaAtExit)
CGOPT(bool, RelaxELFRelocations)
CGOPT_EXP(bool, DataSections)
CGOPT_EXP(bool, FunctionSections)
@ -346,6 +347,12 @@ codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
cl::init(false));
CGBINDOPT(UseCtors);
static cl::opt<bool> LowerGlobalDtorsViaCxaAtExit(
"lower-global-dtors-via-cxa-atexit",
cl::desc("Lower llvm.global_dtors (global destructors) via __cxa_atexit"),
cl::init(true));
CGBINDOPT(LowerGlobalDtorsViaCxaAtExit);
static cl::opt<bool> RelaxELFRelocations(
"relax-elf-relocations",
cl::desc(
@ -529,6 +536,7 @@ codegen::InitTargetOptionsFromCodeGenFlags(const Triple &TheTriple) {
Options.GuaranteedTailCallOpt = getEnableGuaranteedTailCallOpt();
Options.StackSymbolOrdering = getStackSymbolOrdering();
Options.UseInitArray = !getUseCtors();
Options.LowerGlobalDtorsViaCxaAtExit = getLowerGlobalDtorsViaCxaAtExit();
Options.RelaxELFRelocations = getRelaxELFRelocations();
Options.DataSections =
getExplicitDataSections().getValueOr(TheTriple.hasDefaultDataSections());

View File

@ -1176,6 +1176,15 @@ void TargetLoweringObjectFileMachO::Initialize(MCContext &Ctx,
dwarf::DW_EH_PE_indirect | dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_sdata4;
}
MCSection *TargetLoweringObjectFileMachO::getStaticDtorSection(
unsigned Priority, const MCSymbol *KeySym) const {
// TODO(yln): Remove -lower-global-dtors-via-cxa-atexit fallback flag
// (LowerGlobalDtorsViaCxaAtExit) and always issue a fatal error here.
if (TM->Options.LowerGlobalDtorsViaCxaAtExit)
report_fatal_error("@llvm.global_dtors should have been lowered already");
return StaticDtorSection;
}
void TargetLoweringObjectFileMachO::emitModuleMetadata(MCStreamer &Streamer,
Module &M) const {
// Emit the linker options if present.
@ -2175,8 +2184,7 @@ MCSection *TargetLoweringObjectFileWasm::getStaticCtorSection(
MCSection *TargetLoweringObjectFileWasm::getStaticDtorSection(
unsigned Priority, const MCSymbol *KeySym) const {
llvm_unreachable("@llvm.global_dtors should have been lowered already");
return nullptr;
report_fatal_error("@llvm.global_dtors should have been lowered already");
}
//===----------------------------------------------------------------------===//

View File

@ -894,6 +894,12 @@ void TargetPassConfig::addIRPasses() {
addPass(&ShadowStackGCLoweringID);
addPass(createLowerConstantIntrinsicsPass());
// For MachO, lower @llvm.global_dtors into @llvm_global_ctors with
// __cxa_atexit() calls to avoid emitting the deprecated __mod_term_func.
if (TM->getTargetTriple().isOSBinFormatMachO() &&
TM->Options.LowerGlobalDtorsViaCxaAtExit)
addPass(createLowerGlobalDtorsLegacyPass());
// Make sure that no unreachable blocks are instruction selected.
addPass(createUnreachableBlockEliminationPass());

View File

@ -230,6 +230,7 @@
#include "llvm/Transforms/Utils/LibCallsShrinkWrap.h"
#include "llvm/Transforms/Utils/LoopSimplify.h"
#include "llvm/Transforms/Utils/LoopVersioning.h"
#include "llvm/Transforms/Utils/LowerGlobalDtors.h"
#include "llvm/Transforms/Utils/LowerInvoke.h"
#include "llvm/Transforms/Utils/LowerSwitch.h"
#include "llvm/Transforms/Utils/Mem2Reg.h"

View File

@ -76,6 +76,7 @@ MODULE_PASS("invalidate<all>", InvalidateAllAnalysesPass())
MODULE_PASS("ipsccp", IPSCCPPass())
MODULE_PASS("iroutliner", IROutlinerPass())
MODULE_PASS("print-ir-similarity", IRSimilarityAnalysisPrinterPass(dbgs()))
MODULE_PASS("lower-global-dtors", LowerGlobalDtorsPass())
MODULE_PASS("lowertypetests", LowerTypeTestsPass())
MODULE_PASS("metarenamer", MetaRenamerPass())
MODULE_PASS("mergefunc", MergeFunctionsPass())

View File

@ -35,7 +35,6 @@ add_llvm_target(WebAssemblyCodeGen
WebAssemblyInstrInfo.cpp
WebAssemblyLowerBrUnless.cpp
WebAssemblyLowerEmscriptenEHSjLj.cpp
WebAssemblyLowerGlobalDtors.cpp
WebAssemblyLowerRefTypesIntPtrConv.cpp
WebAssemblyMachineFunctionInfo.cpp
WebAssemblyMCInstLower.cpp

View File

@ -26,7 +26,6 @@ class FunctionPass;
// LLVM IR passes.
ModulePass *createWebAssemblyLowerEmscriptenEHSjLj();
ModulePass *createWebAssemblyLowerGlobalDtors();
ModulePass *createWebAssemblyAddMissingPrototypes();
ModulePass *createWebAssemblyFixFunctionBitcasts();
FunctionPass *createWebAssemblyOptimizeReturned();
@ -61,7 +60,6 @@ ModulePass *createWebAssemblyMCLowerPrePass();
// PassRegistry initialization declarations.
void initializeWebAssemblyAddMissingPrototypesPass(PassRegistry &);
void initializeWebAssemblyLowerEmscriptenEHSjLjPass(PassRegistry &);
void initializeLowerGlobalDtorsPass(PassRegistry &);
void initializeFixFunctionBitcastsPass(PassRegistry &);
void initializeOptimizeReturnedPass(PassRegistry &);
void initializeWebAssemblyArgumentMovePass(PassRegistry &);

View File

@ -25,6 +25,7 @@
#include "llvm/CodeGen/RegAllocRegistry.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/Function.h"
#include "llvm/InitializePasses.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Target/TargetOptions.h"
@ -56,7 +57,7 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeWebAssemblyTarget() {
auto &PR = *PassRegistry::getPassRegistry();
initializeWebAssemblyAddMissingPrototypesPass(PR);
initializeWebAssemblyLowerEmscriptenEHSjLjPass(PR);
initializeLowerGlobalDtorsPass(PR);
initializeLowerGlobalDtorsLegacyPassPass(PR);
initializeFixFunctionBitcastsPass(PR);
initializeOptimizeReturnedPass(PR);
initializeWebAssemblyArgumentMovePass(PR);
@ -412,7 +413,7 @@ void WebAssemblyPassConfig::addIRPasses() {
addPass(createWebAssemblyAddMissingPrototypes());
// Lower .llvm.global_dtors into .llvm_global_ctors with __cxa_atexit calls.
addPass(createWebAssemblyLowerGlobalDtors());
addPass(createLowerGlobalDtorsLegacyPass());
// Fix function bitcasts, as WebAssembly requires caller and callee signatures
// to match.

View File

@ -44,6 +44,7 @@ add_llvm_component_library(LLVMTransformUtils
LoopUnrollRuntime.cpp
LoopUtils.cpp
LoopVersioning.cpp
LowerGlobalDtors.cpp
LowerInvoke.cpp
LowerMemIntrinsics.cpp
LowerSwitch.cpp

View File

@ -1,4 +1,4 @@
//===-- WebAssemblyLowerGlobalDtors.cpp - Lower @llvm.global_dtors --------===//
//===-- LowerGlobalDtors.cpp - Lower @llvm.global_dtors -------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@ -9,33 +9,31 @@
/// \file
/// Lower @llvm.global_dtors.
///
/// WebAssembly doesn't have a builtin way to invoke static destructors.
/// Implement @llvm.global_dtors by creating wrapper functions that are
/// registered in @llvm.global_ctors and which contain a call to
/// `__cxa_atexit` to register their destructor functions.
///
//===----------------------------------------------------------------------===//
#include "WebAssembly.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/Transforms/Utils/LowerGlobalDtors.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Utils.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include <map>
using namespace llvm;
#define DEBUG_TYPE "wasm-lower-global-dtors"
#define DEBUG_TYPE "lower-global-dtors"
namespace {
class LowerGlobalDtors final : public ModulePass {
class LowerGlobalDtorsLegacyPass final : public ModulePass {
StringRef getPassName() const override {
return "WebAssembly Lower @llvm.global_dtors";
return "Lower @llvm.global_dtors via `__cxa_atexit`";
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
@ -47,21 +45,35 @@ class LowerGlobalDtors final : public ModulePass {
public:
static char ID;
LowerGlobalDtors() : ModulePass(ID) {}
LowerGlobalDtorsLegacyPass() : ModulePass(ID) {
initializeLowerGlobalDtorsLegacyPassPass(*PassRegistry::getPassRegistry());
}
};
} // End anonymous namespace
char LowerGlobalDtors::ID = 0;
INITIALIZE_PASS(LowerGlobalDtors, DEBUG_TYPE,
"Lower @llvm.global_dtors for WebAssembly", false, false)
char LowerGlobalDtorsLegacyPass::ID = 0;
INITIALIZE_PASS(LowerGlobalDtorsLegacyPass, DEBUG_TYPE,
"Lower @llvm.global_dtors via `__cxa_atexit`", false, false)
ModulePass *llvm::createWebAssemblyLowerGlobalDtors() {
return new LowerGlobalDtors();
ModulePass *llvm::createLowerGlobalDtorsLegacyPass() {
return new LowerGlobalDtorsLegacyPass();
}
bool LowerGlobalDtors::runOnModule(Module &M) {
LLVM_DEBUG(dbgs() << "********** Lower Global Destructors **********\n");
static bool runImpl(Module &M);
bool LowerGlobalDtorsLegacyPass::runOnModule(Module &M) { return runImpl(M); }
PreservedAnalyses LowerGlobalDtorsPass::run(Module &M,
ModuleAnalysisManager &AM) {
bool Changed = runImpl(M);
if (!Changed)
return PreservedAnalyses::all();
PreservedAnalyses PA;
PA.preserveSet<CFGAnalyses>();
return PA;
}
static bool runImpl(Module &M) {
GlobalVariable *GV = M.getGlobalVariable("llvm.global_dtors");
if (!GV || !GV->hasInitializer())
return false;
@ -129,15 +141,14 @@ bool LowerGlobalDtors::runOnModule(Module &M) {
/*isVarArg=*/false));
// Declare __dso_local.
Constant *DsoHandle = M.getNamedValue("__dso_handle");
if (!DsoHandle) {
Type *DsoHandleTy = Type::getInt8Ty(C);
GlobalVariable *Handle = new GlobalVariable(
M, DsoHandleTy, /*isConstant=*/true,
GlobalVariable::ExternalWeakLinkage, nullptr, "__dso_handle");
Handle->setVisibility(GlobalVariable::HiddenVisibility);
DsoHandle = Handle;
}
Type *DsoHandleTy = Type::getInt8Ty(C);
Constant *DsoHandle = M.getOrInsertGlobal("__dso_handle", DsoHandleTy, [&] {
auto *GV = new GlobalVariable(M, DsoHandleTy, /*isConstant=*/true,
GlobalVariable::ExternalWeakLinkage, nullptr,
"__dso_handle");
GV->setVisibility(GlobalVariable::HiddenVisibility);
return GV;
});
// For each unique priority level and associated symbol, generate a function
// to call all the destructors at that level, and a function to register the

View File

@ -34,6 +34,7 @@ void llvm::initializeTransformUtils(PassRegistry &Registry) {
initializeLCSSAWrapperPassPass(Registry);
initializeLibCallsShrinkWrapLegacyPassPass(Registry);
initializeLoopSimplifyPass(Registry);
initializeLowerGlobalDtorsLegacyPassPass(Registry);
initializeLowerInvokeLegacyPassPass(Registry);
initializeLowerSwitchLegacyPassPass(Registry);
initializeNameAnonGlobalLegacyPassPass(Registry);

View File

@ -1,9 +1,15 @@
; RUN: llc < %s -mtriple=arm-apple-darwin | FileCheck %s -check-prefix=DARWIN
; RUN: llc < %s -mtriple=arm-apple-darwin -lower-global-dtors-via-cxa-atexit=false | FileCheck %s -check-prefix=DARWIN-OLD
; RUN: llc < %s -mtriple=arm-linux-gnu -target-abi=apcs | FileCheck %s -check-prefix=ELF
; RUN: llc < %s -mtriple=arm-linux-gnueabi | FileCheck %s -check-prefix=GNUEABI
; DARWIN: l_register_call_dtors:
; DARWIN: bl ___cxa_atexit
; DARWIN: .section __DATA,__mod_init_func,mod_init_funcs
; DARWIN: .section __DATA,__mod_term_func,mod_term_funcs
; DARWIN-NOT: __mod_term_func
; DARWIN-OLD: .section __DATA,__mod_init_func,mod_init_funcs
; DARWIN-OLD: .section __DATA,__mod_term_func,mod_term_funcs
; ELF: .section .ctors,"aw",%progbits
; ELF: .section .dtors,"aw",%progbits

View File

@ -10,9 +10,13 @@
; CHECK-DEFAULT: .section .ctors.64535,"aw",@progbits
; CHECK-DEFAULT: .long construct_1
; CHECK-DARWIN: .long _construct_1
; CHECK-DARWIN-LABEL: .section __DATA,__mod_init_func,mod_init_funcs
; CHECK-DARWIN: .long _construct_1
; CHECK-DARWIN-NEXT: .long l_register_call_dtors.1000
; CHECK-DARWIN-NEXT: .long _construct_2
; CHECK-DARWIN-NEXT: .long l_register_call_dtors.2000
; CHECK-DARWIN-NEXT: .long _construct_3
; CHECK-DARWIN-NEXT: .long l_register_call_dtors.3000
@llvm.global_dtors = appending global [3 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 2000, void ()* @destruct_2, i8* null }, { i32, void ()*, i8* } { i32 1000, void ()* @destruct_1, i8* null }, { i32, void ()*, i8* } { i32 3000, void ()* @destruct_3, i8* null }]
; CHECK-DEFAULT: .section .dtors.62535,"aw",@progbits
@ -22,9 +26,7 @@
; CHECK-DEFAULT: .section .dtors.64535,"aw",@progbits
; CHECK-DEFAULT: .long destruct_1
; CHECK-DARWIN: .long _destruct_1
; CHECK-DARWIN-NEXT: .long _destruct_2
; CHECK-DARWIN-NEXT: .long _destruct_3
; CHECK-DARWIN-NOT: mod_term_func
declare void @construct_1()
declare void @construct_2()

View File

@ -0,0 +1,15 @@
; RUN: opt -passes=lower-global-dtors -S < %s | FileCheck %s
; Test we do not crash when reusing a pre-existing @__dso_handle global with a
; type other than i8, instead make sure we cast it.
%struct.mach_header = type { i32, i32, i32, i32, i32, i32, i32 }
@__dso_handle = external global %struct.mach_header
declare void @foo()
@llvm.global_dtors = appending global [1 x { i32, void ()*, i8* }] [
{ i32, void ()*, i8* } { i32 0, void ()* @foo, i8* null }
]
; CHECK: call i32 @__cxa_atexit(void (i8*)* @call_dtors.0, i8* null, i8* bitcast (%struct.mach_header* @__dso_handle to i8*))

View File

@ -0,0 +1,152 @@
; 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