/* 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>
#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;
  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;
#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 = (-vals0);
          if (vals1 < 0) {
            val1 = (-vals1);
            value = val0 / val1;
          }
          else {
            val1 = vals1;
            value = -(val0 / val1);
          }
        }
        else {
          val0 = vals0;
          if (vals1 < 0) {
            val1 = (-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 = -vals1;
        }
        else {
            val1 = vals1;
        }
        if (vals0 < 0) {
          val0 = (-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 = (-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 = (-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)(-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_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 = fabs(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 = fabs(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;

#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 */
}
