/* ---------------------- xmix_vm.c :
 * Implementation of the functions declared in xmix_vm.h
 * ------------------------------------------------------------------
 * Copyright (C) 2000 Free Software Foundation, Inc.
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *  
 */



#include "xmix_vm.h"

/* auxiliar functions */
G_INLINE_FUNC mix_address_t
get_M_(const mix_vm_t *vm, const mix_ins_t *ins);
G_INLINE_FUNC mix_word_t
get_V_(const mix_vm_t *vm, const mix_ins_t *ins);
G_INLINE_FUNC mix_device_t *
get_dev_ (mix_vm_t *vm, mix_fspec_t type);

G_INLINE_FUNC mix_address_t
get_M_(const mix_vm_t *vm, const mix_ins_t *ins)
{
  if ( ins->index == 0 )
    return ins->address;
  else
    return mix_short_add(ins->address, 
			 mix_word_to_short_fast(get_rI_(vm, ins->index)));
}

G_INLINE_FUNC mix_word_t
get_V_(const mix_vm_t *vm, const mix_ins_t *ins)
{
  return mix_word_get_field(ins->fspec, get_cell_(vm, get_M_(vm,ins)));
}

G_INLINE_FUNC mix_device_t *
get_dev_ (mix_vm_t *vm, mix_fspec_t type) 
{
  if (type >= BD_NO_) return NULL;
  if (vm->devices[type] == NULL)
    vm->devices[type] = vm->factory (type);
  return vm->devices[type];
}

/* Instruction handlers */

static gboolean 
nop_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert(ins->opcode == mix_opNOP);
  inc_loc_(vm);
  return TRUE;
}

static gboolean 
add_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_word_t val = get_V_(vm, ins);
  g_assert(ins->opcode == mix_opADD || ins->opcode == mix_opSUB);
  if ( ins->opcode == mix_opSUB ) mix_word_reverse_sign(val);
  if ( mix_word_add_and_carry(get_rA_(vm), val, NULL, &get_rA_(vm)) )
    set_over_(vm,TRUE);
  inc_loc_(vm);
  return TRUE;
}


static gboolean 
mul_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert(ins->opcode == mix_opMUL);
  mix_word_mul(get_rA_(vm), get_V_(vm,ins), &get_rA_(vm), &get_rX_(vm));
  inc_loc_(vm);
  return TRUE;
}

static gboolean 
div_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert(ins->opcode == mix_opDIV);
  if ( mix_word_div(get_rA_(vm), get_rX_(vm), get_V_(vm,ins),
		    &get_rA_(vm), &get_rX_(vm)) )
    set_over_(vm,TRUE);
  inc_loc_(vm);
  return TRUE;
}

static gboolean 
spc_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert(ins->opcode == mix_opSPC);
  
  switch (mix_ins_id_from_ins(*ins)) {
  case mix_HLT: halt_(vm, TRUE); break;
  case mix_CHAR:
    { 
      guint32 num = mix_word_magnitude(get_rA_(vm));
      mix_char_t z = mix_ascii_to_char('0');
      guint i;
      for (i = 5; 0 < i; --i, num /= 10)
	mix_word_set_byte(&get_rX_(vm), i, z + num % 10);
      for (i = 5; 0 < i; --i, num /= 10)
	mix_word_set_byte(&get_rA_(vm), i, z + num % 10);
      break;
    }
  case mix_NUM: 
    { 
      guint i;
      mix_word_t num = MIX_WORD_ZERO;
      mix_word_t ten = 10;
      for (i = 1; i <= 5; ++i) {
	mix_word_mul(ten, num, NULL, &num);
	mix_word_add_and_carry(num, mix_word_get_byte(get_rA_(vm),i)%10,
			       NULL, &num);
      }
      for (i = 1; i <= 5; ++i) {
	mix_word_mul(ten, num, NULL, &num);
	mix_word_add_and_carry(num, mix_word_get_byte(get_rX_(vm),i)%10,
			       NULL, &num);
      }
      set_rA_(vm,
	      mix_word_is_negative(get_rA_(vm)) ? mix_word_negative(num):num);
      break;
    }
  default: return FALSE;
  }
  inc_loc_(vm);
  return TRUE;
}

static gboolean 
sla_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_short_t n = get_M_(vm,ins);
  
  g_assert(ins->opcode == mix_opSLx);
  g_return_val_if_fail(mix_short_is_positive(n), FALSE);
  
  switch ( mix_ins_id_from_ins(*ins) ) {
  case mix_SLA:
    mix_word_shift_left(get_rA_(vm), MIX_WORD_ZERO, n, &get_rA_(vm), NULL);
    break;
  case mix_SRA:
    mix_word_shift_right(get_rA_(vm), MIX_WORD_ZERO, n, &get_rA_(vm), NULL);
    break;
  case mix_SLAX:
    mix_word_shift_left(get_rA_(vm), get_rX_(vm), n, 
			&get_rA_(vm), &get_rX_(vm));
    break;
  case mix_SRAX:
    mix_word_shift_right(get_rA_(vm), get_rX_(vm), n, 
			&get_rA_(vm), &get_rX_(vm));
    break;
  case mix_SLC:
    mix_word_shift_left_circular(get_rA_(vm), get_rX_(vm), n, 
				 &get_rA_(vm), &get_rX_(vm));
    break;
  case mix_SRC:
    mix_word_shift_right_circular(get_rA_(vm), get_rX_(vm), n, 
				  &get_rA_(vm), &get_rX_(vm));
    break;
  default:
    return FALSE;
  }
  
  inc_loc_(vm);
  return TRUE;
}

static gboolean 
mov_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_short_t from = get_M_(vm,ins), 
    to = mix_word_to_short_fast(get_rI_(vm,1));
  guint k, delta = ins->fspec;
  
  g_assert(ins->opcode == mix_opMOVE);
  g_return_val_if_fail(mix_short_is_positive(from), FALSE);
  g_return_val_if_fail(mix_short_is_positive(to), FALSE);
  g_return_val_if_fail(MEMOK_(from + delta -1), FALSE);
  g_return_val_if_fail(MEMOK_(to + delta - 1), FALSE);
  
  for (k = 0; k < delta; ++k)
    set_cell_(vm, to+k, get_cell_(vm, from+k));
  set_rI_(vm, 1, to+delta);
  inc_loc_(vm);
  return TRUE;
}

static gboolean
lda_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  gint r = 0;
  mix_word_t val;
  mix_ins_id_t id = mix_ins_id_from_ins(*ins);
    
  g_assert(id >= mix_LDA && id <= mix_LDXN);
  
  val = get_V_(vm, ins);
  if ( id > mix_LDX && mix_fspec_left(ins->fspec) == 0) 
    mix_word_reverse_sign(val);
  if ( (id > mix_LDA && id < mix_LDX) || (id > mix_LDAN && id < mix_LDXN) )
    /* Bytes 1-3 of I regs are always null */
    val = mix_word_set_field(mix_fspec_new(1,3),MIX_WORD_ZERO,val);
        
  switch (id) {
  case mix_LDA: case mix_LDAN: r = A_; break;
  case mix_LDX: case mix_LDXN: r = X_; break;
  case mix_LD1: case mix_LD1N: r = I1_; break;
  case mix_LD2: case mix_LD2N: r = I2_; break;
  case mix_LD3: case mix_LD3N: r = I3_; break;
  case mix_LD4: case mix_LD4N: r = I4_; break;
  case mix_LD5: case mix_LD5N: r = I5_; break;
  case mix_LD6: case mix_LD6N: r = I6_; break;
  default: g_assert_not_reached();
  }
  set_reg_(vm, r, val);
  inc_loc_(vm);
  return TRUE;
}


static gboolean
sta_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_address_t addr = get_M_(vm, ins);
  mix_ins_id_t id = mix_ins_id_from_ins(*ins);
  mix_word_t from;
  
  g_assert(id >= mix_STA && id <= mix_STZ);
  switch (id) {
  case mix_STA: from = get_rA_(vm); break;
  case mix_STX: from = get_rX_(vm); break;
  case mix_STJ: from = get_rJ_(vm); break;
  case mix_STZ: from = MIX_WORD_ZERO; break;
  default: from = get_rI_(vm, id - mix_ST1 + 1); break;
  }
  set_cell_(vm, addr,
	    mix_word_store_field(ins->fspec, from,  get_cell_(vm, addr)));
  inc_loc_(vm);
  return TRUE;
}

static gboolean 
jbs_handler_ (mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert (ins->opcode == mix_opJBUS);
  g_return_val_if_fail (ins->fspec < BD_NO_, TRUE);
  g_return_val_if_fail (get_dev_ (vm, ins->fspec) != NULL, TRUE);
  if ( mix_device_busy (get_dev_ (vm, ins->fspec)) ) {
      set_rJ_(vm, get_loc_ (vm));
      set_loc_ (vm, get_M_ (vm, ins));
  } else inc_loc_ (vm);
  return TRUE;
}

static gboolean 
ioc_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_address_t addr;
  mix_device_t *dev;
  gboolean result;
  
  g_assert(ins->opcode == mix_opIOC);
  addr = get_M_ (vm, ins);
  g_return_val_if_fail (ins->fspec < BD_NO_, TRUE);
  dev = get_dev_ (vm, ins->fspec);
  g_return_val_if_fail (dev != NULL, TRUE);
  result =
    mix_device_ioc (dev, addr);
    
  inc_loc_(vm);
  return result;
}

static gboolean 
inp_handler_ (mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_address_t addr;
  mix_device_t *dev;
  gboolean result;
  
  g_assert(ins->opcode == mix_opIN);
  addr = get_M_ (vm, ins);
  g_return_val_if_fail (ins->fspec < BD_NO_, TRUE);
  g_return_val_if_fail (MEMOK_(addr), TRUE);
  dev = get_dev_ (vm, ins->fspec);
  g_return_val_if_fail ( dev != NULL, TRUE);
  g_return_val_if_fail (MEM_CELLS_NO_ - addr > mix_device_block_size(dev),
			TRUE);
  result =
    mix_device_read (dev, get_cell_ptr_ (vm, addr));
    
  inc_loc_(vm);
  return result;
}

static gboolean 
out_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_address_t addr;
  mix_device_t *dev;
  gboolean result;
  
  g_assert(ins->opcode == mix_opOUT);
  addr = get_M_ (vm, ins);
  g_return_val_if_fail (ins->fspec < BD_NO_, TRUE);
  g_return_val_if_fail (MEMOK_(addr), TRUE);
  dev = get_dev_ (vm, ins->fspec);
  g_return_val_if_fail ( dev != NULL, TRUE);
  g_return_val_if_fail (MEM_CELLS_NO_ - addr > mix_device_block_size(dev),
			TRUE);
  result =
    mix_device_write (dev, get_cell_ptr_ (vm, addr));
  
  inc_loc_(vm);
  return result;
}

static gboolean 
jrd_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert(ins->opcode == mix_opJRED);
  g_return_val_if_fail (ins->fspec < BD_NO_, TRUE);
  g_return_val_if_fail (get_dev_ (vm, ins->fspec) != NULL, TRUE);
  inc_loc_ (vm);
  if ( !mix_device_busy (get_dev_ (vm, ins->fspec)) ) 
    {
      set_rJ_(vm, get_loc_(vm));
      set_loc_ (vm, get_M_ (vm, ins));
    }
  return TRUE;
}

static gboolean
jmp_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  gboolean jump = FALSE;
  mix_address_t addr = get_M_(vm, ins);
  mix_ins_id_t id = mix_ins_id_from_ins(*ins);
  
  g_assert(ins->opcode == mix_opJMP);
  g_return_val_if_fail(MEMOK_(addr), FALSE);
  switch ( id ) {
  case mix_JMP:
  case mix_JSJ:
    jump = TRUE;
    break;
  case mix_JOV:
    jump = get_over_(vm);
    if (jump) set_over_(vm, FALSE);
    break;
  case mix_JNOV:
    jump = !get_over_(vm);
    set_over_(vm, FALSE);
    break;
  case mix_JL:
    jump = ( get_cmp_(vm) == mix_LESS );
    break;
  case mix_JE:
    jump = ( get_cmp_(vm) == mix_EQ );
    break;
  case mix_JG:
    jump = ( get_cmp_(vm) == mix_GREAT );
    break;
  case mix_JGE:
    jump = ( get_cmp_(vm) != mix_LESS );
    break;
  case mix_JNE:
    jump = ( get_cmp_(vm) != mix_EQ );
    break;
  case mix_JLE:
    jump = ( get_cmp_(vm) != mix_GREAT );
    break;
  default:
    return FALSE;
  }
  
  inc_loc_(vm);
  if ( jump ) {
    if ( id != mix_JSJ ) set_rJ_(vm, get_loc_(vm));
    set_loc_(vm, addr);
  }
  return TRUE;
}

static gboolean
jpx_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  gboolean jump = FALSE;
  mix_address_t addr = get_M_(vm, ins);
  mix_ins_id_t id = mix_ins_id_from_ins(*ins);
  mix_word_t val;
    
  g_assert(ins->opcode >= mix_opJAx || ins->opcode <= mix_opJXx);
  g_return_val_if_fail(MEMOK_(addr), FALSE);
  
  switch (ins->opcode) {
  case mix_opJAx: val = get_rA_(vm); break;
  case mix_opJXx: val = get_rX_(vm); break;
  default: val = get_rI_(vm, ins->opcode - mix_opJAx);
  }
  
  switch (id) {
  case mix_JAN: case mix_JXN:
  case mix_J1N: case mix_J2N: case mix_J3N:
  case mix_J4N: case mix_J5N: case mix_J6N:
    jump = mix_word_is_negative(val) && val != MIX_WORD_MINUS_ZERO;
    break;
  case mix_JAZ: case mix_JXZ:
  case mix_J1Z: case mix_J2Z: case mix_J3Z:
  case mix_J4Z: case mix_J5Z: case mix_J6Z:
    jump = mix_word_magnitude(val) == MIX_WORD_ZERO;
    break;
  case mix_JAP: case mix_JXP:
  case mix_J1P: case mix_J2P: case mix_J3P:
  case mix_J4P: case mix_J5P: case mix_J6P:
    jump = mix_word_is_positive(val) && val != MIX_WORD_ZERO;
    break;
  case mix_JANN: case mix_JXNN:
  case mix_J1NN: case mix_J2NN: case mix_J3NN:
  case mix_J4NN: case mix_J5NN: case mix_J6NN:
    jump = mix_word_magnitude(val) == MIX_WORD_ZERO 
      || mix_word_is_positive(val);
    break;
  case mix_JANZ: case mix_JXNZ:
  case mix_J1NZ: case mix_J2NZ: case mix_J3NZ:
  case mix_J4NZ: case mix_J5NZ: case mix_J6NZ:
    jump = mix_word_magnitude(val) != MIX_WORD_ZERO;
    break;
  case mix_JANP: case mix_JXNP:
  case mix_J1NP: case mix_J2NP: case mix_J3NP:
  case mix_J4NP: case mix_J5NP: case mix_J6NP:
    jump = mix_word_magnitude(val) == MIX_WORD_ZERO
      || mix_word_is_negative(val);
    break;
  default:
    return FALSE;
  }
  
  inc_loc_(vm);
  if ( jump ) {
    set_rJ_(vm, get_loc_(vm));
    set_loc_(vm, addr);
  }
  return TRUE;
}

static gboolean
ina_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  mix_word_t val = mix_short_to_word_fast(get_M_(vm, ins));
  mix_ins_id_t id = mix_ins_id_from_ins(*ins);
  gint r;
  
  g_assert(id >= mix_INCA && id <= mix_ENNX);
  
  switch (ins->opcode) {
  case mix_opINCA: r = A_; break;
  case mix_opINCX: r = X_; break;
  default: r = I1_ + ins->opcode - mix_opINC1;
  }
  
  switch (id) {
  case mix_ENTA: case mix_ENTX:
    break;
  case mix_ENT1: case mix_ENT2: case mix_ENT3: 
  case mix_ENT4: case mix_ENT5: case mix_ENT6: 
    val = mix_word_set_field(mix_fspec_new(1,3), MIX_WORD_ZERO, val);
    break;
  case mix_INCA: case mix_INCX:
    if ( mix_word_add_and_carry(val, get_reg_(vm, r), NULL, &val) )
      set_over_(vm, TRUE);
    break;
  case mix_INC1: case mix_INC2: case mix_INC3: 
  case mix_INC4: case mix_INC5: case mix_INC6:
    mix_word_add_and_carry(val, get_reg_(vm,r), NULL, &val);
    val = mix_word_set_field(mix_fspec_new(1,3), MIX_WORD_ZERO, val);
    break;
  case mix_DECA: case mix_DECX:
    if ( mix_word_add_and_carry(mix_word_negative(val), get_reg_(vm, r),
				NULL, &val) )
      set_over_(vm, TRUE);
    break;
  case mix_DEC1: case mix_DEC2: case mix_DEC3: 
  case mix_DEC4: case mix_DEC5: case mix_DEC6: 
    mix_word_add_and_carry(mix_word_negative(val), get_reg_(vm,r), NULL, &val);
    val = mix_word_set_field(mix_fspec_new(1,3), MIX_WORD_ZERO, val);
    break;
  case mix_ENN1: case mix_ENN2: case mix_ENN3: 
  case mix_ENN4: case mix_ENN5: case mix_ENN6: 
    val = mix_word_set_field(mix_fspec_new(1,3), MIX_WORD_ZERO, val);
    /* fallthrough */
  case mix_ENNA: case mix_ENNX:
    mix_word_reverse_sign(val);
    break;
  default:
    return FALSE;
  }
  set_reg_(vm, r, val);
  inc_loc_(vm);
  return TRUE;
}

static gboolean
cmp_handler_(mix_vm_t *vm, const mix_ins_t *ins)
{
  g_assert(ins->opcode >= mix_opCMPA && ins->opcode <= mix_opCMPX);

  if ( ins->fspec == 0 ) { /* shortcut: +0 == -0 */
    set_cmp_(vm, mix_EQ);
  } else {
    mix_word_t v = get_V_(vm, ins);
    mix_word_t reg;
    mix_cmpflag_t flag;
    
    switch (ins->opcode) {
    case mix_opCMPA:
      reg = get_rA_(vm);
      break;
    case mix_opCMPX:
      reg = get_rX_(vm);
      break;
    default:
      reg = get_rI_(vm, ins->opcode - mix_opCMPA);
      break;
    }
    reg = mix_word_get_field(ins->fspec, reg);
    mix_word_add_and_carry(reg, mix_word_negative(v), NULL, &reg);
    if ( mix_word_magnitude(reg) == MIX_WORD_ZERO ) flag = mix_EQ;
    else if ( mix_word_is_positive(reg) ) flag = mix_GREAT;
    else flag = mix_LESS;
    set_cmp_(vm, flag);
  }
  inc_loc_(vm);
  return TRUE;
}

ins_handler_t_ ins_handlers_[MIX_BYTE_MAX + 1] = { 
  nop_handler_, add_handler_, add_handler_, mul_handler_, div_handler_,
  spc_handler_, sla_handler_, mov_handler_, lda_handler_, lda_handler_,
  lda_handler_, lda_handler_, lda_handler_, lda_handler_, lda_handler_,
  lda_handler_, lda_handler_, lda_handler_, lda_handler_, lda_handler_,
  lda_handler_, lda_handler_, lda_handler_, lda_handler_, sta_handler_,
  sta_handler_, sta_handler_, sta_handler_, sta_handler_, sta_handler_,
  sta_handler_, sta_handler_, sta_handler_, sta_handler_, jbs_handler_,
  ioc_handler_, inp_handler_, out_handler_, jrd_handler_, jmp_handler_,
  jpx_handler_, jpx_handler_, jpx_handler_, jpx_handler_, jpx_handler_,
  jpx_handler_, jpx_handler_, jpx_handler_, ina_handler_, ina_handler_,
  ina_handler_, ina_handler_, ina_handler_, ina_handler_, ina_handler_,
  ina_handler_, cmp_handler_, cmp_handler_, cmp_handler_, cmp_handler_,
  cmp_handler_, cmp_handler_, cmp_handler_, cmp_handler_, 
};