[BasicAA] Improve precision of alloca vs. inbounds GEP alias queries

If a we have (a) a GEP and (b) a pointer based on an alloca, and the
beginning of the object the GEP points would have a negative offset with
repsect to the alloca, then the GEP can not alias pointer (b).

For example, consider code like:

struct { int f0, int f1, ...} foo;
...
foo alloca;
foo *random = bar(alloca);
int *f0 = &alloca.f0
int *f1 = &random->f1;

Which is lowered, approximately, to:
%alloca = alloca %struct.foo
%random = call %struct.foo* @random(%struct.foo* %alloca)
%f0 = getelementptr inbounds %struct, %struct.foo* %alloca, i32 0, i32 0
%f1 = getelementptr inbounds %struct, %struct.foo* %random, i32 0, i32 1

Assume %f1 and %f0 alias. Then %f1 would point into the object allocated
by %alloca. Since the %f1 GEP is inbounds, that means %random must also
point into the same object. But since %f0 points to the beginning of %alloca,
the highest %f1 can be is (%alloca + 3). This means %random can not be higher
than (%alloca - 1), and so is not inbounds, a contradiction.

Differential Revision: http://reviews.llvm.org/D20495

llvm-svn: 270777
This commit is contained in:
Michael Kuperstein 2016-05-25 22:23:08 +00:00
parent 6ee02c7fce
commit 82069c44ca
3 changed files with 227 additions and 86 deletions

View File

@ -109,6 +109,20 @@ private:
}
};
// Represents the internal structure of a GEP, decomposed into a base pointer,
// constant offsets, and variable scaled indices.
struct DecomposedGEP {
// Base pointer of the GEP
const Value *Base;
// Total constant offset w.r.t the base from indexing into structs
int64_t StructOffset;
// Total constant offset w.r.t the base from indexing through
// pointers/arrays/vectors
int64_t OtherOffset;
// Scaled variable (non-constant) indices.
SmallVector<VariableGEPIndex, 4> VarIndices;
};
/// Track alias queries to guard against recursion.
typedef std::pair<MemoryLocation, MemoryLocation> LocPair;
typedef SmallDenseMap<LocPair, AliasResult, 8> AliasCacheTy;
@ -139,11 +153,13 @@ private:
const DataLayout &DL, unsigned Depth, AssumptionCache *AC,
DominatorTree *DT, bool &NSW, bool &NUW);
static const Value *
DecomposeGEPExpression(const Value *V, int64_t &BaseOffs,
SmallVectorImpl<VariableGEPIndex> &VarIndices,
bool &MaxLookupReached, const DataLayout &DL,
AssumptionCache *AC, DominatorTree *DT);
static bool DecomposeGEPExpression(const Value *V, DecomposedGEP &Decomposed,
const DataLayout &DL, AssumptionCache *AC, DominatorTree *DT);
static bool isGEPBaseAtNegativeOffset(const GEPOperator *GEPOp,
const DecomposedGEP &DecompGEP, const DecomposedGEP &DecompAlloca,
uint64_t AllocaAccessSize);
/// \brief A Heuristic for aliasGEP that searches for a constant offset
/// between the variables.
///

View File

@ -343,16 +343,16 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
/// GetUnderlyingObject and DecomposeGEPExpression must use the same search
/// depth (MaxLookupSearchDepth). When DataLayout not is around, it just looks
/// through pointer casts.
/*static*/ const Value *BasicAAResult::DecomposeGEPExpression(
const Value *V, int64_t &BaseOffs,
SmallVectorImpl<VariableGEPIndex> &VarIndices, bool &MaxLookupReached,
const DataLayout &DL, AssumptionCache *AC, DominatorTree *DT) {
bool BasicAAResult::DecomposeGEPExpression(const Value *V,
DecomposedGEP &Decomposed, const DataLayout &DL, AssumptionCache *AC,
DominatorTree *DT) {
// Limit recursion depth to limit compile time in crazy cases.
unsigned MaxLookup = MaxLookupSearchDepth;
MaxLookupReached = false;
SearchTimes++;
BaseOffs = 0;
Decomposed.StructOffset = 0;
Decomposed.OtherOffset = 0;
Decomposed.VarIndices.clear();
do {
// See if this is a bitcast or GEP.
const Operator *Op = dyn_cast<Operator>(V);
@ -364,7 +364,8 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
continue;
}
}
return V;
Decomposed.Base = V;
return false;
}
if (Op->getOpcode() == Instruction::BitCast ||
@ -388,12 +389,15 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
continue;
}
return V;
Decomposed.Base = V;
return false;
}
// Don't attempt to analyze GEPs over unsized objects.
if (!GEPOp->getSourceElementType()->isSized())
return V;
if (!GEPOp->getSourceElementType()->isSized()) {
Decomposed.Base = V;
return false;
}
unsigned AS = GEPOp->getPointerAddressSpace();
// Walk the indices of the GEP, accumulating them into BaseOff/VarIndices.
@ -409,7 +413,8 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
if (FieldNo == 0)
continue;
BaseOffs += DL.getStructLayout(STy)->getElementOffset(FieldNo);
Decomposed.StructOffset +=
DL.getStructLayout(STy)->getElementOffset(FieldNo);
continue;
}
@ -417,7 +422,8 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
if (const ConstantInt *CIdx = dyn_cast<ConstantInt>(Index)) {
if (CIdx->isZero())
continue;
BaseOffs += DL.getTypeAllocSize(*GTI) * CIdx->getSExtValue();
Decomposed.OtherOffset +=
DL.getTypeAllocSize(*GTI) * CIdx->getSExtValue();
continue;
}
@ -438,18 +444,19 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
// The GEP index scale ("Scale") scales C1*V+C2, yielding (C1*V+C2)*Scale.
// This gives us an aggregate computation of (C1*Scale)*V + C2*Scale.
BaseOffs += IndexOffset.getSExtValue() * Scale;
Decomposed.OtherOffset += IndexOffset.getSExtValue() * Scale;
Scale *= IndexScale.getSExtValue();
// If we already had an occurrence of this index variable, merge this
// scale into it. For example, we want to handle:
// A[x][x] -> x*16 + x*4 -> x*20
// This also ensures that 'x' only appears in the index list once.
for (unsigned i = 0, e = VarIndices.size(); i != e; ++i) {
if (VarIndices[i].V == Index && VarIndices[i].ZExtBits == ZExtBits &&
VarIndices[i].SExtBits == SExtBits) {
Scale += VarIndices[i].Scale;
VarIndices.erase(VarIndices.begin() + i);
for (unsigned i = 0, e = Decomposed.VarIndices.size(); i != e; ++i) {
if (Decomposed.VarIndices[i].V == Index &&
Decomposed.VarIndices[i].ZExtBits == ZExtBits &&
Decomposed.VarIndices[i].SExtBits == SExtBits) {
Scale += Decomposed.VarIndices[i].Scale;
Decomposed.VarIndices.erase(Decomposed.VarIndices.begin() + i);
break;
}
}
@ -461,21 +468,24 @@ static int64_t adjustToPointerSize(int64_t Offset, unsigned PointerSize) {
if (Scale) {
VariableGEPIndex Entry = {Index, ZExtBits, SExtBits,
static_cast<int64_t>(Scale)};
VarIndices.push_back(Entry);
Decomposed.VarIndices.push_back(Entry);
}
}
// Take care of wrap-arounds
BaseOffs = adjustToPointerSize(BaseOffs, PointerSize);
Decomposed.StructOffset =
adjustToPointerSize(Decomposed.StructOffset, PointerSize);
Decomposed.OtherOffset =
adjustToPointerSize(Decomposed.OtherOffset, PointerSize);
// Analyze the base pointer next.
V = GEPOp->getOperand(0);
} while (--MaxLookup);
// If the chain of expressions is too deep, just return early.
MaxLookupReached = true;
Decomposed.Base = V;
SearchLimitReached++;
return V;
return true;
}
/// Returns whether the given pointer value points to memory that is local to
@ -949,6 +959,59 @@ static AliasResult aliasSameBasePointerGEPs(const GEPOperator *GEP1,
return MayAlias;
}
// If a we have (a) a GEP and (b) a pointer based on an alloca, and the
// beginning of the object the GEP points would have a negative offset with
// repsect to the alloca, that means the GEP can not alias pointer (b).
// Note that the pointer based on the alloca may not be a GEP. For
// example, it may be the alloca itself.
//
// For example, consider:
//
// struct { int f0, int f1, ...} foo;
// foo alloca;
// foo* random = bar(alloca);
// int *f0 = &alloca.f0
// int *f1 = &random->f1;
//
// Which is lowered, approximately, to:
//
// %alloca = alloca %struct.foo
// %random = call %struct.foo* @random(%struct.foo* %alloca)
// %f0 = getelementptr inbounds %struct, %struct.foo* %alloca, i32 0, i32 0
// %f1 = getelementptr inbounds %struct, %struct.foo* %random, i32 0, i32 1
//
// Assume %f1 and %f0 alias. Then %f1 would point into the object allocated
// by %alloca. Since the %f1 GEP is inbounds, that means %random must also
// point into the same object. But since %f0 points to the beginning of %alloca,
// the highest %f1 can be is (%alloca + 3). This means %random can not be higher
// than (%alloca - 1), and so is not inbounds, a contradiction.
bool BasicAAResult::isGEPBaseAtNegativeOffset(const GEPOperator *GEPOp,
const DecomposedGEP &DecompGEP, const DecomposedGEP &DecompAlloca,
uint64_t AllocaAccessSize) {
// If the alloca access size is unknown, or the GEP isn't inbounds, bail.
if (AllocaAccessSize == MemoryLocation::UnknownSize || !GEPOp->isInBounds())
return false;
// We need an alloca, and want to know the offset of the pointer
// from the alloca precisely, so no variable indices are allowed.
if (!isa<AllocaInst>(DecompAlloca.Base) || !DecompAlloca.VarIndices.empty())
return false;
int64_t AllocaBaseOffset = DecompAlloca.StructOffset +
DecompAlloca.OtherOffset;
// If the GEP has no variable indices, we know the precise offset
// from the base, then use it. If the GEP has variable indices, we're in
// a bit more trouble: we can't count on the constant offsets that come
// from non-struct sources, since these can be "rewound" by a negative
// variable offset. So use only offsets that came from structs.
int64_t GEPBaseOffset = DecompGEP.StructOffset;
if (DecompGEP.VarIndices.empty())
GEPBaseOffset += DecompGEP.OtherOffset;
return (GEPBaseOffset >= AllocaBaseOffset + (int64_t)AllocaAccessSize);
}
/// Provides a bunch of ad-hoc rules to disambiguate a GEP instruction against
/// another pointer.
///
@ -960,14 +1023,34 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
uint64_t V2Size, const AAMDNodes &V2AAInfo,
const Value *UnderlyingV1,
const Value *UnderlyingV2) {
int64_t GEP1BaseOffset;
bool GEP1MaxLookupReached;
SmallVector<VariableGEPIndex, 4> GEP1VariableIndices;
DecomposedGEP DecompGEP1, DecompGEP2;
bool GEP1MaxLookupReached =
DecomposeGEPExpression(GEP1, DecompGEP1, DL, &AC, DT);
bool GEP2MaxLookupReached =
DecomposeGEPExpression(V2, DecompGEP2, DL, &AC, DT);
int64_t GEP1BaseOffset = DecompGEP1.StructOffset + DecompGEP1.OtherOffset;
int64_t GEP2BaseOffset = DecompGEP2.StructOffset + DecompGEP2.OtherOffset;
assert(DecompGEP1.Base == UnderlyingV1 && DecompGEP2.Base == UnderlyingV2 &&
"DecomposeGEPExpression returned a result different from "
"GetUnderlyingObject");
// If the GEP's offset relative to its base is such that the base would
// fall below the start of the object underlying V2, then the GEP and V2
// cannot alias.
if (!GEP1MaxLookupReached && !GEP2MaxLookupReached &&
isGEPBaseAtNegativeOffset(GEP1, DecompGEP1, DecompGEP2, V2Size))
return NoAlias;
// If we have two gep instructions with must-alias or not-alias'ing base
// pointers, figure out if the indexes to the GEP tell us anything about the
// derived pointer.
if (const GEPOperator *GEP2 = dyn_cast<GEPOperator>(V2)) {
// Check for the GEP base being at a negative offset, this time in the other
// direction.
if (!GEP1MaxLookupReached && !GEP2MaxLookupReached &&
isGEPBaseAtNegativeOffset(GEP2, DecompGEP2, DecompGEP1, V1Size))
return NoAlias;
// Do the base pointers alias?
AliasResult BaseAlias =
aliasCheck(UnderlyingV1, MemoryLocation::UnknownSize, AAMDNodes(),
@ -982,31 +1065,14 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
if (PreciseBaseAlias == NoAlias) {
// See if the computed offset from the common pointer tells us about the
// relation of the resulting pointer.
int64_t GEP2BaseOffset;
bool GEP2MaxLookupReached;
SmallVector<VariableGEPIndex, 4> GEP2VariableIndices;
const Value *GEP2BasePtr =
DecomposeGEPExpression(GEP2, GEP2BaseOffset, GEP2VariableIndices,
GEP2MaxLookupReached, DL, &AC, DT);
const Value *GEP1BasePtr =
DecomposeGEPExpression(GEP1, GEP1BaseOffset, GEP1VariableIndices,
GEP1MaxLookupReached, DL, &AC, DT);
// DecomposeGEPExpression and GetUnderlyingObject should return the
// same result except when DecomposeGEPExpression has no DataLayout.
// FIXME: They always have a DataLayout, so this should become an
// assert.
if (GEP1BasePtr != UnderlyingV1 || GEP2BasePtr != UnderlyingV2) {
return MayAlias;
}
// If the max search depth is reached the result is undefined
if (GEP2MaxLookupReached || GEP1MaxLookupReached)
return MayAlias;
// Same offsets.
if (GEP1BaseOffset == GEP2BaseOffset &&
GEP1VariableIndices == GEP2VariableIndices)
DecompGEP1.VarIndices == DecompGEP2.VarIndices)
return NoAlias;
GEP1VariableIndices.clear();
}
}
@ -1018,24 +1084,6 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
// Otherwise, we have a MustAlias. Since the base pointers alias each other
// exactly, see if the computed offset from the common pointer tells us
// about the relation of the resulting pointer.
const Value *GEP1BasePtr =
DecomposeGEPExpression(GEP1, GEP1BaseOffset, GEP1VariableIndices,
GEP1MaxLookupReached, DL, &AC, DT);
int64_t GEP2BaseOffset;
bool GEP2MaxLookupReached;
SmallVector<VariableGEPIndex, 4> GEP2VariableIndices;
const Value *GEP2BasePtr =
DecomposeGEPExpression(GEP2, GEP2BaseOffset, GEP2VariableIndices,
GEP2MaxLookupReached, DL, &AC, DT);
// DecomposeGEPExpression and GetUnderlyingObject should return the
// same result except when DecomposeGEPExpression has no DataLayout.
// FIXME: They always have a DataLayout, so this should become an assert.
if (GEP1BasePtr != UnderlyingV1 || GEP2BasePtr != UnderlyingV2) {
return MayAlias;
}
// If we know the two GEPs are based off of the exact same pointer (and not
// just the same underlying object), see if that tells us anything about
// the resulting pointers.
@ -1053,7 +1101,7 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
// Subtract the GEP2 pointer from the GEP1 pointer to find out their
// symbolic difference.
GEP1BaseOffset -= GEP2BaseOffset;
GetIndexDifference(GEP1VariableIndices, GEP2VariableIndices);
GetIndexDifference(DecompGEP1.VarIndices, DecompGEP2.VarIndices);
} else {
// Check to see if these two pointers are related by the getelementptr
@ -1075,16 +1123,6 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
// with the first operand of the getelementptr".
return R;
const Value *GEP1BasePtr =
DecomposeGEPExpression(GEP1, GEP1BaseOffset, GEP1VariableIndices,
GEP1MaxLookupReached, DL, &AC, DT);
// DecomposeGEPExpression and GetUnderlyingObject should return the
// same result except when DecomposeGEPExpression has no DataLayout.
// FIXME: They always have a DataLayout, so this should become an assert.
if (GEP1BasePtr != UnderlyingV1) {
return MayAlias;
}
// If the max search depth is reached the result is undefined
if (GEP1MaxLookupReached)
return MayAlias;
@ -1096,14 +1134,14 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
//
// In the other case, if we have getelementptr <ptr>, 0, 0, 0, 0, ... and V2
// must aliases the GEP, the end result is a must alias also.
if (GEP1BaseOffset == 0 && GEP1VariableIndices.empty())
if (GEP1BaseOffset == 0 && DecompGEP1.VarIndices.empty())
return MustAlias;
// If there is a constant difference between the pointers, but the difference
// is less than the size of the associated memory object, then we know
// that the objects are partially overlapping. If the difference is
// greater, we know they do not overlap.
if (GEP1BaseOffset != 0 && GEP1VariableIndices.empty()) {
if (GEP1BaseOffset != 0 && DecompGEP1.VarIndices.empty()) {
if (GEP1BaseOffset >= 0) {
if (V2Size != MemoryLocation::UnknownSize) {
if ((uint64_t)GEP1BaseOffset < V2Size)
@ -1128,22 +1166,22 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
}
}
if (!GEP1VariableIndices.empty()) {
if (!DecompGEP1.VarIndices.empty()) {
uint64_t Modulo = 0;
bool AllPositive = true;
for (unsigned i = 0, e = GEP1VariableIndices.size(); i != e; ++i) {
for (unsigned i = 0, e = DecompGEP1.VarIndices.size(); i != e; ++i) {
// Try to distinguish something like &A[i][1] against &A[42][0].
// Grab the least significant bit set in any of the scales. We
// don't need std::abs here (even if the scale's negative) as we'll
// be ^'ing Modulo with itself later.
Modulo |= (uint64_t)GEP1VariableIndices[i].Scale;
Modulo |= (uint64_t)DecompGEP1.VarIndices[i].Scale;
if (AllPositive) {
// If the Value could change between cycles, then any reasoning about
// the Value this cycle may not hold in the next cycle. We'll just
// give up if we can't determine conditions that hold for every cycle:
const Value *V = GEP1VariableIndices[i].V;
const Value *V = DecompGEP1.VarIndices[i].V;
bool SignKnownZero, SignKnownOne;
ComputeSignBit(const_cast<Value *>(V), SignKnownZero, SignKnownOne, DL,
@ -1151,14 +1189,14 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
// Zero-extension widens the variable, and so forces the sign
// bit to zero.
bool IsZExt = GEP1VariableIndices[i].ZExtBits > 0 || isa<ZExtInst>(V);
bool IsZExt = DecompGEP1.VarIndices[i].ZExtBits > 0 || isa<ZExtInst>(V);
SignKnownZero |= IsZExt;
SignKnownOne &= !IsZExt;
// If the variable begins with a zero then we know it's
// positive, regardless of whether the value is signed or
// unsigned.
int64_t Scale = GEP1VariableIndices[i].Scale;
int64_t Scale = DecompGEP1.VarIndices[i].Scale;
AllPositive =
(SignKnownZero && Scale >= 0) || (SignKnownOne && Scale < 0);
}
@ -1181,7 +1219,7 @@ AliasResult BasicAAResult::aliasGEP(const GEPOperator *GEP1, uint64_t V1Size,
if (AllPositive && GEP1BaseOffset > 0 && V2Size <= (uint64_t)GEP1BaseOffset)
return NoAlias;
if (constantOffsetHeuristic(GEP1VariableIndices, V1Size, V2Size,
if (constantOffsetHeuristic(DecompGEP1.VarIndices, V1Size, V2Size,
GEP1BaseOffset, &AC, DT))
return NoAlias;
}

View File

@ -0,0 +1,87 @@
; RUN: opt < %s -basicaa -aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s
target datalayout = "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i386-unknown-linux-gnu"
declare i32* @random.i32(i32* %ptr)
; CHECK-LABEL: Function: arr:
; CHECK-DAG: MayAlias: i32* %alloca, i32* %p0
; CHECK-DAG: NoAlias: i32* %alloca, i32* %p1
define void @arr() {
%alloca = alloca i32, i32 4
%random = call i32* @random.i32(i32* %alloca)
%p0 = getelementptr inbounds i32, i32* %random, i32 0
%p1 = getelementptr inbounds i32, i32* %random, i32 1
ret void
}
; CHECK-LABEL: Function: arg:
; CHECK-DAG: MayAlias: i32* %arg, i32* %p0
; CHECK-DAG: MayAlias: i32* %arg, i32* %p1
define void @arg(i32* %arg) {
%random = call i32* @random.i32(i32* %arg)
%p0 = getelementptr inbounds i32, i32* %random, i32 0
%p1 = getelementptr inbounds i32, i32* %random, i32 1
ret void
}
; CHECK-LABEL: Function: struct:
; CHECK-DAG: MayAlias: i32* %f0, i32* %p0
; CHECK-DAG: MayAlias: i32* %f1, i32* %p0
; CHECK-DAG: NoAlias: i32* %f0, i32* %p1
; CHECK-DAG: MayAlias: i32* %f1, i32* %p1
%struct = type { i32, i32, i32 }
define void @struct() {
%alloca = alloca %struct
%alloca.i32 = bitcast %struct* %alloca to i32*
%random = call i32* @random.i32(i32* %alloca.i32)
%f0 = getelementptr inbounds %struct, %struct* %alloca, i32 0, i32 0
%f1 = getelementptr inbounds %struct, %struct* %alloca, i32 0, i32 1
%p0 = getelementptr inbounds i32, i32* %random, i32 0
%p1 = getelementptr inbounds i32, i32* %random, i32 1
ret void
}
; CHECK-LABEL: Function: complex1:
; CHECK-DAG: MayAlias: i32* %a2.0, i32* %r2.0
; CHECK-DAG: NoAlias: i32* %a2.0, i32* %r2.1
; CHECK-DAG: MayAlias: i32* %a2.0, i32* %r2.i
; CHECK-DAG: MayAlias: i32* %a2.0, i32* %r2.1i
; CHECK-DAG: NoAlias: i32* %a1, i32* %r2.0
; CHECK-DAG: NoAlias: i32* %a1, i32* %r2.1
; CHECK-DAG: NoAlias: i32* %a1, i32* %r2.i
; CHECK-DAG: NoAlias: i32* %a1, i32* %r2.1i
%complex = type { i32, i32, [4 x i32] }
define void @complex1(i32 %i) {
%alloca = alloca %complex
%alloca.i32 = bitcast %complex* %alloca to i32*
%r.i32 = call i32* @random.i32(i32* %alloca.i32)
%random = bitcast i32* %r.i32 to %complex*
%a1 = getelementptr inbounds %complex, %complex* %alloca, i32 0, i32 1
%a2.0 = getelementptr inbounds %complex, %complex* %alloca, i32 0, i32 2, i32 0
%r2.0 = getelementptr inbounds %complex, %complex* %random, i32 0, i32 2, i32 0
%r2.1 = getelementptr inbounds %complex, %complex* %random, i32 0, i32 2, i32 1
%r2.i = getelementptr inbounds %complex, %complex* %random, i32 0, i32 2, i32 %i
%r2.1i = getelementptr inbounds i32, i32* %r2.1, i32 %i
ret void
}
; CHECK-LABEL: Function: complex2:
; CHECK-DAG: NoAlias: i32* %alloca, i32* %p120
; CHECK-DAG: NoAlias: i32* %alloca, i32* %pi20
; CHECK-DAG: NoAlias: i32* %alloca, i32* %pij1
; CHECK-DAG: MayAlias: i32* %a3, i32* %pij1
%inner = type { i32, i32 }
%outer = type { i32, i32, [10 x %inner] }
declare %outer* @rand_outer(i32* %p)
define void @complex2(i32 %i, i32 %j) {
%alloca = alloca i32, i32 128
%a3 = getelementptr inbounds i32, i32* %alloca, i32 3
%random = call %outer* @rand_outer(i32* %alloca)
%p120 = getelementptr inbounds %outer, %outer* %random, i32 1, i32 2, i32 2, i32 0
%pi20 = getelementptr inbounds %outer, %outer* %random, i32 %i, i32 2, i32 2, i32 0
%pij1 = getelementptr inbounds %outer, %outer* %random, i32 %i, i32 2, i32 %j, i32 1
ret void
}