| /* | |
| * 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.contains(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 |