[LLVM] [SeparateConstOffsetFromGEP] Fix sep-const-offset-from-gep invalid assumption (#183402)
`SeparateConstOffsetFromGEP` assumed the index of a GEP was non-negative (and therefore previous sext/add could be reordered safely) if the GEP was marked `inbounds`. This can only be assumed if the GEP is working off of the base address for the object (counter example: https://alive2.llvm.org/ce/z/FjGgWp). This fix removes the general assumption of inbounds GEPs and replaces it with new checks. The transform is valid when: 1. Value tracking shows the index is known non-negative. 2. The GEP is inbounds and the offset from the base ptr is 0. 3. The GEP is inbounds and the offset is within the threshold `(2^(N-1) - C + 1) * stride`, where N is the bit width of the index, C is a positive constant in the add, and stride is the type size of the GEP. 4. The GEP is inbounds and the object size is within the threshold `(2^(N-1) - C + 1) * stride` for positive C or `(2^(N-1) + C) * stride` for negative C. Alive2 showing the constraints on the threshold: https://alive2.llvm.org/ce/z/nBHM4m
This commit is contained in:
parent
ef375ca232
commit
cfbd53c981
@ -227,16 +227,17 @@ private:
|
||||
/// successful, returns C and update UserChain as a def-use chain from C to V;
|
||||
/// otherwise, UserChain is empty.
|
||||
///
|
||||
/// \p V The given expression
|
||||
/// \p SignExtended Whether V will be sign-extended in the computation of the
|
||||
/// GEP index
|
||||
/// \p ZeroExtended Whether V will be zero-extended in the computation of the
|
||||
/// GEP index
|
||||
/// \p NonNegative Whether V is guaranteed to be non-negative. For example,
|
||||
/// an index of an inbounds GEP is guaranteed to be
|
||||
/// non-negative. Levaraging this, we can better split
|
||||
/// inbounds GEPs.
|
||||
APInt find(Value *V, bool SignExtended, bool ZeroExtended, bool NonNegative);
|
||||
/// \p V The given expression
|
||||
/// \p GEP The base GEP instruction, used for determining relevant
|
||||
/// types, flags, and non-negativity needed for safe
|
||||
/// reassociation
|
||||
/// \p Idx The original index of the GEP
|
||||
/// \p SignExtended Whether V will be sign-extended in the computation of
|
||||
/// the GEP index
|
||||
/// \p ZeroExtended Whether V will be zero-extended in the computation of
|
||||
/// the GEP index
|
||||
APInt find(Value *V, GetElementPtrInst *GEP, Value *Idx, bool SignExtended,
|
||||
bool ZeroExtended);
|
||||
|
||||
/// A helper function to look into both operands of a binary operator.
|
||||
APInt findInEitherOperand(BinaryOperator *BO, bool SignExtended,
|
||||
@ -290,10 +291,11 @@ private:
|
||||
///
|
||||
/// \p SignExtended Whether BO is surrounded by sext
|
||||
/// \p ZeroExtended Whether BO is surrounded by zext
|
||||
/// \p NonNegative Whether BO is known to be non-negative, e.g., an in-bound
|
||||
/// array index.
|
||||
bool CanTraceInto(bool SignExtended, bool ZeroExtended, BinaryOperator *BO,
|
||||
bool NonNegative);
|
||||
/// \p GEP The base GEP instruction, used for determining relevant
|
||||
/// types and flags needed for safe reassociation.
|
||||
/// \p Idx The original index of the GEP
|
||||
bool canTraceInto(bool SignExtended, bool ZeroExtended, BinaryOperator *BO,
|
||||
GetElementPtrInst *GEP, Value *Idx);
|
||||
|
||||
/// The path from the constant offset to the old GEP index. e.g., if the GEP
|
||||
/// index is "a * b + (c + 5)". After running function find, UserChain[0] will
|
||||
@ -474,10 +476,113 @@ FunctionPass *llvm::createSeparateConstOffsetFromGEPPass(bool LowerGEP) {
|
||||
return new SeparateConstOffsetFromGEPLegacyPass(LowerGEP);
|
||||
}
|
||||
|
||||
bool ConstantOffsetExtractor::CanTraceInto(bool SignExtended,
|
||||
bool ZeroExtended,
|
||||
BinaryOperator *BO,
|
||||
bool NonNegative) {
|
||||
// Checks if it is safe to reorder an add/sext result used in a GEP.
|
||||
//
|
||||
// An inbounds GEP does not guarantee that the index is non-negative.
|
||||
// This helper checks first if the index is known non-negative. If the index is
|
||||
// non-negative, the transform is always safe.
|
||||
// Second, it checks whether the GEP is inbounds and directly based on a global
|
||||
// or an alloca, which are required to prove futher transform validity.
|
||||
// If the GEP:
|
||||
// - Has a zero offset from the base, the index is non-negative (any negative
|
||||
// value would produce poison/UB)
|
||||
// - Has ObjectSize < (2^(N-1) - C + 1) * stride, where C is a constant from the
|
||||
// add, stride is the element size of Idx, and N is bitwidth of Idx.
|
||||
// This is because with this pattern:
|
||||
// %add = add iN %val, C
|
||||
// %sext = sext iN %add to i64
|
||||
// %gep = getelementptr inbounds TYPE, %sext
|
||||
// The worst-case is when %val sign-flips to produce the smallest magnitude
|
||||
// negative value, at 2^(N-1)-1. In this case, the add/sext is -(2^(N-1)-C+1),
|
||||
// and the sext/add is 2^(N-1)+C-1 (2^N difference). The original add/sext
|
||||
// only produces a defined GEP when -(2^(N-1)-C+1) is inbounds. So, if
|
||||
// ObjectSize < (2^(N-1) - C + 1) * stride, it is impossible for the
|
||||
// worst-case sign-flip to be defined.
|
||||
// Note that in this case the GEP is not neccesarily non-negative, but any
|
||||
// negative results will still produce the same behavior in the reordered
|
||||
// version with a defined GEP.
|
||||
// This can also work for negative C, but the threshold is instead
|
||||
// (2^(N-1)+C)*stride, since the sign-flip is done in reverse and is instead
|
||||
// producing a large positive value that still needs to be inbounds to the
|
||||
// object size. If C is negative, we cannot make any useful assumptions based
|
||||
// on the offset, since it would need to be extremely large.
|
||||
static bool canReorderAddSextToGEP(const GetElementPtrInst *GEP,
|
||||
const Value *Idx, const BinaryOperator *Add,
|
||||
const DataLayout &DL) {
|
||||
if (isKnownNonNegative(Idx, DL))
|
||||
return true;
|
||||
|
||||
if (!GEP->isInBounds())
|
||||
return false;
|
||||
|
||||
const Value *Ptr = GEP->getPointerOperand();
|
||||
int64_t Offset = 0;
|
||||
const Value *Base =
|
||||
GetPointerBaseWithConstantOffset(const_cast<Value *>(Ptr), Offset, DL);
|
||||
|
||||
// We need one of the operands to be a constant to be able to trace into the
|
||||
// operator.
|
||||
const ConstantInt *CI = dyn_cast<ConstantInt>(Add->getOperand(0));
|
||||
if (!CI)
|
||||
CI = dyn_cast<ConstantInt>(Add->getOperand(1));
|
||||
if (!CI)
|
||||
return false;
|
||||
// Calculate the threshold
|
||||
APInt Threshold;
|
||||
unsigned N = Add->getType()->getIntegerBitWidth();
|
||||
uint64_t Stride =
|
||||
DL.getTypeAllocSize(GEP->getSourceElementType()).getFixedValue();
|
||||
if (!CI->isNegative()) {
|
||||
// (2^(N-1) - C + 1) * stride
|
||||
Threshold = (APInt::getSignedMinValue(N).zext(128) -
|
||||
CI->getValue().zextOrTrunc(128) + 1) *
|
||||
APInt(128, Stride);
|
||||
} else {
|
||||
// (2^(N-1) + C) * stride
|
||||
Threshold = (APInt::getSignedMinValue(N).zext(128) +
|
||||
CI->getValue().zextOrTrunc(128)) *
|
||||
APInt(128, Stride);
|
||||
}
|
||||
|
||||
if (Base && (isa<AllocaInst>(Base) || isa<GlobalObject>(Base)) &&
|
||||
!CI->isNegative()) {
|
||||
// If the offset is zero from an alloca or global, inbounds is sufficient to
|
||||
// prove non-negativity if one add operand is non-negative
|
||||
if (Offset == 0)
|
||||
return true;
|
||||
|
||||
// Check if the Offset < Threshold (positive CI only) otherwise
|
||||
if (Offset < 0)
|
||||
return true;
|
||||
if (APInt(128, (uint64_t)Offset).ult(Threshold))
|
||||
return true;
|
||||
} else {
|
||||
// If we can't determine the offset from the base object, we can still use
|
||||
// the underlying object and type size constraints
|
||||
Base = getUnderlyingObject(Ptr);
|
||||
// Can only prove non-negativity if the base object is known
|
||||
if (!(isa<AllocaInst>(Base) || isa<GlobalObject>(Base)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the ObjectSize < Threshold (for both positive or negative C)
|
||||
uint64_t ObjSize = 0;
|
||||
if (const auto *AI = dyn_cast<AllocaInst>(Base)) {
|
||||
if (auto AllocSize = AI->getAllocationSize(DL))
|
||||
if (!AllocSize->isScalable())
|
||||
ObjSize = AllocSize->getFixedValue();
|
||||
} else if (const auto *GV = dyn_cast<GlobalVariable>(Base)) {
|
||||
ObjSize = DL.getTypeAllocSize(GV->getValueType()).getFixedValue();
|
||||
}
|
||||
if (ObjSize > 0 && APInt(128, ObjSize).ult(Threshold))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConstantOffsetExtractor::canTraceInto(bool SignExtended, bool ZeroExtended,
|
||||
BinaryOperator *BO,
|
||||
GetElementPtrInst *GEP, Value *Idx) {
|
||||
// We only consider ADD, SUB and OR, because a non-zero constant found in
|
||||
// expressions composed of these operations can be easily hoisted as a
|
||||
// constant offset by reassociation.
|
||||
@ -487,7 +592,6 @@ bool ConstantOffsetExtractor::CanTraceInto(bool SignExtended,
|
||||
return false;
|
||||
}
|
||||
|
||||
Value *LHS = BO->getOperand(0), *RHS = BO->getOperand(1);
|
||||
// Do not trace into "or" unless it is equivalent to "add nuw nsw".
|
||||
// This is the case if the or's disjoint flag is set.
|
||||
if (BO->getOpcode() == Instruction::Or &&
|
||||
@ -511,25 +615,27 @@ bool ConstantOffsetExtractor::CanTraceInto(bool SignExtended,
|
||||
// 1 | 0 | sext(BO) == sext(A) op sext(B)
|
||||
// 1 | 1 | zext(sext(BO)) ==
|
||||
// | | zext(sext(A)) op zext(sext(B))
|
||||
if (BO->getOpcode() == Instruction::Add && !ZeroExtended && NonNegative) {
|
||||
if (BO->getOpcode() == Instruction::Add && !ZeroExtended && GEP) {
|
||||
// If a + b >= 0 and (a >= 0 or b >= 0), then
|
||||
// sext(a + b) = sext(a) + sext(b)
|
||||
// even if the addition is not marked nsw.
|
||||
//
|
||||
// Leveraging this invariant, we can trace into an sext'ed inbound GEP
|
||||
// index if the constant offset is non-negative.
|
||||
// index under certain conditions (see canReorderAddSextToGEP).
|
||||
//
|
||||
// Verified in @sext_add in split-gep.ll.
|
||||
if (ConstantInt *ConstLHS = dyn_cast<ConstantInt>(LHS)) {
|
||||
if (!ConstLHS->isNegative())
|
||||
return true;
|
||||
}
|
||||
if (ConstantInt *ConstRHS = dyn_cast<ConstantInt>(RHS)) {
|
||||
if (!ConstRHS->isNegative())
|
||||
return true;
|
||||
}
|
||||
if (canReorderAddSextToGEP(GEP, Idx, BO, DL))
|
||||
return true;
|
||||
}
|
||||
|
||||
// For a sext(add nuw), allow tracing through when the enclosing GEP is both
|
||||
// inbounds and nuw.
|
||||
bool GEPInboundsNUW =
|
||||
GEP ? (GEP->isInBounds() && GEP->hasNoUnsignedWrap()) : false;
|
||||
if (BO->getOpcode() == Instruction::Add && SignExtended && !ZeroExtended &&
|
||||
GEPInboundsNUW && BO->hasNoUnsignedWrap())
|
||||
return true;
|
||||
|
||||
// sext (add/sub nsw A, B) == add/sub nsw (sext A), (sext B)
|
||||
// zext (add/sub nuw A, B) == add/sub nuw (zext A), (zext B)
|
||||
if (BO->getOpcode() == Instruction::Add ||
|
||||
@ -549,10 +655,9 @@ APInt ConstantOffsetExtractor::findInEitherOperand(BinaryOperator *BO,
|
||||
// Save off the current height of the chain, in case we need to restore it.
|
||||
size_t ChainLength = UserChain.size();
|
||||
|
||||
// BO being non-negative does not shed light on whether its operands are
|
||||
// non-negative. Clear the NonNegative flag here.
|
||||
APInt ConstantOffset = find(BO->getOperand(0), SignExtended, ZeroExtended,
|
||||
/* NonNegative */ false);
|
||||
// BO cannot use information from the base GEP at this point, so clear it.
|
||||
APInt ConstantOffset =
|
||||
find(BO->getOperand(0), nullptr, nullptr, SignExtended, ZeroExtended);
|
||||
// If we found a constant offset in the left operand, stop and return that.
|
||||
// This shortcut might cause us to miss opportunities of combining the
|
||||
// constant offsets in both operands, e.g., (a + 4) + (b + 5) => (a + b) + 9.
|
||||
@ -564,8 +669,8 @@ APInt ConstantOffsetExtractor::findInEitherOperand(BinaryOperator *BO,
|
||||
// since visiting the LHS didn't pan out.
|
||||
UserChain.resize(ChainLength);
|
||||
|
||||
ConstantOffset = find(BO->getOperand(1), SignExtended, ZeroExtended,
|
||||
/* NonNegative */ false);
|
||||
ConstantOffset =
|
||||
find(BO->getOperand(1), nullptr, nullptr, SignExtended, ZeroExtended);
|
||||
// If U is a sub operator, negate the constant offset found in the right
|
||||
// operand.
|
||||
if (BO->getOpcode() == Instruction::Sub)
|
||||
@ -578,8 +683,9 @@ APInt ConstantOffsetExtractor::findInEitherOperand(BinaryOperator *BO,
|
||||
return ConstantOffset;
|
||||
}
|
||||
|
||||
APInt ConstantOffsetExtractor::find(Value *V, bool SignExtended,
|
||||
bool ZeroExtended, bool NonNegative) {
|
||||
APInt ConstantOffsetExtractor::find(Value *V, GetElementPtrInst *GEP,
|
||||
Value *Idx, bool SignExtended,
|
||||
bool ZeroExtended) {
|
||||
// TODO(jingyue): We could trace into integer/pointer casts, such as
|
||||
// inttoptr, ptrtoint, bitcast, and addrspacecast. We choose to handle only
|
||||
// integers because it gives good enough results for our benchmarks.
|
||||
@ -595,23 +701,22 @@ APInt ConstantOffsetExtractor::find(Value *V, bool SignExtended,
|
||||
ConstantOffset = CI->getValue();
|
||||
} else if (BinaryOperator *BO = dyn_cast<BinaryOperator>(V)) {
|
||||
// Trace into subexpressions for more hoisting opportunities.
|
||||
if (CanTraceInto(SignExtended, ZeroExtended, BO, NonNegative))
|
||||
if (canTraceInto(SignExtended, ZeroExtended, BO, GEP, Idx))
|
||||
ConstantOffset = findInEitherOperand(BO, SignExtended, ZeroExtended);
|
||||
} else if (isa<TruncInst>(V)) {
|
||||
ConstantOffset =
|
||||
find(U->getOperand(0), SignExtended, ZeroExtended, NonNegative)
|
||||
find(U->getOperand(0), GEP, Idx, SignExtended, ZeroExtended)
|
||||
.trunc(BitWidth);
|
||||
} else if (isa<SExtInst>(V)) {
|
||||
ConstantOffset = find(U->getOperand(0), /* SignExtended */ true,
|
||||
ZeroExtended, NonNegative).sext(BitWidth);
|
||||
ConstantOffset =
|
||||
find(U->getOperand(0), GEP, Idx, /* SignExtended */ true, ZeroExtended)
|
||||
.sext(BitWidth);
|
||||
} else if (isa<ZExtInst>(V)) {
|
||||
// As an optimization, we can clear the SignExtended flag because
|
||||
// sext(zext(a)) = zext(a). Verified in @sext_zext in split-gep.ll.
|
||||
//
|
||||
// Clear the NonNegative flag, because zext(a) >= 0 does not imply a >= 0.
|
||||
ConstantOffset =
|
||||
find(U->getOperand(0), /* SignExtended */ false,
|
||||
/* ZeroExtended */ true, /* NonNegative */ false).zext(BitWidth);
|
||||
ConstantOffset = find(U->getOperand(0), GEP, Idx, /* SignExtended */ false,
|
||||
/* ZeroExtended */ true)
|
||||
.zext(BitWidth);
|
||||
}
|
||||
|
||||
// If we found a non-zero constant offset, add it to the path for
|
||||
@ -780,9 +885,8 @@ Value *ConstantOffsetExtractor::Extract(Value *Idx, GetElementPtrInst *GEP,
|
||||
bool &PreservesNUW) {
|
||||
ConstantOffsetExtractor Extractor(GEP->getIterator());
|
||||
// Find a non-zero constant offset first.
|
||||
APInt ConstantOffset =
|
||||
Extractor.find(Idx, /* SignExtended */ false, /* ZeroExtended */ false,
|
||||
GEP->isInBounds());
|
||||
APInt ConstantOffset = Extractor.find(Idx, GEP, Idx, /* SignExtended */ false,
|
||||
/* ZeroExtended */ false);
|
||||
if (ConstantOffset == 0) {
|
||||
UserChainTail = nullptr;
|
||||
PreservesNUW = true;
|
||||
@ -798,10 +902,8 @@ Value *ConstantOffsetExtractor::Extract(Value *Idx, GetElementPtrInst *GEP,
|
||||
}
|
||||
|
||||
APInt ConstantOffsetExtractor::Find(Value *Idx, GetElementPtrInst *GEP) {
|
||||
// If Idx is an index of an inbound GEP, Idx is guaranteed to be non-negative.
|
||||
return ConstantOffsetExtractor(GEP->getIterator())
|
||||
.find(Idx, /* SignExtended */ false, /* ZeroExtended */ false,
|
||||
GEP->isInBounds());
|
||||
.find(Idx, GEP, Idx, /* SignExtended */ false, /* ZeroExtended */ false);
|
||||
}
|
||||
|
||||
bool SeparateConstOffsetFromGEP::canonicalizeArrayIndicesToIndexSize(
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
@struct_array = global [1024 x %struct.S] zeroinitializer, align 16
|
||||
@float_2d_array = global [32 x [32 x float]] zeroinitializer, align 4
|
||||
@float_array = global [128 x float] zeroinitializer, align 4
|
||||
|
||||
; We should not extract any struct field indices, because fields in a struct
|
||||
; may have different types.
|
||||
@ -30,17 +31,16 @@ entry:
|
||||
}
|
||||
|
||||
; We should be able to trace into sext(a + b) if a + b is non-negative
|
||||
; (e.g., used as an index of an inbounds GEP) and one of a and b is
|
||||
; non-negative.
|
||||
; (e.g., used as an index of an inbounds GEP on a global base ptr) and one of a
|
||||
; or b is non-negative.
|
||||
define ptr @sext_add(i32 %i, i32 %j) {
|
||||
; CHECK-LABEL: define ptr @sext_add(
|
||||
; CHECK-SAME: i32 [[I:%.*]], i32 [[J:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = add i32 [[J]], -2
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = sext i32 [[TMP0]] to i64
|
||||
; CHECK-NEXT: [[TMP2:%.*]] = sext i32 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP3:%.*]] = getelementptr [32 x [32 x float]], ptr @float_2d_array, i64 0, i64 [[TMP2]], i64 [[TMP1]]
|
||||
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[TMP3]], i64 128
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = sext i32 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = sext i32 [[J]] to i64
|
||||
; CHECK-NEXT: [[TMP2:%.*]] = getelementptr [32 x [32 x float]], ptr @float_2d_array, i64 0, i64 [[TMP0]], i64 [[TMP1]]
|
||||
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[TMP2]], i64 120
|
||||
; CHECK-NEXT: ret ptr [[P1]]
|
||||
;
|
||||
entry:
|
||||
@ -48,11 +48,178 @@ entry:
|
||||
%1 = sext i32 %0 to i64 ; inbound sext(i + 1) = sext(i) + 1
|
||||
%2 = add i32 %j, -2
|
||||
; However, inbound sext(j + -2) != sext(j) + -2, e.g., j = INT_MIN
|
||||
; But j = INT_MIN would result in a very large positive result which would be
|
||||
; OOB (and produce poison), so there is no counter example in this case
|
||||
%3 = sext i32 %2 to i64
|
||||
%p = getelementptr inbounds [32 x [32 x float]], ptr @float_2d_array, i64 0, i64 %1, i64 %3
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
; We should trace into sext(a + b) if a + b is an inbounds GEP on a known
|
||||
; base ptr (alloca) if one of a or b is non-negative.
|
||||
define ptr @sext_add_alloca(i32 %i) {
|
||||
; CHECK-LABEL: define ptr @sext_add_alloca(
|
||||
; CHECK-SAME: i32 [[I:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[ARR:%.*]] = alloca [32 x [32 x float]], align 4
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = sext i32 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr [32 x [32 x float]], ptr [[ARR]], i64 0, i64 [[TMP0]], i64 0
|
||||
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[TMP1]], i64 128
|
||||
; CHECK-NEXT: ret ptr [[P1]]
|
||||
;
|
||||
entry:
|
||||
%arr = alloca [32 x [32 x float]], align 4
|
||||
%0 = add i32 %i, 1
|
||||
%1 = sext i32 %0 to i64
|
||||
; inbound sext(i + 1) = sext(i) + 1 because inbounds on base ptr -> non-negative
|
||||
%p = getelementptr inbounds [32 x [32 x float]], ptr %arr, i64 0, i64 %1, i64 0
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
; We cannot trace into sext(a + b) if a + b is an inbounds GEP but not on a
|
||||
; known base ptr even if one of a or b is non-negative.
|
||||
define ptr @sext_add_nonbase(i32 %i, ptr %unknown_arr) {
|
||||
; CHECK-LABEL: define ptr @sext_add_nonbase(
|
||||
; CHECK-SAME: i32 [[I:%.*]], ptr [[ARR:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = add i32 [[I]], 1
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = sext i32 [[TMP0]] to i64
|
||||
; CHECK-NEXT: [[P1:%.*]] = getelementptr inbounds [32 x [32 x float]], ptr [[ARR]], i64 0, i64 [[TMP1]], i64 0
|
||||
; CHECK-NEXT: ret ptr [[P1]]
|
||||
;
|
||||
entry:
|
||||
%0 = add i32 %i, 1
|
||||
%1 = sext i32 %0 to i64
|
||||
; inbound sext(i + 1) != sext(i) + 1 because a wrapped result can still be inbounds if not at start of arr
|
||||
%p = getelementptr inbounds [32 x [32 x float]], ptr %unknown_arr, i64 0, i64 %1, i64 0
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
; We can trace into sext(a + b) if a + b is an inbounds GEP and the known
|
||||
; offset from a known base ptr is within a certain threshold relative to the
|
||||
; bitwidth of the index (offset < (2^(n-1) - C + 1) * bitwidth).
|
||||
define ptr @sext_add_nonzerooffset_inrange(i8 %i, i64 %size) {
|
||||
; CHECK-LABEL: define ptr @sext_add_nonzerooffset_inrange(
|
||||
; CHECK-SAME: i8 [[I:%.*]], i64 [[SIZE:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[ARR:%.*]] = alloca float, i64 %size, align 4
|
||||
; CHECK-NEXT: [[OFFSETARR:%.*]] = getelementptr float, ptr [[ARR]], i64 127
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = sext i8 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr float, ptr [[OFFSETARR]], i64 [[TMP0]]
|
||||
; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[TMP1]], i64 4
|
||||
; CHECK-NEXT: ret ptr [[P2]]
|
||||
;
|
||||
entry:
|
||||
%arr = alloca float, i64 %size, align 4
|
||||
%offsetarr = getelementptr float, ptr %arr, i64 127
|
||||
%add = add i8 %i, 1
|
||||
%sext = sext i8 %add to i64
|
||||
%p = getelementptr inbounds float, ptr %offsetarr, i64 %sext
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
define ptr @sext_add_nonzerooffset_outofrange(i8 %i, i64 %size) {
|
||||
; CHECK-LABEL: define ptr @sext_add_nonzerooffset_outofrange(
|
||||
; CHECK-SAME: i8 [[I:%.*]], i64 [[SIZE:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[ARR:%.*]] = alloca float, i64 [[SIZE]], align 4
|
||||
; CHECK-NEXT: [[ADD:%.*]] = add i8 [[I]], 1
|
||||
; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[ADD]] to i64
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = getelementptr float, ptr [[ARR]], i64 [[SEXT]]
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr float, ptr [[TMP0]], i64 128
|
||||
; CHECK-NEXT: ret ptr [[TMP1]]
|
||||
;
|
||||
entry:
|
||||
%arr = alloca float, i64 %size, align 4
|
||||
%offsetarr = getelementptr float, ptr %arr, i64 128
|
||||
%add = add i8 %i, 1
|
||||
%sext = sext i8 %add to i64
|
||||
%p = getelementptr inbounds float, ptr %offsetarr, i64 %sext
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
; We can trace into sext(a + b) if a + b is an inbounds GEP and the size of the
|
||||
; known base ptr is within a certain threshold relative to the bitwidth of the
|
||||
; index (offset < (2^(n-1) - C + 1) * bitwidth).
|
||||
define ptr @sext_add_unknownoffset_inrange(i8 %i, i64 %off) {
|
||||
; CHECK-LABEL: define ptr @sext_add_unknownoffset_inrange(
|
||||
; CHECK-SAME: i8 [[I:%.*]], i64 [[OFF:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[ARR:%.*]] = alloca float, i64 126, align 4
|
||||
; CHECK-NEXT: [[OFFSETARR:%.*]] = getelementptr float, ptr [[ARR]], i64 [[OFF]]
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = sext i8 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr float, ptr [[OFFSETARR]], i64 [[TMP0]]
|
||||
; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[TMP1]], i64 8
|
||||
; CHECK-NEXT: ret ptr [[P2]]
|
||||
;
|
||||
entry:
|
||||
%arr = alloca float, i64 126, align 4
|
||||
%offsetarr = getelementptr float, ptr %arr, i64 %off
|
||||
%add = add i8 %i, 2
|
||||
%sext = sext i8 %add to i64
|
||||
%p = getelementptr inbounds float, ptr %offsetarr, i64 %sext
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
define ptr @sext_add_unknownoffset_inrange_neg(i8 %i, i64 %off) {
|
||||
; CHECK-LABEL: define ptr @sext_add_unknownoffset_inrange_neg(
|
||||
; CHECK-SAME: i8 [[I:%.*]], i64 [[OFF:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[ARR:%.*]] = alloca float, i64 125, align 4
|
||||
; CHECK-NEXT: [[OFFSETARR:%.*]] = getelementptr float, ptr [[ARR]], i64 [[OFF]]
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = sext i8 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr float, ptr [[OFFSETARR]], i64 [[TMP0]]
|
||||
; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[TMP1]], i64 -8
|
||||
; CHECK-NEXT: ret ptr [[P2]]
|
||||
;
|
||||
entry:
|
||||
%arr = alloca float, i64 125, align 4
|
||||
%offsetarr = getelementptr float, ptr %arr, i64 %off
|
||||
%add = add i8 %i, -2
|
||||
%sext = sext i8 %add to i64
|
||||
%p = getelementptr inbounds float, ptr %offsetarr, i64 %sext
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
define ptr @sext_add_unknownoffset_outofrange(i8 %i, i64 %off) {
|
||||
; CHECK-LABEL: define ptr @sext_add_unknownoffset_outofrange(
|
||||
; CHECK-SAME: i8 [[I:%.*]], i64 [[OFF:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[ARR:%.*]] = alloca float, i64 127, align 4
|
||||
; CHECK-NEXT: [[OFFSETARR:%.*]] = getelementptr float, ptr [[ARR]], i64 [[OFF]]
|
||||
; CHECK-NEXT: [[ADD:%.*]] = add i8 [[I]], 2
|
||||
; CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[ADD]] to i64
|
||||
; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds float, ptr [[OFFSETARR]], i64 [[SEXT]]
|
||||
; CHECK-NEXT: ret ptr [[P]]
|
||||
;
|
||||
entry:
|
||||
%arr = alloca float, i64 127, align 4
|
||||
%offsetarr = getelementptr float, ptr %arr, i64 %off
|
||||
%add = add i8 %i, 2
|
||||
%sext = sext i8 %add to i64
|
||||
%p = getelementptr inbounds float, ptr %offsetarr, i64 %sext
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
; We can trace into sext(a + b) if a + b is non-negative (nsw flag) and one of
|
||||
; a or b is non-negative, even if the gep is not inbounds
|
||||
define ptr @sext_add_nsw(i32 %i, ptr %unknown_arr) {
|
||||
; CHECK-LABEL: define ptr @sext_add_nsw(
|
||||
; CHECK-SAME: i32 [[I:%.*]], ptr [[ARR:%.*]]) {
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: [[TMP0:%.*]] = sext i32 [[I]] to i64
|
||||
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr [32 x [32 x float]], ptr [[ARR]], i64 0, i64 [[TMP0]], i64 0
|
||||
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[TMP1]], i64 128
|
||||
; CHECK-NEXT: ret ptr [[P1]]
|
||||
;
|
||||
entry:
|
||||
%0 = add nsw i32 %i, 1
|
||||
%1 = sext i32 %0 to i64
|
||||
; sext(nsw i + 1) = sext(i) + 1
|
||||
%p = getelementptr [32 x [32 x float]], ptr %unknown_arr, i64 0, i64 %1, i64 0
|
||||
ret ptr %p
|
||||
}
|
||||
|
||||
; We should be able to trace into sext/zext if it can be distributed to both
|
||||
; operands, e.g., sext (add nsw a, b) == add nsw (sext a), (sext b)
|
||||
;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user