
#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <orc/orcprogram.h>
#include <orc/orcutils-private.h>
#include <orc/orcdebug.h>

/**
 * SECTION:orcexecutor
 * @title: OrcExecutor
 * @short_description: Running Orc programs
 */

#define CHUNK_SIZE 16

OrcExecutor *
orc_executor_new (OrcProgram *program)
{
  OrcExecutor *ex;

  ex = orc_malloc(sizeof(OrcExecutor));
  memset(ex,0,sizeof(OrcExecutor));

  orc_executor_set_program (ex, program);

  return ex;
}

void
orc_executor_free (OrcExecutor *ex)
{
  free (ex);
}

void
orc_executor_run (OrcExecutor *ex)
{
  OrcExecutorFunc func = NULL;

  if (ex->program) {
    func = ex->program->code_exec;
  } else {
    OrcCode *code = (OrcCode *)ex->arrays[ORC_VAR_A2];
    func = code->exec;
  }
  if (func) {
    func (ex);
    /* ORC_ERROR("counters %d %d %d", ex->counter1, ex->counter2, ex->counter3); */
  } else {
    orc_executor_emulate (ex);
  }
}

void
orc_executor_run_backup (OrcExecutor *ex)
{
  OrcExecutorFunc func = NULL;

  if (ex->program) {
    func = ex->program->backup_func;
  } else {
    OrcCode *code = (OrcCode *)ex->arrays[ORC_VAR_A2];
    func = code->exec;
  }
  if (func) {
    func (ex);
    /* ORC_ERROR("counters %d %d %d", ex->counter1, ex->counter2, ex->counter3); */
  } else {
    orc_executor_emulate (ex);
  }
}

void
orc_executor_set_program (OrcExecutor *ex, OrcProgram *program)
{
  ex->program = program;
  if (program->code_exec) {
    ex->arrays[ORC_VAR_A1] = (void *)program->code_exec;
  } else {
    ex->arrays[ORC_VAR_A1] = (void *)orc_executor_emulate;
  }
  ex->arrays[ORC_VAR_A2] = program->orccode;
}

void
orc_executor_set_array (OrcExecutor *ex, int var, void *ptr)
{
  if (ex->program->vars[var].alignment &&
      (ORC_PTR_TO_INT (ptr) % ex->program->vars[var].alignment)) {
    ORC_ERROR ("Pointer %p to an array with a different alignment of %d",
        ptr, ex->program->vars[var].alignment);
  }
  ex->arrays[var] = ptr;
}

void
orc_executor_set_stride (OrcExecutor *ex, int var, int stride)
{
  ex->params[var] = stride;
}

void
orc_executor_set_array_str (OrcExecutor *ex, const char *name, void *ptr)
{
  int var;
  var = orc_program_find_var_by_name (ex->program, name);
  if (var >= 0)
    ex->arrays[var] = ptr;
}

void
orc_executor_set_param (OrcExecutor *ex, int var, int value)
{
  ex->params[var] = value;
}

void
orc_executor_set_param_float (OrcExecutor *ex, int var, float value)
{
  orc_union32 u;
  u.f = value;
  ex->params[var] = u.i;
}

void
orc_executor_set_param_int64 (OrcExecutor *ex, int var, orc_int64 value)
{
  ex->params[var] = value & 0xffffffff;
  ex->params[var + (ORC_N_PARAMS)] = (value >> 32) & 0xffffffff;
}

void
orc_executor_set_param_double (OrcExecutor *ex, int var, double value)
{
  orc_union64 u;
  u.f = value;
  ex->params[var] = u.i & 0xffffffff;
  ex->params[var + (ORC_N_PARAMS)] = (u.i >> 32) & 0xffffffff;
}

void
orc_executor_set_param_str (OrcExecutor *ex, const char *name, int value)
{
  int var;
  var = orc_program_find_var_by_name (ex->program, name);
  if (var >= 0)
    ex->params[var] = value;
}

int
orc_executor_get_accumulator (OrcExecutor *ex, int var)
{
  return ex->accumulators[var - ORC_VAR_A1];
}

int
orc_executor_get_accumulator_str (OrcExecutor *ex, const char *name)
{
  int var;
  var = orc_program_find_var_by_name (ex->program, name);
  if (var >= 0)
    return ex->accumulators[var];
  return -1;
}

void
orc_executor_set_n (OrcExecutor *ex, int n)
{
  ex->n = n;
}

void
orc_executor_set_m (OrcExecutor *ex, int m)
{
  ORC_EXECUTOR_M(ex) = m;
}

static void
load_constant (void *data, int size, orc_uint64 value)
{
  switch (size) {
    case 1:
      {
        int l;
        orc_int8 *d = data;
        for(l=0;l<CHUNK_SIZE;l++) {
          d[l] = value;
        }
      }
      break;
    case 2:
      {
        int l;
        orc_int16 *d = data;
        for(l=0;l<CHUNK_SIZE;l++) {
          d[l] = value;
        }
      }
      break;
    case 4:
      {
        int l;
        orc_int32 *d = data;
        for(l=0;l<CHUNK_SIZE;l++) {
          d[l] = value;
        }
      }
      break;
    case 8:
      {
        int l;
        orc_int64 *d = data;
        for(l=0;l<CHUNK_SIZE;l++) {
          d[l] = value;
        }
      }
      break;
    default:
      ORC_ASSERT(0);
  }

}


void
orc_executor_emulate (OrcExecutor *ex)
{
  int i;
  int j;
  int k;
  int m, m_index;
  OrcCode *code;
  OrcInstruction *insn;
  OrcStaticOpcode *opcode;
  OrcOpcodeExecutor *opcode_ex;
  char name_placeholder[40];
  const char* name = name_placeholder;
  void *tmpspace[ORC_N_COMPILER_VARIABLES] = {0};

  memset(name_placeholder, '\0', sizeof(name_placeholder));

  if (ex->program) {
    code = ex->program->orccode;
    if (ex->program->name == NULL) {
      sprintf(name_placeholder, "<unnamed program @ %p>", ex->program);
    } else {
      name = ex->program->name;
    }
  } else {
    code = (OrcCode *)ex->arrays[ORC_VAR_A2];
    sprintf(name_placeholder, "<unnamed source @ %p>", ex);
  }

  ex->accumulators[0] = 0;
  ex->accumulators[1] = 0;
  ex->accumulators[2] = 0;
  ex->accumulators[3] = 0;

  ORC_DEBUG("emulating");

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

  if (code == NULL) {
    ORC_ERROR("attempt to run program that failed to compile");
    ORC_ASSERT(0);
  }

  if (code->is_2d) {
    m = ORC_EXECUTOR_M(ex);
  } else {
    m = 1;
  }

  for(i=0;i<ORC_N_COMPILER_VARIABLES;i++){
    OrcCodeVariable *var = code->vars + i;

    if (var->size) {
      tmpspace[i] = orc_malloc(ORC_MAX_VAR_SIZE * CHUNK_SIZE);
    }
  }

  opcode_ex = orc_malloc(sizeof(OrcOpcodeExecutor)*code->n_insns);

  for(j=0;j<code->n_insns;j++){
    insn = code->insns + j;
    opcode = insn->opcode;

    opcode_ex[j].emulateN = opcode->emulateN;
    opcode_ex[j].shift = 0;
    if (insn->flags & ORC_INSTRUCTION_FLAG_X2) {
      opcode_ex[j].shift = 1;
    } else if (insn->flags & ORC_INSTRUCTION_FLAG_X4) {
      opcode_ex[j].shift = 2;
    }

    for(k=0;k<ORC_STATIC_OPCODE_N_SRC;k++) {
      OrcCodeVariable *var = code->vars + insn->src_args[k];
      if (opcode->src_size[k] == 0) continue;

      if (var->vartype == ORC_VAR_TYPE_CONST) {
        opcode_ex[j].src_ptrs[k] = tmpspace[insn->src_args[k]];
        /* FIXME hack */
        load_constant (tmpspace[insn->src_args[k]], 8,
            var->value.i);
      } else if (var->vartype == ORC_VAR_TYPE_PARAM) {
        opcode_ex[j].src_ptrs[k] = tmpspace[insn->src_args[k]];
        /* FIXME hack */
        if (var->size == 8) {
          load_constant (tmpspace[insn->src_args[k]], 8,
              (orc_uint64)(orc_uint32)ex->params[insn->src_args[k]] |
              (((orc_uint64)(orc_uint32)ex->params[insn->src_args[k] +
               (ORC_N_PARAMS)])<<32));
        } else {
          load_constant (tmpspace[insn->src_args[k]], 8,
              ex->params[insn->src_args[k]]);
        }
      } else if (var->vartype == ORC_VAR_TYPE_TEMP) {
        opcode_ex[j].src_ptrs[k] = tmpspace[insn->src_args[k]];
      } else if (var->vartype == ORC_VAR_TYPE_SRC) {
        if (ORC_PTR_TO_INT(ex->arrays[insn->src_args[k]]) & (var->size - 1)) {
          ORC_ERROR("Unaligned array for src%d, program %s",
              (insn->src_args[k]-ORC_VAR_S1), name);
        }
        opcode_ex[j].src_ptrs[k] = ex->arrays[insn->src_args[k]];
      } else if (var->vartype == ORC_VAR_TYPE_DEST) {
        if (ORC_PTR_TO_INT(ex->arrays[insn->src_args[k]]) & (var->size - 1)) {
          ORC_ERROR("Unaligned array for dest%d, program %s",
              (insn->src_args[k]-ORC_VAR_D1), name);
        }
        opcode_ex[j].src_ptrs[k] = ex->arrays[insn->src_args[k]];
      }
    }
    for(k=0;k<ORC_STATIC_OPCODE_N_DEST;k++) {
      OrcCodeVariable *var = code->vars + insn->dest_args[k];
      if (opcode->dest_size[k] == 0) continue;

      if (var->vartype == ORC_VAR_TYPE_TEMP) {
        ORC_DEBUG("dest vartype tmp %d", insn->dest_args[k]);
        opcode_ex[j].dest_ptrs[k] = tmpspace[insn->dest_args[k]];
      } else if (var->vartype == ORC_VAR_TYPE_ACCUMULATOR) {
        opcode_ex[j].dest_ptrs[k] =
          &ex->accumulators[insn->dest_args[k] - ORC_VAR_A1];
      } else if (var->vartype == ORC_VAR_TYPE_DEST) {
        if (ORC_PTR_TO_INT(ex->arrays[insn->dest_args[k]]) & (var->size - 1)) {
          ORC_ERROR("Unaligned array for dest%d, program %s",
              (insn->dest_args[k]-ORC_VAR_D1), name);
        }
        opcode_ex[j].dest_ptrs[k] = ex->arrays[insn->dest_args[k]];
      }
    }
    ORC_DEBUG("opcode %s %p %p %p", opcode->name,
        opcode_ex[j].dest_ptrs[0], opcode_ex[j].src_ptrs[0],
        opcode_ex[j].src_ptrs[1]);
  }
  
  ORC_DEBUG("src ptr %p stride %d", ex->arrays[ORC_VAR_S1], ex->params[ORC_VAR_S1]);
  for(m_index=0;m_index<m;m_index++){
    ORC_DEBUG("m_index %d m %d", m_index, m);

    for(j=0;j<code->n_insns;j++){
      insn = code->insns + j;
      opcode = insn->opcode;

      for(k=0;k<ORC_STATIC_OPCODE_N_SRC;k++) {
        OrcCodeVariable *var = code->vars + insn->src_args[k];
        if (opcode->src_size[k] == 0) continue;

        if (var->vartype == ORC_VAR_TYPE_SRC) {
          opcode_ex[j].src_ptrs[k] =
            ORC_PTR_OFFSET(ex->arrays[insn->src_args[k]],
                ex->params[insn->src_args[k]]*m_index);
        } else if (var->vartype == ORC_VAR_TYPE_DEST) {
          opcode_ex[j].src_ptrs[k] =
            ORC_PTR_OFFSET(ex->arrays[insn->src_args[k]],
                ex->params[insn->src_args[k]]*m_index);
        }
      }
      for(k=0;k<ORC_STATIC_OPCODE_N_DEST;k++) {
        OrcCodeVariable *var = code->vars + insn->dest_args[k];
        if (opcode->dest_size[k] == 0) continue;

        if (var->vartype == ORC_VAR_TYPE_DEST) {
          opcode_ex[j].dest_ptrs[k] =
            ORC_PTR_OFFSET(ex->arrays[insn->dest_args[k]],
                ex->params[insn->dest_args[k]]*m_index);
        }
      }
    }

    for(i=0;i<ex->n;i+=CHUNK_SIZE){
      for(j=0;j<code->n_insns;j++){
        if (ex->n - i >= CHUNK_SIZE) {
          opcode_ex[j].emulateN (opcode_ex + j, i, CHUNK_SIZE << opcode_ex[j].shift);
        } else {
          opcode_ex[j].emulateN (opcode_ex + j, i, (ex->n - i) << opcode_ex[j].shift);
        }
      }
    }
  }

  free (opcode_ex);
  for(i=0;i<ORC_N_COMPILER_VARIABLES;i++){
    if (tmpspace[i]) free (tmpspace[i]);
  }
}


