[llvm] r347603 - [stack-safety] Local analysis implementation
Vitaly Buka via llvm-commits
llvm-commits at lists.llvm.org
Mon Nov 26 13:57:59 PST 2018
Author: vitalybuka
Date: Mon Nov 26 13:57:59 2018
New Revision: 347603
URL: http://llvm.org/viewvc/llvm-project?rev=347603&view=rev
Log:
[stack-safety] Local analysis implementation
Summary:
Analysis produces StackSafetyInfo which contains information with how allocas
and parameters were used in functions.
>From prototype by Evgenii Stepanov and Vlad Tsyrklevich.
Reviewers: eugenis, vlad.tsyrklevich, pcc, glider
Subscribers: hiraditya, llvm-commits
Differential Revision: https://reviews.llvm.org/D54504
Added:
llvm/trunk/test/Analysis/StackSafetyAnalysis/memintrin.ll
Modified:
llvm/trunk/include/llvm/Analysis/StackSafetyAnalysis.h
llvm/trunk/lib/Analysis/StackSafetyAnalysis.cpp
llvm/trunk/test/Analysis/StackSafetyAnalysis/local.ll
Modified: llvm/trunk/include/llvm/Analysis/StackSafetyAnalysis.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Analysis/StackSafetyAnalysis.h?rev=347603&r1=347602&r2=347603&view=diff
==============================================================================
--- llvm/trunk/include/llvm/Analysis/StackSafetyAnalysis.h (original)
+++ llvm/trunk/include/llvm/Analysis/StackSafetyAnalysis.h Mon Nov 26 13:57:59 2018
@@ -19,8 +19,22 @@
namespace llvm {
+/// Interface to access stack safety analysis results for single function.
class StackSafetyInfo {
public:
+ struct FunctionInfo;
+
+private:
+ std::unique_ptr<FunctionInfo> Info;
+
+public:
+ StackSafetyInfo();
+ StackSafetyInfo(FunctionInfo &&Info);
+ StackSafetyInfo(StackSafetyInfo &&);
+ StackSafetyInfo &operator=(StackSafetyInfo &&);
+ ~StackSafetyInfo();
+
+ // TODO: Add useful for client methods.
void print(raw_ostream &O) const;
};
Modified: llvm/trunk/lib/Analysis/StackSafetyAnalysis.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Analysis/StackSafetyAnalysis.cpp?rev=347603&r1=347602&r2=347603&view=diff
==============================================================================
--- llvm/trunk/lib/Analysis/StackSafetyAnalysis.cpp (original)
+++ llvm/trunk/lib/Analysis/StackSafetyAnalysis.cpp Mon Nov 26 13:57:59 2018
@@ -10,21 +10,388 @@
//===----------------------------------------------------------------------===//
#include "llvm/Analysis/StackSafetyAnalysis.h"
-
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Analysis/ScalarEvolutionExpressions.h"
+#include "llvm/IR/CallSite.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/IntrinsicInst.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
#define DEBUG_TYPE "stack-safety"
-AnalysisKey StackSafetyAnalysis::Key;
+namespace {
+
+/// Rewrite an SCEV expression for a memory access address to an expression that
+/// represents offset from the given alloca.
+class AllocaOffsetRewriter : public SCEVRewriteVisitor<AllocaOffsetRewriter> {
+ const Value *AllocaPtr;
+
+public:
+ AllocaOffsetRewriter(ScalarEvolution &SE, const Value *AllocaPtr)
+ : SCEVRewriteVisitor(SE), AllocaPtr(AllocaPtr) {}
+
+ const SCEV *visit(const SCEV *Expr) {
+ // Only re-write the expression if the alloca is used in an addition
+ // expression (it can be used in other types of expressions if it's cast to
+ // an int and passed as an argument.)
+ if (!isa<SCEVAddRecExpr>(Expr) && !isa<SCEVAddExpr>(Expr) &&
+ !isa<SCEVUnknown>(Expr))
+ return Expr;
+ return SCEVRewriteVisitor<AllocaOffsetRewriter>::visit(Expr);
+ }
+
+ const SCEV *visitUnknown(const SCEVUnknown *Expr) {
+ // FIXME: look through one or several levels of definitions?
+ // This can be inttoptr(AllocaPtr) and SCEV would not unwrap
+ // it for us.
+ if (Expr->getValue() == AllocaPtr)
+ return SE.getZero(Expr->getType());
+ return Expr;
+ }
+};
+
+/// Describes use of address in as a function call argument.
+struct PassAsArgInfo {
+ /// Function being called.
+ const GlobalValue *Callee = nullptr;
+ /// Index of argument which pass address.
+ size_t ParamNo = 0;
+ // Offset range of address from base address (alloca or calling function
+ // argument).
+ // Range should never set to empty-set, that is an invalid access range
+ // that can cause empty-set to be propagated with ConstantRange::add
+ ConstantRange Offset;
+ PassAsArgInfo(const GlobalValue *Callee, size_t ParamNo, ConstantRange Offset)
+ : Callee(Callee), ParamNo(ParamNo), Offset(Offset) {}
+
+ StringRef getName() const { return Callee->getName(); }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const PassAsArgInfo &P) {
+ return OS << "@" << P.getName() << "(arg" << P.ParamNo << ", " << P.Offset
+ << ")";
+}
+
+/// Describe uses of address (alloca or parameter) inside of the function.
+struct UseInfo {
+ // Access range if the address (alloca or parameters).
+ // It is allowed to be empty-set when there are no known accesses.
+ ConstantRange Range;
+
+ // List of calls which pass address as an argument.
+ SmallVector<PassAsArgInfo, 4> Calls;
+
+ explicit UseInfo(unsigned PointerSize) : Range{PointerSize, false} {}
+
+ void updateRange(ConstantRange R) { Range = Range.unionWith(R); }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const UseInfo &U) {
+ OS << U.Range;
+ for (auto &Call : U.Calls)
+ OS << ", " << Call;
+ return OS;
+}
+
+struct AllocaInfo {
+ const AllocaInst *AI = nullptr;
+ uint64_t Size = 0;
+ UseInfo Use;
+
+ AllocaInfo(unsigned PointerSize, const AllocaInst *AI, uint64_t Size)
+ : AI(AI), Size(Size), Use(PointerSize) {}
-void StackSafetyInfo::print(raw_ostream &O) const { O << "Not Implemented\n"; }
+ StringRef getName() const { return AI->getName(); }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const AllocaInfo &A) {
+ return OS << A.getName() << "[" << A.Size << "]: " << A.Use;
+}
+
+struct ParamInfo {
+ const Argument *Arg = nullptr;
+ UseInfo Use;
+
+ explicit ParamInfo(unsigned PointerSize, const Argument *Arg)
+ : Arg(Arg), Use(PointerSize) {}
+
+ StringRef getName() const { return Arg ? Arg->getName() : "<N/A>"; }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const ParamInfo &P) {
+ return OS << P.getName() << "[]: " << P.Use;
+}
+
+/// Calculate the allocation size of a given alloca. Returns 0 if the
+/// size can not be statically determined.
+uint64_t getStaticAllocaAllocationSize(const AllocaInst *AI) {
+ const DataLayout &DL = AI->getModule()->getDataLayout();
+ uint64_t Size = DL.getTypeAllocSize(AI->getAllocatedType());
+ if (AI->isArrayAllocation()) {
+ auto C = dyn_cast<ConstantInt>(AI->getArraySize());
+ if (!C)
+ return 0;
+ Size *= C->getZExtValue();
+ }
+ return Size;
+}
+
+} // end anonymous namespace
+
+/// Describes uses of allocas and parameters inside of a single function.
+struct StackSafetyInfo::FunctionInfo {
+ // May be a Function or a GlobalAlias
+ const GlobalValue *GV = nullptr;
+ // Informations about allocas uses.
+ SmallVector<AllocaInfo, 4> Allocas;
+ // Informations about parameters uses.
+ SmallVector<ParamInfo, 4> Params;
+ // TODO: describe return value as depending on one or more of its arguments.
+
+ FunctionInfo(const StackSafetyInfo &SSI) : FunctionInfo(*SSI.Info) {}
+
+ explicit FunctionInfo(const Function *F) : GV(F){};
+
+ FunctionInfo(FunctionInfo &&) = default;
+
+ bool IsDSOLocal() const { return GV->isDSOLocal(); };
+
+ bool IsInterposable() const { return GV->isInterposable(); };
+
+ StringRef getName() const { return GV->getName(); }
+
+ void print(raw_ostream &O) const {
+ O << " @" << getName() << (IsDSOLocal() ? "" : " dso_preemptable")
+ << (IsInterposable() ? " interposable" : "") << "\n";
+ O << " args uses:\n";
+ for (auto &P : Params)
+ O << " " << P << "\n";
+ O << " allocas uses:\n";
+ for (auto &AS : Allocas)
+ O << " " << AS << "\n";
+ }
+
+private:
+ FunctionInfo(const FunctionInfo &) = default;
+};
+
+namespace {
+
+class StackSafetyLocalAnalysis {
+ const Function &F;
+ const DataLayout &DL;
+ ScalarEvolution &SE;
+ unsigned PointerSize = 0;
+
+ const ConstantRange UnknownRange;
+
+ ConstantRange offsetFromAlloca(Value *Addr, const Value *AllocaPtr);
+ ConstantRange getAccessRange(Value *Addr, const Value *AllocaPtr,
+ uint64_t AccessSize);
+ ConstantRange getMemIntrinsicAccessRange(const MemIntrinsic *MI, const Use &U,
+ const Value *AllocaPtr);
+
+ bool analyzeAllUses(const Value *Ptr, UseInfo &AS);
+
+ ConstantRange getRange(uint64_t Lower, uint64_t Upper) const {
+ return ConstantRange(APInt(PointerSize, Lower), APInt(PointerSize, Upper));
+ }
+
+public:
+ StackSafetyLocalAnalysis(const Function &F, ScalarEvolution &SE)
+ : F(F), DL(F.getParent()->getDataLayout()), SE(SE),
+ PointerSize(DL.getPointerSizeInBits()),
+ UnknownRange(PointerSize, true) {}
+
+ // Run the transformation on the associated function.
+ StackSafetyInfo run();
+};
+
+ConstantRange
+StackSafetyLocalAnalysis::offsetFromAlloca(Value *Addr,
+ const Value *AllocaPtr) {
+ if (!SE.isSCEVable(Addr->getType()))
+ return UnknownRange;
+
+ AllocaOffsetRewriter Rewriter(SE, AllocaPtr);
+ const SCEV *Expr = Rewriter.visit(SE.getSCEV(Addr));
+ ConstantRange Offset = SE.getUnsignedRange(Expr).zextOrTrunc(PointerSize);
+ assert(!Offset.isEmptySet());
+ return Offset;
+}
+
+ConstantRange StackSafetyLocalAnalysis::getAccessRange(Value *Addr,
+ const Value *AllocaPtr,
+ uint64_t AccessSize) {
+ if (!SE.isSCEVable(Addr->getType()))
+ return UnknownRange;
+
+ AllocaOffsetRewriter Rewriter(SE, AllocaPtr);
+ const SCEV *Expr = Rewriter.visit(SE.getSCEV(Addr));
+
+ ConstantRange AccessStartRange =
+ SE.getUnsignedRange(Expr).zextOrTrunc(PointerSize);
+ ConstantRange SizeRange = getRange(0, AccessSize);
+ ConstantRange AccessRange = AccessStartRange.add(SizeRange);
+ assert(!AccessRange.isEmptySet());
+ return AccessRange;
+}
+
+ConstantRange StackSafetyLocalAnalysis::getMemIntrinsicAccessRange(
+ const MemIntrinsic *MI, const Use &U, const Value *AllocaPtr) {
+ if (auto MTI = dyn_cast<MemTransferInst>(MI)) {
+ if (MTI->getRawSource() != U && MTI->getRawDest() != U)
+ return getRange(0, 1);
+ } else {
+ if (MI->getRawDest() != U)
+ return getRange(0, 1);
+ }
+ const auto *Len = dyn_cast<ConstantInt>(MI->getLength());
+ // Non-constant size => unsafe. FIXME: try SCEV getRange.
+ if (!Len)
+ return UnknownRange;
+ ConstantRange AccessRange = getAccessRange(U, AllocaPtr, Len->getZExtValue());
+ return AccessRange;
+}
+
+/// The function analyzes all local uses of Ptr (alloca or argument) and
+/// calculates local access range and all function calls where it was used.
+bool StackSafetyLocalAnalysis::analyzeAllUses(const Value *Ptr, UseInfo &US) {
+ SmallPtrSet<const Value *, 16> Visited;
+ SmallVector<const Value *, 8> WorkList;
+ WorkList.push_back(Ptr);
+
+ // A DFS search through all uses of the alloca in bitcasts/PHI/GEPs/etc.
+ while (!WorkList.empty()) {
+ const Value *V = WorkList.pop_back_val();
+ for (const Use &UI : V->uses()) {
+ auto I = cast<const Instruction>(UI.getUser());
+ assert(V == UI.get());
+
+ switch (I->getOpcode()) {
+ case Instruction::Load: {
+ US.updateRange(
+ getAccessRange(UI, Ptr, DL.getTypeStoreSize(I->getType())));
+ break;
+ }
+
+ case Instruction::VAArg:
+ // "va-arg" from a pointer is safe.
+ break;
+ case Instruction::Store: {
+ if (V == I->getOperand(0)) {
+ // Stored the pointer - conservatively assume it may be unsafe.
+ US.updateRange(UnknownRange);
+ return false;
+ }
+ US.updateRange(getAccessRange(
+ UI, Ptr, DL.getTypeStoreSize(I->getOperand(0)->getType())));
+ break;
+ }
+
+ case Instruction::Ret:
+ // Information leak.
+ // FIXME: Process parameters correctly. This is a leak only if we return
+ // alloca.
+ US.updateRange(UnknownRange);
+ return false;
+
+ case Instruction::Call:
+ case Instruction::Invoke: {
+ ImmutableCallSite CS(I);
+
+ if (const IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
+ if (II->getIntrinsicID() == Intrinsic::lifetime_start ||
+ II->getIntrinsicID() == Intrinsic::lifetime_end)
+ break;
+ }
+
+ if (const MemIntrinsic *MI = dyn_cast<MemIntrinsic>(I)) {
+ US.updateRange(getMemIntrinsicAccessRange(MI, UI, Ptr));
+ break;
+ }
+
+ // FIXME: consult devirt?
+ // Do not follow aliases, otherwise we could inadvertently follow
+ // dso_preemptable aliases or aliases with interposable linkage.
+ const GlobalValue *Callee = dyn_cast<GlobalValue>(
+ CS.getCalledValue()->stripPointerCastsNoFollowAliases());
+ if (!Callee) {
+ US.updateRange(UnknownRange);
+ return false;
+ }
+
+ assert(isa<Function>(Callee) || isa<GlobalAlias>(Callee));
+
+ ImmutableCallSite::arg_iterator B = CS.arg_begin(), E = CS.arg_end();
+ for (ImmutableCallSite::arg_iterator A = B; A != E; ++A) {
+ if (A->get() == V) {
+ ConstantRange Offset = offsetFromAlloca(UI, Ptr);
+ US.Calls.emplace_back(Callee, A - B, Offset);
+ }
+ }
+
+ break;
+ }
+
+ default:
+ if (Visited.insert(I).second)
+ WorkList.push_back(cast<const Instruction>(I));
+ }
+ }
+ }
+
+ return true;
+}
+
+StackSafetyInfo StackSafetyLocalAnalysis::run() {
+ StackSafetyInfo::FunctionInfo Info(&F);
+ assert(!F.isDeclaration() &&
+ "Can't run StackSafety on a function declaration");
+
+ LLVM_DEBUG(dbgs() << "[StackSafety] " << F.getName() << "\n");
+
+ for (auto &I : instructions(F)) {
+ if (auto AI = dyn_cast<AllocaInst>(&I)) {
+ Info.Allocas.emplace_back(PointerSize, AI,
+ getStaticAllocaAllocationSize(AI));
+ AllocaInfo &AS = Info.Allocas.back();
+ analyzeAllUses(AI, AS.Use);
+ }
+ }
+
+ for (const Argument &A : make_range(F.arg_begin(), F.arg_end())) {
+ Info.Params.emplace_back(PointerSize, &A);
+ ParamInfo &PS = Info.Params.back();
+ analyzeAllUses(&A, PS.Use);
+ }
+
+ LLVM_DEBUG(dbgs() << "[StackSafety] done\n");
+ LLVM_DEBUG(Info.print(dbgs()));
+ return StackSafetyInfo(std::move(Info));
+}
+
+} // end anonymous namespace
+
+StackSafetyInfo::StackSafetyInfo() = default;
+StackSafetyInfo::StackSafetyInfo(StackSafetyInfo &&) = default;
+StackSafetyInfo &StackSafetyInfo::operator=(StackSafetyInfo &&) = default;
+
+StackSafetyInfo::StackSafetyInfo(FunctionInfo &&Info)
+ : Info(new FunctionInfo(std::move(Info))) {}
+
+StackSafetyInfo::~StackSafetyInfo() = default;
+
+void StackSafetyInfo::print(raw_ostream &O) const { Info->print(O); }
+
+AnalysisKey StackSafetyAnalysis::Key;
StackSafetyInfo StackSafetyAnalysis::run(Function &F,
FunctionAnalysisManager &AM) {
- return StackSafetyInfo();
+ StackSafetyLocalAnalysis SSLA(F, AM.getResult<ScalarEvolutionAnalysis>(F));
+ return SSLA.run();
}
PreservedAnalyses StackSafetyPrinterPass::run(Function &F,
@@ -49,7 +416,12 @@ void StackSafetyInfoWrapperPass::print(r
SSI.print(O);
}
-bool StackSafetyInfoWrapperPass::runOnFunction(Function &F) { return false; }
+bool StackSafetyInfoWrapperPass::runOnFunction(Function &F) {
+ StackSafetyLocalAnalysis SSLA(
+ F, getAnalysis<ScalarEvolutionWrapperPass>().getSE());
+ SSI = StackSafetyInfo(SSLA.run());
+ return false;
+}
static const char LocalPassArg[] = "stack-safety-local";
static const char LocalPassName[] = "Stack Safety Local Analysis";
Modified: llvm/trunk/test/Analysis/StackSafetyAnalysis/local.ll
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Analysis/StackSafetyAnalysis/local.ll?rev=347603&r1=347602&r2=347603&view=diff
==============================================================================
--- llvm/trunk/test/Analysis/StackSafetyAnalysis/local.ll (original)
+++ llvm/trunk/test/Analysis/StackSafetyAnalysis/local.ll Mon Nov 26 13:57:59 2018
@@ -4,10 +4,345 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
-; CHECK: 'Stack Safety Local Analysis' for function 'Foo'
-; CHECK-NEXT: Not Implemented
+ at sink = global i8* null, align 8
-define dso_local void @Foo() {
+declare void @llvm.memset.p0i8.i32(i8* %dest, i8 %val, i32 %len, i1 %isvolatile)
+declare void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 %isvolatile)
+declare void @llvm.memmove.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 %isvolatile)
+
+; Address leaked.
+define void @LeakAddress() {
+; CHECK-LABEL: @LeakAddress dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: full-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ store i8* %x1, i8** @sink, align 8
+ ret void
+}
+
+define void @StoreInBounds() {
+; CHECK-LABEL: @StoreInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,1){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ store i8 0, i8* %x1, align 1
+ ret void
+}
+
+define void @StoreInBounds2() {
+; CHECK-LABEL: @StoreInBounds2 dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ store i32 0, i32* %x, align 4
+ ret void
+}
+
+define void @StoreInBounds3() {
+; CHECK-LABEL: @StoreInBounds3 dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [2,3){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 2
+ store i8 0, i8* %x2, align 1
+ ret void
+}
+
+; FIXME: ScalarEvolution does not look through ptrtoint/inttoptr.
+define void @StoreInBounds4() {
+; CHECK-LABEL: @StoreInBounds4 dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [2,-1){{$}}
+; CHECK-NOT: ]:
entry:
+ %x = alloca i32, align 4
+ %x1 = ptrtoint i32* %x to i64
+ %x2 = add i64 %x1, 2
+ %x3 = inttoptr i64 %x2 to i8*
+ store i8 0, i8* %x3, align 1
+ ret void
+}
+
+define void @StoreOutOfBounds() {
+; CHECK-LABEL: @StoreOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [2,6){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 2
+ %x3 = bitcast i8* %x2 to i32*
+ store i32 0, i32* %x3, align 1
+ ret void
+}
+
+; There is no difference in load vs store handling.
+define void @LoadInBounds() {
+; CHECK-LABEL: @LoadInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,1){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %v = load i8, i8* %x1, align 1
+ ret void
+}
+
+define void @LoadOutOfBounds() {
+; CHECK-LABEL: @LoadOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [2,6){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 2
+ %x3 = bitcast i8* %x2 to i32*
+ %v = load i32, i32* %x3, align 1
+ ret void
+}
+
+; Leak through ret.
+define i8* @Ret() {
+; CHECK-LABEL: @Ret dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: full-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 2
+ ret i8* %x2
+}
+
+declare void @Foo(i16* %p)
+
+define void @DirectCall() {
+; CHECK-LABEL: @DirectCall dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[8]: empty-set, @Foo(arg0, [2,3)){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i64, align 4
+ %x1 = bitcast i64* %x to i16*
+ %x2 = getelementptr i16, i16* %x1, i64 1
+ call void @Foo(i16* %x2);
+ ret void
+}
+
+; Indirect calls can not be analyzed (yet).
+; FIXME: %p[]: full-set looks invalid
+define void @IndirectCall(void (i8*)* %p) {
+; CHECK-LABEL: @IndirectCall dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: p[]: full-set{{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: full-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ call void %p(i8* %x1);
+ ret void
+}
+
+define void @NonConstantOffset(i1 zeroext %z) {
+; CHECK-LABEL: @NonConstantOffset dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: z[]: full-set{{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %idx = select i1 %z, i64 1, i64 2
+ %x2 = getelementptr i8, i8* %x1, i64 %idx
+ store i8 0, i8* %x2, align 1
+ ret void
+}
+
+define void @NonConstantOffsetOOB(i1 zeroext %z) {
+; CHECK-LABEL: @NonConstantOffsetOOB dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: z[]: full-set{{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,6){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %idx = select i1 %z, i64 1, i64 4
+ %x2 = getelementptr i8, i8* %x1, i64 %idx
+ store i8 0, i8* %x2, align 1
+ ret void
+}
+
+define void @ArrayAlloca() {
+; CHECK-LABEL: @ArrayAlloca dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[40]: [36,40){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, i32 10, align 4
+ %x1 = bitcast i32* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 36
+ %x3 = bitcast i8* %x2 to i32*
+ store i32 0, i32* %x3, align 1
+ ret void
+}
+
+define void @ArrayAllocaOOB() {
+; CHECK-LABEL: @ArrayAllocaOOB dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[40]: [37,41){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, i32 10, align 4
+ %x1 = bitcast i32* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 37
+ %x3 = bitcast i8* %x2 to i32*
+ store i32 0, i32* %x3, align 1
+ ret void
+}
+
+define void @DynamicAllocaUnused(i64 %size) {
+; CHECK-LABEL: @DynamicAllocaUnused dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: size[]: empty-set{{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[0]: empty-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, i64 %size, align 16
+ ret void
+}
+
+; Dynamic alloca with unknown size.
+define void @DynamicAlloca(i64 %size) {
+; CHECK-LABEL: @DynamicAlloca dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: size[]: [0,-12){{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[0]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, i64 %size, align 16
+ store i32 0, i32* %x, align 1
+ ret void
+}
+
+; Dynamic alloca with limited size.
+; FIXME: could be proved safe. Implement.
+define void @DynamicAllocaFiniteSizeRange(i1 zeroext %z) {
+; CHECK-LABEL: @DynamicAllocaFiniteSizeRange dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: z[]: [0,-12){{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[0]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %size = select i1 %z, i64 3, i64 5
+ %x = alloca i32, i64 %size, align 16
+ store i32 0, i32* %x, align 1
+ ret void
+}
+
+define signext i8 @SimpleLoop() {
+; CHECK-LABEL: @SimpleLoop dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[10]: [0,10){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca [10 x i8], align 1
+ %0 = getelementptr inbounds [10 x i8], [10 x i8]* %x, i64 0, i64 0
+ %lftr.limit = getelementptr inbounds [10 x i8], [10 x i8]* %x, i64 0, i64 10
+ br label %for.body
+
+for.body:
+ %sum.010 = phi i8 [ 0, %entry ], [ %add, %for.body ]
+ %p.09 = phi i8* [ %0, %entry ], [ %incdec.ptr, %for.body ]
+ %incdec.ptr = getelementptr inbounds i8, i8* %p.09, i64 1
+ %1 = load volatile i8, i8* %p.09, align 1
+ %add = add i8 %1, %sum.010
+ %exitcond = icmp eq i8* %incdec.ptr, %lftr.limit
+ br i1 %exitcond, label %for.cond.cleanup, label %for.body
+
+for.cond.cleanup:
+ ret i8 %add
+}
+
+; OOB in a loop.
+define signext i8 @SimpleLoopOOB() {
+; CHECK-LABEL: @SimpleLoopOOB dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[10]: [0,11){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca [10 x i8], align 1
+ %0 = getelementptr inbounds [10 x i8], [10 x i8]* %x, i64 0, i64 0
+ ; 11 iterations
+ %lftr.limit = getelementptr inbounds [10 x i8], [10 x i8]* %x, i64 0, i64 11
+ br label %for.body
+
+for.body:
+ %sum.010 = phi i8 [ 0, %entry ], [ %add, %for.body ]
+ %p.09 = phi i8* [ %0, %entry ], [ %incdec.ptr, %for.body ]
+ %incdec.ptr = getelementptr inbounds i8, i8* %p.09, i64 1
+ %1 = load volatile i8, i8* %p.09, align 1
+ %add = add i8 %1, %sum.010
+ %exitcond = icmp eq i8* %incdec.ptr, %lftr.limit
+ br i1 %exitcond, label %for.cond.cleanup, label %for.body
+
+for.cond.cleanup:
+ ret i8 %add
+}
+
+; FIXME: we don't understand that %sz in the memset call is limited to 128 by the preceding check.
+define dso_local void @SizeCheck(i32 %sz) {
+; CHECK-LABEL: @SizeCheck{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: sz[]: [0,1){{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x1[128]: full-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x1 = alloca [128 x i8], align 16
+ %x1.sub = getelementptr inbounds [128 x i8], [128 x i8]* %x1, i64 0, i64 0
+ %cmp = icmp slt i32 %sz, 129
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then:
+ call void @llvm.memset.p0i8.i32(i8* nonnull align 16 %x1.sub, i8 0, i32 %sz, i1 false)
+ br label %if.end
+
+if.end:
ret void
}
Added: llvm/trunk/test/Analysis/StackSafetyAnalysis/memintrin.ll
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Analysis/StackSafetyAnalysis/memintrin.ll?rev=347603&view=auto
==============================================================================
--- llvm/trunk/test/Analysis/StackSafetyAnalysis/memintrin.ll (added)
+++ llvm/trunk/test/Analysis/StackSafetyAnalysis/memintrin.ll Mon Nov 26 13:57:59 2018
@@ -0,0 +1,200 @@
+; RUN: opt -S -analyze -stack-safety-local < %s | FileCheck %s
+; RUN: opt -S -passes="print<stack-safety-local>" -disable-output < %s 2>&1 | FileCheck %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+declare void @llvm.memset.p0i8.i32(i8* %dest, i8 %val, i32 %len, i1 %isvolatile)
+declare void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 %isvolatile)
+declare void @llvm.memmove.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 %isvolatile)
+
+define void @MemsetInBounds() {
+; CHECK-LABEL: MemsetInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ call void @llvm.memset.p0i8.i32(i8* %x1, i8 42, i32 4, i1 false)
+ ret void
+}
+
+; Volatile does not matter for access bounds.
+define void @VolatileMemsetInBounds() {
+; CHECK-LABEL: VolatileMemsetInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ call void @llvm.memset.p0i8.i32(i8* %x1, i8 42, i32 4, i1 true)
+ ret void
+}
+
+define void @MemsetOutOfBounds() {
+; CHECK-LABEL: MemsetOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,5){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ call void @llvm.memset.p0i8.i32(i8* %x1, i8 42, i32 5, i1 false)
+ ret void
+}
+
+define void @MemsetNonConst(i32 %size) {
+; CHECK-LABEL: MemsetNonConst dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: size[]: [0,1){{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: full-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ call void @llvm.memset.p0i8.i32(i8* %x1, i8 42, i32 %size, i1 false)
+ ret void
+}
+
+; FIXME: memintrinsics should look at size range when possible
+; Right now we refuse any non-constant size.
+define void @MemsetNonConstInBounds(i1 zeroext %z) {
+; CHECK-LABEL: MemsetNonConstInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: z[]: [0,1){{$}}
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: full-set{{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %size = select i1 %z, i32 3, i32 4
+ call void @llvm.memset.p0i8.i32(i8* %x1, i8 42, i32 %size, i1 false)
+ ret void
+}
+
+define void @MemcpyInBounds() {
+; CHECK-LABEL: MemcpyInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,4){{$}}
+; CHECK-NEXT: y[4]: [0,4){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %y = alloca i32, align 4
+ %x1 = bitcast i32* %x to i8*
+ %y1 = bitcast i32* %y to i8*
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x1, i8* %y1, i32 4, i1 false)
+ ret void
+}
+
+define void @MemcpySrcOutOfBounds() {
+; CHECK-LABEL: MemcpySrcOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[8]: [0,5){{$}}
+; CHECK-NEXT: y[4]: [0,5){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i64, align 4
+ %y = alloca i32, align 4
+ %x1 = bitcast i64* %x to i8*
+ %y1 = bitcast i32* %y to i8*
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x1, i8* %y1, i32 5, i1 false)
+ ret void
+}
+
+define void @MemcpyDstOutOfBounds() {
+; CHECK-LABEL: MemcpyDstOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,5){{$}}
+; CHECK-NEXT: y[8]: [0,5){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %y = alloca i64, align 4
+ %x1 = bitcast i32* %x to i8*
+ %y1 = bitcast i64* %y to i8*
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x1, i8* %y1, i32 5, i1 false)
+ ret void
+}
+
+define void @MemcpyBothOutOfBounds() {
+; CHECK-LABEL: MemcpyBothOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[4]: [0,9){{$}}
+; CHECK-NEXT: y[8]: [0,9){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i32, align 4
+ %y = alloca i64, align 4
+ %x1 = bitcast i32* %x to i8*
+ %y1 = bitcast i64* %y to i8*
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x1, i8* %y1, i32 9, i1 false)
+ ret void
+}
+
+define void @MemcpySelfInBounds() {
+; CHECK-LABEL: MemcpySelfInBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[8]: [0,8){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i64, align 4
+ %x1 = bitcast i64* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 5
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x1, i8* %x2, i32 3, i1 false)
+ ret void
+}
+
+define void @MemcpySelfSrcOutOfBounds() {
+; CHECK-LABEL: MemcpySelfSrcOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[8]: [0,9){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i64, align 4
+ %x1 = bitcast i64* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 5
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x1, i8* %x2, i32 4, i1 false)
+ ret void
+}
+
+define void @MemcpySelfDstOutOfBounds() {
+; CHECK-LABEL: MemcpySelfDstOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[8]: [0,9){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i64, align 4
+ %x1 = bitcast i64* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 5
+ call void @llvm.memcpy.p0i8.p0i8.i32(i8* %x2, i8* %x1, i32 4, i1 false)
+ ret void
+}
+
+define void @MemmoveSelfBothOutOfBounds() {
+; CHECK-LABEL: MemmoveSelfBothOutOfBounds dso_preemptable{{$}}
+; CHECK-NEXT: args uses:
+; CHECK-NEXT: allocas uses:
+; CHECK-NEXT: x[8]: [0,14){{$}}
+; CHECK-NOT: ]:
+entry:
+ %x = alloca i64, align 4
+ %x1 = bitcast i64* %x to i8*
+ %x2 = getelementptr i8, i8* %x1, i64 5
+ call void @llvm.memmove.p0i8.p0i8.i32(i8* %x1, i8* %x2, i32 9, i1 false)
+ ret void
+}
More information about the llvm-commits
mailing list