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

// OP_JMP
void RaviCodeGenerator::emit_JMP(RaviFunctionDef *def, int A, int sBx, int pc) {

  //#define dojump(ci,i,e)
  // { int a = GETARG_A(i);
  //   if (a > 0) luaF_close(L, ci->u.l.base + a - 1);
  //   ci->u.l.savedpc += GETARG_sBx(i) + e; }
  //
  // dojump(ci, i, 0);

  assert(def->jmp_targets[sBx].jmp1);

  // If the current block is already terminated we
  // need to create a new block
  if (def->builder->GetInsertBlock()->getTerminator()) {
    llvm::BasicBlock *jmp_block = llvm::BasicBlock::Create(
        def->jitState->context(), "OP_JMP_bridge", def->f);
    def->builder->SetInsertPoint(jmp_block);
  }

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

  // if (a > 0) luaF_close(L, ci->u.l.base + a - 1);
  if (A > 0) {
    emit_load_base(def);
    // base + a - 1
    llvm::Value *val = emit_gep_register(def, A - 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 actual jump
  def->builder->CreateBr(def->jmp_targets[sBx].jmp1);

  // Start new block
  llvm::BasicBlock *block = llvm::BasicBlock::Create(def->jitState->context(),
                                                     "OP_JMP_postjmp", def->f);
  def->builder->SetInsertPoint(block);
}

// Handle OP_CALL
// Note that Lua assumes that functions called via OP_CALL
// are Lua functions and secondly that once OP_CALL completes the
// current function will continue within the same luaV_execute()
// call. However in a JIT case each JIT function is a different call
// so we need to take care of the behaviour differences between
// OP_CALL and external calls
void RaviCodeGenerator::emit_CALL(RaviFunctionDef *def, int A, int B, int C,
                                  int pc) {

  // int nresults = c - 1;
  // if (b != 0)
  //   L->top = ra + b;  /* else previous instruction set top */
  // int c = luaD_precall(L, ra, nresults, 1);  /* C or JITed function? */
  // if (c) {
  //   if (c == 1 && nresults >= 0)
  //     L->top = ci->top;  /* adjust results if C function */
  // }
  // else {  /* Lua function */
  //   int b = luaV_execute(L);
  //   if (b) L->top = ci->top;
  // }
  bool traced = emit_debug_trace(def, OP_CALL, pc);
  // Set savedpc before the call
  if (!traced) emit_update_savedpc(def, pc);
  emit_load_base(def);

  // int nresults = c - 1;
  int nresults = C - 1;

  // if (b != 0)
  if (B != 0) {
    //   L->top = ra + b;  /* else previous instruction set top */
    emit_set_L_top_toreg(def, A + B);
  }

  // luaD_precall() returns following
  // 1 - C function called, results to be adjusted
  // 2 - JITed Lua function called, no action
  // 0 - Run interpreter on Lua function, if returns != 0 then update L->top

  // int c = luaD_precall(L, ra, nresults, op_call);  /* C or JITed function? */
  // The last parameter to luaD_precall() tells it that
  // this is an OP_CALL
  llvm::Value *ra = emit_gep_register(def, A);
  llvm::Value *precall_result =
      CreateCall4(def->builder, def->luaD_precallF, def->L, ra,
                  llvm::ConstantInt::get(def->types->C_intT, nresults),
                  def->types->kInt[1]);

  // If luaD_precall() returns 0 then we need to interpret the
  // Lua function
  llvm::Value *do_Lua_interp =
      def->builder->CreateICmpEQ(precall_result, def->types->kInt[0]);

  llvm::BasicBlock *then_block = llvm::BasicBlock::Create(
      def->jitState->context(), "OP_CALL_if_Lua_interp_function", def->f);
  llvm::BasicBlock *else_block = llvm::BasicBlock::Create(
      def->jitState->context(), "OP_CALL_if_not_Lua_interp_function");
  llvm::BasicBlock *end_block =
      llvm::BasicBlock::Create(def->jitState->context(), "OP_CALL_done");
  def->builder->CreateCondBr(do_Lua_interp, then_block, else_block);
  def->builder->SetInsertPoint(then_block);

  // Lua function, not compiled, so call luaV_execute
  llvm::Value *b = def->builder->CreateCall(def->luaV_executeF, def->L);

  // If the return value is non zero then we need to refresh L->top = ci->top
  llvm::Value *b_not_zero = def->builder->CreateICmpNE(b, def->types->kInt[0]);

  llvm::BasicBlock *if_b_block = llvm::BasicBlock::Create(
      def->jitState->context(), "OP_CALL_if_need_reset_L_top", def->f);
  def->builder->CreateCondBr(b_not_zero, if_b_block, end_block);
  def->builder->SetInsertPoint(if_b_block);

  // Set L->top = ci->top
  emit_refresh_L_top(def, def->ci_val);

  // We are done
  def->builder->CreateBr(end_block);

  // Handle the C function or JIT case
  // function already executed by luaD_precall()
  def->f->getBasicBlockList().push_back(else_block);
  def->builder->SetInsertPoint(else_block);

  if (nresults >= 0) {
    // In case the precall returned 1 then a C function was
    // called so we need to update L->top
    //   if (c == 1 && nresults >= 0)
    //     L->top = ci->top;  /* adjust results if C function */
    llvm::Value *precall_C =
        def->builder->CreateICmpEQ(precall_result, def->types->kInt[1]);

    llvm::BasicBlock *then1_block = llvm::BasicBlock::Create(
        def->jitState->context(), "OP_CALL_if_C_function_returned_values",
        def->f);
    def->builder->CreateCondBr(precall_C, then1_block, end_block);
    def->builder->SetInsertPoint(then1_block);

    emit_refresh_L_top(def, def->ci_val);
  }
  def->builder->CreateBr(end_block);
  def->f->getBasicBlockList().push_back(end_block);
  def->builder->SetInsertPoint(end_block);
}

void RaviCodeGenerator::emit_DEFER(RaviFunctionDef *def, int A, int pc) {
  emit_debug_trace(def, OP_RAVI_DEFER, pc);
  emit_load_base(def);
  llvm::Value *ra = emit_gep_register(def, A);
  CreateCall2(def->builder, def->raviV_op_deferF, def->L, ra);
}

}