/*
 * 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
