/*
 * Copyright 2018 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.
 */

//
// DataFlow IR is an SSA representation. It can be built from the main
// Binaryen IR.
//
// THe main initial use case was an IR that could easily be converted to
// Souper IR, and the design favors that.
//

#ifndef wasm_dataflow_node_h
#define wasm_dataflow_node_h

#include "ir/utils.h"
#include "wasm.h"

namespace wasm::DataFlow {

//
// The core IR representation in DataFlow: a Node.
//
// We reuse the Binaryen IR as much as possible: when things are identical
// between the two IRs, we just create an Expr node, which stores the opcode and
// other details, and we can emit them to Souper by reading the Binaryen
// Expression. Other node types here are special things from Souper IR that we
// can't represent that way.
//
// * Souper comparisons return an i1. We extend them immediately if they are
//   going to be used as i32s or i64s.
// * When we use an Expression node, we just use its immediate fields, like the
//   op in a binary, alignment etc. in a load, etc. We don't look into the
//   pointers to child nodes. Instead, the DataFlow IR has its own pointers
//   directly to DataFlow children. In particular, this means that it's easy
//   to create an Expression with the info you need and not care about linking
//   it up to other Expressions.
//

struct Node {
  enum Type {
    Var,  // an unknown variable number (not to be confused with var/param/local
          // in wasm)
    Expr, // a value represented by a Binaryen Expression
    Phi,  // a phi from converging control flow
    Cond, // a blockpc, representing one of the branchs for a Block
    Block, // a source of phis
    Zext, // zero-extend an i1 (from an op where Souper returns i1 but wasm does
          // not, and so we need a special way to get back to an i32/i64 if we
          // operate on that value instead of just passing it straight to
          // Souper).
    Bad   // something we can't handle and should ignore
  } type;

  Node(Type type) : type(type) {}

  // TODO: the others, if we need them
  bool isVar() { return type == Var; }
  bool isExpr() { return type == Expr; }
  bool isPhi() { return type == Phi; }
  bool isCond() { return type == Cond; }
  bool isBlock() { return type == Block; }
  bool isZext() { return type == Zext; }
  bool isBad() { return type == Bad; }

  bool isConst() { return type == Expr && expr->is<Const>(); }

  union {
    // For Var
    wasm::Type wasmType;
    // For Expr
    Expression* expr;
    // For Phi and Cond (the local index for phi, the block
    // index for cond)
    Index index;
  };

  // The wasm expression that we originate from (if such exists). A single
  // wasm instruction may be turned into multiple dataflow IR nodes, and some
  // nodes have no wasm origin (like phis).
  Expression* origin = nullptr;

  // Extra list of related nodes.
  // For Expr, these are the Nodes for the inputs to the expression (e.g.
  // a binary would have 2 in this vector here).
  // For Phi, this is the block and then the list of values to pick from.
  // For Cond, this is the block and node.
  // For Block, this is the list of Conds. Note that that block does not
  // depend on them - the Phis do, but we store them in the block so that
  // we can avoid duplication.
  // For Zext, this is the value we extend.
  std::vector<Node*> values;

  // Constructors
  static Node* makeVar(wasm::Type wasmType) {
    Node* ret = new Node(Var);
    ret->wasmType = wasmType;
    return ret;
  }
  static Node* makeExpr(Expression* expr, Expression* origin) {
    Node* ret = new Node(Expr);
    ret->expr = expr;
    ret->origin = origin;
    return ret;
  }
  static Node* makePhi(Node* block, Index index) {
    Node* ret = new Node(Phi);
    ret->addValue(block);
    ret->index = index;
    return ret;
  }
  static Node* makeCond(Node* block, Index index, Node* node) {
    Node* ret = new Node(Cond);
    ret->addValue(block);
    ret->index = index;
    ret->addValue(node);
    return ret;
  }
  static Node* makeBlock() {
    Node* ret = new Node(Block);
    return ret;
  }
  static Node* makeZext(Node* child, Expression* origin) {
    Node* ret = new Node(Zext);
    ret->addValue(child);
    ret->origin = origin;
    return ret;
  }
  static Node* makeBad() {
    Node* ret = new Node(Bad);
    return ret;
  }

  // Helpers

  void addValue(Node* value) { values.push_back(value); }
  Node* getValue(Index i) { return values.at(i); }

  // Gets the wasm type of the node. If there isn't a valid one,
  // return unreachable.
  wasm::Type getWasmType() {
    switch (type) {
      case Var:
        return wasmType;
      case Expr:
        return expr->type;
      case Phi:
        return getValue(1)->getWasmType();
      case Zext:
        return getValue(0)->getWasmType();
      case Bad:
        return wasm::Type::unreachable;
      default:
        WASM_UNREACHABLE("invalid node type");
    }
  }

  bool operator==(const Node& other) {
    if (type != other.type) {
      return false;
    }
    switch (type) {
      case Var:
      case Block:
        return this == &other;
      case Expr: {
        if (!ExpressionAnalyzer::equal(expr, other.expr)) {
          return false;
        }
        break;
      }
      case Cond: {
        if (index != other.index) {
          return false;
        }
        break;
      }
      default: {}
    }
    if (values.size() != other.values.size()) {
      return false;
    }
    for (Index i = 0; i < values.size(); i++) {
      if (*(values[i]) != *(other.values[i])) {
        return false;
      }
    }
    return true;
  }

  bool operator!=(const Node& other) { return !(*this == other); }

  // As mentioned above, comparisons return i1. This checks
  // if an operation is of that sort.
  bool returnsI1() {
    if (isExpr()) {
      if (auto* binary = expr->dynCast<Binary>()) {
        return binary->isRelational();
      } else if (auto* unary = expr->dynCast<Unary>()) {
        return unary->isRelational();
      }
    }
    return false;
  }
};

} // namespace wasm::DataFlow

#endif // wasm_dataflow_node
