/*
 * Copyright 2016 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 WABT_IR_H_
#define WABT_IR_H_

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "binding-hash.h"
#include "common.h"
#include "opcode.h"

namespace wabt {

enum class VarType {
  Index,
  Name,
};

struct Var {
  // Keep the default constructor trivial so it can be used as a union member.
  Var() = default;
  explicit Var(int64_t index);
  explicit Var(const StringSlice& name);

  Location loc;
  VarType type;
  union {
    int64_t index;
    StringSlice name;
  };
};
typedef std::vector<Var> VarVector;

typedef StringSlice Label;

struct Const {
  // Struct tags to differentiate constructors.
  struct I32 {};
  struct I64 {};
  struct F32 {};
  struct F64 {};

  // Keep the default constructor trivial so it can be used as a union member.
  Const() = default;
  Const(I32, uint32_t);
  Const(I64, uint64_t);
  Const(F32, uint32_t);
  Const(F64, uint64_t);

  Location loc;
  Type type;
  union {
    uint32_t u32;
    uint64_t u64;
    uint32_t f32_bits;
    uint64_t f64_bits;
  };
};
typedef std::vector<Const> ConstVector;

enum class ExprType {
  Binary,
  Block,
  Br,
  BrIf,
  BrTable,
  Call,
  CallIndirect,
  Compare,
  Const,
  Convert,
  CurrentMemory,
  Drop,
  GetGlobal,
  GetLocal,
  GrowMemory,
  If,
  Load,
  Loop,
  Nop,
  Return,
  Select,
  SetGlobal,
  SetLocal,
  Store,
  TeeLocal,
  Unary,
  Unreachable,
};

typedef TypeVector BlockSignature;

struct Block {
  WABT_DISALLOW_COPY_AND_ASSIGN(Block);
  Block();
  explicit Block(struct Expr* first);
  ~Block();

  Label label;
  BlockSignature sig;
  struct Expr* first;
};

struct Expr {
  WABT_DISALLOW_COPY_AND_ASSIGN(Expr);
  Expr();
  explicit Expr(ExprType);
  ~Expr();

  static Expr* CreateBinary(Opcode);
  static Expr* CreateBlock(Block*);
  static Expr* CreateBr(Var);
  static Expr* CreateBrIf(Var);
  static Expr* CreateBrTable(VarVector* targets, Var default_target);
  static Expr* CreateCall(Var);
  static Expr* CreateCallIndirect(Var);
  static Expr* CreateCompare(Opcode);
  static Expr* CreateConst(const Const&);
  static Expr* CreateConvert(Opcode);
  static Expr* CreateCurrentMemory();
  static Expr* CreateDrop();
  static Expr* CreateGetGlobal(Var);
  static Expr* CreateGetLocal(Var);
  static Expr* CreateGrowMemory();
  static Expr* CreateIf(struct Block* true_, struct Expr* false_ = nullptr);
  static Expr* CreateLoad(Opcode, uint32_t align, uint64_t offset);
  static Expr* CreateLoop(struct Block*);
  static Expr* CreateNop();
  static Expr* CreateReturn();
  static Expr* CreateSelect();
  static Expr* CreateSetGlobal(Var);
  static Expr* CreateSetLocal(Var);
  static Expr* CreateStore(Opcode, uint32_t align, uint64_t offset);
  static Expr* CreateTeeLocal(Var);
  static Expr* CreateUnary(Opcode);
  static Expr* CreateUnreachable();

  Location loc;
  ExprType type;
  Expr* next;
  union {
    struct { Opcode opcode; } binary, compare, convert, unary;
    struct Block *block, *loop;
    struct { Var var; } br, br_if;
    struct { VarVector* targets; Var default_target; } br_table;
    struct { Var var; } call, call_indirect;
    struct Const const_;
    struct { Var var; } get_global, set_global;
    struct { Var var; } get_local, set_local, tee_local;
    struct { struct Block* true_; struct Expr* false_; } if_;
    struct { Opcode opcode; uint32_t align; uint64_t offset; } load, store;
  };
};

struct FuncSignature {
  TypeVector param_types;
  TypeVector result_types;
};

struct FuncType {
  WABT_DISALLOW_COPY_AND_ASSIGN(FuncType);
  FuncType();
  ~FuncType();

  StringSlice name;
  FuncSignature sig;
};

struct FuncDeclaration {
  WABT_DISALLOW_COPY_AND_ASSIGN(FuncDeclaration);
  FuncDeclaration();
  ~FuncDeclaration();

  bool has_func_type;
  Var type_var;
  FuncSignature sig;
};

struct Func {
  WABT_DISALLOW_COPY_AND_ASSIGN(Func);
  Func();
  ~Func();

  StringSlice name;
  FuncDeclaration decl;
  TypeVector local_types;
  BindingHash param_bindings;
  BindingHash local_bindings;
  Expr* first_expr;
};

struct Global {
  WABT_DISALLOW_COPY_AND_ASSIGN(Global);
  Global();
  ~Global();

  StringSlice name;
  Type type;
  bool mutable_;
  Expr* init_expr;
};

struct Table {
  WABT_DISALLOW_COPY_AND_ASSIGN(Table);
  Table();
  ~Table();

  StringSlice name;
  Limits elem_limits;
};

struct ElemSegment {
  WABT_DISALLOW_COPY_AND_ASSIGN(ElemSegment);
  ElemSegment();
  ~ElemSegment();

  Var table_var;
  Expr* offset;
  VarVector vars;
};

struct Memory {
  WABT_DISALLOW_COPY_AND_ASSIGN(Memory);
  Memory();
  ~Memory();

  StringSlice name;
  Limits page_limits;
};

struct DataSegment {
  WABT_DISALLOW_COPY_AND_ASSIGN(DataSegment);
  DataSegment();
  ~DataSegment();

  Var memory_var;
  Expr* offset;
  char* data;
  size_t size;
};

struct Import {
  WABT_DISALLOW_COPY_AND_ASSIGN(Import);
  Import();
  ~Import();

  StringSlice module_name;
  StringSlice field_name;
  ExternalKind kind;
  union {
    /* an imported func is has the type Func so it can be more easily
     * included in the Module's vector of funcs; but only the
     * FuncDeclaration will have any useful information */
    Func* func;
    Table* table;
    Memory* memory;
    Global* global;
  };
};

struct Export {
  WABT_DISALLOW_COPY_AND_ASSIGN(Export);
  Export();
  ~Export();

  StringSlice name;
  ExternalKind kind;
  Var var;
};

enum class ModuleFieldType {
  Func,
  Global,
  Import,
  Export,
  FuncType,
  Table,
  ElemSegment,
  Memory,
  DataSegment,
  Start,
};

struct ModuleField {
  WABT_DISALLOW_COPY_AND_ASSIGN(ModuleField);
  ModuleField();
  ~ModuleField();

  Location loc;
  ModuleFieldType type;
  struct ModuleField* next;
  union {
    Func* func;
    Global* global;
    Import* import;
    Export* export_;
    FuncType* func_type;
    Table* table;
    ElemSegment* elem_segment;
    Memory* memory;
    DataSegment* data_segment;
    Var start;
  };
};

struct Module {
  WABT_DISALLOW_COPY_AND_ASSIGN(Module);
  Module();
  ~Module();

  Location loc;
  StringSlice name;
  ModuleField* first_field;
  ModuleField* last_field;

  uint32_t num_func_imports;
  uint32_t num_table_imports;
  uint32_t num_memory_imports;
  uint32_t num_global_imports;

  /* cached for convenience; the pointers are shared with values that are
   * stored in either ModuleField or Import. */
  std::vector<Func*> funcs;
  std::vector<Global*> globals;
  std::vector<Import*> imports;
  std::vector<Export*> exports;
  std::vector<FuncType*> func_types;
  std::vector<Table*> tables;
  std::vector<ElemSegment*> elem_segments;
  std::vector<Memory*> memories;
  std::vector<DataSegment*> data_segments;
  Var* start;

  BindingHash func_bindings;
  BindingHash global_bindings;
  BindingHash export_bindings;
  BindingHash func_type_bindings;
  BindingHash table_bindings;
  BindingHash memory_bindings;
};

enum class RawModuleType {
  Text,
  Binary,
};

/* "raw" means that the binary module has not yet been decoded. This is only
 * necessary when embedded in assert_invalid. In that case we want to defer
 * decoding errors until wabt_check_assert_invalid is called. This isn't needed
 * when parsing text, as assert_invalid always assumes that text parsing
 * succeeds. */
struct RawModule {
  WABT_DISALLOW_COPY_AND_ASSIGN(RawModule);
  RawModule();
  ~RawModule();

  RawModuleType type;
  union {
    Module* text;
    struct {
      Location loc;
      StringSlice name;
      char* data;
      size_t size;
    } binary;
  };
};

enum class ActionType {
  Invoke,
  Get,
};

struct ActionInvoke {
  WABT_DISALLOW_COPY_AND_ASSIGN(ActionInvoke);
  ActionInvoke();

  ConstVector args;
};

struct Action {
  WABT_DISALLOW_COPY_AND_ASSIGN(Action);
  Action();
  ~Action();

  Location loc;
  ActionType type;
  Var module_var;
  StringSlice name;
  union {
    ActionInvoke* invoke;
    struct {} get;
  };
};

enum class CommandType {
  Module,
  Action,
  Register,
  AssertMalformed,
  AssertInvalid,
  /* This is a module that is invalid, but cannot be written as a binary module
   * (e.g. it has unresolvable names.) */
  AssertInvalidNonBinary,
  AssertUnlinkable,
  AssertUninstantiable,
  AssertReturn,
  AssertReturnCanonicalNan,
  AssertReturnArithmeticNan,
  AssertTrap,
  AssertExhaustion,

  First = Module,
  Last = AssertExhaustion,
};
static const int kCommandTypeCount = WABT_ENUM_COUNT(CommandType);

struct Command {
  WABT_DISALLOW_COPY_AND_ASSIGN(Command);
  Command();
  ~Command();

  CommandType type;
  union {
    Module* module;
    Action* action;
    struct { StringSlice module_name; Var var; } register_;
    struct { Action* action; ConstVector* expected; } assert_return;
    struct {
      Action* action;
    } assert_return_canonical_nan, assert_return_arithmetic_nan;
    struct { Action* action; StringSlice text; } assert_trap;
    struct {
      RawModule* module;
      StringSlice text;
    } assert_malformed, assert_invalid, assert_unlinkable,
        assert_uninstantiable;
  };
};
typedef std::vector<std::unique_ptr<Command>> CommandPtrVector;

struct Script {
  WABT_DISALLOW_COPY_AND_ASSIGN(Script);
  Script();

  CommandPtrVector commands;
  BindingHash module_bindings;
};

struct ExprVisitor {
  void* user_data;
  Result (*on_binary_expr)(Expr*, void* user_data);
  Result (*begin_block_expr)(Expr*, void* user_data);
  Result (*end_block_expr)(Expr*, void* user_data);
  Result (*on_br_expr)(Expr*, void* user_data);
  Result (*on_br_if_expr)(Expr*, void* user_data);
  Result (*on_br_table_expr)(Expr*, void* user_data);
  Result (*on_call_expr)(Expr*, void* user_data);
  Result (*on_call_indirect_expr)(Expr*, void* user_data);
  Result (*on_compare_expr)(Expr*, void* user_data);
  Result (*on_const_expr)(Expr*, void* user_data);
  Result (*on_convert_expr)(Expr*, void* user_data);
  Result (*on_current_memory_expr)(Expr*, void* user_data);
  Result (*on_drop_expr)(Expr*, void* user_data);
  Result (*on_get_global_expr)(Expr*, void* user_data);
  Result (*on_get_local_expr)(Expr*, void* user_data);
  Result (*on_grow_memory_expr)(Expr*, void* user_data);
  Result (*begin_if_expr)(Expr*, void* user_data);
  Result (*after_if_true_expr)(Expr*, void* user_data);
  Result (*end_if_expr)(Expr*, void* user_data);
  Result (*on_load_expr)(Expr*, void* user_data);
  Result (*begin_loop_expr)(Expr*, void* user_data);
  Result (*end_loop_expr)(Expr*, void* user_data);
  Result (*on_nop_expr)(Expr*, void* user_data);
  Result (*on_return_expr)(Expr*, void* user_data);
  Result (*on_select_expr)(Expr*, void* user_data);
  Result (*on_set_global_expr)(Expr*, void* user_data);
  Result (*on_set_local_expr)(Expr*, void* user_data);
  Result (*on_store_expr)(Expr*, void* user_data);
  Result (*on_tee_local_expr)(Expr*, void* user_data);
  Result (*on_unary_expr)(Expr*, void* user_data);
  Result (*on_unreachable_expr)(Expr*, void* user_data);
};

ModuleField* append_module_field(Module*);
/* ownership of the function signature is passed to the module */
FuncType* append_implicit_func_type(Location*, Module*, FuncSignature*);

/* destruction functions. not needed unless you're creating your own IR
 elements */
void destroy_expr_list(Expr*);
void destroy_var(Var*);

/* traversal functions */
Result visit_func(Func* func, ExprVisitor*);
Result visit_expr_list(Expr* expr, ExprVisitor*);

/* convenience functions for looking through the IR */
int get_index_from_var(const BindingHash* bindings, const Var* var);
int get_func_index_by_var(const Module* module, const Var* var);
int get_global_index_by_var(const Module* func, const Var* var);
int get_func_type_index_by_var(const Module* module, const Var* var);
int get_func_type_index_by_sig(const Module* module, const FuncSignature* sig);
int get_func_type_index_by_decl(const Module* module,
                                const FuncDeclaration* decl);
int get_table_index_by_var(const Module* module, const Var* var);
int get_memory_index_by_var(const Module* module, const Var* var);
int get_import_index_by_var(const Module* module, const Var* var);
int get_local_index_by_var(const Func* func, const Var* var);
int get_module_index_by_var(const Script* script, const Var* var);

Func* get_func_by_var(const Module* module, const Var* var);
Global* get_global_by_var(const Module* func, const Var* var);
FuncType* get_func_type_by_var(const Module* module, const Var* var);
Table* get_table_by_var(const Module* module, const Var* var);
Memory* get_memory_by_var(const Module* module, const Var* var);
Import* get_import_by_var(const Module* module, const Var* var);
Export* get_export_by_name(const Module* module, const StringSlice* name);
Module* get_first_module(const Script* script);
Module* get_module_by_var(const Script* script, const Var* var);

void make_type_binding_reverse_mapping(
    const TypeVector&,
    const BindingHash&,
    std::vector<std::string>* out_reverse_mapping);

static WABT_INLINE bool decl_has_func_type(const FuncDeclaration* decl) {
  return decl->has_func_type;
}

static WABT_INLINE bool signatures_are_equal(const FuncSignature* sig1,
                                             const FuncSignature* sig2) {
  return sig1->param_types == sig2->param_types &&
         sig1->result_types == sig2->result_types;
}

static WABT_INLINE size_t get_num_params(const Func* func) {
  return func->decl.sig.param_types.size();
}

static WABT_INLINE size_t get_num_results(const Func* func) {
  return func->decl.sig.result_types.size();
}

static WABT_INLINE size_t get_num_locals(const Func* func) {
  return func->local_types.size();
}

static WABT_INLINE size_t get_num_params_and_locals(const Func* func) {
  return get_num_params(func) + get_num_locals(func);
}

static WABT_INLINE Type get_param_type(const Func* func, int index) {
  assert(static_cast<size_t>(index) < func->decl.sig.param_types.size());
  return func->decl.sig.param_types[index];
}

static WABT_INLINE Type get_local_type(const Func* func, int index) {
  assert(static_cast<size_t>(index) < get_num_locals(func));
  return func->local_types[index];
}

static WABT_INLINE Type get_result_type(const Func* func, int index) {
  assert(static_cast<size_t>(index) < func->decl.sig.result_types.size());
  return func->decl.sig.result_types[index];
}

static WABT_INLINE Type get_func_type_param_type(const FuncType* func_type,
                                                 int index) {
  return func_type->sig.param_types[index];
}

static WABT_INLINE size_t get_func_type_num_params(const FuncType* func_type) {
  return func_type->sig.param_types.size();
}

static WABT_INLINE Type get_func_type_result_type(const FuncType* func_type,
                                                  int index) {
  return func_type->sig.result_types[index];
}

static WABT_INLINE size_t get_func_type_num_results(const FuncType* func_type) {
  return func_type->sig.result_types.size();
}

static WABT_INLINE const Location* get_raw_module_location(
    const RawModule* raw) {
  switch (raw->type) {
    case RawModuleType::Binary:
      return &raw->binary.loc;
    case RawModuleType::Text:
      return &raw->text->loc;
    default:
      assert(0);
      return nullptr;
  }
}

}  // namespace wabt

#endif /* WABT_IR_H_ */
