Skip to content

Commit d9b3619

Browse files
tlivelyradekdoulik
authored andcommitted
[analysis] Simplify core analysis code (WebAssembly#6034)
Simplify the monotone analyzer by replacing all the state it used to store in `BlockState` with a simple vector of lattice elements. Use simple indices to refer to both blocks and their associated states in the vector. Remove the ability for transfer functions to control the initial enqueued order of basic blocks since that was a leaky abstraction. Replace the worklist with a UniqueDeferredQueue since that has generally proven to be more efficient in smiilarly contexts, and more importantly, it has a nicer API. Make miscellaneous simplifications to other code as well. Delete a few unit tests that exposed the order in which blocks were analyzed because they printed intermediate results. These tests should be replaced with tests of analyses' public APIs in the future.
1 parent d693077 commit d9b3619

File tree

9 files changed

+87
-319
lines changed

9 files changed

+87
-319
lines changed

src/analysis/cfg.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ struct CFG {
7373
reverse_iterator rbegin() const { return blocks.rbegin(); }
7474
reverse_iterator rend() const { return blocks.rend(); }
7575

76+
const BasicBlock& operator[](size_t i) const { return *(begin() + i); }
77+
7678
static CFG fromFunction(Function* func);
7779

7880
void print(std::ostream& os, Module* wasm = nullptr) const;

src/analysis/lattice.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
#if __cplusplus >= 202002L
2121
#include <concepts>
22-
#endif
22+
#endif // __cplusplus >= 202002L
2323

2424
namespace wasm::analysis {
2525

@@ -45,6 +45,7 @@ concept Lattice = requires(const L& lattice,
4545
typename L::Element& elem) {
4646
// Lattices must have elements.
4747
typename L::Element;
48+
requires std::copyable<typename L::Element>;
4849
// We need to be able to get the bottom element.
4950
{ lattice.getBottom() } noexcept -> std::same_as<typename L::Element>;
5051
// Elements should be comparable. TODO: use <=> and std::three_way_comparable
@@ -57,7 +58,7 @@ concept Lattice = requires(const L& lattice,
5758
{ elem.makeLeastUpperBound(constElem) } noexcept -> std::same_as<bool>;
5859
};
5960

60-
#else
61+
#else // __cplusplus >= 202002L
6162

6263
#define Lattice typename
6364

src/analysis/liveness-transfer-function.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,21 @@ struct LivenessTransferFunction
2828
// to be passed in, where the temp copy is modified in place to produce the
2929
// intermediate states.
3030
void print(std::ostream& os,
31-
const BasicBlock* cfgBlock,
31+
const BasicBlock& bb,
3232
FiniteIntPowersetLattice::Element& inputState) {
3333
os << "Intermediate States (reverse order): " << std::endl;
3434
currState = &inputState;
3535
currState->print(os);
3636
os << std::endl;
37-
auto cfgIter = cfgBlock->rbegin();
3837

3938
// Since we don't store the intermediate states, we need to re-run the
4039
// transfer function on all the CFG node expressions to reconstruct
4140
// the intermediate states here.
42-
while (cfgIter != cfgBlock->rend()) {
43-
os << ShallowExpression{*cfgIter} << std::endl;
44-
visit(*cfgIter);
41+
for (auto it = bb.rbegin(); it != bb.rend(); ++it) {
42+
os << ShallowExpression{*it} << "\n";
43+
visit(*it);
4544
currState->print(os);
46-
os << std::endl;
47-
++cfgIter;
45+
os << "\n";
4846
}
4947
currState = nullptr;
5048
}

src/analysis/monotone-analyzer-impl.h

Lines changed: 40 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,89 +5,61 @@
55
#include <unordered_map>
66

77
#include "monotone-analyzer.h"
8+
#include "support/unique_deferring_queue.h"
89

910
namespace wasm::analysis {
1011

11-
// All states are set to the bottom lattice element using the lattice in this
12-
// constructor.
13-
template<Lattice L>
14-
inline BlockState<L>::BlockState(const BasicBlock* underlyingBlock, L& lattice)
15-
: cfgBlock(underlyingBlock), inputState(lattice.getBottom()) {}
16-
17-
// Prints out inforamtion about a CFG node's state, but not intermediate states.
18-
template<Lattice L> inline void BlockState<L>::print(std::ostream& os) {
19-
os << "CFG Block: " << cfgBlock->getIndex() << std::endl;
20-
os << "Input State: ";
21-
inputState.print(os);
22-
os << std::endl << "Predecessors:";
23-
for (auto pred : cfgBlock->preds()) {
24-
os << " " << pred.getIndex();
25-
}
26-
os << std::endl << "Successors:";
27-
for (auto succ : cfgBlock->succs()) {
28-
os << " " << succ.getIndex();
29-
}
30-
os << std::endl;
31-
}
32-
3312
template<Lattice L, TransferFunction TxFn>
3413
inline MonotoneCFGAnalyzer<L, TxFn>::MonotoneCFGAnalyzer(L& lattice,
3514
TxFn& txfn,
3615
CFG& cfg)
37-
: lattice(lattice), txfn(txfn), cfg(cfg) {
38-
39-
// Construct BlockStates for each BasicBlock.
40-
for (auto it = cfg.begin(); it != cfg.end(); it++) {
41-
stateBlocks.emplace_back(&(*it), lattice);
42-
}
43-
}
16+
: lattice(lattice), txfn(txfn), cfg(cfg),
17+
states(cfg.size(), lattice.getBottom()) {}
4418

4519
template<Lattice L, TransferFunction TxFn>
4620
inline void
4721
MonotoneCFGAnalyzer<L, TxFn>::evaluateFunctionEntry(Function* func) {
48-
txfn.evaluateFunctionEntry(func, stateBlocks[0].inputState);
22+
txfn.evaluateFunctionEntry(func, states[0]);
4923
}
5024

5125
template<Lattice L, TransferFunction TxFn>
5226
inline void MonotoneCFGAnalyzer<L, TxFn>::evaluate() {
53-
std::queue<const BasicBlock*> worklist;
27+
UniqueDeferredQueue<Index> worklist;
5428

55-
// Transfer function enqueues the work in some order which is efficient.
56-
txfn.enqueueWorklist(cfg, worklist);
29+
// Start with all blocks on the work list. TODO: optimize the iteration order
30+
// using e.g. strongly-connected components.
31+
for (Index i = 0; i < cfg.size(); ++i) {
32+
worklist.push(i);
33+
}
5734

5835
while (!worklist.empty()) {
59-
BlockState<L>& currBlockState = stateBlocks[worklist.front()->getIndex()];
60-
worklist.pop();
61-
62-
// For each expression, applies the transfer function, using the expression,
63-
// on the state of the expression it depends upon (here the next expression)
64-
// to arrive at the expression's state. The beginning and end states of the
65-
// CFG block will be updated.
66-
typename L::Element outputState = currBlockState.inputState;
67-
txfn.transfer(currBlockState.cfgBlock, outputState);
68-
69-
// Propagate state to dependents of currBlockState.
70-
for (auto& dep : txfn.getDependents(currBlockState.cfgBlock)) {
71-
// If we need to change the input state of a dependent, we need
72-
// to enqueue the dependent to recalculate it.
73-
if (stateBlocks[dep.getIndex()].inputState.makeLeastUpperBound(
74-
outputState)) {
75-
worklist.push(&dep);
36+
// The index of the block we will analyze.
37+
Index i = worklist.pop();
38+
39+
// Apply the transfer function to the input state to compute the output
40+
// state for the block.
41+
auto state = states[i];
42+
txfn.transfer(cfg[i], state);
43+
44+
// Propagate state to the dependent blocks.
45+
for (auto& dep : txfn.getDependents(cfg[i])) {
46+
// If the input state for the dependent block changes, we need to
47+
// re-analyze it.
48+
if (states[dep.getIndex()].makeLeastUpperBound(state)) {
49+
worklist.push(dep.getIndex());
7650
}
7751
}
7852
}
7953
}
8054

8155
template<Lattice L, TransferFunction TxFn>
8256
inline void MonotoneCFGAnalyzer<L, TxFn>::collectResults() {
83-
for (BlockState currBlockState : stateBlocks) {
84-
typename L::Element inputStateCopy = currBlockState.inputState;
85-
57+
for (Index i = 0, size = cfg.size(); i < size; ++i) {
8658
// The transfer function generates the final set of states and uses it to
8759
// produce useful information. For example, in reaching definitions
8860
// analysis, these final states are used to populate a mapping of
8961
// local.get's to a set of local.set's that affect its value.
90-
txfn.collectResults(currBlockState.cfgBlock, inputStateCopy);
62+
txfn.collectResults(cfg[i], states[i]);
9163
}
9264
}
9365

@@ -96,12 +68,21 @@ inline void MonotoneCFGAnalyzer<L, TxFn>::collectResults() {
9668
template<Lattice L, TransferFunction TxFn>
9769
inline void MonotoneCFGAnalyzer<L, TxFn>::print(std::ostream& os) {
9870
os << "CFG Analyzer" << std::endl;
99-
for (auto state : stateBlocks) {
100-
state.print(os);
101-
typename L::Element temp = state.inputState;
102-
txfn.print(os, state.cfgBlock, temp);
71+
for (Index i = 0, size = cfg.size(); i < size; ++i) {
72+
os << "CFG Block: " << cfg[i].getIndex() << std::endl;
73+
os << "Input State: ";
74+
states[i].print(os);
75+
for (auto& pred : cfg[i].preds()) {
76+
os << " " << pred.getIndex();
77+
}
78+
os << std::endl << "Successors:";
79+
for (auto& succ : cfg[i].succs()) {
80+
os << " " << succ.getIndex();
81+
}
82+
os << "\n";
83+
txfn.print(os, cfg[i], states[i]);
10384
}
104-
os << "End" << std::endl;
85+
os << "End\n";
10586
}
10687

10788
} // namespace wasm::analysis

src/analysis/monotone-analyzer.h

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,15 @@
1111

1212
namespace wasm::analysis {
1313

14-
// A node which contains all the lattice states for a given CFG node.
15-
template<Lattice L> struct BlockState {
16-
17-
// CFG node corresponding to this state block.
18-
const BasicBlock* cfgBlock;
19-
// State at which the analysis flow starts for a CFG. For instance, the ending
20-
// state for backward analysis, or the beginning state for forward analysis.
21-
typename L::Element inputState;
22-
23-
// All states are set to the bottom lattice element in this constructor.
24-
BlockState(const BasicBlock* underlyingBlock, L& lattice);
25-
26-
// Prints out BlockState information, but not any intermediate states.
27-
void print(std::ostream& os);
28-
};
29-
3014
template<Lattice L, TransferFunction TxFn> class MonotoneCFGAnalyzer {
15+
using Element = typename L::Element;
16+
3117
L& lattice;
3218
TxFn& txfn;
3319
CFG& cfg;
34-
std::vector<BlockState<L>> stateBlocks;
20+
21+
// The lattice element representing the program state before each block.
22+
std::vector<Element> states;
3523

3624
public:
3725
// Will constuct BlockState objects corresponding to BasicBlocks from the

src/analysis/transfer-function.h

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,40 @@
1818
#define wasm_analysis_transfer_function_h
1919

2020
#if __cplusplus >= 202002L
21+
2122
#include <concepts>
2223
#include <iterator>
2324
#include <ranges>
24-
#endif
25-
26-
#include <queue>
2725

2826
#include "cfg.h"
2927
#include "lattice.h"
28+
#include "support/unique_deferring_queue.h"
3029

3130
namespace wasm::analysis {
3231

33-
#if __cplusplus >= 202002L
34-
3532
template<typename T>
3633
concept BasicBlockInputRange =
3734
std::ranges::input_range<T> &&
3835
std::is_same<std::ranges::range_value_t<T>, BasicBlock>::value;
3936

4037
template<typename TxFn, typename L>
41-
concept TransferFunctionImpl = requires(TxFn& txfn,
42-
const CFG& cfg,
43-
BasicBlock* bb,
44-
typename L::Element& elem,
45-
std::queue<const BasicBlock*>& bbq) {
38+
concept TransferFunctionImpl = requires(
39+
TxFn& txfn, const CFG& cfg, const BasicBlock& bb, typename L::Element& elem) {
4640
// Apply the transfer function to update a lattice element with information
4741
// from a basic block.
4842
{ txfn.transfer(bb, elem) } noexcept -> std::same_as<void>;
49-
// Initializes the worklist of basic blocks, which can affect performance
50-
// depending on the direction of the analysis. TODO: Unlock performance
51-
// benefits while exposing fewer implementation details.
52-
{ txfn.enqueueWorklist(cfg, bbq) } noexcept -> std::same_as<void>;
5343
// Get a range over the basic blocks that depend on the given block.
5444
{ txfn.getDependents(bb) } noexcept -> BasicBlockInputRange;
5545
};
5646

5747
#define TransferFunction TransferFunctionImpl<L>
5848

59-
#else
49+
} // namespace wasm::analysis
50+
51+
#else // __cplusplus >= 202002L
6052

6153
#define TransferFunction typename
6254

6355
#endif // __cplusplus >= 202002L
6456

65-
} // namespace wasm::analysis
66-
6757
#endif // wasm_analysis_transfer_function_h

src/analysis/visitor-transfer-function.h

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "cfg.h"
77
#include "lattice.h"
8+
#include "support/unique_deferring_queue.h"
89
#include "wasm-traversal.h"
910

1011
namespace wasm::analysis {
@@ -36,71 +37,47 @@ struct VisitorTransferFunc : public Visitor<SubType> {
3637
// Returns an iterable to all the BasicBlocks which depend on currBlock for
3738
// information.
3839
BasicBlock::BasicBlockIterable
39-
getDependents(const BasicBlock* currBlock) noexcept {
40+
getDependents(const BasicBlock& currBlock) noexcept {
4041
if constexpr (Direction == AnalysisDirection::Backward) {
41-
return currBlock->preds();
42+
return currBlock.preds();
4243
} else {
43-
return currBlock->succs();
44+
return currBlock.succs();
4445
}
4546
}
4647

4748
// Executes the transfer function on all the expressions of the corresponding
4849
// CFG node, starting with the node's input state, and changes the input state
4950
// to the final output state of the node in place.
50-
void transfer(const BasicBlock* cfgBlock,
51+
void transfer(const BasicBlock& bb,
5152
typename L::Element& inputState) noexcept {
5253
// If the block is empty, we propagate the state by inputState =
5354
// outputState.
5455

5556
currState = &inputState;
5657
if constexpr (Direction == AnalysisDirection::Backward) {
57-
for (auto cfgIter = cfgBlock->rbegin(); cfgIter != cfgBlock->rend();
58-
++cfgIter) {
58+
for (auto cfgIter = bb.rbegin(); cfgIter != bb.rend(); ++cfgIter) {
5959
static_cast<SubType*>(this)->visit(*cfgIter);
6060
}
6161
} else {
62-
for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end();
63-
++cfgIter) {
64-
static_cast<SubType*>(this)->visit(*cfgIter);
62+
for (auto* inst : bb) {
63+
static_cast<SubType*>(this)->visit(inst);
6564
}
6665
}
6766
currState = nullptr;
6867
}
6968

70-
// Enqueues the worklist before the worklist algorithm is run. We want to
71-
// evaluate the blocks in an order matching the "flow" of the analysis to
72-
// reduce the number of state propagations needed. Thus, for a forward
73-
// analysis, we push all the blocks in order, while for backward analysis, we
74-
// push them in reverse order, so that later blocks are evaluated before
75-
// earlier ones.
76-
void enqueueWorklist(const CFG& cfg,
77-
std::queue<const BasicBlock*>& worklist) noexcept {
78-
if constexpr (Direction == AnalysisDirection::Backward) {
79-
for (auto it = cfg.rbegin(); it != cfg.rend(); ++it) {
80-
worklist.push(&(*it));
81-
}
82-
} else {
83-
for (auto it = cfg.begin(); it != cfg.end(); ++it) {
84-
worklist.push(&(*it));
85-
}
86-
}
87-
}
88-
8969
// This is for collecting results after solving an analysis. Implemented in
9070
// the same way as transfer(), but we also set the collectingResults flag.
91-
void collectResults(const BasicBlock* cfgBlock,
92-
typename L::Element& inputState) {
71+
void collectResults(const BasicBlock& bb, typename L::Element& inputState) {
9372
collectingResults = true;
9473
currState = &inputState;
9574
if constexpr (Direction == AnalysisDirection::Backward) {
96-
for (auto cfgIter = cfgBlock->rbegin(); cfgIter != cfgBlock->rend();
97-
++cfgIter) {
98-
static_cast<SubType*>(this)->visit(*cfgIter);
75+
for (auto it = bb.rbegin(); it != bb.rend(); ++it) {
76+
static_cast<SubType*>(this)->visit(*it);
9977
}
10078
} else {
101-
for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end();
102-
++cfgIter) {
103-
static_cast<SubType*>(this)->visit(*cfgIter);
79+
for (auto it = bb.begin(); it != bb.end(); ++it) {
80+
static_cast<SubType*>(this)->visit(*it);
10481
}
10582
}
10683
currState = nullptr;

0 commit comments

Comments
 (0)