Skip to content

[analysis][NFC] Create a TransferFunction concept #6033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/analysis/cfg-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace wasm::analysis {
template<typename T> struct _indirect_ptr_iterator {
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using different_type = off_t;
using difference_type = off_t;
using reference = const T&;
using pointer = const T*;

Expand Down Expand Up @@ -75,6 +75,10 @@ template<typename T> struct _indirect_ptr_iterator {
return it;
}

off_t operator-(const _indirect_ptr_iterator& other) const {
return ptr - other.ptr;
}

bool operator==(const _indirect_ptr_iterator& other) const {
return ptr == other.ptr;
}
Expand All @@ -98,8 +102,17 @@ template<typename T> struct _indirect_ptr_iterator {
bool operator>=(const _indirect_ptr_iterator& other) const {
return ptr >= other.ptr;
}

friend _indirect_ptr_iterator operator+(off_t n,
const _indirect_ptr_iterator& it) {
return it + n;
}
};

#if __cplusplus >= 202002L
static_assert(std::random_access_iterator<_indirect_ptr_iterator<int>>);
#endif

template<typename T>
_indirect_ptr_iterator<T> operator+(int n,
const _indirect_ptr_iterator<T>& it) {
Expand Down
6 changes: 4 additions & 2 deletions src/analysis/lattice.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#ifndef wasm_analysis_lattice_h
#define wasm_analysis_lattice_h

#if __cplusplus >= 202002L
#include <concepts>
#endif

namespace wasm::analysis {

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

#if __cplusplus >= 202002L

#include <concepts>

template<typename L>
concept Lattice = requires(const L& lattice,
const typename L::Element& constElem,
Expand Down
39 changes: 20 additions & 19 deletions src/analysis/monotone-analyzer-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,30 @@ template<Lattice L> inline void BlockState<L>::print(std::ostream& os) {
os << std::endl;
}

template<Lattice L, typename TransferFunction>
inline MonotoneCFGAnalyzer<L, TransferFunction>::MonotoneCFGAnalyzer(
L& lattice, TransferFunction& transferFunction, CFG& cfg)
: lattice(lattice), transferFunction(transferFunction), cfg(cfg) {
template<Lattice L, TransferFunction TxFn>
inline MonotoneCFGAnalyzer<L, TxFn>::MonotoneCFGAnalyzer(L& lattice,
TxFn& txfn,
CFG& cfg)
: lattice(lattice), txfn(txfn), cfg(cfg) {

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

template<Lattice L, typename TransferFunction>
inline void MonotoneCFGAnalyzer<L, TransferFunction>::evaluateFunctionEntry(
Function* func) {
transferFunction.evaluateFunctionEntry(func, stateBlocks[0].inputState);
template<Lattice L, TransferFunction TxFn>
inline void
MonotoneCFGAnalyzer<L, TxFn>::evaluateFunctionEntry(Function* func) {
txfn.evaluateFunctionEntry(func, stateBlocks[0].inputState);
}

template<Lattice L, typename TransferFunction>
inline void MonotoneCFGAnalyzer<L, TransferFunction>::evaluate() {
template<Lattice L, TransferFunction TxFn>
inline void MonotoneCFGAnalyzer<L, TxFn>::evaluate() {
std::queue<const BasicBlock*> worklist;

// Transfer function enqueues the work in some order which is efficient.
transferFunction.enqueueWorklist(cfg, worklist);
txfn.enqueueWorklist(cfg, worklist);

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

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

template<Lattice L, typename TransferFunction>
inline void MonotoneCFGAnalyzer<L, TransferFunction>::collectResults() {
template<Lattice L, TransferFunction TxFn>
inline void MonotoneCFGAnalyzer<L, TxFn>::collectResults() {
for (BlockState currBlockState : stateBlocks) {
typename L::Element inputStateCopy = currBlockState.inputState;

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

// Currently prints both the basic information and intermediate states of each
// BlockState.
template<Lattice L, typename TransferFunction>
inline void MonotoneCFGAnalyzer<L, TransferFunction>::print(std::ostream& os) {
template<Lattice L, TransferFunction TxFn>
inline void MonotoneCFGAnalyzer<L, TxFn>::print(std::ostream& os) {
os << "CFG Analyzer" << std::endl;
for (auto state : stateBlocks) {
state.print(os);
typename L::Element temp = state.inputState;
transferFunction.print(os, state.cfgBlock, temp);
txfn.print(os, state.cfgBlock, temp);
}
os << "End" << std::endl;
}
Expand Down
67 changes: 4 additions & 63 deletions src/analysis/monotone-analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "cfg.h"
#include "lattice.h"
#include "transfer-function.h"

namespace wasm::analysis {

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

// A transfer function using a lattice <Lattice> is required to have the
// following methods:

// Lattice::Element transfer(const BasicBlock* cfgBlock, Lattice::Element&
// inputState);

// This function takes in a pointer to a CFG BasicBlock and the input state
// associated with it and modifies the input state in-place into the ouptut
// state for the basic block by applying the analysis transfer function to each
// expression in the CFG BasicBlock. Starting with the input state, the transfer
// function is used to change the state to new intermediate states based on each
// expression until we reach the output state. The outuput state will be
// propagated to dependents of the CFG BasicBlock by the worklist algorithm in
// MonotoneCFGAnalyzer.

template<typename TransferFunction, Lattice L>
constexpr bool has_transfer =
std::is_invocable_r<void,
decltype(&TransferFunction::transfer),
TransferFunction,
const BasicBlock*,
typename L::Element&>::value;

// void enqueueWorklist(CFG&, std::queue<const BasicBlock*>& value);

// Loads CFG BasicBlocks in some order into the worklist. Custom specifying the
// order for each analysis brings performance savings. For example, when doing a
// backward analysis, loading the BasicBlocks in reverse order will lead to less
// state propagations, and therefore better performance. The opposite is true
// for a forward analysis.

template<typename TransferFunction, Lattice L>
constexpr bool has_enqueueWorklist =
std::is_invocable_r<void,
decltype(&TransferFunction::enqueueWorklist),
TransferFunction,
CFG&,
std::queue<const BasicBlock*>&>::value;

// BasicBlock::BasicBlockIterable getDependents(const BasicBlock* currBlock);

// Returns an iterable to the CFG BasicBlocks which depend on currBlock for
// information (e.g. predecessors in a backward analysis). Used to select which
// blocks to propagate to after applying the transfer function to a block.

template<typename TransferFunction>
constexpr bool has_getDependents =
std::is_invocable_r<BasicBlock::BasicBlockIterable,
decltype(&TransferFunction::getDependents),
TransferFunction,
const BasicBlock*>::value;

// Combined TransferFunction assertions.
template<typename TransferFunction, Lattice L>
constexpr bool is_TransferFunction = has_transfer<TransferFunction, L> &&
has_enqueueWorklist<TransferFunction, L> &&
has_getDependents<TransferFunction>;

template<Lattice L, typename TransferFunction> class MonotoneCFGAnalyzer {
static_assert(is_TransferFunction<TransferFunction, L>);

template<Lattice L, TransferFunction TxFn> class MonotoneCFGAnalyzer {
L& lattice;
TransferFunction& transferFunction;
TxFn& txfn;
CFG& cfg;
std::vector<BlockState<L>> stateBlocks;

public:
// Will constuct BlockState objects corresponding to BasicBlocks from the
// given CFG.
MonotoneCFGAnalyzer(L& lattice, TransferFunction& transferFunction, CFG& cfg);
MonotoneCFGAnalyzer(L& lattice, TxFn& txfn, CFG& cfg);

// Runs the worklist algorithm to compute the states for the BlockState graph.
void evaluate();
Expand Down
67 changes: 67 additions & 0 deletions src/analysis/transfer-function.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2023 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef wasm_analysis_transfer_function_h
#define wasm_analysis_transfer_function_h

#if __cplusplus >= 202002L
#include <concepts>
#include <iterator>
#include <ranges>
#endif

#include <queue>

#include "cfg.h"
#include "lattice.h"

namespace wasm::analysis {

#if __cplusplus >= 202002L

template<typename T>
concept BasicBlockInputRange =
std::ranges::input_range<T> &&
std::is_same<std::ranges::range_value_t<T>, BasicBlock>::value;

template<typename TxFn, typename L>
concept TransferFunctionImpl = requires(TxFn& txfn,
const CFG& cfg,
BasicBlock* bb,
typename L::Element& elem,
std::queue<const BasicBlock*>& bbq) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍗

// Apply the transfer function to update a lattice element with information
// from a basic block.
{ txfn.transfer(bb, elem) } noexcept -> std::same_as<void>;
// Initializes the worklist of basic blocks, which can affect performance
// depending on the direction of the analysis. TODO: Unlock performance
// benefits while exposing fewer implementation details.
{ txfn.enqueueWorklist(cfg, bbq) } noexcept -> std::same_as<void>;
// Get a range over the basic blocks that depend on the given block.
{ txfn.getDependents(bb) } noexcept -> BasicBlockInputRange;
};

#define TransferFunction TransferFunctionImpl<L>

#else

#define TransferFunction typename

#endif // __cplusplus >= 202002L

} // namespace wasm::analysis

#endif // wasm_analysis_transfer_function_h
9 changes: 6 additions & 3 deletions src/analysis/visitor-transfer-function.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ struct VisitorTransferFunc : public Visitor<SubType> {
public:
// Returns an iterable to all the BasicBlocks which depend on currBlock for
// information.
BasicBlock::BasicBlockIterable getDependents(const BasicBlock* currBlock) {
BasicBlock::BasicBlockIterable
getDependents(const BasicBlock* currBlock) noexcept {
if constexpr (Direction == AnalysisDirection::Backward) {
return currBlock->preds();
} else {
Expand All @@ -46,7 +47,8 @@ struct VisitorTransferFunc : public Visitor<SubType> {
// Executes the transfer function on all the expressions of the corresponding
// CFG node, starting with the node's input state, and changes the input state
// to the final output state of the node in place.
void transfer(const BasicBlock* cfgBlock, typename L::Element& inputState) {
void transfer(const BasicBlock* cfgBlock,
typename L::Element& inputState) noexcept {
// If the block is empty, we propagate the state by inputState =
// outputState.

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