+// $Id: terp.c,v 1.42 2004/12/22 14:33:40 iain Exp $
+// Interpreter engine.
+
+#include "git.h"
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+// -------------------------------------------------------------
+// Global variables
+
+git_sint32* gStackPointer;
+
+#ifdef USE_DIRECT_THREADING
+Opcode* gOpcodeTable;
+#endif
+
+// -------------------------------------------------------------
+// Useful macros for manipulating the stack
+
+#define LOCAL(n) (locals[(n)])
+
+#define PUSH(n) *sp++ = (n)
+#define POP (*--sp)
+#define READ_PC ((git_uint32)(*pc++))
+
+#define CHECK_FREE(n) if ((top - sp) < (n)) goto stack_overflow
+#define CHECK_USED(n) if ((sp - values) < (n)) goto stack_underflow
+
+// -------------------------------------------------------------
+// Functions
+
+void startProgram (size_t cacheSize, enum IOMode ioMode)
+{
+ Block pc; // Program counter (pointer into dynamically generated code)
+
+ git_sint32 L1=0, L2=0, L3=0, L4=0, L5=0, L6=0, L7=0;
+#define S1 L1
+#define S2 L2
+
+ git_sint32* base; // Bottom of the stack.
+ git_sint32* frame; // Bottom of the current stack frame.
+ git_sint32* locals; // Start of the locals section of the current frame.
+ git_sint32* values; // Start of the values section of the current frame.
+ git_sint32* sp; // Next free stack slot.
+ git_sint32* top; // The top of the stack -- that is, the first unusable slot.
+
+ git_sint32 args [64]; // Array of arguments. Count is stored in L2.
+ git_uint32 runCounter = 0;
+
+ git_uint32 ioRock = 0;
+
+ git_uint32 stringTable = memRead32(28);
+ git_uint32 startPos = memRead32(24);
+ git_uint32 stackSize = memRead32(20);
+
+ git_uint32 protectPos = 0;
+ git_uint32 protectSize = 0;
+
+ git_uint32 glulxPC = 0;
+ git_uint32 glulxOpcode = 0;
+
+ acceleration_func accelfunc;
+
+ // Initialise the code cache.
+
+#ifdef USE_DIRECT_THREADING
+ static Opcode opcodeTable [] = {
+#define LABEL(label) &&do_ ## label,
+#include "labels.inc"
+ NULL};
+ gOpcodeTable = opcodeTable;
+#endif
+
+ initCompiler (cacheSize);
+
+ // Initialise the random number generator.
+ srand (time(NULL));
+
+ // Set up the stack.
+
+ base = malloc (stackSize);
+ if (base == NULL)
+ fatalError ("Couldn't allocate stack");
+
+ top = base + (stackSize / 4);
+ frame = locals = values = sp = base;
+
+ // Call the first function.
+
+ L1 = startPos; // Initial PC.
+ L2 = 0; // No arguments.
+ goto do_enter_function_L1;
+
+#ifdef USE_DIRECT_THREADING
+#define NEXT do { ++runCounter; goto **(pc++); } while(0)
+#else
+#define NEXT goto next
+//#define NEXT do { CHECK_USED(0); CHECK_FREE(0); goto next; } while (0)
+next:
+ ++runCounter;
+ switch (*pc++)
+ {
+#define LABEL(foo) case label_ ## foo: goto do_ ## foo;
+#include "labels.inc"
+ default: fatalError("exec: bad opcode");
+ }
+#endif
+
+do_debug_step:
+ // This opcode lets us keep track of how the compiled
+ // code relates to the original glulx code.
+ glulxPC = READ_PC; // Glulx program counter.
+ glulxOpcode = READ_PC; // Glulx opcode number.
+// fprintf (stdout, "\nPC: 0x%08x\nOpcode: 0x%04x\n", glulxPC, glulxOpcode);
+// fprintf (stdout, "Stack:");
+// for (L7 = 0 ; L7 < (sp - base) ; ++L7)
+// fprintf (stdout," 0x%x", base[L7]);
+// fprintf (stdout, "\n");
+ NEXT;
+
+#define LOAD_INSTRUCTIONS(reg) \
+ do_ ## reg ## _const: reg = READ_PC; NEXT; \
+ do_ ## reg ## _stack: CHECK_USED(1); reg = POP; NEXT; \
+ do_ ## reg ## _addr: reg = memRead32 (READ_PC); NEXT; \
+ do_ ## reg ## _local: reg = LOCAL (READ_PC); NEXT
+
+ LOAD_INSTRUCTIONS(L1);
+ LOAD_INSTRUCTIONS(L2);
+ LOAD_INSTRUCTIONS(L3);
+ LOAD_INSTRUCTIONS(L4);
+ LOAD_INSTRUCTIONS(L5);
+ LOAD_INSTRUCTIONS(L6);
+ LOAD_INSTRUCTIONS(L7);
+
+#define STORE_INSTRUCTIONS(reg) \
+ do_ ## reg ## _stack: CHECK_FREE(1); PUSH(reg); NEXT; \
+ do_ ## reg ## _addr: memWrite32 (READ_PC, reg); NEXT; \
+ do_ ## reg ## _local: LOCAL (READ_PC) = reg; NEXT
+
+ STORE_INSTRUCTIONS(S1);
+ STORE_INSTRUCTIONS(S2);
+
+#define DOUBLE_LOAD(mode2) \
+ do_L1_const_L2_ ## mode2: L1 = READ_PC; goto do_L2_ ## mode2; \
+ do_L1_stack_L2_ ## mode2: CHECK_USED(1); L1 = POP; goto do_L2_ ## mode2; \
+ do_L1_local_L2_ ## mode2: L1 = LOCAL (READ_PC); goto do_L2_ ## mode2; \
+ do_L1_addr_L2_ ## mode2: L1 = memRead32 (READ_PC); goto do_L2_ ## mode2
+
+ DOUBLE_LOAD(const);
+ DOUBLE_LOAD(stack);
+ DOUBLE_LOAD(local);
+ DOUBLE_LOAD(addr);
+
+#undef LOAD_INSTRUCTIONS
+#undef STORE_INSTRUCTIONS
+#undef DOUBLE_LOAD
+
+do_L1_addr16: L1 = memRead16 (READ_PC); NEXT;
+do_L1_addr8: L1 = memRead8 (READ_PC); NEXT;
+do_S1_addr16: memWrite16 (READ_PC, S1); NEXT;
+do_S1_addr8: memWrite8 (READ_PC, S1); NEXT;
+
+#define UL7 ((git_uint32)L7)
+
+do_recompile:
+ gBlockHeader->runCounter = runCounter;
+ pc = compile (READ_PC);
+ runCounter = 0;
+ NEXT;
+
+do_jump_abs_L7:
+ gBlockHeader->runCounter = runCounter;
+ pc = getCode (UL7);
+ runCounter = gBlockHeader->runCounter;
+ NEXT;
+
+do_enter_function_L1: // Arg count is in L2.
+
+ // Check for an accelerated function
+ accelfunc = accel_get_func(L1);
+ if (accelfunc) {
+ S1 = accelfunc(L2, (glui32 *) args);
+ goto do_pop_call_stub;
+ }
+
+ frame = sp;
+ // Read the function type.
+ L7 = memRead8(L1++);
+ // Parse the local variables descriptor.
+ L6 = L5 = L4 = 0;
+ do
+ {
+ L6 = memRead8(L1++); // LocalType
+ L5 = memRead8(L1++); // LocalCount
+ if (L6 != 4 && L6 != 0) // We only support 4-byte locals.
+ fatalError("Local variable wasn't 4 bytes wide");
+ L4 += L5; // Cumulative local count.
+ }
+ while (L5 != 0);
+
+ // Write out the stack frame.
+ // Recall that the number of locals is stored in L4.
+
+ CHECK_FREE(3 + L4);
+
+ PUSH (L4*4 + 12); // FrameLen
+ PUSH (12); // LocalsPos
+ if (L4 == 0)
+ L6 = 0;
+ else
+ L6 = (4 << 24) | (L4 << 16);
+ PUSH (L6); // format of locals
+
+ // This is where the local variables start, so:
+ locals = sp;
+
+ // Read the arguments, based on the function type.
+ switch (L7)
+ {
+ case 0xC0: // arguments should be placed on the stack.
+ // argc is in L2; we'll randomly use L5 as scratch.
+ CHECK_FREE(L5 + 1);
+ // Initialise the local variables.
+ for ( ; L4 > 0 ; --L4)
+ PUSH (0);
+ // This is where the temporary values start, so:
+ values = sp;
+ // Push the args onto the stack.
+ for (L5 = 0 ; L5 < L2 ; ++L5)
+ PUSH (args [L5]);
+ // Push the arg count.
+ PUSH (L2);
+ break;
+
+ case 0xC1: // arguments should be written into locals.
+ // argc is in L2, num locals is in L4.
+ // Stuff as many locals as possible with arguments.
+ for (L5 = 1 ; L5 <= L2 && L4 > 0 ; ++L5, --L4)
+ PUSH (args [L2 - L5]);
+ // Initialise any remaining locals.
+ for ( ; L4 > 0 ; --L4)
+ PUSH (0);
+ // This is where the temporary values start, so:
+ values = sp;
+ break;
+
+ default:
+ // This isn't a function!
+ fatalError("Not a function");
+ break;
+ }
+
+ // Start executing the function.
+ L7 = L1;
+ goto do_jump_abs_L7;
+
+ do_nop: NEXT;
+
+#define PEEPHOLE_STORE(tag, code) \
+ do_ ## tag ## _discard: code; NEXT; \
+ do_ ## tag ## _S1_stack: code; goto do_S1_stack; \
+ do_ ## tag ## _S1_local: code; goto do_S1_local; \
+ do_ ## tag ## _S1_addr: code; goto do_S1_addr
+
+ PEEPHOLE_STORE(add, S1 = L1 + L2);
+ PEEPHOLE_STORE(sub, S1 = L1 - L2);
+ PEEPHOLE_STORE(mul, S1 = L1 * L2);
+ PEEPHOLE_STORE(div, if (L2 == 0) fatalError ("Divide by zero"); S1 = L1 / L2);
+ PEEPHOLE_STORE(mod, if (L2 == 0) fatalError ("Divide by zero"); S1 = L1 % L2);
+
+ PEEPHOLE_STORE(neg, S1 = -L1);
+ PEEPHOLE_STORE(bitnot, S1 = ~L1);
+
+ PEEPHOLE_STORE(bitand, S1 = L1 & L2);
+ PEEPHOLE_STORE(bitor, S1 = L1 | L2);
+ PEEPHOLE_STORE(bitxor, S1 = L1 ^ L2);
+
+ PEEPHOLE_STORE(shiftl, if (L2 > 31) S1 = 0; else S1 = L1 << ((git_uint32) L2));
+ PEEPHOLE_STORE(sshiftr, if (L2 > 31) L2 = 31; S1 = ((git_sint32) L1) >> ((git_uint32) L2));
+ PEEPHOLE_STORE(ushiftr, if (L2 > 31) S1 = 0; else S1 = ((git_uint32) L1) >> ((git_uint32) L2));
+
+ PEEPHOLE_STORE(aload, S1 = memRead32 (L1 + (L2<<2)));
+ PEEPHOLE_STORE(aloads, S1 = memRead16 (L1 + (L2<<1)));
+ PEEPHOLE_STORE(aloadb, S1 = memRead8 (L1 + L2));
+ PEEPHOLE_STORE(aloadbit,S1 = (memRead8 (L1 + (L2>>3)) >> (L2 & 7)) & 1);
+
+ PEEPHOLE_STORE(copys, S1 = L1 & 0xFFFF);
+ PEEPHOLE_STORE(copyb, S1 = L1 & 0x00FF);
+ PEEPHOLE_STORE(sexs, S1 = (git_sint32)((signed short)(L1 & 0xFFFF)));
+ PEEPHOLE_STORE(sexb, S1 = (git_sint32)((signed char)(L1 & 0x00FF)));
+
+#define PEEPHOLE_LOAD(tag,reg) \
+ do_ ## tag ## _ ## reg ## _const: reg = READ_PC; goto do_ ## tag; \
+ do_ ## tag ## _ ## reg ## _stack: CHECK_USED(1); reg = POP; goto do_ ## tag; \
+ do_ ## tag ## _ ## reg ## _local: reg = LOCAL(READ_PC); goto do_ ## tag; \
+ do_ ## tag ## _ ## reg ## _addr: reg = memRead32(READ_PC); goto do_ ## tag
+
+ PEEPHOLE_LOAD (return, L1);
+ PEEPHOLE_LOAD (astore, L3);
+ PEEPHOLE_LOAD (astores, L3);
+ PEEPHOLE_LOAD (astoreb, L3);
+ PEEPHOLE_LOAD (astorebit, L3);
+
+#undef PEEPHOLE_STORE
+
+ do_astore: memWrite32 (L1 + (L2<<2), L3); NEXT;
+ do_astores: memWrite16 (L1 + (L2<<1), L3); NEXT;
+ do_astoreb: memWrite8 (L1 + L2, L3); NEXT;
+ do_astorebit:
+ L4 = memRead8(L1 + (L2>>3));
+ if (L3 == 0)
+ L4 &= ~(1 << (L2 & 7));
+ else
+ L4 |= (1 << (L2 & 7));
+ memWrite8(L1 + (L2>>3), L4);
+ NEXT;
+
+#define DO_JUMP(tag, reg, cond) \
+ do_ ## tag ## _var: L7 = READ_PC; if (cond) goto do_goto_ ## reg ## _from_L7; NEXT; \
+ do_ ## tag ## _const: L7 = READ_PC; if (cond) goto do_jump_abs_L7; NEXT; \
+ do_ ## tag ## _by: L7 = READ_PC; if (cond) pc += L7; NEXT; \
+ do_ ## tag ## _return0: if (cond) { L1 = 0; goto do_return; } NEXT; \
+ do_ ## tag ## _return1: if (cond) { L1 = 1; goto do_return; } NEXT
+
+ DO_JUMP(jump, L1, 1 == 1);
+ DO_JUMP(jz, L2, L1 == 0);
+ DO_JUMP(jnz, L2, L1 != 0);
+ DO_JUMP(jeq, L3, L1 == L2);
+ DO_JUMP(jne, L3, L1 != L2);
+ DO_JUMP(jlt, L3, L1 < L2);
+ DO_JUMP(jge, L3, L1 >= L2);
+ DO_JUMP(jgt, L3, L1 > L2);
+ DO_JUMP(jle, L3, L1 <= L2);
+ DO_JUMP(jltu, L3, ((git_uint32)L1 < (git_uint32)L2));
+ DO_JUMP(jgeu, L3, ((git_uint32)L1 >= (git_uint32)L2));
+ DO_JUMP(jgtu, L3, ((git_uint32)L1 > (git_uint32)L2));
+ DO_JUMP(jleu, L3, ((git_uint32)L1 <= (git_uint32)L2));
+
+#undef DO_JUMP
+
+ do_jumpabs: L7 = L1; goto do_jump_abs_L7; NEXT;
+
+ do_goto_L3_from_L7: L1 = L3; goto do_goto_L1_from_L7;
+ do_goto_L2_from_L7: L1 = L2; goto do_goto_L1_from_L7;
+ do_goto_L1_from_L7:
+ if (L1 == 0 || L1 == 1) goto do_return;
+ L7 = L7 + L1 - 2; goto do_jump_abs_L7;
+
+ do_args_stack:
+ // The first argument is topmost in the stack; the count is in L2.
+ CHECK_USED(L2);
+ // We want to store the arguments in 'args' in the same order.
+ for (L3 = L2 - 1 ; L3 >= 0 ; --L3)
+ args [L3] = POP;
+ NEXT;
+
+ // Specialised versions of above:
+ do_args_stack_call_stub_discard:
+ CHECK_USED(L2);
+ for (L3 = L2 - 1 ; L3 >= 0 ; --L3)
+ args [L3] = POP;
+ goto do_call_stub_discard;
+
+ do_args_stack_call_stub_addr:
+ CHECK_USED(L2);
+ for (L3 = L2 - 1 ; L3 >= 0 ; --L3)
+ args [L3] = POP;
+ goto do_call_stub_addr;
+
+ do_args_stack_call_stub_local:
+ CHECK_USED(L2);
+ for (L3 = L2 - 1 ; L3 >= 0 ; --L3)
+ args [L3] = POP;
+ goto do_call_stub_local;
+
+ do_args_stack_call_stub_stack:
+ CHECK_USED(L2);
+ for (L3 = L2 - 1 ; L3 >= 0 ; --L3)
+ args [L3] = POP;
+ goto do_call_stub_stack;
+
+ do_args_3:
+ args [0] = L4;
+ args [1] = L3;
+ args [2] = L2;
+ L2 = 3;
+ NEXT;
+
+ do_args_2:
+ args [0] = L3;
+ args [1] = L2;
+ L2 = 2;
+ NEXT;
+
+ do_args_1:
+ args [0] = L2;
+ L2 = 1;
+ NEXT;
+
+ do_args_0:
+ L2 = 0;
+ NEXT;
+
+ do_undo_stub_discard:
+ CHECK_FREE(4);
+ PUSH (0); // DestType
+ PUSH (0); // DestAddr
+ goto finish_undo_stub;
+
+ do_undo_stub_addr:
+ CHECK_FREE(4);
+ PUSH (1); // DestType
+ PUSH (READ_PC); // DestAddr
+ goto finish_undo_stub;
+
+ do_undo_stub_local:
+ CHECK_FREE(4);
+ PUSH (2); // DestType
+ PUSH (READ_PC); // DestAddr
+ goto finish_undo_stub;
+
+ do_undo_stub_stack:
+ CHECK_FREE(4);
+ PUSH (3); // DestType
+ PUSH (0); // DestAddr
+ goto finish_undo_stub;
+
+finish_undo_stub:
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ saveUndo (base, sp);
+ S1 = 0;
+ goto do_pop_call_stub;
+
+ do_restoreundo:
+ if (restoreUndo (base, protectPos, protectSize) == 0)
+ {
+ sp = gStackPointer;
+ S1 = -1;
+ goto do_pop_call_stub;
+ }
+ S1 = 1;
+ NEXT;
+
+ do_save_stub_discard:
+ CHECK_FREE(4);
+ PUSH (0); // DestType
+ PUSH (0); // DestAddr
+ goto finish_save_stub;
+
+ do_save_stub_addr:
+ CHECK_FREE(4);
+ PUSH (1); // DestType
+ PUSH (READ_PC); // DestAddr
+ goto finish_save_stub;
+
+ do_save_stub_local:
+ CHECK_FREE(4);
+ PUSH (2); // DestType
+ PUSH (READ_PC); // DestAddr
+ goto finish_save_stub;
+
+ do_save_stub_stack:
+ CHECK_FREE(4);
+ PUSH (3); // DestType
+ PUSH (0); // DestAddr
+ goto finish_save_stub;
+
+finish_save_stub:
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ if (ioMode == IO_GLK)
+ S1 = saveToFile (base, sp, L1);
+ else
+ S1 = 1;
+ goto do_pop_call_stub;
+
+ do_restore:
+ if (ioMode == IO_GLK
+ && restoreFromFile (base, L1, protectPos, protectSize) == 0)
+ {
+ sp = gStackPointer;
+ S1 = -1;
+ goto do_pop_call_stub;
+ }
+ S1 = 1;
+ NEXT;
+
+ do_catch_stub_discard:
+ CHECK_FREE(4);
+ L7 = 0;
+ PUSH (0); // DestType
+ goto finish_catch_stub_addr_L7;
+
+ do_catch_stub_addr:
+ CHECK_FREE(4);
+ L7 = READ_PC;
+ memWrite32(L7, (sp-base+4)*4);
+ PUSH (1); // DestType
+ goto finish_catch_stub_addr_L7;
+
+ do_catch_stub_local:
+ CHECK_FREE(4);
+ L7 = READ_PC;
+ memWrite32(L7, (sp-base+4)*4);
+ PUSH (2); // DestType
+ goto finish_catch_stub_addr_L7;
+
+ do_catch_stub_stack:
+ CHECK_FREE(5);
+ PUSH (3); // DestType
+ PUSH (0); // DestAddr
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ L7 = (sp - base)*4; // Catch token.
+ PUSH (L7);
+ NEXT;
+
+finish_catch_stub_addr_L7:
+ PUSH (L7); // DestAddr
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ NEXT;
+
+ do_throw:
+ if (L2 < 16 || L2 > ((sp-base)*4))
+ fatalError ("Invalid catch token in throw");
+ sp = base + L2 / 4;
+ goto do_pop_call_stub;
+
+do_call_stub_discard:
+ CHECK_FREE(4);
+ PUSH (0); // DestType
+ PUSH (0); // DestAddr
+ goto finish_call_stub;
+
+ do_call_stub_addr:
+ CHECK_FREE(4);
+ PUSH (1); // DestType
+ PUSH (READ_PC); // DestAddr
+ goto finish_call_stub;
+
+ do_call_stub_local:
+ CHECK_FREE(4);
+ PUSH (2); // DestType
+ PUSH (READ_PC); // DestAddr
+ goto finish_call_stub;
+
+ do_call_stub_stack:
+ CHECK_FREE(4);
+ PUSH (3); // DestType
+ PUSH (0); // DestAddr
+ goto finish_call_stub;
+
+finish_call_stub:
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ goto do_enter_function_L1;
+
+do_tailcall:
+ // Zap the current stack frame, down to its call stub.
+ sp = frame;
+ // Call the function!
+ goto do_enter_function_L1;
+
+ do_return:
+ sp = frame;
+ // ...
+ // fall through
+ // ...
+ do_pop_call_stub:// L1 holds the return value.
+ if (sp - base < 4)
+ {
+ if (sp == base)
+ // We just exited the top-level function.
+ goto finished;
+ else
+ // Something nasty happened.
+ goto stack_underflow;
+ }
+ L2 = POP; // FramePtr
+ L7 = POP; // PC
+ L6 = POP; // DestAddr
+ switch (POP) // DestType
+ {
+ case 0: // Do not store.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ break;
+
+ case 1: // Store in main memory.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ memWrite32 (L6, L1);
+ break;
+
+ case 2: // Store in local variable.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ LOCAL(L6/4) = L1;
+ break;
+
+ case 3: // Push on stack.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ PUSH (L1);
+ break;
+
+ case 10: // Resume printing a compressed (E1) string.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ goto resume_compressed_string_L7_bit_L6;
+
+ case 11: // Resume executing function code after a string completes.
+ // Don't restore the frame pointer.
+ break;
+
+ case 12: // Resume printing a signed decimal integer.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ goto resume_number_L7_digit_L6;
+
+ case 13: // Resume printing a C-style (E0) string.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ goto resume_c_string_L7;
+
+ case 14: // Resume printing a Unicode (E2) string.
+ frame = base + L2 / 4;
+ locals = frame + frame[1]/4;
+ values = frame + frame[0]/4;
+ goto resume_uni_string_L7;
+
+ default:
+ fatalError("Bad call stub");
+ }
+ // Restore the PC.
+ goto do_jump_abs_L7;
+
+ do_stkcount:
+ S1 = sp - values; NEXT;
+
+ do_stkpeek:
+ if (L1 < 0 || L1 > (sp - values))
+ fatalError("Out of bounds in stkpeek");
+ S1 = sp[-1 - L1]; NEXT;
+
+ do_stkswap:
+ CHECK_USED(2);
+ L1 = POP; L2 = POP; PUSH(L1); PUSH(L2); NEXT;
+
+ do_stkcopy:
+ CHECK_USED(L1);
+ for (L2 = L1 ; L2 > 0 ; --L2)
+ {
+ L3 = sp[-L1];
+ PUSH (L3);
+ }
+ NEXT;
+
+ resume_number_L7_digit_L6:
+ {
+ char buffer [16];
+
+ // If the IO mode is 'null', do nothing.
+ if (ioMode == IO_NULL)
+ goto do_pop_call_stub;
+
+ // Write the number into the buffer.
+ L1 = (L7 < 0) ? -L7 : L7; // Absolute value of number.
+ L2 = 0; // Current buffer position.
+ do
+ {
+ buffer [L2++] = '0' + (L1 % 10);
+ L1 /= 10;
+ }
+ while (L1 > 0);
+
+ if (L7 < 0)
+ buffer [L2++] = '-';
+
+ if (L6 >= L2)
+ goto do_pop_call_stub; // We printed the whole number already.
+
+ // If we're in filter mode, push a call stub
+ // and filter the next character.
+ if (ioMode == IO_FILTER)
+ {
+ // Store the next character in the args array.
+ args[0] = buffer [L2 - L6 - 1];
+ ++L6;
+
+ // Push a call stub to print the next character.
+ CHECK_FREE(4);
+ PUSH(12); // DestType
+ PUSH(L6); // DestAddr (next digit)
+ PUSH(L7); // PC (number to print)
+ PUSH ((frame - base) * 4); // FramePtr
+
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ else
+ {
+ // We're in Glk mode. Just print all the characters.
+ for ( ; L6 < L2 ; ++L6)
+ glk_put_char (buffer [L2 - L6 - 1]);
+ }
+ }
+ goto do_pop_call_stub;
+
+ resume_c_string_L7:
+ // If the IO mode is 'null', or if we've reached the
+ // end of the string, do nothing.
+ L2 = memRead8(L7++);
+ if (L2 == 0 || ioMode == IO_NULL)
+ goto do_pop_call_stub;
+ // Otherwise we're going to have to print something,
+ // If the IO mode is 'filter', filter the next char.
+ if (ioMode == IO_FILTER)
+ {
+ // Store this character in the args array.
+ args [0] = L2;
+ // Push a call stub.
+ CHECK_FREE(4);
+ PUSH(13); // DestType (resume C string)
+ PUSH(L6); // DestAddr (ignored)
+ PUSH(L7); // PC (next char to print)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ // We're in Glk mode. Just print all the characters.
+ while (L2 != 0)
+ {
+ glk_put_char ((unsigned char) L2);
+ L2 = memRead8(L7++);
+ }
+ goto do_pop_call_stub;
+
+ resume_uni_string_L7:
+ // If the IO mode is 'null', or if we've reached the
+ // end of the string, do nothing.
+ L2 = memRead32(L7);
+ L7 += 4;
+ if (L2 == 0 || ioMode == IO_NULL)
+ goto do_pop_call_stub;
+ // Otherwise we're going to have to print something,
+ // If the IO mode is 'filter', filter the next char.
+ if (ioMode == IO_FILTER)
+ {
+ // Store this character in the args array.
+ args [0] = L2;
+ // Push a call stub.
+ CHECK_FREE(4);
+ PUSH(14); // DestType (resume Unicode string)
+ PUSH(L6); // DestAddr (ignored)
+ PUSH(L7); // PC (next char to print)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ // We're in Glk mode. Just print all the characters.
+ while (L2 != 0)
+ {
+#ifdef GLK_MODULE_UNICODE
+ glk_put_char_uni ((glui32) L2);
+#else
+ unsigned char c = (L2 > 0 && L2 < 256) ? L2 : '?';
+ glk_put_char (c);
+#endif // GLK_MODULE_UNICODE
+ L2 = memRead32(L7);
+ L7 += 4;
+ }
+ goto do_pop_call_stub;
+
+ resume_compressed_string_L7_bit_L6:
+ // Load the first string table node into L1.
+ // Its address is stored at stringTable + 8.
+ L1 = memRead32 (stringTable + 8);
+ // Load the node's type byte.
+ L2 = memRead8 (L1++);
+ // Is the root node a branch?
+ if (L2 == 0)
+ {
+ // We'll keep a reservoir of input bits in L5.
+ L5 = memRead8(L7);
+ // Keep following branch nodes until we hit a leaf node.
+ while (L2 == 0)
+ {
+ // Read the next bit.
+ L4 = (L5 >> L6) & 1;
+ // If we're finished reading this byte,
+ // move on to the next one.
+ if (++L6 > 7)
+ {
+ L6 -= 8;
+ L5 = memRead8(++L7);
+ }
+ // Follow the branch.
+ L1 = memRead32(L1 + 4 * L4);
+ L2 = memRead8 (L1++);
+ }
+ }
+ else if (L2 == 2 || L2 == 3)
+ {
+ // The root node prints a single character or a string.
+ // This will produce infinite output in the Null or Glk
+ // I/O modes, so we'll catch that here.
+
+ if (ioMode != IO_FILTER)
+ fatalError ("String table prints infinite strings!");
+
+ // In Filter mode, the output will be sent to the current
+ // filter function, which can change the string table
+ // before returning, so we'll continue and see what happens.
+ }
+ // We're at a leaf node.
+ switch (L2)
+ {
+ case 1: // Terminator.
+ goto do_pop_call_stub;
+
+ case 2: // Single char.
+ if (ioMode == IO_NULL)
+ { /* Do nothing */ }
+ else if (ioMode == IO_GLK)
+ glk_put_char ((unsigned char) memRead8(L1));
+ else
+ {
+ // Store this character in the args array.
+ args [0] = memRead8(L1);
+ // Push a call stub.
+ CHECK_FREE(4);
+ PUSH(10); // DestType
+ PUSH(L6); // DestAddr (bit number in string)
+ PUSH(L7); // PC (byte address in string)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ break;
+
+ case 3: // C string.
+ // Push a 'resume compressed string' call stub.
+ CHECK_FREE(4);
+ PUSH (10); // DestType
+ PUSH (L6); // DestAddr (bit number in string)
+ PUSH (L7); // PC (byte address in string)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Print the C string.
+ L7 = L1;
+ goto resume_c_string_L7;
+
+ case 4: // Unicode char
+ if (ioMode == IO_NULL)
+ { /* Do nothing */ }
+ else if (ioMode == IO_GLK)
+ {
+#ifdef GLK_MODULE_UNICODE
+ glk_put_char_uni (memRead32(L1));
+#else
+ git_uint32 c = memRead32(L1);
+ if (c > 255) c = '?';
+ glk_put_char ((unsigned char) c);
+#endif // GLK_MODULE_UNICODE
+ }
+ else
+ {
+ // Store this character in the args array.
+ args [0] = memRead32(L1);
+ // Push a call stub.
+ CHECK_FREE(4);
+ PUSH(10); // DestType
+ PUSH(L6); // DestAddr (bit number in string)
+ PUSH(L7); // PC (byte address in string)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ break;
+
+ case 5: // Unicode string.
+ // Push a 'resume compressed string' call stub.
+ CHECK_FREE(4);
+ PUSH (10); // DestType
+ PUSH (L6); // DestAddr (bit number in string)
+ PUSH (L7); // PC (byte address in string)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Print the Unicode string.
+ L7 = L1;
+ goto resume_uni_string_L7;
+
+ case 8: // Indirect reference.
+ L3 = memRead32(L1);
+ L2 = 0; goto indirect_L3_args_L2;
+
+ case 9: // Double-indirect reference.
+ L3 = memRead32(L1); L3 = memRead32(L3);
+ L2 = 0; goto indirect_L3_args_L2;
+
+ case 10: // Indirect reference with args.
+ L3 = memRead32(L1);
+ L2 = memRead32(L1 + 4); goto indirect_L3_args_L2;
+
+ case 11: // Double-indirect reference with args.
+ L3 = memRead32(L1); L3 = memRead32(L3);
+ L2 = memRead32(L1 + 4); goto indirect_L3_args_L2;
+
+ indirect_L3_args_L2:
+ // Push a 'resume compressed string' call stub.
+ CHECK_FREE(4);
+ PUSH (10); // DestType
+ PUSH (L6); // DestAddr (bit number in string)
+ PUSH (L7); // PC (byte address in string)
+ PUSH ((frame - base) * 4); // FramePtr
+ // Check the type of the embedded object.
+ switch (memRead8(L3))
+ {
+ case 0xE0: // C string.
+ L7 = L3 + 1;
+ goto resume_c_string_L7;
+
+ case 0xE1: // Compressed string.
+ L7 = L3 + 1;
+ L6 = 0;
+ goto resume_compressed_string_L7_bit_L6;
+
+ case 0xE2: // Unicode string.
+ L7 = L3 + 4; // Skip extra three padding bytes.
+ goto resume_uni_string_L7;
+
+ case 0xC0: case 0xC1: // Function.
+ // Retrieve arguments.
+ for (L1 += 8, L4 = 0; L4 < L2 ; ++L4, L1+=4)
+ args[L4] = memRead32(L1);
+ // Enter function.
+ L1 = L3;
+ goto do_enter_function_L1;
+
+ default: fatalError ("Embedded object in string has unknown type");
+ }
+ break;
+
+ default: fatalError ("Unknown string table node type");
+ }
+ // Start back at the root node again.
+ goto resume_compressed_string_L7_bit_L6;
+
+ do_streamstr:
+ // Push a 'resume function' call stub.
+ CHECK_FREE(4);
+ PUSH (11); // DestType
+ PUSH (0); // Addr
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+
+ // Load the string's type byte.
+ L2 = memRead8(L1++);
+ if (L2 == 0xE0)
+ {
+ // Uncompressed string.
+ L7 = L1;
+ goto resume_c_string_L7;
+ }
+ else if (L2 == 0xE1)
+ {
+ // Compressed string.
+ L7 = L1;
+ L6 = 0;
+ goto resume_compressed_string_L7_bit_L6;
+ }
+ else if (L2 == 0xE2)
+ {
+ // Uncompressed Unicode string.
+ L7 = L1 + 3; // Skip three padding bytes.
+ goto resume_uni_string_L7;
+ }
+ else
+ {
+ fatalError ("Value used in streamstr was not a string");
+ goto finished;
+ }
+
+ do_streamchar:
+ L7 = READ_PC;
+ if (ioMode == IO_NULL)
+ { /* Do nothing */ }
+ else if (ioMode == IO_GLK)
+ {
+ unsigned char c = (L1 & 0xff);
+ glk_put_char (c);
+ }
+ else
+ {
+ // Store this character in the args array.
+ args [0] = (L1 & 0xff);
+ // Push a 'resume function' call stub.
+ CHECK_FREE(4);
+ PUSH (0); // DestType
+ PUSH (0); // Addr
+ PUSH (L7); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ NEXT;
+
+ do_streamunichar:
+ L7 = READ_PC;
+ if (ioMode == IO_NULL)
+ { /* Do nothing */ }
+ else if (ioMode == IO_GLK)
+ {
+#ifdef GLK_MODULE_UNICODE
+ glk_put_char_uni ((glui32) L1);
+#else
+ unsigned char c = (L1 > 0 && L1 < 256) ? L1 : '?';
+ glk_put_char (c);
+#endif // GLK_MODULE_UNICODE
+ }
+ else
+ {
+ // Store this character in the args array.
+ args [0] = L1;
+ // Push a 'resume function' call stub.
+ CHECK_FREE(4);
+ PUSH (0); // DestType
+ PUSH (0); // Addr
+ PUSH (L7); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+ // Call the filter function.
+ L1 = ioRock;
+ L2 = 1;
+ goto do_enter_function_L1;
+ }
+ NEXT;
+
+ do_streamnum:
+ // Push a 'resume function' call stub.
+ CHECK_FREE(4);
+ PUSH (11); // DestType
+ PUSH (0); // Addr
+ PUSH (READ_PC); // PC
+ PUSH ((frame - base) * 4); // FramePtr
+
+ // Print the number.
+ L7 = L1;
+ L6 = 0;
+ goto resume_number_L7_digit_L6;
+
+ // Stub opcodes:
+
+ do_getmemsize:
+ S1 = gEndMem;
+ NEXT;
+
+ do_getiosys:
+ S1 = ioMode;
+ S2 = ioRock;
+ NEXT;
+
+ do_setiosys:
+ switch (L1)
+ {
+ case IO_NULL:
+ case IO_FILTER:
+ case IO_GLK:
+ ioMode = (enum IOMode) L1;
+ ioRock = L2;
+ break;
+
+ default:
+ fatalError ("Illegal I/O mode");
+ break;
+ }
+ NEXT;
+
+ do_quit:
+ goto finished;
+
+ do_restart:
+ // Reset game memory to its initial state.
+ resetMemory(protectPos, protectSize);
+ resetUndo();
+
+ // Reset all the stack pointers.
+ frame = locals = values = sp = base;
+
+ // Call the first function.
+ L1 = startPos; // Initial PC.
+ L2 = 0; // No arguments.
+ goto do_enter_function_L1;
+
+ do_verify:
+ S1 = verifyMemory();
+ NEXT;
+
+ do_random:
+ if (L1 > 0)
+ S1 = rand() % L1;
+ else if (L1 < 0)
+ S1 = -(rand() % -L1);
+ else
+ {
+ // The parameter is zero, so we should generate a
+ // random number in "the full 32-bit range". The rand()
+ // function might not cover the entire range, so we'll
+ // generate the high 16 bits and low 16 bits separately.
+ S1 = (rand() & 0xffff) | (rand() << 16);
+ }
+ NEXT;
+
+ do_setrandom:
+ srand (L1 ? L1 : time(NULL));
+ NEXT;
+
+ do_glk:
+ // The first argument is topmost in the stack; count is in L2.
+ CHECK_USED(L2);
+ // We want to store the arguments in 'args' in the same order.
+ for (L3 = 0 ; L3 < L2 ; ++L3)
+ args [L3] = POP;
+ gStackPointer = sp;
+ S1 = git_perform_glk (L1, L2, (glui32*) args);
+ sp = gStackPointer;
+ NEXT;
+
+ do_binarysearch:
+ S1 = git_binary_search (L1, L2, L3, L4, L5, L6, L7);
+ NEXT;
+
+ do_linearsearch:
+ S1 = git_linear_search (L1, L2, L3, L4, L5, L6, L7);
+ NEXT;
+
+ do_linkedsearch:
+ S1 = git_linked_search (L1, L2, L3, L4, L5, L6);
+ NEXT;
+
+ do_gestalt:
+ S1 = gestalt (L1, L2);
+ NEXT;
+
+ do_getstringtbl: S1 = stringTable; NEXT;
+ do_setstringtbl: stringTable = L1; NEXT;
+
+ do_debugtrap:
+ // TODO: do something useful here.
+ NEXT;
+
+ do_stkroll:
+ // We need to rotate the top L1 elements by L2 places.
+ if (L1 < 0)
+ fatalError ("Negative number of elements to rotate in stkroll");
+ if (L1 > (sp - values))
+ fatalError ("Tried to rotate too many elements in stkroll");
+ // Now, let's normalise L2 into the range [0..L1).
+ if (L2 >= 0)
+ L2 = L2 % L1;
+ else
+ L2 = L1 - (-L2 % L1);
+ // Avoid trivial cases.
+ if (L1 == 0 || L2 == 0 || L2 == L1)
+ NEXT;
+ L2 = L1 - L2;
+ // The problem is reduced to swapping elements [0..L2) with
+ // elements [L2..L1). Let's call these two sequences A and B,
+ // so we need to transform AB into BA. We do this sneakily
+ // with reversals, as follows: AB -> A'B -> A'B' -> (A'B')',
+ // where X' is the reverse of the sequence X.
+#define SWAP(x,y) \
+ do { L4 = sp[(x)-L1];sp[(x)-L1]=sp[(y)-L1];sp[(y)-L1]=L4; } while (0)
+
+ // Reverse [0..L2).
+ for (L3 = 0 ; L3 < L2/2 ; ++L3)
+ SWAP (L3, L2-1-L3);
+ // Reverse [L2..L1).
+ for (L3 = L2 ; L3 < (L2 + (L1-L2)/2) ; ++L3)
+ SWAP (L3, L1-1-(L3-L2));
+ // Reverse [0..L1).
+ for (L3 = 0 ; L3 < L1/2 ; ++L3)
+ SWAP (L3, L1-1-L3);
+
+#undef SWAP
+ // And we're done!
+ NEXT;
+
+ do_setmemsize:
+ S1 = resizeMemory (L1, 0);
+ NEXT;
+
+ do_protect:
+ protectPos = L1;
+ protectSize = L2;
+ NEXT;
+
+ // Memory management (new with glulx spec 3.1)
+
+ do_mzero:
+ if (L1 > 0) {
+ if (L2 < gRamStart || (L2 + L1) > gEndMem)
+ memWriteError(L2);
+ memset(gRam + L2, 0, L1);
+ }
+ NEXT;
+
+ do_mcopy:
+ if (L1 > 0) {
+ if (L2 < 0 || (L2 + L1) > gEndMem)
+ memReadError(L2);
+ if (L3 < gRamStart || (L3 + L1) > gEndMem)
+ memWriteError(L3);
+ // ROM and ROM are stored separately, so this is a bit fiddly...
+ if (L2 > gRamStart) {
+ // Only need to copy from RAM. Might be overlapping, so use memmove.
+ memmove(gRam + L3, gRam + L2, L1);
+ } else if ((L2 + L1) <= gRamStart) {
+ // Only need to copy from ROM. Can't overlap, so memcpy is safe.
+ memcpy(gRam + L3, gRom + L2, L1);
+ } else {
+ // Need to copy from both ROM and RAM.
+ L4 = (L2 + L1) - gRamStart; // Amount of ROM to copy.
+ memcpy(gRam + L3, gRom + L2, L4);
+ memmove(gRam + L3 + L4, gRam + L2 + L4, L1 - L4);
+ }
+ }
+ NEXT;
+
+ do_malloc:
+ S1 = heap_alloc(L1);
+ NEXT;
+
+ do_mfree:
+ heap_free(L1);
+ NEXT;
+
+ // Function acceleration (new with glulx spec 3.1.1)
+
+ do_accelfunc:
+ accel_set_func(L1, L2);
+ NEXT;
+
+ do_accelparam:
+ accel_set_param(L1, L2);
+ NEXT;
+
+ // Special Git opcodes
+
+ do_git_setcacheram:
+ gCacheRAM = (L1 == 0) ? 0 : 1;
+ NEXT;
+
+ do_git_prunecache:
+ pruneCodeCache (L1, L2);
+ NEXT;
+
+ // Error conditions:
+
+ do_error_bad_opcode:
+ fatalError ("Illegal instruction");
+ goto finished;
+
+ stack_overflow:
+ fatalError ("Stack overflow");
+ goto finished;
+
+ stack_underflow:
+ fatalError ("Stack underflow");
+ goto finished;
+
+// ---------------------------------
+
+finished:
+
+ free (base);
+ shutdownCompiler();
+}