mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
1055 lines
29 KiB
C++
1055 lines
29 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "glk/glulx/glulx.h"
|
|
|
|
namespace Glk {
|
|
namespace Glulx {
|
|
|
|
void Glulx::execute_loop() {
|
|
bool done_executing = false;
|
|
int ix;
|
|
uint opcode;
|
|
const operandlist_t *oplist;
|
|
oparg_t inst[MAX_OPERANDS];
|
|
uint value, addr, val0, val1;
|
|
int vals0, vals1;
|
|
uint *arglist;
|
|
uint arglistfix[3];
|
|
#ifdef FLOAT_SUPPORT
|
|
gfloat32 valf, valf1, valf2;
|
|
#endif /* FLOAT_SUPPORT */
|
|
|
|
while (!done_executing && !g_vm->shouldQuit()) {
|
|
|
|
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 = -(int)(val0 / val1);
|
|
}
|
|
} else {
|
|
val0 = vals0;
|
|
if (vals1 < 0) {
|
|
val1 = (-vals1);
|
|
value = -(int)(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 = -(int)(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 = ((uint)(inst[0].value) << (uint)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 = ((uint)(inst[0].value) >> (uint)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 = ((int)(inst[0].value) >> (int)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 &= ~((uint)(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 >= (int)(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;
|
|
(this->*stream_char_handler)(value);
|
|
profile_out(stackptr);
|
|
break;
|
|
case op_streamunichar:
|
|
profile_in(0xE0000002, stackptr, false);
|
|
value = inst[0].value;
|
|
(this->*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:
|
|
#ifdef 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);
|
|
break;
|
|
|
|
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() % (uint)(vals0);
|
|
else
|
|
value = -(int)(glulx_random() % (uint)(-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);
|
|
strid_t saveStream = find_stream_by_id(inst[0].value);
|
|
value = writeGameData(*saveStream).getCode() == Common::kNoError ? 0 : 1;
|
|
pop_callstub(value);
|
|
break;
|
|
}
|
|
|
|
case op_restore: {
|
|
strid_t stream = find_stream_by_id(inst[0].value);
|
|
value = readSaveData(*stream).getCode() == Common::kNoError ? 0 : 1;
|
|
|
|
if (value == 0) {
|
|
/* We've succeeded, and the stack now contains the callstub
|
|
saved during saveundo. Ignore this opcode's operand. */
|
|
value = (uint)-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 = (uint) - 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: {
|
|
uint lx;
|
|
uint count = inst[0].value;
|
|
addr = inst[1].value;
|
|
for (lx = 0; lx < count; lx++, addr++) {
|
|
MemW1(addr, 0);
|
|
}
|
|
}
|
|
break;
|
|
case op_mcopy: {
|
|
uint lx;
|
|
uint count = inst[0].value;
|
|
uint addrsrc = inst[1].value;
|
|
uint 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 = (int)(truncf(valf));
|
|
} else {
|
|
if (isnan(valf) || isinf(valf) || (valf < -2147483647.0))
|
|
vals0 = 0x80000000;
|
|
else
|
|
vals0 = (int)(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 = (int)(roundf(valf));
|
|
} else {
|
|
if (isnan(valf) || isinf(valf) || (valf < -2147483647.0))
|
|
vals0 = 0x80000000;
|
|
else
|
|
vals0 = (int)(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 */
|
|
#ifdef VM_DEBUGGER
|
|
debugger_handle_quit();
|
|
#endif /* VM_DEBUGGER */
|
|
}
|
|
|
|
} // End of namespace Glulx
|
|
} // End of namespace Glk
|