Skip to content

Commit fd9d04c

Browse files
authored
Liveness Analysis Proof of Concept (#5771)
This introduces a limited monotone flow-sensitive liveness analysis on local indices as an initial proof of concept for the creation of a monotone flow-sensitive static analysis framework. Tests are included in test/gtest/cfg.cpp.
1 parent 1545deb commit fd9d04c

File tree

8 files changed

+568
-0
lines changed

8 files changed

+568
-0
lines changed

src/analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
file(GLOB analysis_HEADERS *.h)
22
set(analysis_SOURCES
33
cfg.cpp
4+
sign-lattice.cpp
45
${analysis_HEADERS}
56
)
67
add_library(analysis OBJECT ${analysis_SOURCES})

src/analysis/cfg.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ struct BasicBlock {
4040
iterator end() const { return insts.cend(); }
4141
size_t size() const { return insts.size(); }
4242

43+
using reverse_iterator = std::vector<Expression*>::const_reverse_iterator;
44+
reverse_iterator rbegin() const { return insts.rbegin(); }
45+
reverse_iterator rend() const { return insts.rend(); }
46+
4347
// Iterables for predecessor and successor blocks.
4448
struct Predecessors;
4549
struct Successors;
@@ -48,6 +52,8 @@ struct BasicBlock {
4852

4953
void print(std::ostream& os, Module* wasm = nullptr, size_t start = 0) const;
5054

55+
Index getIndex() const { return index; }
56+
5157
private:
5258
Index index;
5359
std::vector<Expression*> insts;

src/analysis/lattice.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#ifndef wasm_analysis_lattice_h
2+
#define wasm_analysis_lattice_h
3+
4+
#include "wasm.h"
5+
#include <bitset>
6+
#include <iostream>
7+
8+
namespace wasm::analysis {
9+
10+
enum LatticeComparison { NO_RELATION, EQUAL, LESS, GREATER };
11+
12+
template<typename T>
13+
constexpr bool has_compare = std::is_invocable_r<LatticeComparison,
14+
decltype(T::compare),
15+
const T&,
16+
const T&>::value;
17+
template<typename T>
18+
constexpr bool has_getLeastUpperBound = std::
19+
is_invocable_r<void, decltype(&T::getLeastUpperBound), T, const T&>::value;
20+
template<typename T>
21+
constexpr bool has_isTop =
22+
std::is_invocable_r<bool, decltype(T::isTop), const T&>::value;
23+
template<typename T>
24+
constexpr bool has_isBottom =
25+
std::is_invocable_r<bool, decltype(T::isBottom), const T&>::value;
26+
27+
template<typename T>
28+
constexpr bool is_lattice =
29+
has_compare<T>&& has_getLeastUpperBound<T>&& has_isTop<T>&& has_isBottom<T>;
30+
31+
// Represents a powerset lattice element (i.e. a set) as a bitvector. A true
32+
// means that an element is present in the set.
33+
template<size_t N> struct BitsetPowersetLattice {
34+
std::bitset<N> value;
35+
36+
static BitsetPowersetLattice<N> getBottom();
37+
static bool isTop(const BitsetPowersetLattice<N>& element);
38+
static bool isBottom(const BitsetPowersetLattice<N>& element);
39+
40+
// Compares two lattice elements and returns a result indicating the
41+
// left element's relation to the right element.
42+
static LatticeComparison compare(const BitsetPowersetLattice<N>& left,
43+
const BitsetPowersetLattice<N>& right);
44+
45+
// Calculates the LUB of this current (left) lattice element with some right
46+
// element. It then updates this current lattice element to the LUB in place.
47+
void getLeastUpperBound(const BitsetPowersetLattice<N>& right);
48+
49+
// Prints out the bits in the bitvector for a lattice element.
50+
void print(std::ostream& os);
51+
};
52+
53+
} // namespace wasm::analysis
54+
55+
#include "powerset-lattice-impl.h"
56+
57+
#endif // wasm_analysis_lattice_h
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#ifndef wasm_analysis_monotone_analyzer_impl_h
2+
#define wasm_analysis_monotone_analyzer_impl_h
3+
4+
#include <iostream>
5+
#include <unordered_map>
6+
7+
#include "monotone-analyzer.h"
8+
9+
namespace wasm::analysis {
10+
template<size_t N>
11+
inline BlockState<N>::BlockState(const BasicBlock* underlyingBlock)
12+
: index(underlyingBlock->getIndex()), cfgBlock(underlyingBlock),
13+
beginningState(BitsetPowersetLattice<N>::getBottom()),
14+
endState(BitsetPowersetLattice<N>::getBottom()),
15+
currState(BitsetPowersetLattice<N>::getBottom()) {}
16+
17+
template<size_t N> inline void BlockState<N>::addPredecessor(BlockState* pred) {
18+
predecessors.push_back(pred);
19+
}
20+
21+
template<size_t N> inline void BlockState<N>::addSuccessor(BlockState* succ) {
22+
successors.push_back(succ);
23+
}
24+
25+
template<size_t N>
26+
inline BitsetPowersetLattice<N>& BlockState<N>::getFirstState() {
27+
return beginningState;
28+
}
29+
30+
template<size_t N>
31+
inline BitsetPowersetLattice<N>& BlockState<N>::getLastState() {
32+
return endState;
33+
}
34+
35+
// In our current limited implementation, we just update a new live variable
36+
// when it it is used in a get or set.
37+
template<size_t N> inline void BlockState<N>::visitLocalSet(LocalSet* curr) {
38+
currState.value[curr->index] = false;
39+
}
40+
41+
template<size_t N> inline void BlockState<N>::visitLocalGet(LocalGet* curr) {
42+
currState.value[curr->index] = true;
43+
}
44+
45+
template<size_t N> inline void BlockState<N>::transfer() {
46+
// If the block is empty, we propagate the state by endState = currState, then
47+
// currState = beginningState
48+
49+
// compute transfer function for all expressions in the CFG block
50+
auto cfgIter = cfgBlock->rbegin();
51+
currState = endState;
52+
53+
while (cfgIter != cfgBlock->rend()) {
54+
// run transfer function.
55+
BlockState<N>::visit(*cfgIter);
56+
++cfgIter;
57+
}
58+
beginningState = currState;
59+
}
60+
61+
template<size_t N> inline void BlockState<N>::print(std::ostream& os) {
62+
os << "State Block: " << index << std::endl;
63+
os << "State at beginning: ";
64+
beginningState.print(os);
65+
os << std::endl << "State at end: ";
66+
endState.print(os);
67+
os << std::endl << "Intermediate States (reverse order): " << std::endl;
68+
69+
currState = endState;
70+
currState.print(os);
71+
os << std::endl;
72+
auto cfgIter = cfgBlock->rbegin();
73+
74+
while (cfgIter != cfgBlock->rend()) {
75+
// run transfer function.
76+
os << ShallowExpression{*cfgIter} << std::endl;
77+
BlockState<N>::visit(*cfgIter);
78+
currState.print(os);
79+
os << std::endl;
80+
++cfgIter;
81+
}
82+
}
83+
84+
template<size_t N>
85+
MonotoneCFGAnalyzer<N> inline MonotoneCFGAnalyzer<N>::fromCFG(CFG* cfg) {
86+
MonotoneCFGAnalyzer<N> result;
87+
88+
// Map BasicBlocks to each BlockState's index
89+
std::unordered_map<const BasicBlock*, size_t> basicBlockToState;
90+
size_t index = 0;
91+
for (auto it = cfg->begin(); it != cfg->end(); it++) {
92+
result.stateBlocks.emplace_back(&(*it));
93+
basicBlockToState[&(*it)] = index++;
94+
}
95+
96+
// Update predecessors and successors of each BlockState object
97+
// according to the BasicBlock's predecessors and successors.
98+
for (index = 0; index < result.stateBlocks.size(); ++index) {
99+
BlockState<N>& currBlock = result.stateBlocks.at(index);
100+
BasicBlock::Predecessors preds = currBlock.cfgBlock->preds();
101+
BasicBlock::Successors succs = currBlock.cfgBlock->succs();
102+
for (auto pred : preds) {
103+
currBlock.addPredecessor(&result.stateBlocks[basicBlockToState[&pred]]);
104+
}
105+
106+
for (auto succ : succs) {
107+
currBlock.addSuccessor(&result.stateBlocks[basicBlockToState[&succ]]);
108+
}
109+
}
110+
111+
return result;
112+
}
113+
114+
template<size_t N> inline void MonotoneCFGAnalyzer<N>::evaluate() {
115+
std::queue<Index> worklist;
116+
117+
for (auto it = stateBlocks.rbegin(); it != stateBlocks.rend(); ++it) {
118+
worklist.push(it->index);
119+
}
120+
121+
while (!worklist.empty()) {
122+
BlockState<N>& currBlockState = stateBlocks[worklist.front()];
123+
worklist.pop();
124+
currBlockState.transfer();
125+
126+
// Propagate state to dependents
127+
for (size_t j = 0; j < currBlockState.predecessors.size(); ++j) {
128+
BitsetPowersetLattice<N>& predLast =
129+
currBlockState.predecessors[j]->getLastState();
130+
131+
LatticeComparison cmp = BitsetPowersetLattice<N>::compare(
132+
predLast, currBlockState.getFirstState());
133+
134+
if (cmp == LatticeComparison::NO_RELATION ||
135+
cmp == LatticeComparison::LESS) {
136+
predLast.getLeastUpperBound(currBlockState.getFirstState());
137+
worklist.push(currBlockState.predecessors[j]->index);
138+
}
139+
}
140+
}
141+
}
142+
143+
template<size_t N> inline void MonotoneCFGAnalyzer<N>::print(std::ostream& os) {
144+
os << "CFG Analyzer" << std::endl;
145+
for (auto state : stateBlocks) {
146+
state.print(os);
147+
}
148+
os << "End" << std::endl;
149+
}
150+
151+
} // namespace wasm::analysis
152+
153+
#endif // wasm_analysis_monotone_analyzer_impl_h

src/analysis/monotone-analyzer.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#ifndef wasm_analysis_monotone_analyzer_h
2+
#define wasm_analysis_monotone_analyzer_h
3+
4+
#include <iostream>
5+
#include <queue>
6+
#include <vector>
7+
8+
#include "cfg.h"
9+
#include "lattice.h"
10+
#include "wasm-traversal.h"
11+
12+
namespace wasm::analysis {
13+
14+
template<size_t N> struct MonotoneCFGAnalyzer;
15+
16+
// A node which contains all the lattice states for a given CFG node.
17+
template<size_t N> struct BlockState : public Visitor<BlockState<N>> {
18+
static_assert(is_lattice<BitsetPowersetLattice<N>>);
19+
BlockState(const BasicBlock* underlyingBlock);
20+
21+
void addPredecessor(BlockState* pred);
22+
void addSuccessor(BlockState* succ);
23+
24+
BitsetPowersetLattice<N>& getFirstState();
25+
BitsetPowersetLattice<N>& getLastState();
26+
27+
// Transfer function implementation. Modifies the state for a particular
28+
// expression type.
29+
void visitLocalSet(LocalSet* curr);
30+
void visitLocalGet(LocalGet* curr);
31+
32+
// Executes the transfer function on all the expressions of the corresponding
33+
// CFG and then propagates the state to all predecessors (which depend on the
34+
// current node).
35+
void transfer();
36+
37+
// prints out all states.
38+
void print(std::ostream& os);
39+
40+
private:
41+
// The index of the block is same as the CFG index.
42+
Index index;
43+
const BasicBlock* cfgBlock;
44+
// State at beginning of CFG node.
45+
BitsetPowersetLattice<N> beginningState;
46+
// State at the end of the CFG node.
47+
BitsetPowersetLattice<N> endState;
48+
// Holds intermediate state values.
49+
BitsetPowersetLattice<N> currState;
50+
std::vector<BlockState*> predecessors;
51+
std::vector<BlockState*> successors;
52+
53+
friend MonotoneCFGAnalyzer<N>;
54+
};
55+
56+
template<size_t N> struct MonotoneCFGAnalyzer {
57+
static_assert(is_lattice<BitsetPowersetLattice<N>>);
58+
59+
// Constructs a graph of BlockState objects which parallels
60+
// the CFG graph. Each CFG node corresponds to a BlockState node.
61+
static MonotoneCFGAnalyzer<N> fromCFG(CFG* cfg);
62+
63+
// Runs the worklist algorithm to compute the states for the BlockList graph.
64+
void evaluate();
65+
66+
void print(std::ostream& os);
67+
68+
private:
69+
std::vector<BlockState<N>> stateBlocks;
70+
friend BlockState<N>;
71+
};
72+
73+
} // namespace wasm::analysis
74+
75+
#include "monotone-analyzer-impl.h"
76+
77+
#endif // wasm_analysis_monotone_analyzer_h
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#ifndef wasm_analysis_lattice_impl_h
2+
#define wasm_analysis_lattice_impl_h
3+
4+
#include "lattice.h"
5+
6+
namespace wasm::analysis {
7+
8+
template<size_t N>
9+
inline BitsetPowersetLattice<N> BitsetPowersetLattice<N>::getBottom() {
10+
// Return the empty set as the bottom lattice element.
11+
BitsetPowersetLattice<N> result{0};
12+
return result;
13+
}
14+
15+
template<size_t N>
16+
inline bool
17+
BitsetPowersetLattice<N>::isTop(const BitsetPowersetLattice<N>& element) {
18+
// Top lattice element is the set containing all possible elements.
19+
return element.value.all();
20+
}
21+
22+
template<size_t N>
23+
inline bool
24+
BitsetPowersetLattice<N>::isBottom(const BitsetPowersetLattice<N>& element) {
25+
// Bottom lattice element is the empty set.
26+
return element.value.none();
27+
}
28+
29+
template<size_t N>
30+
inline LatticeComparison
31+
BitsetPowersetLattice<N>::compare(const BitsetPowersetLattice<N>& left,
32+
const BitsetPowersetLattice<N>& right) {
33+
size_t leftCount = left.value.count();
34+
size_t rightCount = right.value.count();
35+
36+
// If left has more elements, left might be a superset of right.
37+
if (leftCount > rightCount) {
38+
if ((left.value | right.value) == left.value) {
39+
return GREATER;
40+
}
41+
// If right has more elements, right might be a superset of left.
42+
} else if (leftCount < rightCount) {
43+
if ((left.value | right.value) == right.value) {
44+
return LESS;
45+
}
46+
} else if (left.value == right.value) {
47+
return EQUAL;
48+
}
49+
50+
return NO_RELATION;
51+
}
52+
53+
// Implement LUB as an OR of the two bitsets.
54+
template<size_t N>
55+
inline void BitsetPowersetLattice<N>::getLeastUpperBound(
56+
const BitsetPowersetLattice<N>& right) {
57+
value |= right.value;
58+
}
59+
60+
template<size_t N>
61+
inline void BitsetPowersetLattice<N>::print(std::ostream& os) {
62+
os << value;
63+
}
64+
65+
}; // namespace wasm::analysis
66+
67+
#endif // wasm_analysis_lattice_impl_h

0 commit comments

Comments
 (0)