// vim: set ts=8 sts=2 sw=2 tw=99 et:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "BaseRuntime.h"
#include "sp_vm_engine.h"
#include "x86/jit_x86.h"
#include "sp_vm_basecontext.h"
#include "engine2.h"

#include "md5/md5.h"

using namespace SourcePawn;

static inline bool
IsPointerCellAligned(void *p)
{
  return uintptr_t(p) % 4 == 0;
}

BaseRuntime::BaseRuntime()
  : m_Debug(&m_plugin),
    m_pCtx(NULL), 
    m_PubFuncs(NULL),
    m_PubJitFuncs(NULL),
    co_(NULL),
    m_CompSerial(0)
{
  memset(&m_plugin, 0, sizeof(m_plugin));

  m_MaxFuncs = 0;
  m_NumFuncs = 0;
  float_table_ = NULL;
  function_map_ = NULL;
  alt_pcode_ = NULL;
  
  memset(m_CodeHash, 0, sizeof(m_CodeHash));
  memset(m_DataHash, 0, sizeof(m_DataHash));

  ke::AutoLock lock(g_Jit.Mutex());
  g_Jit.RegisterRuntime(this);
}

BaseRuntime::~BaseRuntime()
{
  // The watchdog thread takes the global JIT lock while it patches all
  // runtimes. It is not enough to ensure that the unlinking of the runtime is
  // protected; we cannot delete functions or code while the watchdog might be
  // executing. Therefore, the entire destructor is guarded.
  ke::AutoLock lock(g_Jit.Mutex());

  g_Jit.DeregisterRuntime(this);

  for (uint32_t i = 0; i < m_plugin.num_publics; i++)
    delete m_PubFuncs[i];
  delete [] m_PubFuncs;
  delete [] m_PubJitFuncs;
  delete [] float_table_;
  delete [] function_map_;
  delete [] alt_pcode_;

  for (size_t i = 0; i < m_JitFunctions.length(); i++)
    delete m_JitFunctions[i];

  delete m_pCtx;
  if (co_)
    co_->Abort();

  free(m_plugin.base);
  delete [] m_plugin.memory;
  delete [] m_plugin.publics;
  delete [] m_plugin.pubvars;
  delete [] m_plugin.natives;
  free(m_plugin.name);
}

void
BaseRuntime::SetupFloatNativeRemapping()
{
  float_table_ = new floattbl_t[m_plugin.num_natives];
  for (size_t i = 0; i < m_plugin.num_natives; i++) {
    const char *name = m_plugin.natives[i].name;
    if (!strcmp(name, "FloatAbs")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FABS;
    } else if (!strcmp(name, "FloatAdd")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FLOATADD;
    } else if (!strcmp(name, "FloatSub")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FLOATSUB;
    } else if (!strcmp(name, "FloatMul")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FLOATMUL;
    } else if (!strcmp(name, "FloatDiv")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FLOATDIV;
    } else if (!strcmp(name, "float")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FLOAT;
    } else if (!strcmp(name, "FloatCompare")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_FLOATCMP;
    } else if (!strcmp(name, "RoundToZero")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_RND_TO_ZERO;
    } else if (!strcmp(name, "RoundToCeil")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_RND_TO_CEIL;
    } else if (!strcmp(name, "RoundToFloor")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_RND_TO_FLOOR;
    } else if (!strcmp(name, "RoundToNearest")) {
      float_table_[i].found = true;
      float_table_[i].index = OP_RND_TO_NEAREST;
    }
  }
}

unsigned
BaseRuntime::GetNativeReplacement(size_t index)
{
  if (!float_table_[index].found)
    return OP_NOP;
  return float_table_[index].index;
}

void
BaseRuntime::SetName(const char *name)
{
  m_plugin.name = strdup(name);
}

static cell_t InvalidNative(IPluginContext *pCtx, const cell_t *params)
{
  return pCtx->ThrowNativeErrorEx(SP_ERROR_INVALID_NATIVE, "Invalid native");
}

int BaseRuntime::CreateFromMemory(sp_file_hdr_t *hdr, uint8_t *base)
{
  char *nameptr;
  uint8_t sectnum = 0;
  sp_file_section_t *secptr = (sp_file_section_t *)(base + sizeof(sp_file_hdr_t));

  memset(&m_plugin, 0, sizeof(m_plugin));

  m_plugin.base = base;
  m_plugin.base_size = hdr->imagesize;

  if (hdr->version == 0x0101)
    m_plugin.debug.unpacked = true;

  /* We have to read the name section first */
  for (sectnum = 0; sectnum < hdr->sections; sectnum++) {
    nameptr = (char *)(base + hdr->stringtab + secptr[sectnum].nameoffs);
    if (strcmp(nameptr, ".names") == 0) {
      m_plugin.stringbase = (const char *)(base + secptr[sectnum].dataoffs);
      break;
    }
  }

  sectnum = 0;

  /* Now read the rest of the sections */
  while (sectnum < hdr->sections) {
    nameptr = (char *)(base + hdr->stringtab + secptr->nameoffs);

    if (!(m_plugin.pcode) && !strcmp(nameptr, ".code")) {
      sp_file_code_t *cod = (sp_file_code_t *)(base + secptr->dataoffs);

      if (cod->codeversion < SP_CODEVERS_JIT1)
        return SP_ERROR_CODE_TOO_OLD;
      if (cod->codeversion > SP_CODEVERS_JIT2)
        return SP_ERROR_CODE_TOO_NEW;

      m_plugin.pcode = base + secptr->dataoffs + cod->code;
      m_plugin.pcode_size = cod->codesize;
      m_plugin.flags = cod->flags;
      m_plugin.pcode_version = cod->codeversion;
      if (!IsPointerCellAligned(m_plugin.pcode)) {
        // The JIT requires that pcode is cell-aligned, so if it's not, we
        // remap the code segment to a new address.
        alt_pcode_ = new uint8_t[m_plugin.pcode_size];
        memcpy(alt_pcode_, m_plugin.pcode, m_plugin.pcode_size);
        assert(IsPointerCellAligned(alt_pcode_));

        m_plugin.pcode = alt_pcode_;
      }
    } else if (!(m_plugin.data) && !strcmp(nameptr, ".data")) {
      sp_file_data_t *dat = (sp_file_data_t *)(base + secptr->dataoffs);
      m_plugin.data = base + secptr->dataoffs + dat->data;
      m_plugin.data_size = dat->datasize;
      m_plugin.mem_size = dat->memsize;
      m_plugin.memory = new uint8_t[m_plugin.mem_size];
      memcpy(m_plugin.memory, m_plugin.data, m_plugin.data_size);
    } else if ((m_plugin.publics == NULL) && !strcmp(nameptr, ".publics")) {
      sp_file_publics_t *publics;

      publics = (sp_file_publics_t *)(base + secptr->dataoffs);
      m_plugin.num_publics = secptr->size / sizeof(sp_file_publics_t);

      if (m_plugin.num_publics > 0) {
        m_plugin.publics = new sp_public_t[m_plugin.num_publics];

        for (uint32_t i = 0; i < m_plugin.num_publics; i++) {
          m_plugin.publics[i].code_offs = publics[i].address;
          m_plugin.publics[i].funcid = (i << 1) | 1;
          m_plugin.publics[i].name = m_plugin.stringbase + publics[i].name;
        }
      }
    } else if ((m_plugin.pubvars == NULL) && !strcmp(nameptr, ".pubvars")) {
      sp_file_pubvars_t *pubvars;

      pubvars = (sp_file_pubvars_t *)(base + secptr->dataoffs);
      m_plugin.num_pubvars = secptr->size / sizeof(sp_file_pubvars_t);

      if (m_plugin.num_pubvars > 0) {
        m_plugin.pubvars = new sp_pubvar_t[m_plugin.num_pubvars];

        for (uint32_t i = 0; i < m_plugin.num_pubvars; i++) {
          m_plugin.pubvars[i].name = m_plugin.stringbase + pubvars[i].name;
          m_plugin.pubvars[i].offs = (cell_t *)(m_plugin.memory + pubvars[i].address);
        }
      }
    } else if ((m_plugin.natives == NULL) && !strcmp(nameptr, ".natives")) {
      sp_file_natives_t *natives;

      natives = (sp_file_natives_t *)(base + secptr->dataoffs);
      m_plugin.num_natives = secptr->size / sizeof(sp_file_natives_t);

      if (m_plugin.num_natives > 0) {
        m_plugin.natives = new sp_native_t[m_plugin.num_natives];

        for (uint32_t i = 0; i < m_plugin.num_natives; i++) {
          m_plugin.natives[i].flags = 0;
          m_plugin.natives[i].pfn = InvalidNative;
          m_plugin.natives[i].status = SP_NATIVE_UNBOUND;
          m_plugin.natives[i].user = NULL;
          m_plugin.natives[i].name = m_plugin.stringbase + natives[i].name;
        }
      }
    } else if (!(m_plugin.debug.files) && !strcmp(nameptr, ".dbg.files")) {
      m_plugin.debug.files = (sp_fdbg_file_t *)(base + secptr->dataoffs);
    } else if (!(m_plugin.debug.lines) && !strcmp(nameptr, ".dbg.lines")) {
      m_plugin.debug.lines = (sp_fdbg_line_t *)(base + secptr->dataoffs);
    } else if (!(m_plugin.debug.symbols) && !strcmp(nameptr, ".dbg.symbols")) {
      m_plugin.debug.symbols = (sp_fdbg_symbol_t *)(base + secptr->dataoffs);
    } else if (!(m_plugin.debug.lines_num) && !strcmp(nameptr, ".dbg.info")) {
      sp_fdbg_info_t *inf = (sp_fdbg_info_t *)(base + secptr->dataoffs);
      m_plugin.debug.files_num = inf->num_files;
      m_plugin.debug.lines_num = inf->num_lines;
      m_plugin.debug.syms_num = inf->num_syms;
    } else if (!(m_plugin.debug.stringbase) && !strcmp(nameptr, ".dbg.strings")) {
      m_plugin.debug.stringbase = (const char *)(base + secptr->dataoffs);
    } else if (strcmp(nameptr, ".dbg.natives") == 0) {
      m_plugin.debug.unpacked = false;
    }

    secptr++;
    sectnum++;
  }

  if (m_plugin.pcode == NULL || m_plugin.data == NULL)
    return SP_ERROR_FILE_FORMAT;

  if ((m_plugin.flags & SP_FLAG_DEBUG) && (
    !(m_plugin.debug.files) || 
    !(m_plugin.debug.lines) || 
    !(m_plugin.debug.symbols) || 
    !(m_plugin.debug.stringbase) ))
  {
    return SP_ERROR_FILE_FORMAT;
  }

  if (m_plugin.num_publics > 0) {
    m_PubFuncs = new CFunction *[m_plugin.num_publics];
    memset(m_PubFuncs, 0, sizeof(CFunction *) * m_plugin.num_publics);
    m_PubJitFuncs = new JitFunction *[m_plugin.num_publics];
    memset(m_PubJitFuncs, 0, sizeof(JitFunction *) * m_plugin.num_publics);
  }

  MD5 md5_pcode;
  md5_pcode.update(m_plugin.pcode, m_plugin.pcode_size);
  md5_pcode.finalize();
  md5_pcode.raw_digest(m_CodeHash);
  
  MD5 md5_data;
  md5_data.update(m_plugin.data, m_plugin.data_size);
  md5_data.finalize();
  md5_data.raw_digest(m_DataHash);

  m_plugin.profiler = g_engine2.GetProfiler();
  m_pCtx = new BaseContext(this);
  co_ = g_Jit.StartCompilation(this);

  SetupFloatNativeRemapping();
  function_map_size_ = m_plugin.pcode_size / sizeof(cell_t) + 1;
  function_map_ = new JitFunction *[function_map_size_];
  memset(function_map_, 0, function_map_size_ * sizeof(JitFunction *));

  return SP_ERROR_NONE;
}

void
BaseRuntime::AddJittedFunction(JitFunction *fn)
{
  m_JitFunctions.append(fn);

  cell_t pcode_offset = fn->GetPCodeAddress();
  assert(pcode_offset % 4 == 0);

  uint32_t pcode_index = pcode_offset / 4;
  assert(pcode_index < function_map_size_);

  function_map_[pcode_index] = fn;
}

JitFunction *
BaseRuntime::GetJittedFunctionByOffset(cell_t pcode_offset)
{
  assert(pcode_offset % 4 == 0);

  uint32_t pcode_index = pcode_offset / 4;
  assert(pcode_index < function_map_size_);

  return function_map_[pcode_index];
}

int
BaseRuntime::FindNativeByName(const char *name, uint32_t *index)
{
  for (uint32_t i=0; i<m_plugin.num_natives; i++) {
    if (strcmp(m_plugin.natives[i].name, name) == 0) {
      if (index)
        *index = i;
      return SP_ERROR_NONE;
    }
  }

  return SP_ERROR_NOT_FOUND;
}

int
BaseRuntime::GetNativeByIndex(uint32_t index, sp_native_t **native)
{
  if (index >= m_plugin.num_natives)
    return SP_ERROR_INDEX;

  if (native)
    *native = &(m_plugin.natives[index]);

  return SP_ERROR_NONE;
}


uint32_t
BaseRuntime::GetNativesNum()
{
  return m_plugin.num_natives;
}

int
BaseRuntime::FindPublicByName(const char *name, uint32_t *index)
{
  int diff, high, low;
  uint32_t mid;

  high = m_plugin.num_publics - 1;
  low = 0;

  while (low <= high) {
    mid = (low + high) / 2;
    diff = strcmp(m_plugin.publics[mid].name, name);
    if (diff == 0) {
      if (index)
        *index = mid;
      return SP_ERROR_NONE;
    } else if (diff < 0) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return SP_ERROR_NOT_FOUND;
}

int
BaseRuntime::GetPublicByIndex(uint32_t index, sp_public_t **pblic)
{
  if (index >= m_plugin.num_publics)
    return SP_ERROR_INDEX;

  if (pblic)
    *pblic = &(m_plugin.publics[index]);

  return SP_ERROR_NONE;
}

uint32_t
BaseRuntime::GetPublicsNum()
{
  return m_plugin.num_publics;
}

int
BaseRuntime::GetPubvarByIndex(uint32_t index, sp_pubvar_t **pubvar)
{
  if (index >= m_plugin.num_pubvars)
    return SP_ERROR_INDEX;

  if (pubvar)
    *pubvar = &(m_plugin.pubvars[index]);

  return SP_ERROR_NONE;
}

int
BaseRuntime::FindPubvarByName(const char *name, uint32_t *index)
{
  int diff, high, low;
  uint32_t mid;

  high = m_plugin.num_pubvars - 1;
  low = 0;

  while (low <= high) {
    mid = (low + high) / 2;
    diff = strcmp(m_plugin.pubvars[mid].name, name);
    if (diff == 0) {
      if (index)
        *index = mid;
      return SP_ERROR_NONE;
    } else if (diff < 0) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return SP_ERROR_NOT_FOUND;
}

int
BaseRuntime::GetPubvarAddrs(uint32_t index, cell_t *local_addr, cell_t **phys_addr)
{
  if (index >= m_plugin.num_pubvars)
    return SP_ERROR_INDEX;

  *local_addr = (uint8_t *)m_plugin.pubvars[index].offs - m_plugin.memory;
  *phys_addr = m_plugin.pubvars[index].offs;

  return SP_ERROR_NONE;
}

uint32_t
BaseRuntime::GetPubVarsNum()
{
  return m_plugin.num_pubvars;
}

IPluginContext *
BaseRuntime::GetDefaultContext()
{
  return m_pCtx;
}

IPluginDebugInfo *
BaseRuntime::GetDebugInfo()
{
  return &m_Debug;
}

IPluginFunction *
BaseRuntime::GetFunctionById(funcid_t func_id)
{
  CFunction *pFunc = NULL;

  if (func_id & 1) {
    func_id >>= 1;
    if (func_id >= m_plugin.num_publics)
      return NULL;
    pFunc = m_PubFuncs[func_id];
    if (!pFunc) {
      m_PubFuncs[func_id] = new CFunction(this, 
        (func_id << 1) | 1,
        func_id);
      pFunc = m_PubFuncs[func_id];
    }
  }

  return pFunc;
}

IPluginFunction *
BaseRuntime::GetFunctionByName(const char *public_name)
{
  uint32_t index;

  if (FindPublicByName(public_name, &index) != SP_ERROR_NONE)
    return NULL;

  CFunction *pFunc = m_PubFuncs[index];
  if (!pFunc) {
    sp_public_t *pub = NULL;
    GetPublicByIndex(index, &pub);
    if (pub)
      m_PubFuncs[index] = new CFunction(this, (index << 1) | 1, index);
    pFunc = m_PubFuncs[index];
  }

  return pFunc;
}

bool BaseRuntime::IsDebugging()
{
  return true;
}

void BaseRuntime::SetPauseState(bool paused)
{
  if (paused)
  {
    m_plugin.run_flags |= SPFLAG_PLUGIN_PAUSED;
  }
  else
  {
    m_plugin.run_flags &= ~SPFLAG_PLUGIN_PAUSED;
  }
}

bool BaseRuntime::IsPaused()
{
  return ((m_plugin.run_flags & SPFLAG_PLUGIN_PAUSED) == SPFLAG_PLUGIN_PAUSED);
}

size_t BaseRuntime::GetMemUsage()
{
  size_t mem = 0;

  mem += sizeof(this);
  mem += sizeof(sp_plugin_t);
  mem += sizeof(BaseContext);
  mem += m_plugin.base_size;

  return mem;
}

unsigned char *BaseRuntime::GetCodeHash()
{
  return m_CodeHash;
}

unsigned char *BaseRuntime::GetDataHash()
{
  return m_DataHash;
}

BaseContext *BaseRuntime::GetBaseContext()
{
  return m_pCtx;
}

int
BaseRuntime::ApplyCompilationOptions(ICompilation *co)
{
  if (co == NULL)
    return SP_ERROR_NONE;

  co_ = g_Jit.ApplyOptions(co_, co);
  m_plugin.prof_flags = ((CompData *)co_)->profile;

  return SP_ERROR_NONE;
}

int
BaseRuntime::CreateBlank(uint32_t heastk)
{
  memset(&m_plugin, 0, sizeof(m_plugin));

  /* Align to cell_t bytes */
  heastk += sizeof(cell_t);
  heastk -= heastk % sizeof(cell_t);

  m_plugin.mem_size = heastk;
  m_plugin.memory = new uint8_t[heastk];

  m_plugin.profiler = g_engine2.GetProfiler();
  m_pCtx = new BaseContext(this);
  co_ = g_Jit.StartCompilation(this);

  return SP_ERROR_NONE;
}
