Skip to content

Commit

Permalink
[CFLAA] Add some interproc. analysis to CFLAnders.
Browse files Browse the repository at this point in the history
This patch adds function summary support to CFLAnders. It also comes
with a lot of tests! Woohoo!

Patch by Jia Chen.

Differential Revision: https://reviews.llvm.org/D22450

llvm-svn: 276026
  • Loading branch information
gburgessiv committed Jul 19, 2016
1 parent 6524bd8 commit 3b05984
Show file tree
Hide file tree
Showing 15 changed files with 636 additions and 8 deletions.
32 changes: 32 additions & 0 deletions llvm/lib/Analysis/AliasAnalysisSummary.h
Expand Up @@ -120,13 +120,45 @@ inline bool operator==(InterfaceValue LHS, InterfaceValue RHS) {
inline bool operator!=(InterfaceValue LHS, InterfaceValue RHS) {
return !(LHS == RHS);
}
inline bool operator<(InterfaceValue LHS, InterfaceValue RHS) {
return LHS.Index < RHS.Index ||
(LHS.Index == RHS.Index && LHS.DerefLevel < RHS.DerefLevel);
}
inline bool operator>(InterfaceValue LHS, InterfaceValue RHS) {
return RHS < LHS;
}
inline bool operator<=(InterfaceValue LHS, InterfaceValue RHS) {
return !(RHS < LHS);
}
inline bool operator>=(InterfaceValue LHS, InterfaceValue RHS) {
return !(LHS < RHS);
}

/// We use ExternalRelation to describe an externally visible aliasing relations
/// between parameters/return value of a function.
struct ExternalRelation {
InterfaceValue From, To;
};

inline bool operator==(ExternalRelation LHS, ExternalRelation RHS) {
return LHS.From == RHS.From && LHS.To == RHS.To;
}
inline bool operator!=(ExternalRelation LHS, ExternalRelation RHS) {
return !(LHS == RHS);
}
inline bool operator<(ExternalRelation LHS, ExternalRelation RHS) {
return LHS.From < RHS.From || (LHS.From == RHS.From && LHS.To < RHS.To);
}
inline bool operator>(ExternalRelation LHS, ExternalRelation RHS) {
return RHS < LHS;
}
inline bool operator<=(ExternalRelation LHS, ExternalRelation RHS) {
return !(RHS < LHS);
}
inline bool operator>=(ExternalRelation LHS, ExternalRelation RHS) {
return !(LHS < RHS);
}

/// We use ExternalAttribute to describe an externally visible AliasAttrs
/// for parameters/return value.
struct ExternalAttribute {
Expand Down
164 changes: 156 additions & 8 deletions llvm/lib/Analysis/CFLAndersAliasAnalysis.cpp
Expand Up @@ -106,10 +106,17 @@ enum class MatchState : uint8_t {
FlowToMemAliasReadWrite,
};

typedef std::bitset<7> StateSet;
LLVM_CONSTEXPR StateSet ReadOnlyStateMask =
(1 << static_cast<uint8_t>(MatchState::FlowFromReadOnly)) |
(1 << static_cast<uint8_t>(MatchState::FlowFromMemAliasReadOnly));
LLVM_CONSTEXPR StateSet WriteOnlyStateMask =
(1 << static_cast<uint8_t>(MatchState::FlowToWriteOnly)) |
(1 << static_cast<uint8_t>(MatchState::FlowToMemAliasWriteOnly));

// We use ReachabilitySet to keep track of value aliases (The nonterminal "V" in
// the paper) during the analysis.
class ReachabilitySet {
typedef std::bitset<7> StateSet;
typedef DenseMap<InstantiatedValue, StateSet> ValueStateMap;
typedef DenseMap<InstantiatedValue, ValueStateMap> ValueReachMap;
ValueReachMap ReachMap;
Expand All @@ -120,6 +127,7 @@ class ReachabilitySet {

// Insert edge 'From->To' at state 'State'
bool insert(InstantiatedValue From, InstantiatedValue To, MatchState State) {
assert(From != To);
auto &States = ReachMap[To][From];
auto Idx = static_cast<size_t>(State);
if (!States.test(Idx)) {
Expand Down Expand Up @@ -207,6 +215,14 @@ struct WorkListItem {
InstantiatedValue To;
MatchState State;
};

struct ValueSummary {
struct Record {
InterfaceValue IValue;
unsigned DerefLevel;
};
SmallVector<Record, 4> FromRecords, ToRecords;
};
}

class CFLAndersAAResult::FunctionInfo {
Expand All @@ -225,24 +241,51 @@ class CFLAndersAAResult::FunctionInfo {
AliasAttrs getAttrs(const Value *) const;

public:
FunctionInfo(const ReachabilitySet &, AliasAttrMap);
FunctionInfo(const Function &, const SmallVectorImpl<Value *> &,
const ReachabilitySet &, AliasAttrMap);

bool mayAlias(const Value *LHS, const Value *RHS) const;
const AliasSummary &getAliasSummary() const { return Summary; }
};

CFLAndersAAResult::FunctionInfo::FunctionInfo(const ReachabilitySet &ReachSet,
AliasAttrMap AMap) {
// Populate AttrMap
static bool hasReadOnlyState(StateSet Set) {
return (Set & ReadOnlyStateMask).any();
}

static bool hasWriteOnlyState(StateSet Set) {
return (Set & WriteOnlyStateMask).any();
}

static Optional<InterfaceValue>
getInterfaceValue(InstantiatedValue IValue,
const SmallVectorImpl<Value *> &RetVals) {
auto Val = IValue.Val;

Optional<unsigned> Index;
if (auto Arg = dyn_cast<Argument>(Val))
Index = Arg->getArgNo() + 1;
else if (is_contained(RetVals, Val))
Index = 0;

if (Index)
return InterfaceValue{*Index, IValue.DerefLevel};
return None;
}

static void populateAttrMap(DenseMap<const Value *, AliasAttrs> &AttrMap,
const AliasAttrMap &AMap) {
for (const auto &Mapping : AMap.mappings()) {
auto IVal = Mapping.first;

// AttrMap only cares about top-level values
if (IVal.DerefLevel == 0)
AttrMap[IVal.Val] = Mapping.second;
}
}

// Populate AliasMap
static void
populateAliasMap(DenseMap<const Value *, std::vector<const Value *>> &AliasMap,
const ReachabilitySet &ReachSet) {
for (const auto &OuterMapping : ReachSet.value_mappings()) {
// AliasMap only cares about top-level values
if (OuterMapping.first.DerefLevel > 0)
Expand All @@ -259,8 +302,112 @@ CFLAndersAAResult::FunctionInfo::FunctionInfo(const ReachabilitySet &ReachSet,
// Sort AliasList for faster lookup
std::sort(AliasList.begin(), AliasList.end(), std::less<const Value *>());
}
}

static void populateExternalRelations(
SmallVectorImpl<ExternalRelation> &ExtRelations, const Function &Fn,
const SmallVectorImpl<Value *> &RetVals, const ReachabilitySet &ReachSet) {
// If a function only returns one of its argument X, then X will be both an
// argument and a return value at the same time. This is an edge case that
// needs special handling here.
for (const auto &Arg : Fn.args()) {
if (is_contained(RetVals, &Arg)) {
auto ArgVal = InterfaceValue{Arg.getArgNo() + 1, 0};
auto RetVal = InterfaceValue{0, 0};
ExtRelations.push_back(ExternalRelation{ArgVal, RetVal});
}
}

// Below is the core summary construction logic.
// A naive solution of adding only the value aliases that are parameters or
// return values in ReachSet to the summary won't work: It is possible that a
// parameter P is written into an intermediate value I, and the function
// subsequently returns *I. In that case, *I is does not value alias anything
// in ReachSet, and the naive solution will miss a summary edge from (P, 1) to
// (I, 1).
// To account for the aforementioned case, we need to check each non-parameter
// and non-return value for the possibility of acting as an intermediate.
// 'ValueMap' here records, for each value, which InterfaceValues read from or
// write into it. If both the read list and the write list of a given value
// are non-empty, we know that a particular value is an intermidate and we
// need to add summary edges from the writes to the reads.
DenseMap<Value *, ValueSummary> ValueMap;
for (const auto &OuterMapping : ReachSet.value_mappings()) {
if (auto Dst = getInterfaceValue(OuterMapping.first, RetVals)) {
for (const auto &InnerMapping : OuterMapping.second) {
// If Src is a param/return value, we get a same-level assignment.
if (auto Src = getInterfaceValue(InnerMapping.first, RetVals)) {
// This may happen if both Dst and Src are return values
if (*Dst == *Src)
continue;

if (hasReadOnlyState(InnerMapping.second))
ExtRelations.push_back(ExternalRelation{*Dst, *Src});
// No need to check for WriteOnly state, since ReachSet is symmetric
} else {
// If Src is not a param/return, add it to ValueMap
auto SrcIVal = InnerMapping.first;
if (hasReadOnlyState(InnerMapping.second))
ValueMap[SrcIVal.Val].FromRecords.push_back(
ValueSummary::Record{*Dst, SrcIVal.DerefLevel});
if (hasWriteOnlyState(InnerMapping.second))
ValueMap[SrcIVal.Val].ToRecords.push_back(
ValueSummary::Record{*Dst, SrcIVal.DerefLevel});
}
}
}
}

for (const auto &Mapping : ValueMap) {
for (const auto &FromRecord : Mapping.second.FromRecords) {
for (const auto &ToRecord : Mapping.second.ToRecords) {
auto ToLevel = ToRecord.DerefLevel;
auto FromLevel = FromRecord.DerefLevel;
// Same-level assignments should have already been processed by now
if (ToLevel == FromLevel)
continue;

auto SrcIndex = FromRecord.IValue.Index;
auto SrcLevel = FromRecord.IValue.DerefLevel;
auto DstIndex = ToRecord.IValue.Index;
auto DstLevel = ToRecord.IValue.DerefLevel;
if (ToLevel > FromLevel)
SrcLevel += ToLevel - FromLevel;
else
DstLevel += FromLevel - ToLevel;

ExtRelations.push_back(
ExternalRelation{InterfaceValue{SrcIndex, SrcLevel},
InterfaceValue{DstIndex, DstLevel}});
}
}
}

// Remove duplicates in ExtRelations
std::sort(ExtRelations.begin(), ExtRelations.end());
ExtRelations.erase(std::unique(ExtRelations.begin(), ExtRelations.end()),
ExtRelations.end());
}

static void populateExternalAttributes(
SmallVectorImpl<ExternalAttribute> &ExtAttributes, const Function &Fn,
const SmallVectorImpl<Value *> &RetVals, const AliasAttrMap &AMap) {
for (const auto &Mapping : AMap.mappings()) {
if (auto IVal = getInterfaceValue(Mapping.first, RetVals)) {
auto Attr = getExternallyVisibleAttrs(Mapping.second);
if (Attr.any())
ExtAttributes.push_back(ExternalAttribute{*IVal, Attr});
}
}
}

// TODO: Populate function summary here
CFLAndersAAResult::FunctionInfo::FunctionInfo(
const Function &Fn, const SmallVectorImpl<Value *> &RetVals,
const ReachabilitySet &ReachSet, AliasAttrMap AMap) {
populateAttrMap(AttrMap, AMap);
populateExternalAttributes(Summary.RetParamAttributes, Fn, RetVals, AMap);
populateAliasMap(AliasMap, ReachSet);
populateExternalRelations(Summary.RetParamRelations, Fn, RetVals, ReachSet);
}

AliasAttrs CFLAndersAAResult::FunctionInfo::getAttrs(const Value *V) const {
Expand Down Expand Up @@ -510,7 +657,8 @@ CFLAndersAAResult::buildInfoFrom(const Function &Fn) {
// to it
auto IValueAttrMap = buildAttrMap(Graph, ReachSet);

return FunctionInfo(ReachSet, std::move(IValueAttrMap));
return FunctionInfo(Fn, GraphBuilder.getReturnValues(), ReachSet,
std::move(IValueAttrMap));
}

void CFLAndersAAResult::scan(const Function &Fn) {
Expand Down
22 changes: 22 additions & 0 deletions llvm/test/Analysis/CFLAliasAnalysis/Andersen/basic-interproc.ll
@@ -0,0 +1,22 @@
; This testcase ensures that CFL AA won't be too conservative when trying to do
; interprocedural analysis on simple callee

; RUN: opt < %s -disable-basicaa -cfl-anders-aa -aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s
; RUN: opt < %s -aa-pipeline=cfl-anders-aa -passes=aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s

; CHECK-LABEL: Function: noop_callee
; CHECK: MayAlias: i32* %arg1, i32* %arg2
define void @noop_callee(i32* %arg1, i32* %arg2) {
store i32 0, i32* %arg1
store i32 0, i32* %arg2
ret void
}
; CHECK-LABEL: Function: test_noop
; CHECK: NoAlias: i32* %a, i32* %b
define void @test_noop() {
%a = alloca i32, align 4
%b = alloca i32, align 4
call void @noop_callee(i32* %a, i32* %b)

ret void
}
@@ -0,0 +1,33 @@
; This testcase ensures that CFL AA answers queries soundly when callee tries
; to escape the memory pointed to by its parameters

; RUN: opt < %s -disable-basicaa -cfl-anders-aa -aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s
; RUN: opt < %s -aa-pipeline=cfl-anders-aa -passes=aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s

declare void @opaque(i32*)
define void @escape_arg_deref(i32** %arg) {
%arg_deref = load i32*, i32** %arg
call void @opaque(i32* %arg_deref)
ret void
}
; CHECK-LABEL: Function: test_arg_deref_escape
; CHECK: NoAlias: i32* %a, i32** %x
; CHECK: NoAlias: i32* %b, i32** %x
; CHECK: NoAlias: i32* %a, i32* %b
; CHECK: NoAlias: i32** %p, i32** %x
; CHECK: NoAlias: i32* %a, i32** %p
; CHECK: NoAlias: i32* %b, i32** %p
; CHECK: MayAlias: i32* %a, i32* %c
; CHECK: NoAlias: i32* %b, i32* %c
; CHECK: NoAlias: i32* %c, i32** %p
define void @test_arg_deref_escape(i32** %x) {
%a = alloca i32, align 4
%b = alloca i32, align 4
%p = alloca i32*, align 4

store i32* %a, i32** %p
call void @escape_arg_deref(i32** %p)
%c = load i32*, i32** %x

ret void
}
@@ -0,0 +1,31 @@
; This testcase ensures that CFL AA answers queries soundly when callee tries
; to escape its parameters

; RUN: opt < %s -disable-basicaa -cfl-anders-aa -aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s
; RUN: opt < %s -aa-pipeline=cfl-anders-aa -passes=aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s

declare void @opaque(i32*)
define void @escape_arg(i32* %arg) {
call void @opaque(i32* %arg)
ret void
}
; CHECK-LABEL: Function: test_arg_escape
; CHECK: NoAlias: i32* %a, i32** %x
; CHECK: NoAlias: i32* %b, i32** %x
; CHECK: NoAlias: i32* %a, i32* %b
; CHECK: NoAlias: i32* %c, i32** %x
; CHECK: NoAlias: i32* %a, i32* %c
; CHECK: NoAlias: i32* %b, i32* %c
; CHECK: MayAlias: i32* %a, i32* %d
; CHECK: MayAlias: i32* %b, i32* %d
; CHECK: NoAlias: i32* %c, i32* %d
define void @test_arg_escape(i32** %x) {
%a = alloca i32, align 4
%b = alloca i32, align 4
%c = alloca i32, align 4
call void @escape_arg(i32* %a)
call void @escape_arg(i32* %b)
%d = load i32*, i32** %x

ret void
}
21 changes: 21 additions & 0 deletions llvm/test/Analysis/CFLAliasAnalysis/Andersen/interproc-ret-arg.ll
@@ -0,0 +1,21 @@
; This testcase ensures that CFL AA answers queries soundly when callee tries
; to return one of its parameters

; RUN: opt < %s -disable-basicaa -cfl-anders-aa -aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s
; RUN: opt < %s -aa-pipeline=cfl-anders-aa -passes=aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s

define i32* @return_arg_callee(i32* %arg1, i32* %arg2) {
ret i32* %arg1
}
; CHECK-LABEL: Function: test_return_arg
; CHECK: NoAlias: i32* %a, i32* %b
; CHECK: MayAlias: i32* %a, i32* %c
; CHECK: NoAlias: i32* %b, i32* %c
define void @test_return_arg() {
%a = alloca i32, align 4
%b = alloca i32, align 4

%c = call i32* @return_arg_callee(i32* %a, i32* %b)

ret void
}

0 comments on commit 3b05984

Please sign in to comment.