blob: e582751fa2221fa0321c60f71ae176e0f7f73941 [file] [log] [blame] [edit]
/*
* Copyright 2017 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_ir_local_graph_h
#define wasm_ir_local_graph_h
#include "support/small_set.h"
#include "wasm.h"
namespace wasm {
//
// Finds the connections between local.gets and local.sets, creating
// a graph of those ties. This is useful for "ssa-style" optimization,
// in which you want to know exactly which sets are relevant for a
// a get, so it is as if each get has just one set, logically speaking
// (see the SSA pass for actually creating new local indexes based
// on this).
//
// Note that this is not guaranteed to be precise in unreachable code. That is,
// we do not make the effort to represent the exact sets for each get, and may
// overestimate them (specifically, we may mark the entry value as possible,
// even if unreachability prevents that; doing so helps simplify and optimize
// the code, which we think is worthwhile for the possible annoyance in
// debugging etc.; and it has no downside for optimization, since unreachable
// code will be removed anyhow).
//
// There are two options here, the normal LocalGraph which is eager and computes
// everything up front, which is faster if most things end up needed, and a lazy
// one which computes on demand, which can be much faster if we only need a
// small subset of queries.
//
// Base class for both LocalGraph and LazyLocalGraph (not meant for direct use).
struct LocalGraphBase {
protected:
// If a module is passed in, it is used to find which features are needed in
// the computation (for example, if exception handling is disabled, then we
// can generate a simpler CFG, as calls cannot throw).
LocalGraphBase(Function* func, Module* module = nullptr)
: func(func), module(module) {}
public:
// A set of sets, returned from the query about which sets can be read from a
// get. Typically only one or two apply there, so this is a small set.
using Sets = SmallSet<LocalSet*, 2>;
// Where each get and set is.
using Locations = std::map<Expression*, Expression**>;
// A map of each get to the sets relevant to it (i.e., that it can read from).
using GetSetsMap = std::unordered_map<LocalGet*, Sets>;
// Sets of gets or sets, that are influenced, returned from get*Influences().
using SetInfluences = std::unordered_set<LocalGet*>;
using GetInfluences = std::unordered_set<LocalSet*>;
using SetInfluencesMap = std::unordered_map<LocalSet*, SetInfluences>;
using GetInfluencesMap = std::unordered_map<LocalGet*, GetInfluences>;
protected:
Function* func;
Module* module;
std::set<Index> SSAIndexes;
};
struct LocalGraph : public LocalGraphBase {
LocalGraph(Function* func, Module* module = nullptr);
// Get the sets relevant for a local.get.
//
// A nullptr set means there is no local.set for that value, which means it is
// the initial value from the function entry: 0 for a var, the received value
// for a param.
//
// Often there is a single set, or a phi or two items, so we use a small set.
const Sets& getSets(LocalGet* get) const {
auto iter = getSetsMap.find(get);
if (iter == getSetsMap.end()) {
// A missing entry means there is nothing there (and we saved a little
// space by not putting something there).
//
// Use a canonical constant empty set to avoid allocation.
static const Sets empty;
return empty;
}
return iter->second;
}
// We compute the locations of gets and sets while doing the main computation
// and make it accessible for users, for easy replacing of things without
// extra work.
Locations locations;
// Checks if two gets are equivalent, that is, definitely have the same
// value.
bool equivalent(LocalGet* a, LocalGet* b);
// Optional: compute the influence graphs between sets and gets (useful for
// algorithms that propagate changes). Set influences are the gets that can
// read from it; get influences are the sets that can (directly) read from it.
void computeSetInfluences();
void computeGetInfluences();
void computeInfluences() {
computeSetInfluences();
computeGetInfluences();
}
const SetInfluences& getSetInfluences(LocalSet* set) const {
auto iter = setInfluences.find(set);
if (iter == setInfluences.end()) {
// Use a canonical constant empty set to avoid allocation.
static const SetInfluences empty;
return empty;
}
return iter->second;
}
const GetInfluences& getGetInfluences(LocalGet* get) const {
auto iter = getInfluences.find(get);
if (iter == getInfluences.end()) {
// Use a canonical constant empty set to avoid allocation.
static const GetInfluences empty;
return empty;
}
return iter->second;
}
// Optional: Compute the local indexes that are SSA, in the sense of
// * a single set for all the gets for that local index
// * the set dominates all the gets (logically implied by the former
// property)
// * no other set (aside from the zero-init)
// The third property is not exactly standard SSA, but is useful since we are
// not in SSA form in our IR. To see why it matters, consider these:
//
// x = 0 // zero init
// [..]
// x = 10
// y = x + 20
// x = 30 // !!!
// f(y)
//
// The !!! line violates that property - it is another set for x, and it may
// interfere say with replacing f(y) with f(x + 20). Instead, if we know the
// only other possible set for x is the zero init, then things like the !!!
// line cannot exist, and it is valid to replace f(y) with f(x + 20). (This
// could be simpler, but in wasm the zero init always exists.)
void computeSSAIndexes();
bool isSSA(Index x);
private:
GetSetsMap getSetsMap;
SetInfluencesMap setInfluences;
GetInfluencesMap getInfluences;
};
// The internal implementation of the flow analysis used to compute things. This
// must be declared in the header so that LazyLocalGraph can declare a unique
// ptr to it, below.
struct LocalGraphFlower;
struct LazyLocalGraph : public LocalGraphBase {
// We optionally receive an expression class to consider relevant for queries,
// see below. (Only expressions of this class can be queried later.)
LazyLocalGraph(Function* func,
Module* module = nullptr,
std::optional<Expression::Id> queryClass = std::nullopt);
~LazyLocalGraph();
// Similar APIs as in LocalGraph, but lazy versions. Each of them does a
// lookup, and if there is a missing entry then we did not do the computation
// yet, and then we do it and memoize it.
const Sets& getSets(LocalGet* get) const {
auto iter = getSetsMap.find(get);
if (iter == getSetsMap.end()) {
computeGetSets(get);
iter = getSetsMap.find(get);
assert(iter != getSetsMap.end());
}
return iter->second;
}
const SetInfluences& getSetInfluences(LocalSet* set) const {
auto iter = setInfluences.find(set);
if (iter == setInfluences.end()) {
computeSetInfluences(set);
iter = setInfluences.find(set);
assert(iter != setInfluences.end());
}
return iter->second;
}
const GetInfluences& getGetInfluences(LocalGet* get) const {
if (!getInfluences) {
computeGetInfluences();
assert(getInfluences);
}
return (*getInfluences)[get];
}
bool isSSA(Index index) const {
auto iter = SSAIndexes.find(index);
if (iter == SSAIndexes.end()) {
auto ret = computeSSA(index);
// The result must have been memoized.
assert(SSAIndexes.count(index));
return ret;
}
return iter->second;
}
const Locations& getLocations() const {
if (!locations) {
computeLocations();
assert(locations);
}
return *locations;
}
// Query whether it is valid to move a LocalSet to a new position, that is,
// that moving it will not alter observable behavior. That means that no
// Localget is able to observe a different value than before. This returns the
// set of LocalGets that may do so, that is, the gets for which we detected a
// problem, hence if the set is empty, the set is valid to move.
//
// For example:
//
// 1. set
// 2. get
// 3. call
// 4. get
//
// If we move the set to the call, then the get on line 2 could observe a
// different value (the pre-existing value in that local, before line 1), and
// we'd return that get here.
//
// |to| must be of the class |queryClass| that is defined during construction.
// That is, we must decide ahead of time which places we want to query about
// moving LocalSets to, and can then issue queries on any of those, in an
// efficient manner.
//
// This assumes that |to| is in a position dominated by |set|, that is we are
// moving the set "forward". (In particular, that implies that the new
// position will have monotonically *less* influence than before - we don't
// need to scan all possible gets of that index in the entire function, we can
// look only on the gets influenced by the set, and see how the new position
// behaves regarding them.)
SetInfluences canMoveSet(LocalSet* set, Expression* to);
private:
std::optional<Expression::Id> queryClass;
// These data structures are mutable so that we can memoize.
mutable GetSetsMap getSetsMap;
mutable SetInfluencesMap setInfluences;
// The entire |getInfluences| is computed once the first request for one
// arrives, so the entire thing is either present or not, unlike setInfluences
// which is fine-grained. The difference is that the influences of a get may
// include sets of other indexes, so there is no simple way to lazify that
// computation.
mutable std::optional<GetInfluencesMap> getInfluences;
// A map if indexes to a bool indicating if the index is SSA. If there is no
// entry, it has not yet been computed.
mutable std::unordered_map<Index, bool> SSAIndexes;
mutable std::optional<Locations> locations;
// Compute the sets for a get and store them on getSetsMap.
void computeGetSets(LocalGet* get) const;
// Compute influences for a set and store them on setInfluences.
void computeSetInfluences(LocalSet* set) const;
// Compute influences for all gets and store them on getInfluences.
void computeGetInfluences() const;
// Compute whether an index is SSA and store that on SSAIndexes.
bool computeSSA(Index index) const;
// Compute locations and store them on getInfluences.
void computeLocations() const;
// This remains alive as long as we are, so that we can compute things lazily.
// It is mutable as when we construct this is an internal detail, that does
// not cause observable differences in API calls.
mutable std::unique_ptr<LocalGraphFlower> flower;
// We create |flower| lazily.
void makeFlower() const;
};
} // namespace wasm
#endif // wasm_ir_local_graph_h