// Copyright (c) 2018 Google LLC.
//
// 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 SOURCE_OPT_SSA_REWRITE_PASS_H_
#define SOURCE_OPT_SSA_REWRITE_PASS_H_

#include <queue>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "source/opt/basic_block.h"
#include "source/opt/ir_context.h"
#include "source/opt/mem_pass.h"

namespace spvtools {
namespace opt {

// Utility class for passes that need to rewrite a function into SSA.  This
// converts load/store operations on function-local variables into SSA IDs,
// which allows them to be the target of optimizing transformations.
//
// Store and load operations to these variables are converted into
// operations on SSA IDs.  Phi instructions are added when needed.  See the
// SSA construction paper for algorithmic details
// (https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6)
class SSARewriter {
 public:
  SSARewriter(MemPass* pass) : pass_(pass) {}

  // Rewrites SSA-target variables in function |fp| into SSA.  This is the
  // entry point for the SSA rewrite algorithm.  SSA-target variables are
  // locally defined variables that meet the criteria set by IsSSATargetVar.
  //
  // Returns whether the function was modified or not, and whether or not the
  // rewrite was successful.
  Pass::Status RewriteFunctionIntoSSA(Function* fp);

 private:
  class PhiCandidate {
   public:
    explicit PhiCandidate(uint32_t var, uint32_t result, BasicBlock* block)
        : var_id_(var),
          result_id_(result),
          bb_(block),
          phi_args_(),
          copy_of_(0),
          is_complete_(false),
          users_() {}

    uint32_t var_id() const { return var_id_; }
    uint32_t result_id() const { return result_id_; }
    BasicBlock* bb() const { return bb_; }
    std::vector<uint32_t>& phi_args() { return phi_args_; }
    const std::vector<uint32_t>& phi_args() const { return phi_args_; }
    uint32_t copy_of() const { return copy_of_; }
    bool is_complete() const { return is_complete_; }
    std::vector<uint32_t>& users() { return users_; }
    const std::vector<uint32_t>& users() const { return users_; }

    // Marks this phi candidate as a trivial copy of |orig_id|.
    void MarkCopyOf(uint32_t orig_id) { copy_of_ = orig_id; }

    // Marks this phi candidate as incomplete.
    void MarkIncomplete() { is_complete_ = false; }

    // Marks this phi candidate as complete.
    void MarkComplete() { is_complete_ = true; }

    // Returns true if this Phi candidate is ready to be emitted.
    bool IsReady() const { return is_complete() && copy_of() == 0; }

    // Pretty prints this Phi candidate into a string and returns it. |cfg| is
    // needed to lookup basic block predecessors.
    std::string PrettyPrint(const CFG* cfg) const;

    // Registers |operand_id| as a user of this Phi candidate.
    void AddUser(uint32_t operand_id) { users_.push_back(operand_id); }

   private:
    // Variable ID that this Phi is merging.
    uint32_t var_id_;

    // SSA ID generated by this Phi (i.e., this is the result ID of the eventual
    // Phi instruction).
    uint32_t result_id_;

    // Basic block to hold this Phi.
    BasicBlock* bb_;

    // Vector of operands for every predecessor block of |bb|.  This vector is
    // organized so that the Ith slot contains the argument coming from the Ith
    // predecessor of |bb|.
    std::vector<uint32_t> phi_args_;

    // If this Phi is a trivial copy of another Phi, this is the ID of the
    // original. If this is 0, it means that this is not a trivial Phi.
    uint32_t copy_of_;

    // False, if this Phi candidate has no arguments or at least one argument is
    // %0.
    bool is_complete_;

    // List of all users for this Phi instruction. Each element is the result ID
    // of the load instruction replaced by this Phi, or the result ID of a Phi
    // candidate that has this Phi in its list of operands.
    std::vector<uint32_t> users_;
  };

  // Type used to keep track of store operations in each basic block.
  typedef std::unordered_map<BasicBlock*,
                             std::unordered_map<uint32_t, uint32_t>>
      BlockDefsMap;

  // Generates all the SSA rewriting decisions for basic block |bb|.  This
  // populates the Phi candidate table (|phi_candidate_|) and the load
  // replacement table (|load_replacement_).  Returns true if successful.
  bool GenerateSSAReplacements(BasicBlock* bb);

  // Seals block |bb|.  Sealing a basic block means |bb| and all its
  // predecessors of |bb| have been scanned for loads/stores.
  void SealBlock(BasicBlock* bb);

  // Returns true if |bb| has been sealed.
  bool IsBlockSealed(BasicBlock* bb) { return sealed_blocks_.count(bb) != 0; }

  // Returns the Phi candidate with result ID |id| if it exists in the table
  // |phi_candidates_|. If no such Phi candidate exists, it returns nullptr.
  PhiCandidate* GetPhiCandidate(uint32_t id) {
    auto it = phi_candidates_.find(id);
    return (it != phi_candidates_.end()) ? &it->second : nullptr;
  }

  // Replaces all the users of Phi candidate |phi_cand| to be users of
  // |repl_id|.
  void ReplacePhiUsersWith(const PhiCandidate& phi_cand, uint32_t repl_id);

  // Returns the value ID that should replace the load ID in the given
  // replacement pair |repl|.  The replacement is a pair (|load_id|, |val_id|).
  // If |val_id| is itself replaced by another value in the table, this function
  // will look the replacement for |val_id| until it finds one that is not
  // itself replaced.  For instance, given:
  //
  //            %34 = OpLoad %float %f1
  //            OpStore %t %34
  //            %36 = OpLoad %float %t
  //
  // Assume that %f1 is reached by a Phi candidate %42, the load
  // replacement table will have the following entries:
  //
  //            %34 -> %42
  //            %36 -> %34
  //
  // So, when looking for the replacement for %36, we should not use
  // %34. Rather, we should use %42.  To do this, the chain of
  // replacements must be followed until we reach an element that has
  // no replacement.
  uint32_t GetReplacement(std::pair<uint32_t, uint32_t> repl);

  // Returns the argument at index |ix| from |phi_candidate|. If argument |ix|
  // comes from a trivial Phi, it follows the copy-of chain from that trivial
  // Phi until it finds the original Phi candidate.
  //
  // This is only valid after all Phi candidates have been completed. It can
  // only be called when generating the IR for these Phis.
  uint32_t GetPhiArgument(const PhiCandidate* phi_candidate, uint32_t ix);

  // Applies all the SSA replacement decisions.  This replaces loads/stores to
  // SSA target variables with their corresponding SSA IDs, and inserts Phi
  // instructions for them.
  bool ApplyReplacements();

  // Registers a definition for variable |var_id| in basic block |bb| with
  // value |val_id|.
  void WriteVariable(uint32_t var_id, BasicBlock* bb, uint32_t val_id) {
    defs_at_block_[bb][var_id] = val_id;
    if (auto* pc = GetPhiCandidate(val_id)) {
      pc->AddUser(bb->id());
    }
  }

  // Returns the value of |var_id| at |bb| if |defs_at_block_| contains it.
  // Otherwise, returns 0.
  uint32_t GetValueAtBlock(uint32_t var_id, BasicBlock* bb);

  // Processes the store operation |inst| in basic block |bb|. This extracts
  // the variable ID being stored into, determines whether the variable is an
  // SSA-target variable, and, if it is, it stores its value in the
  // |defs_at_block_| map.
  void ProcessStore(Instruction* inst, BasicBlock* bb);

  // Processes the load operation |inst| in basic block |bb|. This extracts
  // the variable ID being stored into, determines whether the variable is an
  // SSA-target variable, and, if it is, it reads its reaching definition by
  // calling |GetReachingDef|.  Returns true if successful.
  bool ProcessLoad(Instruction* inst, BasicBlock* bb);

  // Reads the current definition for variable |var_id| in basic block |bb|.
  // If |var_id| is not defined in block |bb| it walks up the predecessors of
  // |bb|, creating new Phi candidates along the way, if needed.
  //
  // It returns the value for |var_id| from the RHS of the current reaching
  // definition for |var_id|.
  uint32_t GetReachingDef(uint32_t var_id, BasicBlock* bb);

  // Adds arguments to |phi_candidate| by getting the reaching definition of
  // |phi_candidate|'s variable on each of the predecessors of its basic
  // block. After populating the argument list, it determines whether all its
  // arguments are the same.  If so, it returns the ID of the argument that
  // this Phi copies.
  uint32_t AddPhiOperands(PhiCandidate* phi_candidate);

  // Creates a Phi candidate instruction for variable |var_id| in basic block
  // |bb|.
  //
  // Since the rewriting algorithm may remove Phi candidates when it finds
  // them to be trivial, we avoid the expense of creating actual Phi
  // instructions by keeping a pool of Phi candidates (|phi_candidates_|)
  // during rewriting.
  //
  // Once the candidate Phi is created, it returns its ID.
  PhiCandidate& CreatePhiCandidate(uint32_t var_id, BasicBlock* bb);

  // Attempts to remove a trivial Phi candidate |phi_cand|. Trivial Phis are
  // those that only reference themselves and one other value |val| any number
  // of times. This will try to remove any other Phis that become trivial
  // after |phi_cand| is removed.
  //
  // If |phi_cand| is trivial, it returns the SSA ID for the value that should
  // replace it.  Otherwise, it returns the SSA ID for |phi_cand|.
  uint32_t TryRemoveTrivialPhi(PhiCandidate* phi_cand);

  // Finalizes |phi_candidate| by replacing every argument that is still %0
  // with its reaching definition.
  void FinalizePhiCandidate(PhiCandidate* phi_candidate);

  // Finalizes processing of Phi candidates.  Once the whole function has been
  // scanned for loads and stores, the CFG will still have some incomplete and
  // trivial Phis.  This will add missing arguments and remove trivial Phi
  // candidates.
  void FinalizePhiCandidates();

  // Prints the table of Phi candidates to std::cerr.
  void PrintPhiCandidates() const;

  // Prints the load replacement table to std::cerr.
  void PrintReplacementTable() const;

  // Map holding the value of every SSA-target variable at every basic block
  // where the variable is stored. defs_at_block_[block][var_id] = val_id
  // means that there is a store or Phi instruction for variable |var_id| at
  // basic block |block| with value |val_id|.
  BlockDefsMap defs_at_block_;

  // Map, indexed by Phi ID, holding all the Phi candidates created during SSA
  // rewriting.  |phi_candidates_[id]| returns the Phi candidate whose result
  // is |id|.
  std::unordered_map<uint32_t, PhiCandidate> phi_candidates_;

  // Queue of incomplete Phi candidates. These are Phi candidates created at
  // unsealed blocks. They need to be completed before they are instantiated
  // in ApplyReplacements.
  std::queue<PhiCandidate*> incomplete_phis_;

  // List of completed Phi candidates.  These are the only candidates that
  // will become real Phi instructions.
  std::vector<PhiCandidate*> phis_to_generate_;

  // SSA replacement table.  This maps variable IDs, resulting from a load
  // operation, to the value IDs that will replace them after SSA rewriting.
  // After all the rewriting decisions are made, a final scan through the IR
  // is done to replace all uses of the original load ID with the value ID.
  std::unordered_map<uint32_t, uint32_t> load_replacement_;

  // Set of blocks that have been sealed already.
  std::unordered_set<BasicBlock*> sealed_blocks_;

  // Memory pass requesting the SSA rewriter.
  MemPass* pass_;
};

class SSARewritePass : public MemPass {
 public:
  SSARewritePass() = default;

  const char* name() const override { return "ssa-rewrite"; }
  Status Process() override;
};

}  // namespace opt
}  // namespace spvtools

#endif  // SOURCE_OPT_SSA_REWRITE_PASS_H_
