Skip to content

Commit cf66f01

Browse files
committed
[Attributor] Share code for abstract interpretation of allocation sizes with getObjectSize [NFC-ish]
The basic idea is that we can parameterize the getObjectSize implementation with a callback which lets us replace the operand before analysis if desired. This is what Attributor is doing during it's abstract interpretation, and allows us to have one copy of the code. Note this is not NFC for two reasons: * The existing attributor code is wrong. (Well, this is under-specified to be honest, but at least inconsistent.) The intermediate math needs to be done in the index type of the pointer space. Imagine e.g. i64 arguments in a 32 bit address space. * I did not preserve the behavior in getAPInt where we return 0 for a partially analyzed value. This looks simply wrong in the original code, and nothing test wise contradicts that. Differential Revision: https://reviews.llvm.org/D117241
1 parent 049ae93 commit cf66f01

File tree

3 files changed

+102
-75
lines changed

3 files changed

+102
-75
lines changed

llvm/include/llvm/Analysis/MemoryBuiltins.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ bool isAllocRemovable(const CallBase *V, const TargetLibraryInfo *TLI);
117117
/// Gets the alignment argument for an aligned_alloc-like function
118118
Value *getAllocAlignment(const CallBase *V, const TargetLibraryInfo *TLI);
119119

120+
/// Return the size of the requested allocation. With a trivial mapper, this is
121+
/// identical to calling getObjectSize(..., Exact). A mapper function can be
122+
/// used to replace one Value* (operand to the allocation) with another. This
123+
/// is useful when doing abstract interpretation.
124+
Optional<APInt> getAllocSize(const CallBase *CB,
125+
const TargetLibraryInfo *TLI,
126+
std::function<const Value*(const Value*)> Mapper);
127+
120128
/// If this allocation function initializes memory to a fixed value, return
121129
/// said value in the requested type. Otherwise, return nullptr.
122130
Constant *getInitialValueOfAllocation(const CallBase *Alloc,

llvm/lib/Analysis/MemoryBuiltins.cpp

Lines changed: 84 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,85 @@ Value *llvm::getAllocAlignment(const CallBase *V,
305305
return V->getOperand(FnData->AlignParam);
306306
}
307307

308+
/// When we're compiling N-bit code, and the user uses parameters that are
309+
/// greater than N bits (e.g. uint64_t on a 32-bit build), we can run into
310+
/// trouble with APInt size issues. This function handles resizing + overflow
311+
/// checks for us. Check and zext or trunc \p I depending on IntTyBits and
312+
/// I's value.
313+
static bool CheckedZextOrTrunc(APInt &I, unsigned IntTyBits) {
314+
// More bits than we can handle. Checking the bit width isn't necessary, but
315+
// it's faster than checking active bits, and should give `false` in the
316+
// vast majority of cases.
317+
if (I.getBitWidth() > IntTyBits && I.getActiveBits() > IntTyBits)
318+
return false;
319+
if (I.getBitWidth() != IntTyBits)
320+
I = I.zextOrTrunc(IntTyBits);
321+
return true;
322+
}
323+
324+
Optional<APInt>
325+
llvm::getAllocSize(const CallBase *CB,
326+
const TargetLibraryInfo *TLI,
327+
std::function<const Value*(const Value*)> Mapper) {
328+
// Note: This handles both explicitly listed allocation functions and
329+
// allocsize. The code structure could stand to be cleaned up a bit.
330+
Optional<AllocFnsTy> FnData = getAllocationSize(CB, TLI);
331+
if (!FnData)
332+
return None;
333+
334+
// Get the index type for this address space, results and intermediate
335+
// computations are performed at that width.
336+
auto &DL = CB->getModule()->getDataLayout();
337+
const unsigned IntTyBits = DL.getIndexTypeSizeInBits(CB->getType());
338+
339+
// Handle strdup-like functions separately.
340+
if (FnData->AllocTy == StrDupLike) {
341+
APInt Size(IntTyBits, GetStringLength(Mapper(CB->getArgOperand(0))));
342+
if (!Size)
343+
return None;
344+
345+
// Strndup limits strlen.
346+
if (FnData->FstParam > 0) {
347+
const ConstantInt *Arg =
348+
dyn_cast<ConstantInt>(Mapper(CB->getArgOperand(FnData->FstParam)));
349+
if (!Arg)
350+
return None;
351+
352+
APInt MaxSize = Arg->getValue().zextOrSelf(IntTyBits);
353+
if (Size.ugt(MaxSize))
354+
Size = MaxSize + 1;
355+
}
356+
return Size;
357+
}
358+
359+
const ConstantInt *Arg =
360+
dyn_cast<ConstantInt>(Mapper(CB->getArgOperand(FnData->FstParam)));
361+
if (!Arg)
362+
return None;
363+
364+
APInt Size = Arg->getValue();
365+
if (!CheckedZextOrTrunc(Size, IntTyBits))
366+
return None;
367+
368+
// Size is determined by just 1 parameter.
369+
if (FnData->SndParam < 0)
370+
return Size;
371+
372+
Arg = dyn_cast<ConstantInt>(Mapper(CB->getArgOperand(FnData->SndParam)));
373+
if (!Arg)
374+
return None;
375+
376+
APInt NumElems = Arg->getValue();
377+
if (!CheckedZextOrTrunc(NumElems, IntTyBits))
378+
return None;
379+
380+
bool Overflow;
381+
Size = Size.umul_ov(NumElems, Overflow);
382+
if (Overflow)
383+
return None;
384+
return Size;
385+
}
386+
308387
Constant *llvm::getInitialValueOfAllocation(const CallBase *Alloc,
309388
const TargetLibraryInfo *TLI,
310389
Type *Ty) {
@@ -535,20 +614,8 @@ SizeOffsetType ObjectSizeOffsetVisitor::compute(Value *V) {
535614
return unknown();
536615
}
537616

538-
/// When we're compiling N-bit code, and the user uses parameters that are
539-
/// greater than N bits (e.g. uint64_t on a 32-bit build), we can run into
540-
/// trouble with APInt size issues. This function handles resizing + overflow
541-
/// checks for us. Check and zext or trunc \p I depending on IntTyBits and
542-
/// I's value.
543617
bool ObjectSizeOffsetVisitor::CheckedZextOrTrunc(APInt &I) {
544-
// More bits than we can handle. Checking the bit width isn't necessary, but
545-
// it's faster than checking active bits, and should give `false` in the
546-
// vast majority of cases.
547-
if (I.getBitWidth() > IntTyBits && I.getActiveBits() > IntTyBits)
548-
return false;
549-
if (I.getBitWidth() != IntTyBits)
550-
I = I.zextOrTrunc(IntTyBits);
551-
return true;
618+
return ::CheckedZextOrTrunc(I, IntTyBits);
552619
}
553620

554621
SizeOffsetType ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
@@ -589,53 +656,10 @@ SizeOffsetType ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
589656
}
590657

591658
SizeOffsetType ObjectSizeOffsetVisitor::visitCallBase(CallBase &CB) {
592-
Optional<AllocFnsTy> FnData = getAllocationSize(&CB, TLI);
593-
if (!FnData)
594-
return unknown();
595-
596-
// Handle strdup-like functions separately.
597-
if (FnData->AllocTy == StrDupLike) {
598-
APInt Size(IntTyBits, GetStringLength(CB.getArgOperand(0)));
599-
if (!Size)
600-
return unknown();
601-
602-
// Strndup limits strlen.
603-
if (FnData->FstParam > 0) {
604-
ConstantInt *Arg =
605-
dyn_cast<ConstantInt>(CB.getArgOperand(FnData->FstParam));
606-
if (!Arg)
607-
return unknown();
608-
609-
APInt MaxSize = Arg->getValue().zextOrSelf(IntTyBits);
610-
if (Size.ugt(MaxSize))
611-
Size = MaxSize + 1;
612-
}
613-
return std::make_pair(Size, Zero);
614-
}
615-
616-
ConstantInt *Arg = dyn_cast<ConstantInt>(CB.getArgOperand(FnData->FstParam));
617-
if (!Arg)
618-
return unknown();
619-
620-
APInt Size = Arg->getValue();
621-
if (!CheckedZextOrTrunc(Size))
622-
return unknown();
623-
624-
// Size is determined by just 1 parameter.
625-
if (FnData->SndParam < 0)
626-
return std::make_pair(Size, Zero);
627-
628-
Arg = dyn_cast<ConstantInt>(CB.getArgOperand(FnData->SndParam));
629-
if (!Arg)
630-
return unknown();
631-
632-
APInt NumElems = Arg->getValue();
633-
if (!CheckedZextOrTrunc(NumElems))
634-
return unknown();
635-
636-
bool Overflow;
637-
Size = Size.umul_ov(NumElems, Overflow);
638-
return Overflow ? unknown() : std::make_pair(Size, Zero);
659+
auto Mapper = [](const Value *V) { return V; };
660+
if (Optional<APInt> Size = getAllocSize(&CB, TLI, Mapper))
661+
return std::make_pair(*Size, Zero);
662+
return unknown();
639663
}
640664

641665
SizeOffsetType

llvm/lib/Transforms/IPO/AttributorAttributes.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6017,22 +6017,17 @@ struct AAHeapToStackFunction final : public AAHeapToStack {
60176017

60186018
Optional<APInt> getSize(Attributor &A, const AbstractAttribute &AA,
60196019
AllocationInfo &AI) {
6020+
auto Mapper = [&](const Value *V) -> const Value* {
6021+
bool Dead = false;
6022+
if (Optional<Constant *> SimpleV = A.getAssumedConstant(*V, AA, Dead))
6023+
if (*SimpleV)
6024+
return *SimpleV;
6025+
return V;
6026+
};
60206027

6021-
if (AI.Kind == AllocationInfo::AllocationKind::MALLOC)
6022-
return getAPInt(A, AA, *AI.CB->getArgOperand(0));
6023-
6024-
if (AI.Kind == AllocationInfo::AllocationKind::ALIGNED_ALLOC)
6025-
return getAPInt(A, AA, *AI.CB->getArgOperand(1));
6026-
6027-
assert(AI.Kind == AllocationInfo::AllocationKind::CALLOC &&
6028-
"Expected only callocs are left");
6029-
Optional<APInt> Num = getAPInt(A, AA, *AI.CB->getArgOperand(0));
6030-
Optional<APInt> Size = getAPInt(A, AA, *AI.CB->getArgOperand(1));
6031-
if (!Num.hasValue() || !Size.hasValue())
6032-
return llvm::None;
6033-
bool Overflow = false;
6034-
Size = Size.getValue().umul_ov(Num.getValue(), Overflow);
6035-
return Overflow ? llvm::None : Size;
6028+
const Function *F = getAnchorScope();
6029+
const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F);
6030+
return getAllocSize(AI.CB, TLI, Mapper);
60366031
}
60376032

60386033
/// Collection of all malloc-like calls in a function with associated

0 commit comments

Comments
 (0)