/* exec.c: Glulxe code for program execution. The main interpreter loop.
    Designed by Andrew Plotkin <erkyrath@eblong.com>
    http://eblong.com/zarf/glulx/index.html
*/

#include "glk.h"
#include "glulxe.h"
#include "opcodes.h"

#ifdef FLOAT_SUPPORT
#include <math.h>

#ifdef DOUBLE_SUPPORT   /* Inside FLOAT_SUPPORT! */
/* A couple of macros which test a pair of glui32 words as a double */
#define DOUBLE_PAIR_ISINF(vhi, vlo) (((vhi) == 0x7FF00000 || (vhi) == 0xFFF00000) && (vlo) == 0)
#define DOUBLE_PAIR_ISNAN(vhi, vlo) (((vhi) & 0x7FF00000) == 0x7FF00000 && (((vhi) & 0xFFFFF) != 0 || (vlo) != 0))
#endif /* DOUBLE_SUPPORT */

#endif /* FLOAT_SUPPORT */

/* execute_loop():
   The main interpreter loop. This repeats until the program is done.
*/
void execute_loop()
{
  int done_executing = FALSE;
  int ix;
  glui32 opcode;
  const operandlist_t *oplist;
  oparg_t inst[MAX_OPERANDS];
  glui32 value, addr, val0, val1;
  glsi32 vals0, vals1;
  glui32 *arglist;
  glui32 arglistfix[3];
  
#ifdef FLOAT_SUPPORT
  gfloat32 valf, valf1, valf2;
#ifdef DOUBLE_SUPPORT   /* Inside FLOAT_SUPPORT! */
  glui32 val0hi, val0lo, val1hi, val1lo;
  gfloat64 vald, vald1, vald2;
#endif /* DOUBLE_SUPPORT */
#endif /* FLOAT_SUPPORT */

  while (!done_executing) {

    profile_tick();
    debugger_tick();
    /* Do OS-specific processing, if appropriate. */
    glk_tick();
    
    /* Stash the current opcode's address, in case the interpreter needs to serialize the VM state out-of-band. */
    prevpc = pc;
    
    /* Fetch the opcode number. */
    opcode = Mem1(pc);
    pc++;
    if (opcode & 0x80) {
      /* More than one-byte opcode. */
      if (opcode & 0x40) {
        /* Four-byte opcode */
        opcode &= 0x3F;
        opcode = (opcode << 8) | Mem1(pc);
        pc++;
        opcode = (opcode << 8) | Mem1(pc);
        pc++;
        opcode = (opcode << 8) | Mem1(pc);
        pc++;
      }
      else {
        /* Two-byte opcode */
        opcode &= 0x7F;
        opcode = (opcode << 8) | Mem1(pc);
        pc++;
      }
    }

    /* Now we have an opcode number. */
    
    /* Fetch the structure that describes how the operands for this
       opcode are arranged. This is a pointer to an immutable, 
       static object. */
    if (opcode < 0x80)
      oplist = fast_operandlist[opcode];
    else
      oplist = lookup_operandlist(opcode);

    if (!oplist)
      fatal_error_i("Encountered unknown opcode.", opcode);

    /* Based on the oplist structure, load the actual operand values
       into inst. This moves the PC up to the end of the instruction. */
    parse_operands(inst, oplist);

    /* Perform the opcode. This switch statement is split in two, based
       on some paranoid suspicions about the ability of compilers to
       optimize large-range switches. Ignore that. */

    if (opcode < 0x80) {

      switch (opcode) {

      case op_nop:
        break;

      case op_add:
        value = inst[0].value + inst[1].value;
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_sub:
        value = inst[0].value - inst[1].value;
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_mul:
        value = inst[0].value * inst[1].value;
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_div:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals1 == 0)
          fatal_error("Division by zero.");
        /* Since C doesn't guarantee the results of division of negative
           numbers, we carefully convert everything to positive values
           first. They have to be unsigned values, too, otherwise the
           0x80000000 case goes wonky. */
        if (vals0 < 0) {
          val0 = (-(glui32)vals0);
          if (vals1 < 0) {
            val1 = (-(glui32)vals1);
            value = val0 / val1;
          }
          else {
            val1 = vals1;
            value = -(val0 / val1);
          }
        }
        else {
          val0 = vals0;
          if (vals1 < 0) {
            val1 = (-(glui32)vals1);
            value = -(val0 / val1);
          }
          else {
            val1 = vals1;
            value = val0 / val1;
          }
        }
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_mod:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals1 == 0)
          fatal_error("Division by zero doing remainder.");
        if (vals1 < 0) {
            val1 = -(glui32)vals1;
        }
        else {
            val1 = vals1;
        }
        if (vals0 < 0) {
          val0 = (-(glui32)vals0);
          value = -(val0 % val1);
        }
        else {
          val0 = vals0;
          value = val0 % val1;
        }
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_neg:
        vals0 = inst[0].value;
        value = (-(glui32)vals0);
        store_operand(inst[1].desttype, inst[1].value, value);
        break;

      case op_bitand:
        value = (inst[0].value & inst[1].value);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_bitor:
        value = (inst[0].value | inst[1].value);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_bitxor:
        value = (inst[0].value ^ inst[1].value);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_bitnot:
        value = ~(inst[0].value);
        store_operand(inst[1].desttype, inst[1].value, value);
        break;

      case op_shiftl:
        vals0 = inst[1].value;
        if (vals0 < 0 || vals0 >= 32)
          value = 0;
        else
          value = ((glui32)(inst[0].value) << (glui32)vals0);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_ushiftr:
        vals0 = inst[1].value;
        if (vals0 < 0 || vals0 >= 32)
          value = 0;
        else
          value = ((glui32)(inst[0].value) >> (glui32)vals0);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_sshiftr:
        vals0 = inst[1].value;
        if (vals0 < 0 || vals0 >= 32) {
          if (inst[0].value & 0x80000000)
            value = 0xFFFFFFFF;
          else
            value = 0;
        }
        else {
          /* This is somewhat foolhardy -- C doesn't guarantee that
             right-shifting a signed value replicates the sign bit.
             We'll assume it for now. */
          value = ((glsi32)(inst[0].value) >> (glsi32)vals0);
        }
        store_operand(inst[2].desttype, inst[2].value, value);
        break;

      case op_jump:
        value = inst[0].value;
        /* fall through to PerformJump label. */

      PerformJump: /* goto label for successful jumping... ironic, no? */
        if (value == 0 || value == 1) {
          /* Return from function. This is exactly what happens in
             return_op, but it's only a few lines of code, so I won't
             bother with a "goto". */
          leave_function();
          if (stackptr == 0) {
            done_executing = TRUE;
            break;
          }
          pop_callstub(value); /* zero or one */
        }
        else {
          /* Branch to a new PC value. */
          pc = (pc + value - 2);
        }
        break;

      case op_jz:
        if (inst[0].value == 0) {
          value = inst[1].value;
          goto PerformJump;
        }
        break;
      case op_jnz:
        if (inst[0].value != 0) {
          value = inst[1].value;
          goto PerformJump;
        }
        break;
      case op_jeq:
        if (inst[0].value == inst[1].value) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jne:
        if (inst[0].value != inst[1].value) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jlt:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals0 < vals1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jgt:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals0 > vals1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jle:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals0 <= vals1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jge:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals0 >= vals1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jltu:
        val0 = inst[0].value;
        val1 = inst[1].value;
        if (val0 < val1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jgtu:
        val0 = inst[0].value;
        val1 = inst[1].value;
        if (val0 > val1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jleu:
        val0 = inst[0].value;
        val1 = inst[1].value;
        if (val0 <= val1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jgeu:
        val0 = inst[0].value;
        val1 = inst[1].value;
        if (val0 >= val1) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;

      case op_call:
        value = inst[1].value;
        arglist = pop_arguments(value, 0);
        push_callstub(inst[2].desttype, inst[2].value);
        enter_function(inst[0].value, value, arglist);
        break;
      case op_return:
        leave_function();
        if (stackptr == 0) {
          done_executing = TRUE;
          break;
        }
        pop_callstub(inst[0].value);
        break;
      case op_tailcall:
        value = inst[1].value;
        arglist = pop_arguments(value, 0);
        leave_function();
        enter_function(inst[0].value, value, arglist);
        break;

      case op_catch:
        push_callstub(inst[0].desttype, inst[0].value);
        value = inst[1].value;
        val0 = stackptr;
        store_operand(inst[0].desttype, inst[0].value, val0);
        goto PerformJump;
        break;
      case op_throw:
        profile_fail("throw");
        value = inst[0].value;
        stackptr = inst[1].value;
        pop_callstub(value);
        break;

      case op_copy:
        value = inst[0].value;
#ifdef TOLERATE_SUPERGLUS_BUG
        if (inst[1].desttype == 1 && inst[1].value == 0)
            inst[1].desttype = 0;
#endif /* TOLERATE_SUPERGLUS_BUG */
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_copys:
        value = inst[0].value;
        store_operand_s(inst[1].desttype, inst[1].value, value);
        break;
      case op_copyb:
        value = inst[0].value;
        store_operand_b(inst[1].desttype, inst[1].value, value);
        break;

      case op_sexs:
        val0 = inst[0].value;
        if (val0 & 0x8000)
          val0 |= 0xFFFF0000;
        else
          val0 &= 0x0000FFFF;
        store_operand(inst[1].desttype, inst[1].value, val0);
        break;
      case op_sexb:
        val0 = inst[0].value;
        if (val0 & 0x80)
          val0 |= 0xFFFFFF00;
        else
          val0 &= 0x000000FF;
        store_operand(inst[1].desttype, inst[1].value, val0);
        break;

      case op_aload:
        value = inst[0].value;
        value += 4 * inst[1].value;
        val0 = Mem4(value);
        store_operand(inst[2].desttype, inst[2].value, val0);
        break;
      case op_aloads:
        value = inst[0].value;
        value += 2 * inst[1].value;
        val0 = Mem2(value);
        store_operand(inst[2].desttype, inst[2].value, val0);
        break;
      case op_aloadb:
        value = inst[0].value;
        value += inst[1].value;
        val0 = Mem1(value);
        store_operand(inst[2].desttype, inst[2].value, val0);
        break;
      case op_aloadbit:
        value = inst[0].value;
        vals0 = inst[1].value;
        val1 = (vals0 & 7);
        if (vals0 >= 0)
          value += (vals0 >> 3);
        else
          value -= (1 + ((-1 - vals0) >> 3));
        if (Mem1(value) & (1 << val1))
          val0 = 1;
        else
          val0 = 0;
        store_operand(inst[2].desttype, inst[2].value, val0);
        break;

      case op_astore:
        value = inst[0].value;
        value += 4 * inst[1].value;
        val0 = inst[2].value;
        MemW4(value, val0);
        break;
      case op_astores:
        value = inst[0].value;
        value += 2 * inst[1].value;
        val0 = inst[2].value;
        MemW2(value, val0);
        break;
      case op_astoreb:
        value = inst[0].value;
        value += inst[1].value;
        val0 = inst[2].value;
        MemW1(value, val0);
        break;
      case op_astorebit:
        value = inst[0].value;
        vals0 = inst[1].value;
        val1 = (vals0 & 7);
        if (vals0 >= 0)
          value += (vals0 >> 3);
        else
          value -= (1 + ((-1 - vals0) >> 3));
        val0 = Mem1(value);
        if (inst[2].value)
          val0 |= (1 << val1);
        else
          val0 &= ~((glui32)(1 << val1));
        MemW1(value, val0);
        break;

      case op_stkcount:
        value = (stackptr - valstackbase) / 4;
        store_operand(inst[0].desttype, inst[0].value, value);
        break;
      case op_stkpeek:
        vals0 = inst[0].value * 4;
        if (vals0 < 0 || vals0 >= (stackptr - valstackbase))
          fatal_error("Stkpeek outside current stack range.");
        value = Stk4(stackptr - (vals0+4));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_stkswap:
        if (stackptr < valstackbase+8) {
          fatal_error("Stack underflow in stkswap.");
        }
        val0 = Stk4(stackptr-4);
        val1 = Stk4(stackptr-8);
        StkW4(stackptr-4, val1);
        StkW4(stackptr-8, val0);
        break;
      case op_stkcopy:
        vals0 = inst[0].value;
        if (vals0 < 0)
          fatal_error("Negative operand in stkcopy.");
        if (vals0 == 0)
          break;
        if (stackptr < valstackbase+vals0*4)
          fatal_error("Stack underflow in stkcopy.");
        if (stackptr + vals0*4 > stacksize) 
          fatal_error("Stack overflow in stkcopy.");
        addr = stackptr - vals0*4;
        for (ix=0; ix<vals0; ix++) {
          value = Stk4(addr + ix*4);
          StkW4(stackptr + ix*4, value);
        }
        stackptr += vals0*4;
        break;
      case op_stkroll:
        vals0 = inst[0].value;
        vals1 = inst[1].value;
        if (vals0 < 0)
          fatal_error("Negative operand in stkroll.");
        if (stackptr < valstackbase+vals0*4)
          fatal_error("Stack underflow in stkroll.");
        if (vals0 == 0)
          break;
        /* The following is a bit ugly. We want to do vals1 = vals0-vals1,
           because rolling down is sort of easier than rolling up. But
           we also want to take the result mod vals0. The % operator is
           annoying for negative numbers, so we need to do this in two 
           cases. */
        if (vals1 > 0) {
          vals1 = vals1 % vals0;
          vals1 = (vals0) - vals1;
        }
        else {
          vals1 = (-(glui32)vals1) % vals0;
        }
        if (vals1 == 0)
          break;
        addr = stackptr - vals0*4;
        for (ix=0; ix<vals1; ix++) {
          value = Stk4(addr + ix*4);
          StkW4(stackptr + ix*4, value);
        }
        for (ix=0; ix<vals0; ix++) {
          value = Stk4(addr + (vals1+ix)*4);
          StkW4(addr + ix*4, value);
        }
        break;

      case op_streamchar:
        profile_in(0xE0000001, stackptr, FALSE);
        value = inst[0].value & 0xFF;
        (*stream_char_handler)(value);
        profile_out(stackptr);
        break;
      case op_streamunichar:
        profile_in(0xE0000002, stackptr, FALSE);
        value = inst[0].value;
        (*stream_unichar_handler)(value);
        profile_out(stackptr);
        break;
      case op_streamnum:
        profile_in(0xE0000003, stackptr, FALSE);
        vals0 = inst[0].value;
        stream_num(vals0, FALSE, 0);
        profile_out(stackptr);
        break;
      case op_streamstr:
        profile_in(0xE0000004, stackptr, FALSE);
        stream_string(inst[0].value, 0, 0);
        profile_out(stackptr);
        break;

      default:
        fatal_error_i("Executed unknown opcode.", opcode);
      }
    }
    else {

      switch (opcode) {

      case op_gestalt:
        value = do_gestalt(inst[0].value, inst[1].value);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;

      case op_debugtrap:
#if VM_DEBUGGER
        /* We block and handle debug commands, but only if the
           library has invoked debug features. (Meaning, has
           the cycle handler ever been called.) */
        if (debugger_ever_invoked()) {
          debugger_block_and_debug("user debugtrap, pausing...");
          break;
        }
#endif /* VM_DEBUGGER */
        fatal_error_i("user debugtrap encountered.", inst[0].value);

      case op_jumpabs:
        pc = inst[0].value;
        break;

      case op_callf:
        push_callstub(inst[1].desttype, inst[1].value);
        enter_function(inst[0].value, 0, arglistfix);
        break;
      case op_callfi:
        arglistfix[0] = inst[1].value;
        push_callstub(inst[2].desttype, inst[2].value);
        enter_function(inst[0].value, 1, arglistfix);
        break;
      case op_callfii:
        arglistfix[0] = inst[1].value;
        arglistfix[1] = inst[2].value;
        push_callstub(inst[3].desttype, inst[3].value);
        enter_function(inst[0].value, 2, arglistfix);
        break;
      case op_callfiii:
        arglistfix[0] = inst[1].value;
        arglistfix[1] = inst[2].value;
        arglistfix[2] = inst[3].value;
        push_callstub(inst[4].desttype, inst[4].value);
        enter_function(inst[0].value, 3, arglistfix);
        break;

      case op_getmemsize:
        store_operand(inst[0].desttype, inst[0].value, endmem);
        break;
      case op_setmemsize:
        value = change_memsize(inst[0].value, FALSE);
        store_operand(inst[1].desttype, inst[1].value, value);
        break;

      case op_getstringtbl:
        value = stream_get_table();
        store_operand(inst[0].desttype, inst[0].value, value);
        break;
      case op_setstringtbl:
        stream_set_table(inst[0].value);
        break;

      case op_getiosys:
        stream_get_iosys(&val0, &val1);
        store_operand(inst[0].desttype, inst[0].value, val0);
        store_operand(inst[1].desttype, inst[1].value, val1);
        break;
      case op_setiosys:
        stream_set_iosys(inst[0].value, inst[1].value);
        break;

      case op_glk:
        profile_in(0xF0000000+inst[0].value, stackptr, FALSE);
        value = inst[1].value;
        arglist = pop_arguments(value, 0);
        val0 = perform_glk(inst[0].value, value, arglist);
#ifdef TOLERATE_SUPERGLUS_BUG
        if (inst[2].desttype == 1 && inst[2].value == 0)
            inst[2].desttype = 0;
#endif /* TOLERATE_SUPERGLUS_BUG */
        store_operand(inst[2].desttype, inst[2].value, val0);
        profile_out(stackptr);
        break;

      case op_random:
        vals0 = inst[0].value;
        if (vals0 == 0)
          value = glulx_random();
        else if (vals0 >= 1)
          value = glulx_random() % (glui32)(vals0);
        else 
          value = -(glulx_random() % (glui32)(-(glui32)vals0));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_setrandom:
        glulx_setrandom(inst[0].value);
        break;

      case op_verify:
        value = perform_verify();
        store_operand(inst[0].desttype, inst[0].value, value);
        break;

      case op_restart:
        profile_fail("restart");
        vm_restart();
        break;

      case op_protect:
        val0 = inst[0].value;
        val1 = val0 + inst[1].value;
        if (val0 == val1) {
          val0 = 0;
          val1 = 0;
        }
        protectstart = val0;
        protectend = val1;
        break;

      case op_save:
        push_callstub(inst[1].desttype, inst[1].value);
        value = perform_save(find_stream_by_id(inst[0].value));
        pop_callstub(value);
        break;

      case op_restore:
        value = perform_restore(find_stream_by_id(inst[0].value), FALSE);
        if (value == 0) {
          /* We've succeeded, and the stack now contains the callstub
             saved during saveundo. Ignore this opcode's operand. */
          value = -1;
          pop_callstub(value);
        }
        else {
          /* We've failed, so we must store the failure in this opcode's
             operand. */
          store_operand(inst[1].desttype, inst[1].value, value);
        }
        break;

      case op_saveundo:
        push_callstub(inst[0].desttype, inst[0].value);
        value = perform_saveundo();
        pop_callstub(value);
        break;

      case op_restoreundo:
        value = perform_restoreundo();
        if (value == 0) {
          /* We've succeeded, and the stack now contains the callstub
             saved during saveundo. Ignore this opcode's operand. */
          value = -1;
          pop_callstub(value);
        }
        else {
          /* We've failed, so we must store the failure in this opcode's
             operand. */
          store_operand(inst[0].desttype, inst[0].value, value);
        }
        break;

      case op_hasundo:
        value = has_undo();
        store_operand(inst[0].desttype, inst[0].value, value);
        break;

      case op_discardundo:
        discard_undo();
        break;

      case op_quit:
        done_executing = TRUE;
        break;

      case op_linearsearch:
        value = linear_search(inst[0].value, inst[1].value, inst[2].value, 
          inst[3].value, inst[4].value, inst[5].value, inst[6].value);
        store_operand(inst[7].desttype, inst[7].value, value);
        break;
      case op_binarysearch:
        value = binary_search(inst[0].value, inst[1].value, inst[2].value, 
          inst[3].value, inst[4].value, inst[5].value, inst[6].value);
        store_operand(inst[7].desttype, inst[7].value, value);
        break;
      case op_linkedsearch:
        value = linked_search(inst[0].value, inst[1].value, inst[2].value, 
          inst[3].value, inst[4].value, inst[5].value);
        store_operand(inst[6].desttype, inst[6].value, value);
        break;

      case op_mzero: {
        glui32 lx;
        glui32 count = inst[0].value;
        addr = inst[1].value;
        for (lx=0; lx<count; lx++, addr++) {
          MemW1(addr, 0);
        }
        }
        break;
      case op_mcopy: {
        glui32 lx;
        glui32 count = inst[0].value;
        glui32 addrsrc = inst[1].value;
        glui32 addrdest = inst[2].value;
        if (addrdest < addrsrc) {
          for (lx=0; lx<count; lx++, addrsrc++, addrdest++) {
            value = Mem1(addrsrc);
            MemW1(addrdest, value);
          }
        }
        else {
          addrsrc += (count-1);
          addrdest += (count-1);
          for (lx=0; lx<count; lx++, addrsrc--, addrdest--) {
            value = Mem1(addrsrc);
            MemW1(addrdest, value);
          }
        }
        }
        break;
      case op_malloc:
        value = heap_alloc(inst[0].value);
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_mfree:
        heap_free(inst[0].value);
        break;

      case op_accelfunc:
        accel_set_func(inst[0].value, inst[1].value);
        break;
      case op_accelparam:
        accel_set_param(inst[0].value, inst[1].value);
        break;

#ifdef FLOAT_SUPPORT

      case op_numtof:
        vals0 = inst[0].value;
        value = encode_float((gfloat32)vals0);
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_ftonumz:
        valf = decode_float(inst[0].value);
        if (!signbit(valf)) {
          if (isnan(valf) || isinf(valf) || (valf > 2147483647.0))
            vals0 = 0x7FFFFFFF;
          else
            vals0 = (glsi32)(truncf(valf));
        }
        else {
          if (isnan(valf) || isinf(valf) || (valf < -2147483647.0))
            vals0 = 0x80000000;
          else
            vals0 = (glsi32)(truncf(valf));
        }
        store_operand(inst[1].desttype, inst[1].value, vals0);
        break;
      case op_ftonumn:
        valf = decode_float(inst[0].value);
        if (!signbit(valf)) {
          if (isnan(valf) || isinf(valf) || (valf > 2147483647.0))
            vals0 = 0x7FFFFFFF;
          else
            vals0 = (glsi32)(roundf(valf));
        }
        else {
          if (isnan(valf) || isinf(valf) || (valf < -2147483647.0))
            vals0 = 0x80000000;
          else
            vals0 = (glsi32)(roundf(valf));
        }
        store_operand(inst[1].desttype, inst[1].value, vals0);
        break;

      case op_fadd:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        value = encode_float(valf1 + valf2);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_fsub:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        value = encode_float(valf1 - valf2);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_fmul:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        value = encode_float(valf1 * valf2);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
      case op_fdiv:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        value = encode_float(valf1 / valf2);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;

      case op_fmod:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        valf = fmodf(valf1, valf2);
        val0 = encode_float(valf);
        val1 = encode_float((valf1-valf) / valf2);
        if (val1 == 0x0 || val1 == 0x80000000) {
          /* When the quotient is zero, the sign has been lost in the
             shuffle. We'll set that by hand, based on the original
             arguments. */
          val1 = (inst[0].value ^ inst[1].value) & 0x80000000;
        }
        store_operand(inst[2].desttype, inst[2].value, val0);
        store_operand(inst[3].desttype, inst[3].value, val1);
        break;

      case op_floor:
        valf = decode_float(inst[0].value);
        value = encode_float(floorf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_ceil:
        valf = decode_float(inst[0].value);
        value = encode_float(ceilf(valf));
        if (value == 0x0 || value == 0x80000000) {
          /* When the result is zero, the sign may have been lost in the
             shuffle. (This is a bug in some C libraries.) We'll set the
             sign by hand, based on the original argument. */
          value = inst[0].value & 0x80000000;
        }
        store_operand(inst[1].desttype, inst[1].value, value);
        break;

      case op_sqrt:
        valf = decode_float(inst[0].value);
        value = encode_float(sqrtf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_log:
        valf = decode_float(inst[0].value);
        value = encode_float(logf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_exp:
        valf = decode_float(inst[0].value);
        value = encode_float(expf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_pow:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        value = encode_float(glulx_powf(valf1, valf2));
        store_operand(inst[2].desttype, inst[2].value, value);
        break;

      case op_sin:
        valf = decode_float(inst[0].value);
        value = encode_float(sinf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_cos:
        valf = decode_float(inst[0].value);
        value = encode_float(cosf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_tan:
        valf = decode_float(inst[0].value);
        value = encode_float(tanf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_asin:
        valf = decode_float(inst[0].value);
        value = encode_float(asinf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_acos:
        valf = decode_float(inst[0].value);
        value = encode_float(acosf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_atan:
        valf = decode_float(inst[0].value);
        value = encode_float(atanf(valf));
        store_operand(inst[1].desttype, inst[1].value, value);
        break;
      case op_atan2:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        value = encode_float(atan2f(valf1, valf2));
        store_operand(inst[2].desttype, inst[2].value, value);
        break;

      case op_jisinf:
        /* Infinity is well-defined, so we don't bother to convert to
           float. */
        val0 = inst[0].value;
        if (val0 == 0x7F800000 || val0 == 0xFF800000) {
          value = inst[1].value;
          goto PerformJump;
        }
        break;
      case op_jisnan:
        /* NaN is well-defined, so we don't bother to convert to
           float. */
        val0 = inst[0].value;
        if ((val0 & 0x7F800000) == 0x7F800000 && (val0 & 0x007FFFFF) != 0) {
          value = inst[1].value;
          goto PerformJump;
        }
        break;

      case op_jfeq:
        if ((inst[2].value & 0x7F800000) == 0x7F800000 && (inst[2].value & 0x007FFFFF) != 0) {
          /* The delta is NaN, which can never match. */
          val0 = 0;
        }
        else if ((inst[0].value == 0x7F800000 || inst[0].value == 0xFF800000)
          && (inst[1].value == 0x7F800000 || inst[1].value == 0xFF800000)) {
          /* Both are infinite. Opposite infinities are never equal,
             even if the difference is infinite, so this is easy. */
          val0 = (inst[0].value == inst[1].value);
        }
        else {
          valf1 = decode_float(inst[1].value) - decode_float(inst[0].value);
          valf2 = fabsf(decode_float(inst[2].value));
          val0 = (valf1 <= valf2 && valf1 >= -valf2);
        }
        if (val0) {
          value = inst[3].value;
          goto PerformJump;
        }
        break;
      case op_jfne:
        if ((inst[2].value & 0x7F800000) == 0x7F800000 && (inst[2].value & 0x007FFFFF) != 0) {
          /* The delta is NaN, which can never match. */
          val0 = 0;
        }
        else if ((inst[0].value == 0x7F800000 || inst[0].value == 0xFF800000)
          && (inst[1].value == 0x7F800000 || inst[1].value == 0xFF800000)) {
          /* Both are infinite. Opposite infinities are never equal,
             even if the difference is infinite, so this is easy. */
          val0 = (inst[0].value == inst[1].value);
        }
        else {
          valf1 = decode_float(inst[1].value) - decode_float(inst[0].value);
          valf2 = fabsf(decode_float(inst[2].value));
          val0 = (valf1 <= valf2 && valf1 >= -valf2);
        }
        if (!val0) {
          value = inst[3].value;
          goto PerformJump;
        }
        break;

      case op_jflt:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        if (valf1 < valf2) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jfgt:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        if (valf1 > valf2) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jfle:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        if (valf1 <= valf2) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jfge:
        valf1 = decode_float(inst[0].value);
        valf2 = decode_float(inst[1].value);
        if (valf1 >= valf2) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;

#ifdef DOUBLE_SUPPORT   /* Inside FLOAT_SUPPORT! */
        
      case op_numtod:
        vals0 = inst[0].value;
        encode_double((gfloat64)vals0, &val0hi, &val0lo);
        store_operand(inst[1].desttype, inst[1].value, val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0hi);
        break;
      case op_dtonumz:
        vald = decode_double(inst[0].value, inst[1].value);
        if (!signbit(vald)) {
          if (isnan(vald) || isinf(vald) || (vald > 2147483647.0))
            vals0 = 0x7FFFFFFF;
          else
            vals0 = (glsi32)(trunc(vald));
        }
        else {
          if (isnan(vald) || isinf(vald) || (vald < -2147483647.0))
            vals0 = 0x80000000;
          else
            vals0 = (glsi32)(trunc(vald));
        }
        store_operand(inst[2].desttype, inst[2].value, vals0);
        break;
      case op_dtonumn:
        vald = decode_double(inst[0].value, inst[1].value);
        if (!signbit(vald)) {
          if (isnan(vald) || isinf(vald) || (vald > 2147483647.0))
            vals0 = 0x7FFFFFFF;
          else
            vals0 = (glsi32)(round(vald));
        }
        else {
          if (isnan(vald) || isinf(vald) || (vald < -2147483647.0))
            vals0 = 0x80000000;
          else
            vals0 = (glsi32)(round(vald));
        }
        store_operand(inst[2].desttype, inst[2].value, vals0);
        break;
      case op_ftod:
        valf = decode_float(inst[0].value);
        encode_double((gfloat64)valf, &val0hi, &val0lo);
        store_operand(inst[1].desttype, inst[1].value, val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0hi);
        break;
      case op_dtof:
        vald = decode_double(inst[0].value, inst[1].value);
        value = encode_float((gfloat32)vald);
        store_operand(inst[2].desttype, inst[2].value, value);
        break;
        
      case op_dadd:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        encode_double(vald1 + vald2, &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
      case op_dsub:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        encode_double(vald1 - vald2, &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
      case op_dmul:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        encode_double(vald1 * vald2, &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
      case op_ddiv:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        encode_double(vald1 / vald2, &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
        
      case op_dmodr:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        vald = fmod(vald1, vald2);
        encode_double(vald, &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
      case op_dmodq:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        vald = fmod(vald1, vald2);
        vald = (vald1-vald) / vald2;
        encode_double(vald, &val0hi, &val0lo);
        if ((val0hi == 0x0 || val0hi == 0x80000000) && val0lo == 0x0) {
          /* When the quotient is zero, the sign has been lost in the
             shuffle. We'll set that by hand, based on the original
             arguments. */
          val0hi = (inst[0].value ^ inst[2].value) & 0x80000000;
        }
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
        
      case op_dfloor:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(floor(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dceil:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(ceil(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
        
      case op_dsqrt:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(sqrt(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dlog:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(log(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dexp:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(exp(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dpow:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        encode_double(glulx_pow(vald1, vald2), &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;

      case op_dsin:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(sin(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dcos:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(cos(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dtan:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(tan(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dasin:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(asin(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_dacos:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(acos(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_datan:
        vald = decode_double(inst[0].value, inst[1].value);
        encode_double(atan(vald), &val0hi, &val0lo);
        store_operand(inst[2].desttype, inst[2].value, val0lo);
        store_operand(inst[3].desttype, inst[3].value, val0hi);
        break;
      case op_datan2:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        vald = atan2(vald1, vald2);
        encode_double(vald, &val0hi, &val0lo);
        store_operand(inst[4].desttype, inst[4].value, val0lo);
        store_operand(inst[5].desttype, inst[5].value, val0hi);
        break;
        
      case op_jdisinf:
        /* Infinity is well-defined, so we don't bother to convert to
           float. */
        val0 = inst[0].value;
        val1 = inst[1].value;
        if (DOUBLE_PAIR_ISINF(val0, val1)) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;
      case op_jdisnan:
        /* NaN is well-defined, so we don't bother to convert to
           float. */
        val0 = inst[0].value;
        val1 = inst[1].value;
        if (DOUBLE_PAIR_ISNAN(val0, val1)) {
          value = inst[2].value;
          goto PerformJump;
        }
        break;

      case op_jdeq:
        if (DOUBLE_PAIR_ISNAN(inst[4].value, inst[5].value)) {
          /* The delta is NaN, which can never match. */
          val0 = 0;
        }
        else if (DOUBLE_PAIR_ISINF(inst[0].value, inst[1].value)
          && DOUBLE_PAIR_ISINF(inst[2].value, inst[3].value)) {
          /* Both are infinite. Opposite infinities are never equal,
             even if the difference is infinite, so this is easy.
             (We only need to compare the high words, because the low
             word of both INF and -INF is zero.) */
          val0 = (inst[0].value == inst[2].value);
        }
        else {
          vald1 = decode_double(inst[2].value, inst[3].value) - decode_double(inst[0].value, inst[1].value);
          vald2 = fabs(decode_double(inst[4].value, inst[5].value));
          val0 = (vald1 <= vald2 && vald1 >= -vald2);
        }
        if (val0) {
          value = inst[6].value;
          goto PerformJump;
        }
        break;
      case op_jdne:
        if (DOUBLE_PAIR_ISNAN(inst[4].value, inst[5].value)) {
          /* The delta is NaN, which can never match. */
          val0 = 0;
        }
        else if (DOUBLE_PAIR_ISINF(inst[0].value, inst[1].value)
          && DOUBLE_PAIR_ISINF(inst[2].value, inst[3].value)) {
          /* Both are infinite. Opposite infinities are never equal,
             even if the difference is infinite, so this is easy.
             (We only need to compare the high words, because the low
             word of both INF and -INF is zero.) */
          val0 = (inst[0].value == inst[2].value);
        }
        else {
          vald1 = decode_double(inst[2].value, inst[3].value) - decode_double(inst[0].value, inst[1].value);
          vald2 = fabs(decode_double(inst[4].value, inst[5].value));
          val0 = (vald1 <= vald2 && vald1 >= -vald2);
        }
        if (!val0) {
          value = inst[6].value;
          goto PerformJump;
        }
        break;
        
      case op_jdlt:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        if (vald1 < vald2) {
          value = inst[4].value;
          goto PerformJump;
        }
        break;
      case op_jdgt:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        if (vald1 > vald2) {
          value = inst[4].value;
          goto PerformJump;
        }
        break;
      case op_jdle:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        if (vald1 <= vald2) {
          value = inst[4].value;
          goto PerformJump;
        }
        break;
      case op_jdge:
        vald1 = decode_double(inst[0].value, inst[1].value);
        vald2 = decode_double(inst[2].value, inst[3].value);
        if (vald1 >= vald2) {
          value = inst[4].value;
          goto PerformJump;
        }
        break;

#endif /* DOUBLE_SUPPORT */
        
#endif /* FLOAT_SUPPORT */

#ifdef GLULX_EXTEND_OPCODES
      GLULX_EXTEND_OPCODES
#endif /* GLULX_EXTEND_OPCODES */

      default:
        fatal_error_i("Executed unknown opcode.", opcode);
      }
    }
  }
  /* done executing */
#if VM_DEBUGGER
  debugger_handle_quit();
#endif /* VM_DEBUGGER */
}
