}
void
-CodeGenFunction::EmitNewArrayInitializer(const CXXNewExpr *E,
- QualType elementType,
- llvm::Value *beginPtr,
- llvm::Value *numElements) {
+CodeGenFunction::EmitNewArrayInitializer(const CXXNewExpr *E,
+ QualType ElementType,
+ llvm::Value *BeginPtr,
+ llvm::Value *NumElements,
+ llvm::Value *AllocSizeWithoutCookie) {
+ // If we have a type with trivial initialization and no initializer,
+ // there's nothing to do.
if (!E->hasInitializer())
- return; // We have a POD type.
+ return;
- llvm::Value *explicitPtr = beginPtr;
- // Find the end of the array, hoisted out of the loop.
- llvm::Value *endPtr =
- Builder.CreateInBoundsGEP(beginPtr, numElements, "array.end");
+ llvm::Value *CurPtr = BeginPtr;
- unsigned initializerElements = 0;
+ unsigned InitListElements = 0;
const Expr *Init = E->getInitializer();
- llvm::AllocaInst *endOfInit = nullptr;
- QualType::DestructionKind dtorKind = elementType.isDestructedType();
- EHScopeStack::stable_iterator cleanup;
- llvm::Instruction *cleanupDominator = nullptr;
+ llvm::AllocaInst *EndOfInit = nullptr;
+ QualType::DestructionKind DtorKind = ElementType.isDestructedType();
+ EHScopeStack::stable_iterator Cleanup;
+ llvm::Instruction *CleanupDominator = nullptr;
// If the initializer is an initializer list, first do the explicit elements.
if (const InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) {
- initializerElements = ILE->getNumInits();
+ InitListElements = ILE->getNumInits();
// If this is a multi-dimensional array new, we will initialize multiple
// elements with each init list element.
QualType AllocType = E->getAllocatedType();
if (const ConstantArrayType *CAT = dyn_cast_or_null<ConstantArrayType>(
AllocType->getAsArrayTypeUnsafe())) {
- unsigned AS = explicitPtr->getType()->getPointerAddressSpace();
+ unsigned AS = CurPtr->getType()->getPointerAddressSpace();
llvm::Type *AllocPtrTy = ConvertTypeForMem(AllocType)->getPointerTo(AS);
- explicitPtr = Builder.CreateBitCast(explicitPtr, AllocPtrTy);
- initializerElements *= getContext().getConstantArrayElementCount(CAT);
+ CurPtr = Builder.CreateBitCast(CurPtr, AllocPtrTy);
+ InitListElements *= getContext().getConstantArrayElementCount(CAT);
}
- // Enter a partial-destruction cleanup if necessary.
- if (needsEHCleanup(dtorKind)) {
- // In principle we could tell the cleanup where we are more
+ // Enter a partial-destruction Cleanup if necessary.
+ if (needsEHCleanup(DtorKind)) {
+ // In principle we could tell the Cleanup where we are more
// directly, but the control flow can get so varied here that it
// would actually be quite complex. Therefore we go through an
// alloca.
- endOfInit = CreateTempAlloca(beginPtr->getType(), "array.endOfInit");
- cleanupDominator = Builder.CreateStore(beginPtr, endOfInit);
- pushIrregularPartialArrayCleanup(beginPtr, endOfInit, elementType,
- getDestroyer(dtorKind));
- cleanup = EHStack.stable_begin();
+ EndOfInit = CreateTempAlloca(BeginPtr->getType(), "array.init.end");
+ CleanupDominator = Builder.CreateStore(BeginPtr, EndOfInit);
+ pushIrregularPartialArrayCleanup(BeginPtr, EndOfInit, ElementType,
+ getDestroyer(DtorKind));
+ Cleanup = EHStack.stable_begin();
}
for (unsigned i = 0, e = ILE->getNumInits(); i != e; ++i) {
// Tell the cleanup that it needs to destroy up to this
// element. TODO: some of these stores can be trivially
// observed to be unnecessary.
- if (endOfInit) Builder.CreateStore(explicitPtr, endOfInit);
+ if (EndOfInit)
+ Builder.CreateStore(Builder.CreateBitCast(CurPtr, BeginPtr->getType()),
+ EndOfInit);
+ // FIXME: If the last initializer is an incomplete initializer list for
+ // an array, and we have an array filler, we can fold together the two
+ // initialization loops.
StoreAnyExprIntoOneUnit(*this, ILE->getInit(i),
- ILE->getInit(i)->getType(), explicitPtr);
- explicitPtr = Builder.CreateConstGEP1_32(explicitPtr, 1,
- "array.exp.next");
+ ILE->getInit(i)->getType(), CurPtr);
+ CurPtr = Builder.CreateConstInBoundsGEP1_32(CurPtr, 1, "array.exp.next");
}
// The remaining elements are filled with the array filler expression.
Init = ILE->getArrayFiller();
- explicitPtr = Builder.CreateBitCast(explicitPtr, beginPtr->getType());
+ // Extract the initializer for the individual array elements by pulling
+ // out the array filler from all the nested initializer lists. This avoids
+ // generating a nested loop for the initialization.
+ while (Init && Init->getType()->isConstantArrayType()) {
+ auto *SubILE = dyn_cast<InitListExpr>(Init);
+ if (!SubILE)
+ break;
+ assert(SubILE->getNumInits() == 0 && "explicit inits in array filler?");
+ Init = SubILE->getArrayFiller();
+ }
+
+ // Switch back to initializing one base element at a time.
+ CurPtr = Builder.CreateBitCast(CurPtr, BeginPtr->getType());
}
- llvm::ConstantInt *constNum = dyn_cast<llvm::ConstantInt>(numElements);
+ // Attempt to perform zero-initialization using memset.
+ auto TryMemsetInitialization = [&]() -> bool {
+ // FIXME: If the type is a pointer-to-data-member under the Itanium ABI,
+ // we can initialize with a memset to -1.
+ if (!CGM.getTypes().isZeroInitializable(ElementType))
+ return false;
+
+ // Optimization: since zero initialization will just set the memory
+ // to all zeroes, generate a single memset to do it in one shot.
+
+ // Subtract out the size of any elements we've already initialized.
+ auto *RemainingSize = AllocSizeWithoutCookie;
+ if (InitListElements) {
+ // We know this can't overflow; we check this when doing the allocation.
+ auto *InitializedSize = llvm::ConstantInt::get(
+ RemainingSize->getType(),
+ getContext().getTypeSizeInChars(ElementType).getQuantity() *
+ InitListElements);
+ RemainingSize = Builder.CreateSub(RemainingSize, InitializedSize);
+ }
+
+ // Create the memset.
+ CharUnits Alignment = getContext().getTypeAlignInChars(ElementType);
+ Builder.CreateMemSet(CurPtr, Builder.getInt8(0), RemainingSize,
+ Alignment.getQuantity(), false);
+ return true;
+ };
+
+ // If this is a constructor call, try to optimize it out, and failing that
+ // emit a single loop to initialize all remaining elements.
+ if (const CXXConstructExpr *CCE = dyn_cast_or_null<CXXConstructExpr>(Init)){
+ CXXConstructorDecl *Ctor = CCE->getConstructor();
+ if (Ctor->isTrivial()) {
+ // If new expression did not specify value-initialization, then there
+ // is no initialization.
+ if (!CCE->requiresZeroInitialization() || Ctor->getParent()->isEmpty())
+ return;
+
+ if (TryMemsetInitialization())
+ return;
+ }
+
+ // Store the new Cleanup position for irregular Cleanups.
+ //
+ // FIXME: Share this cleanup with the constructor call emission rather than
+ // having it create a cleanup of its own.
+ if (EndOfInit) Builder.CreateStore(CurPtr, EndOfInit);
+
+ // Emit a constructor call loop to initialize the remaining elements.
+ if (InitListElements)
+ NumElements = Builder.CreateSub(
+ NumElements,
+ llvm::ConstantInt::get(NumElements->getType(), InitListElements));
+ EmitCXXAggrConstructorCall(Ctor, NumElements, CurPtr,
+ CCE->arg_begin(), CCE->arg_end(),
+ CCE->requiresZeroInitialization());
+ return;
+ }
+
+ // If this is value-initialization, we can usually use memset.
+ ImplicitValueInitExpr IVIE(ElementType);
+ if (Init && isa<ImplicitValueInitExpr>(Init)) {
+ if (TryMemsetInitialization())
+ return;
+
+ // Switch to an ImplicitValueInitExpr for the element type. This handles
+ // only one case: multidimensional array new of pointers to members. In
+ // all other cases, we already have an initializer for the array element.
+ Init = &IVIE;
+ }
+
+ // At this point we should have found an initializer for the individual
+ // elements of the array.
+ assert(getContext().hasSameUnqualifiedType(ElementType, Init->getType()) &&
+ "got wrong type of element to initialize");
+
+ llvm::ConstantInt *ConstNum = dyn_cast<llvm::ConstantInt>(NumElements);
// If all elements have already been initialized, skip the whole loop.
- if (constNum && constNum->getZExtValue() <= initializerElements) {
- // If there was a cleanup, deactivate it.
- if (cleanupDominator)
- DeactivateCleanupBlock(cleanup, cleanupDominator);
+ if (ConstNum && ConstNum->getZExtValue() <= InitListElements) {
+ // If there was a Cleanup, deactivate it.
+ if (CleanupDominator)
+ DeactivateCleanupBlock(Cleanup, CleanupDominator);
return;
}
- // Create the continuation block.
- llvm::BasicBlock *contBB = createBasicBlock("new.loop.end");
+ // Create the loop blocks.
+ llvm::BasicBlock *EntryBB = Builder.GetInsertBlock();
+ llvm::BasicBlock *LoopBB = createBasicBlock("new.loop");
+ llvm::BasicBlock *ContBB = createBasicBlock("new.loop.end");
+
+ // Find the end of the array, hoisted out of the loop.
+ llvm::Value *EndPtr =
+ Builder.CreateInBoundsGEP(BeginPtr, NumElements, "array.end");
// If the number of elements isn't constant, we have to now check if there is
// anything left to initialize.
- if (!constNum) {
- llvm::BasicBlock *nonEmptyBB = createBasicBlock("new.loop.nonempty");
- llvm::Value *isEmpty = Builder.CreateICmpEQ(explicitPtr, endPtr,
+ if (!ConstNum) {
+ llvm::Value *IsEmpty = Builder.CreateICmpEQ(CurPtr, EndPtr,
"array.isempty");
- Builder.CreateCondBr(isEmpty, contBB, nonEmptyBB);
- EmitBlock(nonEmptyBB);
+ Builder.CreateCondBr(IsEmpty, ContBB, LoopBB);
}
// Enter the loop.
- llvm::BasicBlock *entryBB = Builder.GetInsertBlock();
- llvm::BasicBlock *loopBB = createBasicBlock("new.loop");
-
- EmitBlock(loopBB);
+ EmitBlock(LoopBB);
// Set up the current-element phi.
- llvm::PHINode *curPtr =
- Builder.CreatePHI(explicitPtr->getType(), 2, "array.cur");
- curPtr->addIncoming(explicitPtr, entryBB);
-
- // Store the new cleanup position for irregular cleanups.
- if (endOfInit) Builder.CreateStore(curPtr, endOfInit);
-
- // Enter a partial-destruction cleanup if necessary.
- if (!cleanupDominator && needsEHCleanup(dtorKind)) {
- pushRegularPartialArrayCleanup(beginPtr, curPtr, elementType,
- getDestroyer(dtorKind));
- cleanup = EHStack.stable_begin();
- cleanupDominator = Builder.CreateUnreachable();
+ llvm::PHINode *CurPtrPhi =
+ Builder.CreatePHI(CurPtr->getType(), 2, "array.cur");
+ CurPtrPhi->addIncoming(CurPtr, EntryBB);
+ CurPtr = CurPtrPhi;
+
+ // Store the new Cleanup position for irregular Cleanups.
+ if (EndOfInit) Builder.CreateStore(CurPtr, EndOfInit);
+
+ // Enter a partial-destruction Cleanup if necessary.
+ if (!CleanupDominator && needsEHCleanup(DtorKind)) {
+ pushRegularPartialArrayCleanup(BeginPtr, CurPtr, ElementType,
+ getDestroyer(DtorKind));
+ Cleanup = EHStack.stable_begin();
+ CleanupDominator = Builder.CreateUnreachable();
}
// Emit the initializer into this element.
- StoreAnyExprIntoOneUnit(*this, Init, E->getAllocatedType(), curPtr);
+ StoreAnyExprIntoOneUnit(*this, Init, Init->getType(), CurPtr);
- // Leave the cleanup if we entered one.
- if (cleanupDominator) {
- DeactivateCleanupBlock(cleanup, cleanupDominator);
- cleanupDominator->eraseFromParent();
+ // Leave the Cleanup if we entered one.
+ if (CleanupDominator) {
+ DeactivateCleanupBlock(Cleanup, CleanupDominator);
+ CleanupDominator->eraseFromParent();
}
- // FIXME: The code below intends to initialize the individual array base
- // elements, one at a time - but when dealing with multi-dimensional arrays -
- // the pointer arithmetic can get confused - so the fix below entails casting
- // to the allocated type to ensure that we get the pointer arithmetic right.
- // It seems like the right approach here, it to really initialize the
- // individual array base elements one at a time since it'll generate less
- // code. I think the problem is that the wrong type is being passed into
- // StoreAnyExprIntoOneUnit, but directly fixing that doesn't really work,
- // because the Init expression has the wrong type at this point.
- // So... this is ok for a quick fix, but we can and should do a lot better
- // here long-term.
-
// Advance to the next element by adjusting the pointer type as necessary.
- // For new int[10][20][30], alloc type is int[20][30], base type is 'int'.
- QualType AllocType = E->getAllocatedType();
- llvm::Type *AllocPtrTy = ConvertTypeForMem(AllocType)->getPointerTo(
- curPtr->getType()->getPointerAddressSpace());
- llvm::Value *curPtrAllocTy = Builder.CreateBitCast(curPtr, AllocPtrTy);
- llvm::Value *nextPtrAllocTy =
- Builder.CreateConstGEP1_32(curPtrAllocTy, 1, "array.next");
- // Cast it back to the base type so that we can compare it to the endPtr.
- llvm::Value *nextPtr =
- Builder.CreateBitCast(nextPtrAllocTy, endPtr->getType());
+ llvm::Value *NextPtr =
+ Builder.CreateConstInBoundsGEP1_32(CurPtr, 1, "array.next");
+
// Check whether we've gotten to the end of the array and, if so,
// exit the loop.
- llvm::Value *isEnd = Builder.CreateICmpEQ(nextPtr, endPtr, "array.atend");
- Builder.CreateCondBr(isEnd, contBB, loopBB);
- curPtr->addIncoming(nextPtr, Builder.GetInsertBlock());
+ llvm::Value *IsEnd = Builder.CreateICmpEQ(NextPtr, EndPtr, "array.atend");
+ Builder.CreateCondBr(IsEnd, ContBB, LoopBB);
+ CurPtrPhi->addIncoming(NextPtr, Builder.GetInsertBlock());
- EmitBlock(contBB);
+ EmitBlock(ContBB);
}
-static void EmitZeroMemSet(CodeGenFunction &CGF, QualType T,
- llvm::Value *NewPtr, llvm::Value *Size) {
- CGF.EmitCastToVoidPtr(NewPtr);
- CharUnits Alignment = CGF.getContext().getTypeAlignInChars(T);
- CGF.Builder.CreateMemSet(NewPtr, CGF.Builder.getInt8(0), Size,
- Alignment.getQuantity(), false);
-}
-
static void EmitNewInitializer(CodeGenFunction &CGF, const CXXNewExpr *E,
QualType ElementType,
llvm::Value *NewPtr,
llvm::Value *NumElements,
llvm::Value *AllocSizeWithoutCookie) {
- const Expr *Init = E->getInitializer();
- if (E->isArray()) {
- if (const CXXConstructExpr *CCE = dyn_cast_or_null<CXXConstructExpr>(Init)){
- CXXConstructorDecl *Ctor = CCE->getConstructor();
- if (Ctor->isTrivial()) {
- // If new expression did not specify value-initialization, then there
- // is no initialization.
- if (!CCE->requiresZeroInitialization() || Ctor->getParent()->isEmpty())
- return;
-
- if (CGF.CGM.getTypes().isZeroInitializable(ElementType)) {
- // Optimization: since zero initialization will just set the memory
- // to all zeroes, generate a single memset to do it in one shot.
- EmitZeroMemSet(CGF, ElementType, NewPtr, AllocSizeWithoutCookie);
- return;
- }
- }
-
- CGF.EmitCXXAggrConstructorCall(Ctor, NumElements, NewPtr,
- CCE->arg_begin(), CCE->arg_end(),
- CCE->requiresZeroInitialization());
- return;
- } else if (Init && isa<ImplicitValueInitExpr>(Init) &&
- CGF.CGM.getTypes().isZeroInitializable(ElementType)) {
- // Optimization: since zero initialization will just set the memory
- // to all zeroes, generate a single memset to do it in one shot.
- EmitZeroMemSet(CGF, ElementType, NewPtr, AllocSizeWithoutCookie);
- return;
- }
- CGF.EmitNewArrayInitializer(E, ElementType, NewPtr, NumElements);
- return;
- }
-
- if (!Init)
- return;
-
- StoreAnyExprIntoOneUnit(CGF, Init, E->getAllocatedType(), NewPtr);
+ if (E->isArray())
+ CGF.EmitNewArrayInitializer(E, ElementType, NewPtr, NumElements,
+ AllocSizeWithoutCookie);
+ else if (const Expr *Init = E->getInitializer())
+ StoreAnyExprIntoOneUnit(CGF, Init, E->getAllocatedType(), NewPtr);
}
/// Emit a call to an operator new or operator delete function, as implicitly
//
// { 4, 5, 6 }
//
-// CHECK: %[[S_1:.*]] = getelementptr [3 x %[[S]]]* %[[S_0]], i32 1
+// CHECK: %[[S_1:.*]] = getelementptr inbounds [3 x %[[S]]]* %[[S_0]], i32 1
//
// CHECK: %[[S_1_0:.*]] = getelementptr inbounds [3 x %[[S]]]* %[[S_1]], i64 0, i64 0
// CHECK: call void @_ZN1SC1Ei(%[[S]]* %[[S_1_0]], i32 4)
// CHECK: store i64 %[[ELTS]], i64* %[[COOKIE]]
// CHECK: %[[START_AS_i8:.*]] = getelementptr inbounds i8* %[[ALLOC]], i64 8
// CHECK: %[[START_AS_S:.*]] = bitcast i8* %[[START_AS_i8]] to %[[S]]*
-// CHECK: %[[END_AS_S:.*]] = getelementptr inbounds %[[S]]* %[[START_AS_S]], i64 %[[ELTS]]
//
// Explicit initializers:
//
//
// { 4, 5, 6 }
//
-// CHECK: %[[S_1:.*]] = getelementptr [3 x %[[S]]]* %[[S_0]], i32 1
+// CHECK: %[[S_1:.*]] = getelementptr inbounds [3 x %[[S]]]* %[[S_0]], i32 1
//
// CHECK: %[[S_1_0:.*]] = getelementptr inbounds [3 x %[[S]]]* %[[S_1]], i64 0, i64 0
// CHECK: call void @_ZN1SC1Ei(%[[S]]* %[[S_1_0]], i32 4)
// CHECK: %[[S_1_2:.*]] = getelementptr inbounds %[[S]]* %[[S_1_1]], i64 1
// CHECK: call void @_ZN1SC1Ei(%[[S]]* %[[S_1_2]], i32 6)
//
-// CHECK: %[[S_2:.*]] = getelementptr [3 x %[[S]]]* %[[S_1]], i32 1
+// And the rest.
+//
+// CHECK: %[[S_2:.*]] = getelementptr inbounds [3 x %[[S]]]* %[[S_1]], i32 1
// CHECK: %[[S_2_AS_S:.*]] = bitcast [3 x %[[S]]]* %[[S_2]] to %[[S]]*
-// CHECK: icmp eq %[[S]]* %[[S_2_AS_S]], %[[END_AS_S]]
-// CHECK: br i1
//
-// S[n-2][3] initialization loop:
+// CHECK: %[[REST:.*]] = sub i64 %[[ELTS]], 6
+// CHECK: icmp eq i64 %[[REST]], 0
+// CHECK: br i1
//
-// CHECK: %[[END_INNER:.*]] = getelementptr inbounds %[[S]]* %{{.*}}, i64 3
+// CHECK: %[[END:.*]] = getelementptr inbounds %[[S]]* %[[S_2_AS_S]], i64 %[[REST]]
// CHECK: br label
//
-// S[3] initialization loop:
+// CHECK: %[[CUR:.*]] = phi %[[S]]* [ %[[S_2_AS_S]], {{.*}} ], [ %[[NEXT:.*]], {{.*}} ]
+// CHECK: call void @_ZN1SC1Ev(%[[S]]* %[[CUR]])
+// CHECK: %[[NEXT]] = getelementptr inbounds %[[S]]* %[[CUR]], i64 1
+// CHECK: icmp eq %[[S]]* %[[NEXT]], %[[END]]
+// CHECK: br i1
//
-// CHECK: call void @_ZN1SC1Ev(%[[S]]*
-// CHECK: %[[NEXT_INNER:.*]] = getelementptr inbounds %[[S]]* %{{.*}}, i64 1
-// CHECK: icmp eq %[[S]]* %[[NEXT_INNER]], %[[END_INNER]]
-// CHECK: br i1
+// CHECK: }
+
+struct T { int a; };
+void *r = new T[n][3]{ { 1, 2, 3 }, { 4, 5, 6 } };
+
+// CHECK-LABEL: define
//
-// CHECK: %[[NEXT_OUTER:.*]] = getelementptr [3 x %[[S]]]* %{{.*}}, i32 1
-// CHECK: %[[NEXT_OUTER_AS_S:.*]] = bitcast [3 x %[[S]]]* %[[NEXT_OUTER]] to %[[S]]*
-// CHECK: icmp eq %[[S]]* %[[NEXT_OUTER_AS_S]], %[[END_AS_S]]
-// CHECK: br i1
+// CHECK: load i32* @n
+// CHECK: call {{.*}} @llvm.umul.with.overflow.i64(i64 %[[N:.*]], i64 12)
+// CHECK: %[[ELTS:.*]] = mul i64 %[[N]], 3
+//
+// No cookie.
+// CHECK-NOT: @llvm.uadd.with.overflow
+//
+// CHECK: %[[ALLOC:.*]] = call noalias i8* @_Znam(i64 %{{.*}})
+//
+// CHECK: %[[START_AS_T:.*]] = bitcast i8* %[[ALLOC]] to %[[T:.*]]*
+//
+// Explicit initializers:
+//
+// { 1, 2, 3 }
+//
+// CHECK: %[[T_0:.*]] = bitcast %[[T]]* %[[START_AS_T]] to [3 x %[[T]]]*
+//
+// CHECK: %[[T_0_0:.*]] = getelementptr inbounds [3 x %[[T]]]* %[[T_0]], i64 0, i64 0
+// CHECK: %[[T_0_0_0:.*]] = getelementptr inbounds %[[T]]* %[[T_0_0]], i32 0, i32 0
+// CHECK: store i32 1, i32* %[[T_0_0_0]]
+// CHECK: %[[T_0_1:.*]] = getelementptr inbounds %[[T]]* %[[T_0_0]], i64 1
+// CHECK: %[[T_0_1_0:.*]] = getelementptr inbounds %[[T]]* %[[T_0_1]], i32 0, i32 0
+// CHECK: store i32 2, i32* %[[T_0_1_0]]
+// CHECK: %[[T_0_2:.*]] = getelementptr inbounds %[[T]]* %[[T_0_1]], i64 1
+// CHECK: %[[T_0_2_0:.*]] = getelementptr inbounds %[[T]]* %[[T_0_2]], i32 0, i32 0
+// CHECK: store i32 3, i32* %[[T_0_2_0]]
+//
+// { 4, 5, 6 }
+//
+// CHECK: %[[T_1:.*]] = getelementptr inbounds [3 x %[[T]]]* %[[T_0]], i32 1
+//
+// CHECK: %[[T_1_0:.*]] = getelementptr inbounds [3 x %[[T]]]* %[[T_1]], i64 0, i64 0
+// CHECK: %[[T_1_0_0:.*]] = getelementptr inbounds %[[T]]* %[[T_1_0]], i32 0, i32 0
+// CHECK: store i32 4, i32* %[[T_1_0_0]]
+// CHECK: %[[T_1_1:.*]] = getelementptr inbounds %[[T]]* %[[T_1_0]], i64 1
+// CHECK: %[[T_1_1_0:.*]] = getelementptr inbounds %[[T]]* %[[T_1_1]], i32 0, i32 0
+// CHECK: store i32 5, i32* %[[T_1_1_0]]
+// CHECK: %[[T_1_2:.*]] = getelementptr inbounds %[[T]]* %[[T_1_1]], i64 1
+// CHECK: %[[T_1_2_0:.*]] = getelementptr inbounds %[[T]]* %[[T_1_2]], i32 0, i32 0
+// CHECK: store i32 6, i32* %[[T_1_2_0]]
+//
+// And the rest gets memset to 0.
+//
+// CHECK: %[[T_2:.*]] = getelementptr inbounds [3 x %[[T]]]* %[[T_1]], i32 1
+// CHECK: %[[T_2_AS_T:.*]] = bitcast [3 x %[[T]]]* %[[T_2]] to %[[T]]*
+//
+// CHECK: %[[SIZE:.*]] = sub i64 %{{.*}}, 24
+// CHECK: %[[REST:.*]] = bitcast %[[T]]* %[[T_2_AS_T]] to i8*
+// CHECK: call void @llvm.memset.p0i8.i64(i8* %[[REST]], i8 0, i64 %[[SIZE]], i32 4, i1 false)
//
// CHECK: }
+