Skip to content

Commit 13af69a

Browse files
tlivelyradekdoulik
authored andcommitted
[analysis][NFC] Create a TransferFunction concept (WebAssembly#6033)
Factor the static assertions for transfer functions out into a new transfer-function.h header. The concept requires the `getDependents` method to return an input range of basic blocks, and to satisfy that requirement, fix up _indirect_ptr_iterator in cfg-impl.h so that it is a proper iterator. Remove part of the lattice fuzzer that was using a placeholder transfer function in a way that does not satisfy the new type constraints; most of that code will be overhauled in the future anyway.
1 parent 94f830e commit 13af69a

File tree

7 files changed

+138
-176
lines changed

7 files changed

+138
-176
lines changed

src/analysis/cfg-impl.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace wasm::analysis {
2727
template<typename T> struct _indirect_ptr_iterator {
2828
using iterator_category = std::random_access_iterator_tag;
2929
using value_type = T;
30-
using different_type = off_t;
30+
using difference_type = off_t;
3131
using reference = const T&;
3232
using pointer = const T*;
3333

@@ -75,6 +75,10 @@ template<typename T> struct _indirect_ptr_iterator {
7575
return it;
7676
}
7777

78+
off_t operator-(const _indirect_ptr_iterator& other) const {
79+
return ptr - other.ptr;
80+
}
81+
7882
bool operator==(const _indirect_ptr_iterator& other) const {
7983
return ptr == other.ptr;
8084
}
@@ -98,8 +102,17 @@ template<typename T> struct _indirect_ptr_iterator {
98102
bool operator>=(const _indirect_ptr_iterator& other) const {
99103
return ptr >= other.ptr;
100104
}
105+
106+
friend _indirect_ptr_iterator operator+(off_t n,
107+
const _indirect_ptr_iterator& it) {
108+
return it + n;
109+
}
101110
};
102111

112+
#if __cplusplus >= 202002L
113+
static_assert(std::random_access_iterator<_indirect_ptr_iterator<int>>);
114+
#endif
115+
103116
template<typename T>
104117
_indirect_ptr_iterator<T> operator+(int n,
105118
const _indirect_ptr_iterator<T>& it) {

src/analysis/lattice.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
#ifndef wasm_analysis_lattice_h
1818
#define wasm_analysis_lattice_h
1919

20+
#if __cplusplus >= 202002L
21+
#include <concepts>
22+
#endif
23+
2024
namespace wasm::analysis {
2125

2226
enum LatticeComparison { NO_RELATION, EQUAL, LESS, GREATER };
@@ -35,8 +39,6 @@ inline LatticeComparison reverseComparison(LatticeComparison comparison) {
3539

3640
#if __cplusplus >= 202002L
3741

38-
#include <concepts>
39-
4042
template<typename L>
4143
concept Lattice = requires(const L& lattice,
4244
const typename L::Element& constElem,

src/analysis/monotone-analyzer-impl.h

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,30 @@ template<Lattice L> inline void BlockState<L>::print(std::ostream& os) {
3030
os << std::endl;
3131
}
3232

33-
template<Lattice L, typename TransferFunction>
34-
inline MonotoneCFGAnalyzer<L, TransferFunction>::MonotoneCFGAnalyzer(
35-
L& lattice, TransferFunction& transferFunction, CFG& cfg)
36-
: lattice(lattice), transferFunction(transferFunction), cfg(cfg) {
33+
template<Lattice L, TransferFunction TxFn>
34+
inline MonotoneCFGAnalyzer<L, TxFn>::MonotoneCFGAnalyzer(L& lattice,
35+
TxFn& txfn,
36+
CFG& cfg)
37+
: lattice(lattice), txfn(txfn), cfg(cfg) {
3738

3839
// Construct BlockStates for each BasicBlock.
3940
for (auto it = cfg.begin(); it != cfg.end(); it++) {
4041
stateBlocks.emplace_back(&(*it), lattice);
4142
}
4243
}
4344

44-
template<Lattice L, typename TransferFunction>
45-
inline void MonotoneCFGAnalyzer<L, TransferFunction>::evaluateFunctionEntry(
46-
Function* func) {
47-
transferFunction.evaluateFunctionEntry(func, stateBlocks[0].inputState);
45+
template<Lattice L, TransferFunction TxFn>
46+
inline void
47+
MonotoneCFGAnalyzer<L, TxFn>::evaluateFunctionEntry(Function* func) {
48+
txfn.evaluateFunctionEntry(func, stateBlocks[0].inputState);
4849
}
4950

50-
template<Lattice L, typename TransferFunction>
51-
inline void MonotoneCFGAnalyzer<L, TransferFunction>::evaluate() {
51+
template<Lattice L, TransferFunction TxFn>
52+
inline void MonotoneCFGAnalyzer<L, TxFn>::evaluate() {
5253
std::queue<const BasicBlock*> worklist;
5354

5455
// Transfer function enqueues the work in some order which is efficient.
55-
transferFunction.enqueueWorklist(cfg, worklist);
56+
txfn.enqueueWorklist(cfg, worklist);
5657

5758
while (!worklist.empty()) {
5859
BlockState<L>& currBlockState = stateBlocks[worklist.front()->getIndex()];
@@ -63,10 +64,10 @@ inline void MonotoneCFGAnalyzer<L, TransferFunction>::evaluate() {
6364
// to arrive at the expression's state. The beginning and end states of the
6465
// CFG block will be updated.
6566
typename L::Element outputState = currBlockState.inputState;
66-
transferFunction.transfer(currBlockState.cfgBlock, outputState);
67+
txfn.transfer(currBlockState.cfgBlock, outputState);
6768

6869
// Propagate state to dependents of currBlockState.
69-
for (auto& dep : transferFunction.getDependents(currBlockState.cfgBlock)) {
70+
for (auto& dep : txfn.getDependents(currBlockState.cfgBlock)) {
7071
// If we need to change the input state of a dependent, we need
7172
// to enqueue the dependent to recalculate it.
7273
if (stateBlocks[dep.getIndex()].inputState.makeLeastUpperBound(
@@ -77,28 +78,28 @@ inline void MonotoneCFGAnalyzer<L, TransferFunction>::evaluate() {
7778
}
7879
}
7980

80-
template<Lattice L, typename TransferFunction>
81-
inline void MonotoneCFGAnalyzer<L, TransferFunction>::collectResults() {
81+
template<Lattice L, TransferFunction TxFn>
82+
inline void MonotoneCFGAnalyzer<L, TxFn>::collectResults() {
8283
for (BlockState currBlockState : stateBlocks) {
8384
typename L::Element inputStateCopy = currBlockState.inputState;
8485

8586
// The transfer function generates the final set of states and uses it to
8687
// produce useful information. For example, in reaching definitions
8788
// analysis, these final states are used to populate a mapping of
8889
// local.get's to a set of local.set's that affect its value.
89-
transferFunction.collectResults(currBlockState.cfgBlock, inputStateCopy);
90+
txfn.collectResults(currBlockState.cfgBlock, inputStateCopy);
9091
}
9192
}
9293

9394
// Currently prints both the basic information and intermediate states of each
9495
// BlockState.
95-
template<Lattice L, typename TransferFunction>
96-
inline void MonotoneCFGAnalyzer<L, TransferFunction>::print(std::ostream& os) {
96+
template<Lattice L, TransferFunction TxFn>
97+
inline void MonotoneCFGAnalyzer<L, TxFn>::print(std::ostream& os) {
9798
os << "CFG Analyzer" << std::endl;
9899
for (auto state : stateBlocks) {
99100
state.print(os);
100101
typename L::Element temp = state.inputState;
101-
transferFunction.print(os, state.cfgBlock, temp);
102+
txfn.print(os, state.cfgBlock, temp);
102103
}
103104
os << "End" << std::endl;
104105
}

src/analysis/monotone-analyzer.h

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "cfg.h"
99
#include "lattice.h"
10+
#include "transfer-function.h"
1011

1112
namespace wasm::analysis {
1213

@@ -26,76 +27,16 @@ template<Lattice L> struct BlockState {
2627
void print(std::ostream& os);
2728
};
2829

29-
// A transfer function using a lattice <Lattice> is required to have the
30-
// following methods:
31-
32-
// Lattice::Element transfer(const BasicBlock* cfgBlock, Lattice::Element&
33-
// inputState);
34-
35-
// This function takes in a pointer to a CFG BasicBlock and the input state
36-
// associated with it and modifies the input state in-place into the ouptut
37-
// state for the basic block by applying the analysis transfer function to each
38-
// expression in the CFG BasicBlock. Starting with the input state, the transfer
39-
// function is used to change the state to new intermediate states based on each
40-
// expression until we reach the output state. The outuput state will be
41-
// propagated to dependents of the CFG BasicBlock by the worklist algorithm in
42-
// MonotoneCFGAnalyzer.
43-
44-
template<typename TransferFunction, Lattice L>
45-
constexpr bool has_transfer =
46-
std::is_invocable_r<void,
47-
decltype(&TransferFunction::transfer),
48-
TransferFunction,
49-
const BasicBlock*,
50-
typename L::Element&>::value;
51-
52-
// void enqueueWorklist(CFG&, std::queue<const BasicBlock*>& value);
53-
54-
// Loads CFG BasicBlocks in some order into the worklist. Custom specifying the
55-
// order for each analysis brings performance savings. For example, when doing a
56-
// backward analysis, loading the BasicBlocks in reverse order will lead to less
57-
// state propagations, and therefore better performance. The opposite is true
58-
// for a forward analysis.
59-
60-
template<typename TransferFunction, Lattice L>
61-
constexpr bool has_enqueueWorklist =
62-
std::is_invocable_r<void,
63-
decltype(&TransferFunction::enqueueWorklist),
64-
TransferFunction,
65-
CFG&,
66-
std::queue<const BasicBlock*>&>::value;
67-
68-
// BasicBlock::BasicBlockIterable getDependents(const BasicBlock* currBlock);
69-
70-
// Returns an iterable to the CFG BasicBlocks which depend on currBlock for
71-
// information (e.g. predecessors in a backward analysis). Used to select which
72-
// blocks to propagate to after applying the transfer function to a block.
73-
74-
template<typename TransferFunction>
75-
constexpr bool has_getDependents =
76-
std::is_invocable_r<BasicBlock::BasicBlockIterable,
77-
decltype(&TransferFunction::getDependents),
78-
TransferFunction,
79-
const BasicBlock*>::value;
80-
81-
// Combined TransferFunction assertions.
82-
template<typename TransferFunction, Lattice L>
83-
constexpr bool is_TransferFunction = has_transfer<TransferFunction, L> &&
84-
has_enqueueWorklist<TransferFunction, L> &&
85-
has_getDependents<TransferFunction>;
86-
87-
template<Lattice L, typename TransferFunction> class MonotoneCFGAnalyzer {
88-
static_assert(is_TransferFunction<TransferFunction, L>);
89-
30+
template<Lattice L, TransferFunction TxFn> class MonotoneCFGAnalyzer {
9031
L& lattice;
91-
TransferFunction& transferFunction;
32+
TxFn& txfn;
9233
CFG& cfg;
9334
std::vector<BlockState<L>> stateBlocks;
9435

9536
public:
9637
// Will constuct BlockState objects corresponding to BasicBlocks from the
9738
// given CFG.
98-
MonotoneCFGAnalyzer(L& lattice, TransferFunction& transferFunction, CFG& cfg);
39+
MonotoneCFGAnalyzer(L& lattice, TxFn& txfn, CFG& cfg);
9940

10041
// Runs the worklist algorithm to compute the states for the BlockState graph.
10142
void evaluate();

src/analysis/transfer-function.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2023 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_analysis_transfer_function_h
18+
#define wasm_analysis_transfer_function_h
19+
20+
#if __cplusplus >= 202002L
21+
#include <concepts>
22+
#include <iterator>
23+
#include <ranges>
24+
#endif
25+
26+
#include <queue>
27+
28+
#include "cfg.h"
29+
#include "lattice.h"
30+
31+
namespace wasm::analysis {
32+
33+
#if __cplusplus >= 202002L
34+
35+
template<typename T>
36+
concept BasicBlockInputRange =
37+
std::ranges::input_range<T> &&
38+
std::is_same<std::ranges::range_value_t<T>, BasicBlock>::value;
39+
40+
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) {
46+
// Apply the transfer function to update a lattice element with information
47+
// from a basic block.
48+
{ 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>;
53+
// Get a range over the basic blocks that depend on the given block.
54+
{ txfn.getDependents(bb) } noexcept -> BasicBlockInputRange;
55+
};
56+
57+
#define TransferFunction TransferFunctionImpl<L>
58+
59+
#else
60+
61+
#define TransferFunction typename
62+
63+
#endif // __cplusplus >= 202002L
64+
65+
} // namespace wasm::analysis
66+
67+
#endif // wasm_analysis_transfer_function_h

src/analysis/visitor-transfer-function.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ struct VisitorTransferFunc : public Visitor<SubType> {
3535
public:
3636
// Returns an iterable to all the BasicBlocks which depend on currBlock for
3737
// information.
38-
BasicBlock::BasicBlockIterable getDependents(const BasicBlock* currBlock) {
38+
BasicBlock::BasicBlockIterable
39+
getDependents(const BasicBlock* currBlock) noexcept {
3940
if constexpr (Direction == AnalysisDirection::Backward) {
4041
return currBlock->preds();
4142
} else {
@@ -46,7 +47,8 @@ struct VisitorTransferFunc : public Visitor<SubType> {
4647
// Executes the transfer function on all the expressions of the corresponding
4748
// CFG node, starting with the node's input state, and changes the input state
4849
// to the final output state of the node in place.
49-
void transfer(const BasicBlock* cfgBlock, typename L::Element& inputState) {
50+
void transfer(const BasicBlock* cfgBlock,
51+
typename L::Element& inputState) noexcept {
5052
// If the block is empty, we propagate the state by inputState =
5153
// outputState.
5254

@@ -71,7 +73,8 @@ struct VisitorTransferFunc : public Visitor<SubType> {
7173
// analysis, we push all the blocks in order, while for backward analysis, we
7274
// push them in reverse order, so that later blocks are evaluated before
7375
// earlier ones.
74-
void enqueueWorklist(CFG& cfg, std::queue<const BasicBlock*>& worklist) {
76+
void enqueueWorklist(const CFG& cfg,
77+
std::queue<const BasicBlock*>& worklist) noexcept {
7578
if constexpr (Direction == AnalysisDirection::Backward) {
7679
for (auto it = cfg.rbegin(); it != cfg.rend(); ++it) {
7780
worklist.push(&(*it));

0 commit comments

Comments
 (0)