/******************************************************************************
* Copyright (C) 2015-2020 Dibyendu Majumdar
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/
#include "ravi_llvmcodegen.h"

namespace ravi {

// Although the name is EQ this actually
// implements EQ, LE and LT - by using the supplied lua function to call.
void RaviCodeGenerator::emit_EQ(RaviFunctionDef *def, int A, int B, int C,
                                int j, int jA, llvm::Constant *callee,
                                OpCode opCode, int pc) {
  //  case OP_EQ: {
  //    TValue *rb = RKB(i);
  //    TValue *rc = RKC(i);
  //    Protect(
  //      if (cast_int(luaV_equalobj(L, rb, rc)) != GETARG_A(i))
  //        ci->u.l.savedpc++;
  //      else
  //        donextjump(ci);
  //    )
  //  } break;

  bool traced = emit_debug_trace(def, opCode, pc);

  // Load pointer to base
  emit_load_base(def);

  // Get pointer to register B
  llvm::Value *regB = emit_gep_register_or_constant(def, B);
  // Get pointer to register C
  llvm::Value *regC = emit_gep_register_or_constant(def, C);

  llvm::Value *result = NULL;
  switch (opCode) {

  case OP_RAVI_LT_II:
  case OP_RAVI_LE_II:
  case OP_RAVI_EQ_II: {
    llvm::Instruction *p1 = emit_load_reg_i(def, regB);
    llvm::Instruction *p2 = emit_load_reg_i(def, regC);

    switch (opCode) {
    case OP_RAVI_EQ_II:
      result = def->builder->CreateICmpEQ(p1, p2, "EQ_II_result");
      break;
    case OP_RAVI_LT_II:
      result = def->builder->CreateICmpSLT(p1, p2, "LT_II_result");
      break;
    case OP_RAVI_LE_II:
      result = def->builder->CreateICmpSLE(p1, p2, "LE_II_result");
      break;
    default:
      assert(0);
    }
    result =
        def->builder->CreateZExt(result, def->types->C_intT, "II_result_int");

  } break;

  case OP_RAVI_LT_FF:
  case OP_RAVI_LE_FF:
  case OP_RAVI_EQ_FF: {
    llvm::Instruction *p1 = emit_load_reg_n(def, regB);
    llvm::Instruction *p2 = emit_load_reg_n(def, regC);

    switch (opCode) {
    case OP_RAVI_EQ_FF:
      result = def->builder->CreateFCmpOEQ(p1, p2, "EQ_FF_result");
      break;
    case OP_RAVI_LT_FF:
      result = def->builder->CreateFCmpULT(p1, p2, "LT_FF_result");
      break;
    case OP_RAVI_LE_FF:
      result = def->builder->CreateFCmpULE(p1, p2, "LE_FF_result");
      break;
    default:
      assert(0);
    }
    result =
        def->builder->CreateZExt(result, def->types->C_intT, "FF_result_int");

  } break;

  default:
    // As below may invoke metamethod we need to set savedpc
    if (!traced) emit_update_savedpc(def, pc);
    // Call luaV_equalobj with register B and C
    result = CreateCall3(def->builder, callee, def->L, regB, regC);
  }

  // Test if result is equal to operand A
  llvm::Value *result_eq_A = def->builder->CreateICmpEQ(
      result, llvm::ConstantInt::get(def->types->C_intT, A));

  // If result == A then we need to execute the next statement which is a jump
  llvm::BasicBlock *then_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.then", def->f);
  llvm::BasicBlock *else_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.else");
  def->builder->CreateCondBr(result_eq_A, then_block, else_block);
  def->builder->SetInsertPoint(then_block);

  // if (a > 0) luaF_close(L, ci->u.l.base + a - 1);
  if (jA > 0) {
    // jA is the A operand of the Jump instruction

    // Reload pointer to base as the call to luaV_equalobj() may
    // have invoked a Lua function and as a result the stack may have
    // been reallocated - so the previous base pointer could be stale
    emit_load_base(def);

    // base + a - 1
    llvm::Value *val = emit_gep_register(def, jA - 1);
    if (!traced)
      emit_update_savedpc(def, pc);
    // Call luaF_close
    CreateCall3(def->builder, def->luaF_closeF, def->L, val, def->types->kInt[LUA_OK]);
  }
  // Do the jump
  def->builder->CreateBr(def->jmp_targets[j].jmp1);
  // Add the else block and make it current so that the next instruction flows
  // here
  def->f->getBasicBlockList().push_back(else_block);
  def->builder->SetInsertPoint(else_block);
}

llvm::Value *RaviCodeGenerator::emit_boolean_testfalse(RaviFunctionDef *def,
                                                       llvm::Value *reg,
                                                       bool donot) {
  // (isnil() || isbool() && b == 0)

  llvm::IRBuilder<> TmpB(def->entry, def->entry->begin());
  llvm::Value *var = TmpB.CreateAlloca(
      llvm::Type::getInt1Ty(def->jitState->context()), nullptr, "b");

  llvm::Value *type = emit_load_type(def, reg);

  // Test if type == LUA_TNIL (0)
  llvm::Value *isnil = emit_is_value_of_type(def, type, LUA__TNIL, "is.nil");
  llvm::BasicBlock *then_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.nil", def->f);
  llvm::BasicBlock *else_block =
      llvm::BasicBlock::Create(def->jitState->context(), "not.nil");
  llvm::BasicBlock *end_block =
      llvm::BasicBlock::Create(def->jitState->context(), "end");
  def->builder->CreateCondBr(isnil, then_block, else_block);
  def->builder->SetInsertPoint(then_block);

  emit_store_local_int(def, isnil, var);
  def->builder->CreateBr(end_block);

  def->f->getBasicBlockList().push_back(else_block);
  def->builder->SetInsertPoint(else_block);
  // value is not nil
  // so check if bool and b == 0

  // Test if type == LUA_TBOOLEAN
  llvm::Value *isbool =
      emit_is_value_of_type(def, type, LUA__TBOOLEAN, "is.boolean");
  // Test if bool value == 0
  llvm::Value *bool_value = emit_load_reg_b(def, reg);
  llvm::Value *boolzero =
      def->builder->CreateICmpEQ(bool_value, def->types->kInt[0]);
  // Test type == LUA_TBOOLEAN && bool value == 0
  llvm::Value *andvalue = def->builder->CreateAnd(isbool, boolzero);

  emit_store_local_int(def, andvalue, var);
  def->builder->CreateBr(end_block);

  def->f->getBasicBlockList().push_back(end_block);
  def->builder->SetInsertPoint(end_block);

  llvm::Value *result = nullptr;
  if (donot) {
    auto ins = emit_load_local_int(def, var);
    result = def->builder->CreateNot(ins);
  } else {
    auto ins = emit_load_local_int(def, var);
    result = ins;
  }

  return result;
}

void RaviCodeGenerator::emit_TEST(RaviFunctionDef *def, int A, int B, int C,
                                  int j, int jA, int pc) {

  //    case OP_TEST: {
  //      if (GETARG_C(i) ? l_isfalse(ra) : !l_isfalse(ra))
  //        ci->u.l.savedpc++;
  //      else
  //        donextjump(ci);
  //    } break;

  bool traced = emit_debug_trace(def, OP_TEST, pc);
  // Load pointer to base
  emit_load_base(def);

  // Get pointer to register A
  llvm::Value *ra = emit_gep_register(def, A);
  // v = C ? is_false(ra) : !is_false(ra)
  llvm::Value *v = C ? emit_boolean_testfalse(def, ra, false)
                     : emit_boolean_testfalse(def, ra, true);

  // Test NOT v
  llvm::Value *result = def->builder->CreateNot(v);
  // If !v then we need to execute the next statement which is a jump
  llvm::BasicBlock *then_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.then", def->f);
  llvm::BasicBlock *else_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.else");
  def->builder->CreateCondBr(result, then_block, else_block);
  def->builder->SetInsertPoint(then_block);

  // if (a > 0) luaF_close(L, ci->u.l.base + a - 1);
  if (jA > 0) {
    // jA is the A operand of the Jump instruction

    // base + a - 1
    llvm::Value *val = emit_gep_register(def, jA - 1);
    if (!traced)
      emit_update_savedpc(def, pc);
    // Call luaF_close
    CreateCall3(def->builder, def->luaF_closeF, def->L, val, def->types->kInt[LUA_OK]);
  }
  // Do the jump
  def->builder->CreateBr(def->jmp_targets[j].jmp1);

  // Add the else block and make it current so that the next instruction flows
  // here
  def->f->getBasicBlockList().push_back(else_block);
  def->builder->SetInsertPoint(else_block);
}

void RaviCodeGenerator::emit_NOT(RaviFunctionDef *def, int A, int B, int pc) {
  //  case OP_NOT: {
  //    TValue *rb = RB(i);
  //    int res = l_isfalse(rb);  /* next assignment may change this value */
  //    setbvalue(ra, res);
  //  } break;
  emit_debug_trace(def, OP_NOT, pc);
  emit_load_base(def);
  // Get pointer to register B
  llvm::Value *rb = emit_gep_register(def, B);
  llvm::Value *v = emit_boolean_testfalse(def, rb, false);
  llvm::Value *result = def->builder->CreateZExt(v, def->types->C_intT, "i");
  llvm::Value *ra = emit_gep_register(def, A);
  emit_store_reg_b_withtype(def, result, ra);
}

void RaviCodeGenerator::emit_TESTSET(RaviFunctionDef *def, int A, int B, int C,
                                     int j, int jA, int pc) {

  //  case OP_TESTSET: {
  //    TValue *rb = RB(i);
  //    if (GETARG_C(i) ? l_isfalse(rb) : !l_isfalse(rb))
  //      ci->u.l.savedpc++;
  //    else {
  //      setobjs2s(L, ra, rb);
  //      donextjump(ci);
  //    }
  //  } break;

  bool traced = emit_debug_trace(def, OP_TESTSET, pc);
  // Load pointer to base
  emit_load_base(def);

  // Get pointer to register B
  llvm::Value *rb = emit_gep_register(def, B);
  // v = C ? is_false(ra) : !is_false(ra)
  llvm::Value *v = C ? emit_boolean_testfalse(def, rb, false)
                     : emit_boolean_testfalse(def, rb, true);

  // Test NOT v
  llvm::Value *result = def->builder->CreateNot(v);
  // If !v then we need to execute the next statement which is a jump
  llvm::BasicBlock *then_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.then", def->f);
  llvm::BasicBlock *else_block =
      llvm::BasicBlock::Create(def->jitState->context(), "if.else");
  def->builder->CreateCondBr(result, then_block, else_block);
  def->builder->SetInsertPoint(then_block);

  // Get pointer to register A
  llvm::Value *ra = emit_gep_register(def, A);
  emit_assign(def, ra, rb);

  // if (a > 0) luaF_close(L, ci->u.l.base + a - 1);
  if (jA > 0) {
    // jA is the A operand of the Jump instruction

    // base + a - 1
    llvm::Value *val = emit_gep_register(def, jA - 1);
    if (!traced)
      emit_update_savedpc(def, pc);
    // Call luaF_close
    CreateCall3(def->builder, def->luaF_closeF, def->L, val, def->types->kInt[LUA_OK]);
  }
  // Do the jump
  def->builder->CreateBr(def->jmp_targets[j].jmp1);

  // Add the else block and make it current so that the next instruction flows
  // here
  def->f->getBasicBlockList().push_back(else_block);
  def->builder->SetInsertPoint(else_block);
}
}
