diff options
author | Paul Gilbert | 2019-04-14 21:07:25 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-04-17 20:46:06 -0700 |
commit | 6307fd08ce7cbb0b939f520f9741aef884f66f88 (patch) | |
tree | 22d665b969305df24735777df38ac638a9b7a29f /engines | |
parent | c3ebb5e68a0b36d992a48b508ab15c13a2edfd9c (diff) | |
download | scummvm-rg350-6307fd08ce7cbb0b939f520f9741aef884f66f88.tar.gz scummvm-rg350-6307fd08ce7cbb0b939f520f9741aef884f66f88.tar.bz2 scummvm-rg350-6307fd08ce7cbb0b939f520f9741aef884f66f88.zip |
GLK: GLULXE: Added exec methods
Diffstat (limited to 'engines')
-rw-r--r-- | engines/glk/glulxe/exec.cpp | 1069 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe.cpp | 1 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe.h | 9 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe_types.h | 218 | ||||
-rw-r--r-- | engines/glk/module.mk | 1 |
5 files changed, 1255 insertions, 43 deletions
diff --git a/engines/glk/glulxe/exec.cpp b/engines/glk/glulxe/exec.cpp new file mode 100644 index 0000000000..f3b511f43c --- /dev/null +++ b/engines/glk/glulxe/exec.cpp @@ -0,0 +1,1069 @@ +/* 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "engines/glk/glulxe/glulxe.h" + +namespace Glk { +namespace Glulxe { + +void Glulxe::execute_loop() { + bool done_executing = false; + int ix; + uint opcode; + 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) { + + 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; + (*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() % (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); + 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 = (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 */ +#if VM_DEBUGGER + debugger_handle_quit(); +#endif /* VM_DEBUGGER */ +} + +} // End of namespace Glulxe +} // End of namespace Glk diff --git a/engines/glk/glulxe/glulxe.cpp b/engines/glk/glulxe/glulxe.cpp index 6fdfb05ba5..1f05b9432a 100644 --- a/engines/glk/glulxe/glulxe.cpp +++ b/engines/glk/glulxe/glulxe.cpp @@ -34,6 +34,7 @@ Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, memmap(nullptr), stack(nullptr), ramstart(0), endgamefile(0), origendmem(0), stacksize(0), startfuncaddr(0), checksum(0), stackptr(0), frameptr(0), pc(0), prevpc(0), origstringtable(0), stringtable(0), valstackbase(0), localsbase(0), endmem(0), protectstart(0), protectend(0), + stream_char_handler(nullptr), stream_unichar_handler(nullptr), // Accel classes_table(0), indiv_prop_start(0), class_metaclass(0), object_metaclass(0), routine_metaclass(0), string_metaclass(0), self(0), num_attr_bytes(0), cpv__start(0), diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h index 839e9eb524..fb7f63b3d6 100644 --- a/engines/glk/glulxe/glulxe.h +++ b/engines/glk/glulxe/glulxe.h @@ -60,6 +60,9 @@ public: uint protectstart, protectend; uint prevpc; + void (*Glulxe::stream_char_handler)(unsigned char ch); + void (*Glulxe::stream_unichar_handler)(uint ch); + /** * \defgroup accel fields * @{ @@ -288,7 +291,11 @@ public: * \defgroup Exec access methods * @{ */ - void execute_loop(void); + + /** + * The main interpreter loop. This repeats until the program is done + */ + void execute_loop(); /**@}*/ diff --git a/engines/glk/glulxe/glulxe_types.h b/engines/glk/glulxe/glulxe_types.h index b06e43536a..b4c9a14569 100644 --- a/engines/glk/glulxe/glulxe_types.h +++ b/engines/glk/glulxe/glulxe_types.h @@ -26,53 +26,53 @@ #include "common/scummsys.h" namespace Glk { -namespace Glulxe { + namespace Glulxe { -class Glulxe; + class Glulxe; -/** - * Comment this definition to turn off memory-address checking. With verification on, - * all reads and writes to main memory will be checked to ensure they're in range. - * This is slower, but prevents malformed game files from crashing the interpreter. - */ + /** + * Comment this definition to turn off memory-address checking. With verification on, + * all reads and writes to main memory will be checked to ensure they're in range. + * This is slower, but prevents malformed game files from crashing the interpreter. + */ #define VERIFY_MEMORY_ACCESS (1) -/** - * Uncomment this definition to permit an exception for memory-address checking for @glk and @copy - * opcodes that try to write to memory address 0. This was a bug in old Superglus-built game files. - */ -/* #define TOLERATE_SUPERGLUS_BUG (1) */ + /** + * Uncomment this definition to permit an exception for memory-address checking for @glk and @copy + * opcodes that try to write to memory address 0. This was a bug in old Superglus-built game files. + */ + /* #define TOLERATE_SUPERGLUS_BUG (1) */ -/** - * Uncomment this definition to turn on Glulx VM profiling. In this mode, all function calls are timed, - * and the timing information is written to a data file called "profile-raw". - * (Build note: on Linux, glibc may require you to also define _BSD_SOURCE or _DEFAULT_SOURCE or both - * for the timeradd() macro.) - */ -/* #define VM_PROFILING (1) */ + /** + * Uncomment this definition to turn on Glulx VM profiling. In this mode, all function calls are timed, + * and the timing information is written to a data file called "profile-raw". + * (Build note: on Linux, glibc may require you to also define _BSD_SOURCE or _DEFAULT_SOURCE or both + * for the timeradd() macro.) + */ + /* #define VM_PROFILING (1) */ -/** - * Uncomment this definition to turn on the Glulx debugger. You should only do this when debugging - * facilities are desired; it slows down the interpreter. If you do, you will need to build with libxml2; - * see the Makefile. - */ -/* #define VM_DEBUGGER (1) */ + /** + * Uncomment this definition to turn on the Glulx debugger. You should only do this when debugging + * facilities are desired; it slows down the interpreter. If you do, you will need to build with libxml2; + * see the Makefile. + */ + /* #define VM_DEBUGGER (1) */ -/** - * Comment this definition to turn off floating-point support. You might need to do this if you are building - * on a very limited platform with no math library. */ + /** + * Comment this definition to turn off floating-point support. You might need to do this if you are building + * on a very limited platform with no math library. */ #define FLOAT_SUPPORT (1) -/** - * Comment this definition to not cache the original state of RAM in (real) memory. This saves some memory, - * but slows down save/restore/undo operations, which will have to read the original state off disk - * every time. - */ + /** + * Comment this definition to not cache the original state of RAM in (real) memory. This saves some memory, + * but slows down save/restore/undo operations, which will have to read the original state off disk + * every time. + */ #define SERIALIZE_CACHE_RAM (1) -/** - * Some macros to read and write integers to memory, always in big-endian format. - */ + /** + * Some macros to read and write integers to memory, always in big-endian format. + */ #define Read4(ptr) READ_BE_UINT32(ptr) #define Read2(ptr) READ_BE_UINT16(ptr) #define Read1(ptr) ((byte)(((byte *)(ptr))[0])) @@ -95,12 +95,12 @@ class Glulxe; #define MemW2(adr, vl) (VerifyW(adr, 2), Write2(memmap+(adr), (vl))) #define MemW4(adr, vl) (VerifyW(adr, 4), Write4(memmap+(adr), (vl))) -/** - * Macros to access values on the stack. These *must* be used with proper alignment! - * (That is, Stk4 and StkW4 must take addresses which are multiples of four, etc.) - * If the alignment rules are not followed, the program will see performance - * degradation or even crashes, depending on the machine CPU. - */ + /** + * Macros to access values on the stack. These *must* be used with proper alignment! + * (That is, Stk4 and StkW4 must take addresses which are multiples of four, etc.) + * If the alignment rules are not followed, the program will see performance + * degradation or even crashes, depending on the machine CPU. + */ #define Stk1(adr) \ (*((unsigned char *)(stack+(adr)))) #define Stk2(adr) \ @@ -115,6 +115,140 @@ class Glulxe; #define StkW4(adr, vl) \ (*((uint32 *)(stack+(adr))) = (uint32)(vl)) +enum Opcode { + op_nop = 0x00, + + op_add = 0x10, + op_sub = 0x11, + op_mul = 0x12, + op_div = 0x13, + op_mod = 0x14, + op_neg = 0x15, + op_bitand = 0x18, + op_bitor = 0x19, + op_bitxor = 0x1A, + op_bitnot = 0x1B, + op_shiftl = 0x1C, + op_sshiftr = 0x1D, + op_ushiftr = 0x1E, + + op_jump = 0x20, + op_jz = 0x22, + op_jnz = 0x23, + op_jeq = 0x24, + op_jne = 0x25, + op_jlt = 0x26, + op_jge = 0x27, + op_jgt = 0x28, + op_jle = 0x29, + op_jltu = 0x2A, + op_jgeu = 0x2B, + op_jgtu = 0x2C, + op_jleu = 0x2D, + + op_call = 0x30, + op_return = 0x31, + op_catch = 0x32, + op_throw = 0x33, + op_tailcall = 0x34, + + op_copy = 0x40, + op_copys = 0x41, + op_copyb = 0x42, + op_sexs = 0x44, + op_sexb = 0x45, + op_aload = 0x48, + op_aloads = 0x49, + op_aloadb = 0x4A, + op_aloadbit = 0x4B, + op_astore = 0x4C, + op_astores = 0x4D, + op_astoreb = 0x4E, + op_astorebit = 0x4F, + + op_stkcount = 0x50, + op_stkpeek = 0x51, + op_stkswap = 0x52, + op_stkroll = 0x53, + op_stkcopy = 0x54, + + op_streamchar = 0x70, + op_streamnum = 0x71, + op_streamstr = 0x72, + op_streamunichar = 0x73, + + op_gestalt = 0x100, + op_debugtrap = 0x101, + op_getmemsize = 0x102, + op_setmemsize = 0x103, + op_jumpabs = 0x104, + + op_random = 0x110, + op_setrandom = 0x111, + + op_quit = 0x120, + op_verify = 0x121, + op_restart = 0x122, + op_save = 0x123, + op_restore = 0x124, + op_saveundo = 0x125, + op_restoreundo = 0x126, + op_protect = 0x127, + + op_glk = 0x130, + + op_getstringtbl = 0x140, + op_setstringtbl = 0x141, + op_getiosys = 0x148, + op_setiosys = 0x149, + + op_linearsearch = 0x150, + op_binarysearch = 0x151, + op_linkedsearch = 0x152, + + op_callf = 0x160, + op_callfi = 0x161, + op_callfii = 0x162, + op_callfiii = 0x163, + + op_mzero = 0x170, + op_mcopy = 0x171, + op_malloc = 0x178, + op_mfree = 0x179, + + op_accelfunc = 0x180, + op_accelparam = 0x181, + + op_numtof = 0x190, + op_ftonumz = 0x191, + op_ftonumn = 0x192, + op_ceil = 0x198, + op_floor = 0x199, + op_fadd = 0x1A0, + op_fsub = 0x1A1, + op_fmul = 0x1A2, + op_fdiv = 0x1A3, + op_fmod = 0x1A4, + op_sqrt = 0x1A8, + op_exp = 0x1A9, + op_log = 0x1AA, + op_pow = 0x1AB, + op_sin = 0x1B0, + op_cos = 0x1B1, + op_tan = 0x1B2, + op_asin = 0x1B3, + op_acos = 0x1B4, + op_atan = 0x1B5, + op_atan2 = 0x1B6, + op_jfeq = 0x1C0, + op_jfne = 0x1C1, + op_jflt = 0x1C2, + op_jfle = 0x1C3, + op_jfgt = 0x1C4, + op_jfge = 0x1C5, + op_jisnan = 0x1C8, + op_jisinf = 0x1C9 +}; struct dispatch_splot_struct { int numwanted; diff --git a/engines/glk/module.mk b/engines/glk/module.mk index 6fe51c9a4e..8a593580fb 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -59,6 +59,7 @@ MODULE_OBJS := \ frotz/windows.o \ glulxe/accel.o \ glulxe/detection.o \ + glulxe/exec.o \ glulxe/float.o \ glulxe/glkop.o \ glulxe/glulxe.o \ |