.. code-block:: llvm
- %0 = type { i32, void ()* }
- @llvm.global_ctors = appending global [1 x %0] [%0 { i32 65535, void ()* @ctor }]
+ %0 = type { i32, void ()*, i8* }
+ @llvm.global_ctors = appending global [1 x %0] [%0 { i32 65535, void ()* @ctor, i8* @data }]
The ``@llvm.global_ctors`` array contains a list of constructor
-functions and associated priorities. The functions referenced by this
-array will be called in ascending order of priority (i.e. lowest first)
-when the module is loaded. The order of functions with the same priority
-is not defined.
+functions, priorities, and an optional associated global or function.
+The functions referenced by this array will be called in ascending order
+of priority (i.e. lowest first) when the module is loaded. The order of
+functions with the same priority is not defined.
+
+If the third field is present, non-null, and points to a global variable
+or function, the initializer function will only run if the associated
+data from the current module is not discarded.
.. _llvmglobaldtors:
.. code-block:: llvm
- %0 = type { i32, void ()* }
- @llvm.global_dtors = appending global [1 x %0] [%0 { i32 65535, void ()* @dtor }]
+ %0 = type { i32, void ()*, i8* }
+ @llvm.global_dtors = appending global [1 x %0] [%0 { i32 65535, void ()* @dtor, i8* @data }]
+
+The ``@llvm.global_dtors`` array contains a list of destructor
+functions, priorities, and an optional associated global or function.
+The functions referenced by this array will be called in descending
+order of priority (i.e. highest first) when the module is loaded. The
+order of functions with the same priority is not defined.
-The ``@llvm.global_dtors`` array contains a list of destructor functions
-and associated priorities. The functions referenced by this array will
-be called in descending order of priority (i.e. highest first) when the
-module is loaded. The order of functions with the same priority is not
-defined.
+If the third field is present, non-null, and points to a global variable
+or function, the destructor function will only run if the associated
+data from the current module is not discarded.
Instruction Reference
=====================
MachineModuleInfo *MMI) const override;
void InitializeELF(bool UseInitArray_);
- const MCSection *
- getStaticCtorSection(unsigned Priority = 65535) const override;
- const MCSection *
- getStaticDtorSection(unsigned Priority = 65535) const override;
+ const MCSection *getStaticCtorSection(unsigned Priority,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) const override;
+ const MCSection *getStaticDtorSection(unsigned Priority,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) const override;
};
void emitModuleFlags(MCStreamer &Streamer,
ArrayRef<Module::ModuleFlagEntry> ModuleFlags,
Mangler &Mang, const TargetMachine &TM) const override;
+
+ const MCSection *getStaticCtorSection(unsigned Priority,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) const override;
+ const MCSection *getStaticDtorSection(unsigned Priority,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) const override;
};
} // end namespace llvm
getTTypeReference(const MCSymbolRefExpr *Sym, unsigned Encoding,
MCStreamer &Streamer) const;
- virtual const MCSection *
- getStaticCtorSection(unsigned Priority = 65535) const {
- (void)Priority;
+ virtual const MCSection *getStaticCtorSection(unsigned Priority,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) const {
return StaticCtorSection;
}
- virtual const MCSection *
- getStaticDtorSection(unsigned Priority = 65535) const {
- (void)Priority;
+
+ virtual const MCSection *getStaticDtorSection(unsigned Priority,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) const {
return StaticDtorSection;
}
// Look for global variables which need to be renamed.
for (Module::global_iterator
GI = TheModule->global_begin(), GE = TheModule->global_end();
- GI != GE; ++GI)
- UpgradeGlobalVariable(GI);
+ GI != GE;) {
+ GlobalVariable *GV = GI++;
+ UpgradeGlobalVariable(GV);
+ }
+
// Force deallocation of memory for these vectors to favor the client that
// want lazy deserialization.
std::vector<std::pair<GlobalVariable*, unsigned> >().swap(GlobalInits);
}
}
+namespace {
+struct Structor {
+ Structor() : Priority(0), Func(nullptr), ComdatKey(nullptr) {}
+ int Priority;
+ llvm::Constant *Func;
+ llvm::GlobalValue *ComdatKey;
+};
+} // end namespace
+
/// EmitXXStructorList - Emit the ctor or dtor list taking into account the init
/// priority.
void AsmPrinter::EmitXXStructorList(const Constant *List, bool isCtor) {
const ConstantArray *InitList = dyn_cast<ConstantArray>(List);
if (!InitList) return; // Not an array!
StructType *ETy = dyn_cast<StructType>(InitList->getType()->getElementType());
- if (!ETy || ETy->getNumElements() != 2) return; // Not an array of pairs!
+ // FIXME: Only allow the 3-field form in LLVM 4.0.
+ if (!ETy || ETy->getNumElements() < 2 || ETy->getNumElements() > 3)
+ return; // Not an array of two or three elements!
if (!isa<IntegerType>(ETy->getTypeAtIndex(0U)) ||
!isa<PointerType>(ETy->getTypeAtIndex(1U))) return; // Not (int, ptr).
+ if (ETy->getNumElements() == 3 && !isa<PointerType>(ETy->getTypeAtIndex(2U)))
+ return; // Not (int, ptr, ptr).
// Gather the structors in a form that's convenient for sorting by priority.
- typedef std::pair<unsigned, Constant *> Structor;
SmallVector<Structor, 8> Structors;
- for (unsigned i = 0, e = InitList->getNumOperands(); i != e; ++i) {
- ConstantStruct *CS = dyn_cast<ConstantStruct>(InitList->getOperand(i));
+ for (Value *O : InitList->operands()) {
+ ConstantStruct *CS = dyn_cast<ConstantStruct>(O);
if (!CS) continue; // Malformed.
if (CS->getOperand(1)->isNullValue())
break; // Found a null terminator, skip the rest.
ConstantInt *Priority = dyn_cast<ConstantInt>(CS->getOperand(0));
if (!Priority) continue; // Malformed.
- Structors.push_back(std::make_pair(Priority->getLimitedValue(65535),
- CS->getOperand(1)));
+ Structors.push_back(Structor());
+ Structor &S = Structors.back();
+ S.Priority = Priority->getLimitedValue(65535);
+ S.Func = CS->getOperand(1);
+ if (ETy->getNumElements() == 3 && !CS->getOperand(2)->isNullValue())
+ S.ComdatKey = dyn_cast<GlobalValue>(CS->getOperand(2)->stripPointerCasts());
}
// Emit the function pointers in the target-specific order
const DataLayout *DL = TM.getDataLayout();
unsigned Align = Log2_32(DL->getPointerPrefAlignment());
- std::stable_sort(Structors.begin(), Structors.end(), less_first());
- for (unsigned i = 0, e = Structors.size(); i != e; ++i) {
+ std::stable_sort(Structors.begin(), Structors.end(),
+ [](const Structor &L,
+ const Structor &R) { return L.Priority < R.Priority; });
+ for (Structor &S : Structors) {
+ const TargetLoweringObjectFile &Obj = getObjFileLowering();
+ const MCSymbol *KeySym = nullptr;
+ const MCSection *KeySec = nullptr;
+ if (S.ComdatKey) {
+ KeySym = getSymbol(S.ComdatKey);
+ KeySec = getObjFileLowering().SectionForGlobal(S.ComdatKey, *Mang, TM);
+ }
const MCSection *OutputSection =
- (isCtor ?
- getObjFileLowering().getStaticCtorSection(Structors[i].first) :
- getObjFileLowering().getStaticDtorSection(Structors[i].first));
+ (isCtor ? Obj.getStaticCtorSection(S.Priority, KeySym, KeySec)
+ : Obj.getStaticDtorSection(S.Priority, KeySym, KeySec));
OutStreamer.SwitchSection(OutputSection);
if (OutStreamer.getCurrentSection() != OutStreamer.getPreviousSection())
EmitAlignment(Align);
- EmitXXStructor(Structors[i].second);
+ EmitXXStructor(S.Func);
}
}
return DataRelROSection;
}
-const MCSection *
-TargetLoweringObjectFileELF::getStaticCtorSection(unsigned Priority) const {
+const MCSection *TargetLoweringObjectFileELF::getStaticCtorSection(
+ unsigned Priority, const MCSymbol *KeySym, const MCSection *KeySec) const {
// The default scheme is .ctor / .dtor, so we have to invert the priority
// numbering.
if (Priority == 65535)
}
}
-const MCSection *
-TargetLoweringObjectFileELF::getStaticDtorSection(unsigned Priority) const {
+const MCSection *TargetLoweringObjectFileELF::getStaticDtorSection(
+ unsigned Priority, const MCSymbol *KeySym, const MCSection *KeySec) const {
// The default scheme is .ctor / .dtor, so we have to invert the priority
// numbering.
if (Priority == 65535)
}
}
}
+
+static const MCSection *getAssociativeCOFFSection(MCContext &Ctx,
+ const MCSection *Sec,
+ const MCSymbol *KeySym,
+ const MCSection *KeySec) {
+ // Return the normal section if we don't have to be associative.
+ if (!KeySym)
+ return Sec;
+
+ // Make an associative section with the same name and kind as the normal
+ // section.
+ const MCSectionCOFF *SecCOFF = cast<MCSectionCOFF>(Sec);
+ const MCSectionCOFF *KeySecCOFF = cast<MCSectionCOFF>(KeySec);
+ unsigned Characteristics =
+ SecCOFF->getCharacteristics() | COFF::IMAGE_SCN_LNK_COMDAT;
+ return Ctx.getCOFFSection(SecCOFF->getSectionName(), Characteristics,
+ SecCOFF->getKind(), KeySym->getName(),
+ COFF::IMAGE_COMDAT_SELECT_ASSOCIATIVE, KeySecCOFF);
+}
+
+const MCSection *TargetLoweringObjectFileCOFF::getStaticCtorSection(
+ unsigned Priority, const MCSymbol *KeySym, const MCSection *KeySec) const {
+ return getAssociativeCOFFSection(getContext(), StaticCtorSection, KeySym, KeySec);
+}
+
+const MCSection *TargetLoweringObjectFileCOFF::getStaticDtorSection(
+ unsigned Priority, const MCSymbol *KeySym, const MCSection *KeySec) const {
+ return getAssociativeCOFFSection(getContext(), StaticDtorSection, KeySym, KeySec);
+}
return Upgraded;
}
+static bool UpgradeGlobalStructors(GlobalVariable *GV) {
+ ArrayType *ATy = dyn_cast<ArrayType>(GV->getType()->getElementType());
+ StructType *OldTy =
+ ATy ? dyn_cast<StructType>(ATy->getElementType()) : nullptr;
+
+ // Only upgrade an array of a two field struct with the appropriate field
+ // types.
+ if (!OldTy || OldTy->getNumElements() != 2)
+ return false;
+
+ // Get the upgraded 3 element type.
+ PointerType *VoidPtrTy = Type::getInt8Ty(GV->getContext())->getPointerTo();
+ Type *Tys[3] = {
+ OldTy->getElementType(0),
+ OldTy->getElementType(1),
+ VoidPtrTy
+ };
+ StructType *NewTy =
+ StructType::get(GV->getContext(), Tys, /*isPacked=*/false);
+
+ // Build new constants with a null third field filled in.
+ ConstantArray *OldInit = dyn_cast<ConstantArray>(GV->getInitializer());
+ if (!OldInit)
+ return false;
+ std::vector<Constant *> Initializers;
+ for (Use &U : OldInit->operands()) {
+ ConstantStruct *Init = cast<ConstantStruct>(&U);
+ Constant *NewInit =
+ ConstantStruct::get(NewTy, Init->getOperand(0), Init->getOperand(1),
+ Constant::getNullValue(VoidPtrTy), nullptr);
+ Initializers.push_back(NewInit);
+ }
+ assert(Initializers.size() == ATy->getNumElements());
+
+ // Replace the old GV with a new one.
+ ATy = ArrayType::get(NewTy, Initializers.size());
+ Constant *NewInit = ConstantArray::get(ATy, Initializers);
+ GlobalVariable *NewGV = new GlobalVariable(
+ *GV->getParent(), ATy, GV->isConstant(), GV->getLinkage(), NewInit, "",
+ GV, GV->getThreadLocalMode(), GV->getType()->getAddressSpace(),
+ GV->isExternallyInitialized());
+ NewGV->copyAttributesFrom(GV);
+ NewGV->takeName(GV);
+ assert(GV->use_empty() && "program cannot use initializer list");
+ GV->eraseFromParent();
+ return true;
+}
+
bool llvm::UpgradeGlobalVariable(GlobalVariable *GV) {
+ if (GV->getName() == "llvm.global_ctors" ||
+ GV->getName() == "llvm.global_dtors")
+ return UpgradeGlobalStructors(GV);
+
// Nothing to do yet.
return false;
}
"invalid linkage for intrinsic global variable", &GV);
// Don't worry about emitting an error for it not being an array,
// visitGlobalValue will complain on appending non-array.
- if (ArrayType *ATy = dyn_cast<ArrayType>(GV.getType())) {
+ if (ArrayType *ATy = dyn_cast<ArrayType>(GV.getType()->getElementType())) {
StructType *STy = dyn_cast<StructType>(ATy->getElementType());
PointerType *FuncPtrTy =
FunctionType::get(Type::getVoidTy(*Context), false)->getPointerTo();
- Assert1(STy && STy->getNumElements() == 2 &&
+ // FIXME: Reject the 2-field form in LLVM 4.0.
+ Assert1(STy && (STy->getNumElements() == 2 ||
+ STy->getNumElements() == 3) &&
STy->getTypeAtIndex(0u)->isIntegerTy(32) &&
STy->getTypeAtIndex(1) == FuncPtrTy,
"wrong type for intrinsic global variable", &GV);
+ if (STy->getNumElements() == 3) {
+ Type *ETy = STy->getTypeAtIndex(2);
+ Assert1(ETy->isPointerTy() &&
+ cast<PointerType>(ETy)->getElementType()->isIntegerTy(8),
+ "wrong type for intrinsic global variable", &GV);
+ }
}
}
for (User::op_iterator OI = Init->op_begin(), OE = Init->op_end();
OI != OE; ++OI) {
Value *Op = *OI;
- // llvm.global_ctors is an array of pairs where the second members
- // are constructor functions.
+ // llvm.global_ctors is an array of three-field structs where the second
+ // members are constructor functions.
Function *F = dyn_cast<Function>(cast<ConstantStruct>(Op)->getOperand(1));
// If the user used a constructor function with the wrong signature and
// it got bitcasted or whatever, look the other way.
void installGlobalCtors(GlobalVariable *GCL,
const std::vector<Function *> &Ctors) {
// If we made a change, reassemble the initializer list.
- Constant *CSVals[2];
- CSVals[0] = ConstantInt::get(Type::getInt32Ty(GCL->getContext()), 65535);
- CSVals[1] = nullptr;
+ Constant *CSVals[3];
StructType *StructTy =
cast<StructType>(GCL->getType()->getElementType()->getArrayElementType());
// Create the new init list.
std::vector<Constant *> CAList;
- for (unsigned i = 0, e = Ctors.size(); i != e; ++i) {
- if (Ctors[i]) {
- CSVals[1] = Ctors[i];
+ for (Function *F : Ctors) {
+ Type *Int32Ty = Type::getInt32Ty(GCL->getContext());
+ if (F) {
+ CSVals[0] = ConstantInt::get(Int32Ty, 65535);
+ CSVals[1] = F;
} else {
- Type *FTy = FunctionType::get(Type::getVoidTy(GCL->getContext()), false);
- PointerType *PFTy = PointerType::getUnqual(FTy);
- CSVals[1] = Constant::getNullValue(PFTy);
- CSVals[0] =
- ConstantInt::get(Type::getInt32Ty(GCL->getContext()), 0x7fffffff);
+ CSVals[0] = ConstantInt::get(Int32Ty, 0x7fffffff);
+ CSVals[1] = Constant::getNullValue(StructTy->getElementType(1));
}
- CAList.push_back(ConstantStruct::get(StructTy, CSVals));
+ // FIXME: Only allow the 3-field form in LLVM 4.0.
+ size_t NumElts = StructTy->getNumElements();
+ if (NumElts > 2)
+ CSVals[2] = Constant::getNullValue(StructTy->getElementType(2));
+ CAList.push_back(
+ ConstantStruct::get(StructTy, makeArrayRef(CSVals, NumElts)));
}
// Create the array initializer.
Module &M, Function *F, int Priority) {
IRBuilder<> IRB(M.getContext());
FunctionType *FnTy = FunctionType::get(IRB.getVoidTy(), false);
- StructType *Ty = StructType::get(
- IRB.getInt32Ty(), PointerType::getUnqual(FnTy), NULL);
-
- Constant *RuntimeCtorInit = ConstantStruct::get(
- Ty, IRB.getInt32(Priority), F, NULL);
// Get the current set of static global constructors and add the new ctor
// to the list.
SmallVector<Constant *, 16> CurrentCtors;
- if (GlobalVariable * GVCtor = M.getNamedGlobal(Array)) {
+ StructType *EltTy;
+ if (GlobalVariable *GVCtor = M.getNamedGlobal(Array)) {
+ // If there is a global_ctors array, use the existing struct type, which can
+ // have 2 or 3 fields.
+ ArrayType *ATy = cast<ArrayType>(GVCtor->getType()->getElementType());
+ EltTy = cast<StructType>(ATy->getElementType());
if (Constant *Init = GVCtor->getInitializer()) {
unsigned n = Init->getNumOperands();
CurrentCtors.reserve(n + 1);
CurrentCtors.push_back(cast<Constant>(Init->getOperand(i)));
}
GVCtor->eraseFromParent();
+ } else {
+ // Use a simple two-field struct if there isn't one already.
+ EltTy = StructType::get(IRB.getInt32Ty(), PointerType::getUnqual(FnTy),
+ nullptr);
}
+ // Build a 2 or 3 field global_ctor entry. We don't take a comdat key.
+ Constant *CSVals[3];
+ CSVals[0] = IRB.getInt32(Priority);
+ CSVals[1] = F;
+ // FIXME: Drop support for the two element form in LLVM 4.0.
+ if (EltTy->getNumElements() >= 3)
+ CSVals[2] = llvm::Constant::getNullValue(IRB.getInt8PtrTy());
+ Constant *RuntimeCtorInit =
+ ConstantStruct::get(EltTy, makeArrayRef(CSVals, EltTy->getNumElements()));
+
CurrentCtors.push_back(RuntimeCtorInit);
// Create a new initializer.
- ArrayType *AT = ArrayType::get(RuntimeCtorInit->getType(),
- CurrentCtors.size());
+ ArrayType *AT = ArrayType::get(EltTy, CurrentCtors.size());
Constant *NewInit = ConstantArray::get(AT, CurrentCtors);
// Create the new global variable and replace all uses of
--- /dev/null
+; RUN: llvm-as %s -o %t.new.bc
+; RUN: llvm-link %t.new.bc %S/Inputs/old_global_ctors.3.4.bc | llvm-dis | FileCheck %s
+
+; old_global_ctors.3.4.bc contains the following LLVM IL, assembled into
+; bitcode by llvm-as from 3.4. It uses a two element @llvm.global_ctors array.
+; ---
+; declare void @a_global_ctor()
+; declare void @b_global_ctor()
+;
+; @llvm.global_ctors = appending global [2 x { i32, void ()* } ] [
+; { i32, void ()* } { i32 65535, void ()* @a_global_ctor },
+; { i32, void ()* } { i32 65535, void ()* @b_global_ctor }
+; ]
+; ---
+
+declare void @c_global_ctor()
+declare void @d_global_ctor()
+
+@llvm.global_ctors = appending global [2 x { i32, void ()*, i8* } ] [
+ { i32, void ()*, i8* } { i32 65535, void ()* @c_global_ctor, i8* null },
+ { i32, void ()*, i8* } { i32 65535, void ()* @d_global_ctor, i8* null }
+]
+
+; CHECK: @llvm.global_ctors = appending global [4 x { i32, void ()*, i8* }] [
+; CHECK-DAG: { i32, void ()*, i8* } { i32 65535, void ()* @a_global_ctor, i8* null }
+; CHECK-DAG: { i32, void ()*, i8* } { i32 65535, void ()* @b_global_ctor, i8* null }
+; CHECK-DAG: { i32, void ()*, i8* } { i32 65535, void ()* @c_global_ctor, i8* null }
+; CHECK-DAG: { i32, void ()*, i8* } { i32 65535, void ()* @d_global_ctor, i8* null }
+; CHECK: ]
@.str2 = private unnamed_addr constant [12 x i8] c"destructing\00", align 1
@.str3 = private unnamed_addr constant [5 x i8] c"main\00", align 1
-@llvm.global_ctors = appending global [1 x { i32, void ()* }] [{ i32, void ()* } { i32 65535, void ()* @a_global_ctor }]
-@llvm.global_dtors = appending global [1 x { i32, void ()* }] [{ i32, void ()* } { i32 65535, void ()* @a_global_dtor }]
+%ini = type { i32, void()*, i8* }
+
+@llvm.global_ctors = appending global [2 x %ini ] [
+ %ini { i32 65535, void ()* @a_global_ctor, i8* null },
+ %ini { i32 65535, void ()* @b_global_ctor, i8* bitcast (i32* @b to i8*) }
+]
+@llvm.global_dtors = appending global [1 x %ini ] [%ini { i32 65535, void ()* @a_global_dtor, i8* null }]
declare i32 @puts(i8*)
ret void
}
+@b = global i32 zeroinitializer
+
+define void @b_global_ctor() nounwind {
+ store i32 42, i32* @b
+ ret void
+}
+
define void @a_global_dtor() nounwind {
%1 = call i32 @puts(i8* getelementptr inbounds ([12 x i8]* @.str2, i32 0, i32 0))
ret void
; WIN32: .section .CRT$XCU,"rd"
; WIN32: a_global_ctor
+; WIN32: .section .CRT$XCU,"rd",associative .bss,{{_?}}b
+; WIN32: b_global_ctor
; WIN32: .section .CRT$XTX,"rd"
; WIN32: a_global_dtor
; MINGW32: .section .ctors,"wd"
; MINGW32: a_global_ctor
+; MINGW32: .section .ctors,"wd",associative .bss,{{_?}}b
+; MINGW32: b_global_ctor
; MINGW32: .section .dtors,"wd"
; MINGW32: a_global_dtor
-; RUN: opt < %s -globalopt -S | not grep CTOR
-@llvm.global_ctors = appending global [11 x { i32, void ()* }] [ { i32, void ()* } { i32 65535, void ()* @CTOR1 }, { i32, void ()* } { i32 65535, void ()* @CTOR1 }, { i32, void ()* } { i32 65535, void ()* @CTOR2 }, { i32, void ()* } { i32 65535, void ()* @CTOR3 }, { i32, void ()* } { i32 65535, void ()* @CTOR4 }, { i32, void ()* } { i32 65535, void ()* @CTOR5 }, { i32, void ()* } { i32 65535, void ()* @CTOR6 }, { i32, void ()* } { i32 65535, void ()* @CTOR7 }, { i32, void ()* } { i32 65535, void ()* @CTOR8 }, { i32, void ()* } { i32 65535, void ()* @CTOR9 }, { i32, void ()* } { i32 2147483647, void ()* null } ] ; <[10 x { i32, void ()* }]*> [#uses=0]
+; RUN: opt < %s -globalopt -S | FileCheck %s
+; CHECK-NOT: CTOR
+%ini = type { i32, void()*, i8* }
+@llvm.global_ctors = appending global [11 x %ini] [
+ %ini { i32 65535, void ()* @CTOR1, i8* null },
+ %ini { i32 65535, void ()* @CTOR1, i8* null },
+ %ini { i32 65535, void ()* @CTOR2, i8* null },
+ %ini { i32 65535, void ()* @CTOR3, i8* null },
+ %ini { i32 65535, void ()* @CTOR4, i8* null },
+ %ini { i32 65535, void ()* @CTOR5, i8* null },
+ %ini { i32 65535, void ()* @CTOR6, i8* null },
+ %ini { i32 65535, void ()* @CTOR7, i8* null },
+ %ini { i32 65535, void ()* @CTOR8, i8* null },
+ %ini { i32 65535, void ()* @CTOR9, i8* null },
+ %ini { i32 2147483647, void ()* null, i8* null }
+]
+
@G = global i32 0 ; <i32*> [#uses=1]
@G2 = global i32 0 ; <i32*> [#uses=1]
@G3 = global i32 -123 ; <i32*> [#uses=2]
--- /dev/null
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+@llvm.global_ctors = appending global [1 x { i32, void()*, i8 } ] [
+ { i32, void()*, i8 } { i32 65535, void ()* null, i8 0 }
+]
+; CHECK: wrong type for intrinsic global variable
+
+@llvm.global_dtors = appending global [1 x { i32, void()*, i8*, i8 } ] [
+ { i32, void()*, i8*, i8 } { i32 65535, void ()* null, i8* null, i8 0}
+]
+; CHECK: wrong type for intrinsic global variable