Add Glulxe and Git. They compile but don't work yet.
[rodin/chimara.git] / interpreters / git / terp.c
diff --git a/interpreters/git/terp.c b/interpreters/git/terp.c
new file mode 100644 (file)
index 0000000..8377c5d
--- /dev/null
@@ -0,0 +1,1300 @@
+// $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();
+}