/* 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>#include <float.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(); /* 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; 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)Lines 1–500 of 1043. Load more
/* 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> #include <float.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(); /* 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; 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)