Implement ARM static local initialization guards, which are more compact than

Itanium guards and use a slightly different compiled-in API.



git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@113330 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
John McCall 2010-09-08 01:44:27 +00:00
Родитель 607a1788d9
Коммит 5cd91b5134
12 изменённых файлов: 273 добавлений и 160 удалений

Просмотреть файл

@ -321,7 +321,7 @@ CodeGenFunction::BuildVirtualCall(const CXXDestructorDecl *DD, CXXDtorType Type,
/// Implementation for CGCXXABI. Possibly this should be moved into
/// the incomplete ABI implementations?
CGCXXABI::~CGCXXABI() {}
void CGCXXABI::_anchor() {}
static void ErrorUnsupportedABI(CodeGenFunction &CGF,
llvm::StringRef S) {
@ -469,3 +469,9 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, llvm::Value *Ptr,
AllocPtr = llvm::Constant::getNullValue(CGF.Builder.getInt8PtrTy());
CookieSize = CharUnits::Zero();
}
void CGCXXABI::EmitStaticLocalInit(CodeGenFunction &CGF,
const VarDecl &D,
llvm::GlobalVariable *GV) {
ErrorUnsupportedABI(CGF, "static local variable initialization");
}

Просмотреть файл

@ -69,9 +69,11 @@ protected:
ASTContext &getContext() const { return CGM.getContext(); }
virtual void _anchor();
public:
virtual ~CGCXXABI();
virtual ~CGCXXABI() {}
/// Gets the mangle context.
virtual MangleContext &getMangleContext() = 0;
@ -217,6 +219,14 @@ public:
QualType ElementType, llvm::Value *&NumElements,
llvm::Value *&AllocPtr, CharUnits &CookieSize);
/*************************** Static local guards ****************************/
/// Emits the initializer and destructor setup for the given static
/// local variable, given that it's reachable and couldn't be
/// emitted as a constant.
virtual void EmitStaticLocalInit(CodeGenFunction &CGF, const VarDecl &D,
llvm::GlobalVariable *DeclPtr);
};
/// Creates an instance of a C++ ABI class.

Просмотреть файл

@ -189,12 +189,12 @@ CodeGenFunction::AddInitializerToGlobalBlockVarDecl(const VarDecl &D,
if (!Init) {
if (!getContext().getLangOptions().CPlusPlus)
CGM.ErrorUnsupported(D.getInit(), "constant l-value expression");
else {
else if (Builder.GetInsertBlock()) {
// Since we have a static initializer, this global variable can't
// be constant.
GV->setConstant(false);
EmitStaticCXXBlockVarDeclInit(D, GV);
EmitCXXStaticLocalInit(D, GV);
}
return GV;
}

Просмотреть файл

@ -42,6 +42,8 @@ static void EmitDeclInit(CodeGenFunction &CGF, const VarDecl &D,
}
}
/// Emit code to cause the destruction of the given variable with
/// static storage duration.
static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D,
llvm::Constant *DeclPtr) {
CodeGenModule &CGM = CGF.CGM;
@ -138,6 +140,11 @@ CodeGenFunction::EmitCXXGlobalDtorRegistration(llvm::Constant *DtorFn,
Builder.CreateCall(AtExitFn, &Args[0], llvm::array_endof(Args));
}
void CodeGenFunction::EmitCXXStaticLocalInit(const VarDecl &D,
llvm::GlobalVariable *DeclPtr) {
CGM.getCXXABI().EmitStaticLocalInit(*this, D, DeclPtr);
}
static llvm::Function *
CreateGlobalInitOrDestructFunction(CodeGenModule &CGM,
const llvm::FunctionType *FTy,
@ -283,143 +290,6 @@ void CodeGenFunction::GenerateCXXGlobalDtorFunc(llvm::Function *Fn,
FinishFunction();
}
static llvm::Constant *getGuardAcquireFn(CodeGenFunction &CGF) {
// int __cxa_guard_acquire(__int64_t *guard_object);
const llvm::Type *Int64PtrTy =
llvm::Type::getInt64PtrTy(CGF.getLLVMContext());
std::vector<const llvm::Type*> Args(1, Int64PtrTy);
const llvm::FunctionType *FTy =
llvm::FunctionType::get(CGF.ConvertType(CGF.getContext().IntTy),
Args, /*isVarArg=*/false);
return CGF.CGM.CreateRuntimeFunction(FTy, "__cxa_guard_acquire");
}
static llvm::Constant *getGuardReleaseFn(CodeGenFunction &CGF) {
// void __cxa_guard_release(__int64_t *guard_object);
const llvm::Type *Int64PtrTy =
llvm::Type::getInt64PtrTy(CGF.getLLVMContext());
std::vector<const llvm::Type*> Args(1, Int64PtrTy);
const llvm::FunctionType *FTy =
llvm::FunctionType::get(llvm::Type::getVoidTy(CGF.getLLVMContext()),
Args, /*isVarArg=*/false);
return CGF.CGM.CreateRuntimeFunction(FTy, "__cxa_guard_release");
}
static llvm::Constant *getGuardAbortFn(CodeGenFunction &CGF) {
// void __cxa_guard_abort(__int64_t *guard_object);
const llvm::Type *Int64PtrTy =
llvm::Type::getInt64PtrTy(CGF.getLLVMContext());
std::vector<const llvm::Type*> Args(1, Int64PtrTy);
const llvm::FunctionType *FTy =
llvm::FunctionType::get(llvm::Type::getVoidTy(CGF.getLLVMContext()),
Args, /*isVarArg=*/false);
return CGF.CGM.CreateRuntimeFunction(FTy, "__cxa_guard_abort");
}
namespace {
struct CallGuardAbort : EHScopeStack::Cleanup {
llvm::GlobalVariable *Guard;
CallGuardAbort(llvm::GlobalVariable *Guard) : Guard(Guard) {}
void Emit(CodeGenFunction &CGF, bool IsForEH) {
// It shouldn't be possible for this to throw, but if it can,
// this should allow for the possibility of an invoke.
CGF.Builder.CreateCall(getGuardAbortFn(CGF), Guard)
->setDoesNotThrow();
}
};
}
void
CodeGenFunction::EmitStaticCXXBlockVarDeclInit(const VarDecl &D,
llvm::GlobalVariable *GV) {
// Bail out early if this initializer isn't reachable.
if (!Builder.GetInsertBlock()) return;
bool ThreadsafeStatics = getContext().getLangOptions().ThreadsafeStatics;
llvm::SmallString<256> GuardVName;
CGM.getCXXABI().getMangleContext().mangleGuardVariable(&D, GuardVName);
// Create the guard variable.
llvm::GlobalVariable *GuardVariable =
new llvm::GlobalVariable(CGM.getModule(), Int64Ty,
false, GV->getLinkage(),
llvm::Constant::getNullValue(Int64Ty),
GuardVName.str());
// Load the first byte of the guard variable.
const llvm::Type *PtrTy
= llvm::PointerType::get(llvm::Type::getInt8Ty(VMContext), 0);
llvm::Value *V =
Builder.CreateLoad(Builder.CreateBitCast(GuardVariable, PtrTy), "tmp");
llvm::BasicBlock *InitCheckBlock = createBasicBlock("init.check");
llvm::BasicBlock *EndBlock = createBasicBlock("init.end");
// Check if the first byte of the guard variable is zero.
Builder.CreateCondBr(Builder.CreateIsNull(V, "tobool"),
InitCheckBlock, EndBlock);
EmitBlock(InitCheckBlock);
// Variables used when coping with thread-safe statics and exceptions.
if (ThreadsafeStatics) {
// Call __cxa_guard_acquire.
V = Builder.CreateCall(getGuardAcquireFn(*this), GuardVariable);
llvm::BasicBlock *InitBlock = createBasicBlock("init");
Builder.CreateCondBr(Builder.CreateIsNotNull(V, "tobool"),
InitBlock, EndBlock);
// Call __cxa_guard_abort along the exceptional edge.
if (Exceptions)
EHStack.pushCleanup<CallGuardAbort>(EHCleanup, GuardVariable);
EmitBlock(InitBlock);
}
if (D.getType()->isReferenceType()) {
unsigned Alignment = getContext().getDeclAlign(&D).getQuantity();
QualType T = D.getType();
RValue RV = EmitReferenceBindingToExpr(D.getInit(), &D);
EmitStoreOfScalar(RV.getScalarVal(), GV, /*Volatile=*/false, Alignment, T);
} else
EmitDeclInit(*this, D, GV);
if (ThreadsafeStatics) {
// Pop the guard-abort cleanup if we pushed one.
if (Exceptions)
PopCleanupBlock();
// Call __cxa_guard_release. This cannot throw.
Builder.CreateCall(getGuardReleaseFn(*this), GuardVariable);
} else {
llvm::Value *One =
llvm::ConstantInt::get(llvm::Type::getInt8Ty(VMContext), 1);
Builder.CreateStore(One, Builder.CreateBitCast(GuardVariable, PtrTy));
}
// Register the call to the destructor.
if (!D.getType()->isReferenceType())
EmitDeclDestroy(*this, D, GV);
EmitBlock(EndBlock);
}
/// GenerateCXXAggrDestructorHelper - Generates a helper function which when
/// invoked, calls the default destructor on array elements in reverse order of
/// construction.

Просмотреть файл

@ -1565,11 +1565,6 @@ public:
llvm::GlobalVariable *GV);
/// EmitStaticCXXBlockVarDeclInit - Create the initializer for a C++ runtime
/// initialized static block var decl.
void EmitStaticCXXBlockVarDeclInit(const VarDecl &D,
llvm::GlobalVariable *GV);
/// EmitCXXGlobalVarDeclInit - Create the initializer for a C++
/// variable with global storage.
void EmitCXXGlobalVarDeclInit(const VarDecl &D, llvm::Constant *DeclPtr);
@ -1579,6 +1574,8 @@ public:
void EmitCXXGlobalDtorRegistration(llvm::Constant *DtorFn,
llvm::Constant *DeclPtr);
void EmitCXXStaticLocalInit(const VarDecl &D, llvm::GlobalVariable *DeclPtr);
/// GenerateCXXGlobalInitFunc - Generates code for initializing global
/// variables.
void GenerateCXXGlobalInitFunc(llvm::Function *Fn,

Просмотреть файл

@ -119,6 +119,9 @@ public:
void ReadArrayCookie(CodeGenFunction &CGF, llvm::Value *Ptr,
QualType ElementType, llvm::Value *&NumElements,
llvm::Value *&AllocPtr, CharUnits &CookieSize);
void EmitStaticLocalInit(CodeGenFunction &CGF, const VarDecl &D,
llvm::GlobalVariable *DeclPtr);
};
class ARMCXXABI : public ItaniumCXXABI {
@ -1021,3 +1024,159 @@ void ARMCXXABI::ReadArrayCookie(CodeGenFunction &CGF,
NumElements = CGF.Builder.CreateLoad(NumElementsPtr);
}
/*********************** Static local initialization **************************/
static llvm::Constant *getGuardAcquireFn(CodeGenModule &CGM,
const llvm::PointerType *GuardPtrTy) {
// int __cxa_guard_acquire(__guard *guard_object);
std::vector<const llvm::Type*> Args(1, GuardPtrTy);
const llvm::FunctionType *FTy =
llvm::FunctionType::get(CGM.getTypes().ConvertType(CGM.getContext().IntTy),
Args, /*isVarArg=*/false);
return CGM.CreateRuntimeFunction(FTy, "__cxa_guard_acquire");
}
static llvm::Constant *getGuardReleaseFn(CodeGenModule &CGM,
const llvm::PointerType *GuardPtrTy) {
// void __cxa_guard_release(__guard *guard_object);
std::vector<const llvm::Type*> Args(1, GuardPtrTy);
const llvm::FunctionType *FTy =
llvm::FunctionType::get(llvm::Type::getVoidTy(CGM.getLLVMContext()),
Args, /*isVarArg=*/false);
return CGM.CreateRuntimeFunction(FTy, "__cxa_guard_release");
}
static llvm::Constant *getGuardAbortFn(CodeGenModule &CGM,
const llvm::PointerType *GuardPtrTy) {
// void __cxa_guard_abort(__guard *guard_object);
std::vector<const llvm::Type*> Args(1, GuardPtrTy);
const llvm::FunctionType *FTy =
llvm::FunctionType::get(llvm::Type::getVoidTy(CGM.getLLVMContext()),
Args, /*isVarArg=*/false);
return CGM.CreateRuntimeFunction(FTy, "__cxa_guard_abort");
}
namespace {
struct CallGuardAbort : EHScopeStack::Cleanup {
llvm::GlobalVariable *Guard;
CallGuardAbort(llvm::GlobalVariable *Guard) : Guard(Guard) {}
void Emit(CodeGenFunction &CGF, bool IsForEH) {
CGF.Builder.CreateCall(getGuardAbortFn(CGF.CGM, Guard->getType()), Guard)
->setDoesNotThrow();
}
};
}
/// The ARM code here follows the Itanium code closely enough that we
/// just special-case it at particular places.
void ItaniumCXXABI::EmitStaticLocalInit(CodeGenFunction &CGF,
const VarDecl &D,
llvm::GlobalVariable *GV) {
CGBuilderTy &Builder = CGF.Builder;
bool ThreadsafeStatics = getContext().getLangOptions().ThreadsafeStatics;
// Guard variables are 64 bits in the generic ABI and 32 bits on ARM.
const llvm::IntegerType *GuardTy
= (IsARM ? Builder.getInt32Ty() : Builder.getInt64Ty());
const llvm::PointerType *GuardPtrTy = GuardTy->getPointerTo();
// Create the guard variable.
llvm::SmallString<256> GuardVName;
getMangleContext().mangleItaniumGuardVariable(&D, GuardVName);
llvm::GlobalVariable *GuardVariable =
new llvm::GlobalVariable(CGM.getModule(), GuardTy,
false, GV->getLinkage(),
llvm::ConstantInt::get(GuardTy, 0),
GuardVName.str());
// Test whether the variable has completed initialization.
llvm::Value *IsInitialized;
// ARM C++ ABI 3.2.3.1:
// To support the potential use of initialization guard variables
// as semaphores that are the target of ARM SWP and LDREX/STREX
// synchronizing instructions we define a static initialization
// guard variable to be a 4-byte aligned, 4- byte word with the
// following inline access protocol.
// #define INITIALIZED 1
// if ((obj_guard & INITIALIZED) != INITIALIZED) {
// if (__cxa_guard_acquire(&obj_guard))
// ...
// }
if (IsARM) {
llvm::Value *V = Builder.CreateLoad(GuardVariable);
V = Builder.CreateAnd(V, Builder.getInt32(1));
IsInitialized = Builder.CreateIsNull(V, "guard.uninitialized");
// Itanium C++ ABI 3.3.2:
// The following is pseudo-code showing how these functions can be used:
// if (obj_guard.first_byte == 0) {
// if ( __cxa_guard_acquire (&obj_guard) ) {
// try {
// ... initialize the object ...;
// } catch (...) {
// __cxa_guard_abort (&obj_guard);
// throw;
// }
// ... queue object destructor with __cxa_atexit() ...;
// __cxa_guard_release (&obj_guard);
// }
// }
} else {
// Load the first byte of the guard variable.
const llvm::Type *PtrTy = Builder.getInt8PtrTy();
llvm::Value *V =
Builder.CreateLoad(Builder.CreateBitCast(GuardVariable, PtrTy), "tmp");
IsInitialized = Builder.CreateIsNull(V, "guard.uninitialized");
}
llvm::BasicBlock *InitCheckBlock = CGF.createBasicBlock("init.check");
llvm::BasicBlock *EndBlock = CGF.createBasicBlock("init.end");
// Check if the first byte of the guard variable is zero.
Builder.CreateCondBr(IsInitialized, InitCheckBlock, EndBlock);
CGF.EmitBlock(InitCheckBlock);
// Variables used when coping with thread-safe statics and exceptions.
if (ThreadsafeStatics) {
// Call __cxa_guard_acquire.
llvm::Value *V
= Builder.CreateCall(getGuardAcquireFn(CGM, GuardPtrTy), GuardVariable);
llvm::BasicBlock *InitBlock = CGF.createBasicBlock("init");
Builder.CreateCondBr(Builder.CreateIsNotNull(V, "tobool"),
InitBlock, EndBlock);
// Call __cxa_guard_abort along the exceptional edge.
CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, GuardVariable);
CGF.EmitBlock(InitBlock);
}
// Emit the initializer and add a global destructor if appropriate.
CGF.EmitCXXGlobalVarDeclInit(D, GV);
if (ThreadsafeStatics) {
// Pop the guard-abort cleanup if we pushed one.
CGF.PopCleanupBlock();
// Call __cxa_guard_release. This cannot throw.
Builder.CreateCall(getGuardReleaseFn(CGM, GuardPtrTy), GuardVariable);
} else {
Builder.CreateStore(llvm::ConstantInt::get(GuardTy, 1), GuardVariable);
}
CGF.EmitBlock(EndBlock);
}

Просмотреть файл

@ -2451,8 +2451,8 @@ MangleContext::mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
/// mangleGuardVariable - Returns the mangled name for a guard variable
/// for the passed in VarDecl.
void MangleContext::mangleGuardVariable(const VarDecl *D,
llvm::SmallVectorImpl<char> &Res) {
void MangleContext::mangleItaniumGuardVariable(const VarDecl *D,
llvm::SmallVectorImpl<char> &Res) {
// <special-name> ::= GV <object name> # Guard variable for one-time
// # initialization
CXXNameMangler Mangler(*this, Res);

Просмотреть файл

@ -119,8 +119,6 @@ public:
virtual void mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
const ThisAdjustment &ThisAdjustment,
llvm::SmallVectorImpl<char> &);
virtual void mangleGuardVariable(const VarDecl *D,
llvm::SmallVectorImpl<char> &);
virtual void mangleReferenceTemporary(const VarDecl *D,
llvm::SmallVectorImpl<char> &);
virtual void mangleCXXVTable(const CXXRecordDecl *RD,
@ -139,6 +137,10 @@ public:
void mangleBlock(GlobalDecl GD,
const BlockDecl *BD, llvm::SmallVectorImpl<char> &);
// This is pretty lame.
void mangleItaniumGuardVariable(const VarDecl *D,
llvm::SmallVectorImpl<char> &);
void mangleInitDiscriminator() {
Discriminator = 0;
}

Просмотреть файл

@ -91,8 +91,6 @@ public:
virtual void mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
const ThisAdjustment &ThisAdjustment,
llvm::SmallVectorImpl<char> &);
virtual void mangleGuardVariable(const VarDecl *D,
llvm::SmallVectorImpl<char> &);
virtual void mangleCXXVTable(const CXXRecordDecl *RD,
llvm::SmallVectorImpl<char> &);
virtual void mangleCXXVTT(const CXXRecordDecl *RD,
@ -1175,10 +1173,6 @@ void MicrosoftMangleContext::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
llvm::SmallVectorImpl<char> &) {
assert(false && "Can't yet mangle destructor thunks!");
}
void MicrosoftMangleContext::mangleGuardVariable(const VarDecl *D,
llvm::SmallVectorImpl<char> &) {
assert(false && "Can't yet mangle guard variables!");
}
void MicrosoftMangleContext::mangleCXXVTable(const CXXRecordDecl *RD,
llvm::SmallVectorImpl<char> &) {
assert(false && "Can't yet mangle virtual tables!");

Просмотреть файл

@ -1,5 +1,10 @@
// RUN: %clang_cc1 %s -triple=thumbv7-apple-darwin3.0.0-iphoneos -fno-use-cxa-atexit -target-abi apcs-gnu -emit-llvm -o - -fexceptions | FileCheck %s
// CHECK: @_ZZN5test74testEvE1x = internal global i32 0, align 4
// CHECK: @_ZGVZN5test74testEvE1x = internal global i32 0
// CHECK: @_ZZN5test84testEvE1x = internal global [[TEST8A:.*]] zeroinitializer, align 1
// CHECK: @_ZGVZN5test84testEvE1x = internal global i32 0
typedef typeof(sizeof(int)) size_t;
class foo {
@ -277,6 +282,76 @@ namespace test6 {
}
}
namespace test7 {
int foo();
// Static and guard tested at top of file
// CHECK: define void @_ZN5test74testEv()
void test() {
// CHECK: [[T0:%.*]] = load i32* @_ZGVZN5test74testEvE1x
// CHECK-NEXT: [[T1:%.*]] = and i32 [[T0]], 1
// CHECK-NEXT: [[T2:%.*]] = icmp eq i32 [[T1]], 0
// CHECK-NEXT: br i1 [[T2]]
// -> fallthrough, end
// CHECK: [[T3:%.*]] = call i32 @__cxa_guard_acquire(i32* @_ZGVZN5test74testEvE1x)
// CHECK-NEXT: [[T4:%.*]] = icmp ne i32 [[T3]], 0
// CHECK-NEXT: br i1 [[T4]]
// -> fallthrough, end
// CHECK: [[INIT:%.*]] = invoke i32 @_ZN5test73fooEv()
// CHECK: store i32 [[INIT]], i32* @_ZZN5test74testEvE1x, align 4
// CHECK-NEXT: call void @__cxa_guard_release(i32* @_ZGVZN5test74testEvE1x)
// CHECK-NEXT: br label
// -> end
// end:
// CHECK: ret void
static int x = foo();
// CHECK: call i8* @llvm.eh.exception()
// CHECK: call void @__cxa_guard_abort(i32* @_ZGVZN5test74testEvE1x)
// CHECK: call void @_Unwind_Resume_or_Rethrow
}
}
namespace test8 {
struct A {
A();
~A();
};
// Static and guard tested at top of file
// CHECK: define void @_ZN5test84testEv()
void test() {
// CHECK: [[T0:%.*]] = load i32* @_ZGVZN5test84testEvE1x
// CHECK-NEXT: [[T1:%.*]] = and i32 [[T0]], 1
// CHECK-NEXT: [[T2:%.*]] = icmp eq i32 [[T1]], 0
// CHECK-NEXT: br i1 [[T2]]
// -> fallthrough, end
// CHECK: [[T3:%.*]] = call i32 @__cxa_guard_acquire(i32* @_ZGVZN5test84testEvE1x)
// CHECK-NEXT: [[T4:%.*]] = icmp ne i32 [[T3]], 0
// CHECK-NEXT: br i1 [[T4]]
// -> fallthrough, end
// CHECK: [[INIT:%.*]] = invoke [[TEST8A]]* @_ZN5test81AC1Ev([[TEST8A]]* @_ZZN5test84testEvE1x)
// FIXME: Here we register a global destructor that
// unconditionally calls the destructor. That's what we've always
// done for -fno-use-cxa-atexit here, but that's really not
// semantically correct at all.
// CHECK: call void @__cxa_guard_release(i32* @_ZGVZN5test84testEvE1x)
// CHECK-NEXT: br label
// -> end
// end:
// CHECK: ret void
static A x;
// CHECK: call i8* @llvm.eh.exception()
// CHECK: call void @__cxa_guard_abort(i32* @_ZGVZN5test84testEvE1x)
// CHECK: call void @_Unwind_Resume_or_Rethrow
}
}
// CHECK: define linkonce_odr [[C:%.*]]* @_ZTv0_n12_N5test21CD1Ev(
// CHECK: call [[C]]* @_ZN5test21CD1Ev(
// CHECK: ret [[C]]* undef

Просмотреть файл

@ -14,8 +14,8 @@ struct A {
void f() {
// CHECK: call i32 @__cxa_guard_acquire
// CHECK: call void @_ZN1AC1Ev
// CHECK: call void @__cxa_guard_release
// CHECK: call i32 @__cxa_atexit(void (i8*)* bitcast (void (%struct.A*)* @_ZN1AD1Ev to void (i8*)*), i8* getelementptr inbounds (%struct.A* @_ZZ1fvE1a, i32 0, i32 0), i8* bitcast (i8** @__dso_handle to i8*))
// CHECK: call void @__cxa_guard_release
static A a;
}

Просмотреть файл

@ -11,8 +11,8 @@ struct Y { };
void f() {
// CHECK: call i32 @__cxa_guard_acquire(i64* @_ZGVZ1fvE1x)
// CHECK: invoke void @_ZN1XC1Ev
// CHECK: call void @__cxa_guard_release(i64* @_ZGVZ1fvE1x)
// CHECK-NEXT: call i32 @__cxa_atexit
// CHECK: call i32 @__cxa_atexit
// CHECK-NEXT: call void @__cxa_guard_release(i64* @_ZGVZ1fvE1x)
// CHECK: br
static X x;