Add Glulxe and Git. They compile but don't work yet.
[rodin/chimara.git] / interpreters / glulxe / operand.c
diff --git a/interpreters/glulxe/operand.c b/interpreters/glulxe/operand.c
new file mode 100644 (file)
index 0000000..bb5a7a3
--- /dev/null
@@ -0,0 +1,580 @@
+/* operand.c: Glulxe code for instruction operands, reading and writing.
+    Designed by Andrew Plotkin <erkyrath@eblong.com>
+    http://eblong.com/zarf/glulx/index.html
+*/
+
+#include "glk.h"
+#include "glulxe.h"
+#include "opcodes.h"
+
+/* ### We could save a few cycles per operand by generating a function for
+   each operandlist type. */
+
+/* fast_operandlist[]:
+   This is a handy array in which to look up operandlists quickly.
+   It stores the operandlists for the first 128 opcodes, which are
+   the ones used most frequently.
+*/
+operandlist_t *fast_operandlist[0x80];
+
+/* The actual immutable structures which lookup_operandlist()
+   returns. */
+static operandlist_t list_none = { 0, 4, NULL };
+
+static int array_S[1] = { modeform_Store };
+static operandlist_t list_S = { 1, 4, array_S };
+static int array_LS[2] = { modeform_Load, modeform_Store };
+static operandlist_t list_LS = { 2, 4, array_LS };
+static int array_LLS[3] = { modeform_Load, modeform_Load, modeform_Store };
+static operandlist_t list_LLS = { 3, 4, array_LLS };
+static int array_LLLS[4] = { modeform_Load, modeform_Load, modeform_Load, modeform_Store };
+static operandlist_t list_LLLS = { 4, 4, array_LLLS };
+static int array_LLLLS[5] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
+static operandlist_t list_LLLLS = { 5, 4, array_LLLLS };
+static int array_LLLLLS[6] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
+static operandlist_t list_LLLLLS = { 6, 4, array_LLLLLS };
+static int array_LLLLLLS[7] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
+static operandlist_t list_LLLLLLS = { 7, 4, array_LLLLLLS };
+static int array_LLLLLLLS[8] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
+static operandlist_t list_LLLLLLLS = { 8, 4, array_LLLLLLLS };
+
+static int array_L[1] = { modeform_Load };
+static operandlist_t list_L = { 1, 4, array_L };
+static int array_LL[2] = { modeform_Load, modeform_Load };
+static operandlist_t list_LL = { 2, 4, array_LL };
+static int array_LLL[3] = { modeform_Load, modeform_Load, modeform_Load };
+static operandlist_t list_LLL = { 3, 4, array_LLL };
+static operandlist_t list_2LS = { 2, 2, array_LS };
+static operandlist_t list_1LS = { 2, 1, array_LS };
+static int array_SL[2] = { modeform_Store, modeform_Load };
+static operandlist_t list_SL = { 2, 4, array_SL };
+static int array_SS[2] = { modeform_Store, modeform_Store };
+static operandlist_t list_SS = { 2, 4, array_SS };
+
+/* init_operands():
+   Set up the fast-lookup array of operandlists. This is called just
+   once, when the terp starts up. 
+*/
+void init_operands()
+{
+  int ix;
+  for (ix=0; ix<0x80; ix++)
+    fast_operandlist[ix] = lookup_operandlist(ix);
+}
+
+/* lookup_operandlist():
+   Return the operandlist for a given opcode. For opcodes in the range
+   00..7F, it's faster to use the array fast_operandlist[]. 
+*/
+operandlist_t *lookup_operandlist(glui32 opcode)
+{
+  switch (opcode) {
+  case op_nop: 
+    return &list_none;
+
+  case op_add:
+  case op_sub:
+  case op_mul:
+  case op_div:
+  case op_mod:
+  case op_bitand:
+  case op_bitor:
+  case op_bitxor:
+  case op_shiftl:
+  case op_sshiftr:
+  case op_ushiftr:
+    return &list_LLS;
+
+  case op_neg:
+  case op_bitnot:
+    return &list_LS;
+
+  case op_jump:
+  case op_jumpabs:
+    return &list_L;
+  case op_jz:
+  case op_jnz:
+    return &list_LL;
+  case op_jeq:
+  case op_jne:
+  case op_jlt:
+  case op_jge:
+  case op_jgt:
+  case op_jle:
+  case op_jltu:
+  case op_jgeu:
+  case op_jgtu:
+  case op_jleu:
+    return &list_LLL;
+
+  case op_call:
+    return &list_LLS;
+  case op_return:
+    return &list_L;
+  case op_catch:
+    return &list_SL;
+  case op_throw:
+    return &list_LL;
+  case op_tailcall:
+    return &list_LL;
+
+  case op_sexb:
+  case op_sexs:
+    return &list_LS;
+
+  case op_copy:
+    return &list_LS;
+  case op_copys:
+    return &list_2LS;
+  case op_copyb:
+    return &list_1LS;
+  case op_aload:
+  case op_aloads:
+  case op_aloadb:
+  case op_aloadbit:
+    return &list_LLS;
+  case op_astore:
+  case op_astores:
+  case op_astoreb:
+  case op_astorebit:
+    return &list_LLL;
+
+  case op_stkcount:
+    return &list_S;
+  case op_stkpeek:
+    return &list_LS;
+  case op_stkswap: 
+    return &list_none;
+  case op_stkroll:
+    return &list_LL;
+  case op_stkcopy:
+    return &list_L;
+
+  case op_streamchar:
+  case op_streamunichar:
+  case op_streamnum:
+  case op_streamstr:
+    return &list_L;
+  case op_getstringtbl:
+    return &list_S;
+  case op_setstringtbl:
+    return &list_L;
+  case op_getiosys:
+    return &list_SS;
+  case op_setiosys:
+    return &list_LL;
+
+  case op_random:
+    return &list_LS;
+  case op_setrandom:
+    return &list_L;
+
+  case op_verify:
+    return &list_S;
+  case op_restart:
+    return &list_none;
+  case op_save:
+  case op_restore:
+    return &list_LS;
+  case op_saveundo:
+  case op_restoreundo:
+    return &list_S;
+  case op_protect:
+    return &list_LL;
+
+  case op_quit:
+    return &list_none;
+
+  case op_gestalt:
+    return &list_LLS;
+
+  case op_debugtrap: 
+    return &list_L;
+
+  case op_getmemsize:
+    return &list_S;
+  case op_setmemsize:
+    return &list_LS;
+
+  case op_linearsearch:
+    return &list_LLLLLLLS;
+  case op_binarysearch:
+    return &list_LLLLLLLS;
+  case op_linkedsearch:
+    return &list_LLLLLLS;
+
+  case op_glk:
+    return &list_LLS;
+
+  case op_callf:
+    return &list_LS;
+  case op_callfi:
+    return &list_LLS;
+  case op_callfii:
+    return &list_LLLS;
+  case op_callfiii:
+    return &list_LLLLS;
+
+  case op_mzero:
+    return &list_LL;
+  case op_mcopy:
+    return &list_LLL;
+  case op_malloc:
+    return &list_LS;
+  case op_mfree:
+    return &list_L;
+
+  case op_accelfunc:
+  case op_accelparam:
+    return &list_LL;
+
+  default: 
+    return NULL;
+  }
+}
+
+/* parse_operands():
+   Read the list of operands of an instruction, and put the values
+   in inst. This assumes that the PC is at the beginning of the
+   operand mode list (right after an opcode number.) Upon return,
+   the PC will be at the beginning of the next instruction.
+*/
+void parse_operands(instruction_t *inst, operandlist_t *oplist)
+{
+  int ix;
+  int numops = oplist->num_ops;
+  int argsize = oplist->arg_size;
+  glui32 modeaddr = pc;
+  int modeval;
+
+  inst->desttype = 0;
+
+  pc += (numops+1) / 2;
+
+  for (ix=0; ix<numops; ix++) {
+    int mode;
+    glui32 value;
+    glui32 addr;
+
+    if ((ix & 1) == 0) {
+      modeval = Mem1(modeaddr);
+      mode = (modeval & 0x0F);
+    }
+    else {
+      mode = ((modeval >> 4) & 0x0F);
+      modeaddr++;
+    }
+
+    if (oplist->formlist[ix] == modeform_Load) {
+
+      switch (mode) {
+
+      case 8: /* pop off stack */
+        if (stackptr < valstackbase+4) {
+          fatal_error("Stack underflow in operand.");
+        }
+        stackptr -= 4;
+        value = Stk4(stackptr);
+        break;
+
+      case 0: /* constant zero */
+        value = 0;
+        break;
+
+      case 1: /* one-byte constant */
+        /* Sign-extend from 8 bits to 32 */
+        value = (glsi32)(signed char)(Mem1(pc));
+        pc++;
+        break;
+
+      case 2: /* two-byte constant */
+        /* Sign-extend the first byte from 8 bits to 32; the subsequent
+           byte must not be sign-extended. */
+        value = (glsi32)(signed char)(Mem1(pc));
+        pc++;
+        value = (value << 8) | (glui32)(Mem1(pc));
+        pc++;
+        break;
+
+      case 3: /* four-byte constant */
+        /* Bytes must not be sign-extended. */
+        value = Mem4(pc);
+        pc += 4;
+        break;
+
+      case 15: /* main memory RAM, four-byte address */
+        addr = Mem4(pc);
+        addr += ramstart;
+        pc += 4;
+        goto MainMemAddr; 
+
+      case 14: /* main memory RAM, two-byte address */
+        addr = (glui32)Mem2(pc);
+        addr += ramstart;
+        pc += 2;
+        goto MainMemAddr; 
+
+      case 13: /* main memory RAM, one-byte address */
+        addr = (glui32)(Mem1(pc));
+        addr += ramstart;
+        pc++;
+        goto MainMemAddr; 
+        
+      case 7: /* main memory, four-byte address */
+        addr = Mem4(pc);
+        pc += 4;
+        goto MainMemAddr;
+
+      case 6: /* main memory, two-byte address */
+        addr = (glui32)Mem2(pc);
+        pc += 2;
+        goto MainMemAddr;
+
+      case 5: /* main memory, one-byte address */
+        addr = (glui32)(Mem1(pc));
+        pc++;
+        /* fall through */
+
+      MainMemAddr:
+        /* cases 5, 6, 7, 13, 14, 15 all wind up here. */
+        if (argsize == 4) {
+          value = Mem4(addr);
+        }
+        else if (argsize == 2) {
+          value = Mem2(addr);
+        }
+        else {
+          value = Mem1(addr);
+        }
+        break;
+
+      case 11: /* locals, four-byte address */
+        addr = Mem4(pc);
+        pc += 4;
+        goto LocalsAddr;
+
+      case 10: /* locals, two-byte address */
+        addr = (glui32)Mem2(pc);
+        pc += 2;
+        goto LocalsAddr; 
+
+      case 9: /* locals, one-byte address */
+        addr = (glui32)(Mem1(pc));
+        pc++;
+        /* fall through */
+
+      LocalsAddr:
+        /* cases 9, 10, 11 all wind up here. It's illegal for addr to not
+           be four-byte aligned, but we don't check this explicitly. 
+           A "strict mode" interpreter probably should. It's also illegal
+           for addr to be less than zero or greater than the size of
+           the locals segment. */
+        addr += localsbase;
+        if (argsize == 4) {
+          value = Stk4(addr);
+        }
+        else if (argsize == 2) {
+          value = Stk2(addr);
+        }
+        else {
+          value = Stk1(addr);
+        }
+        break;
+
+      default:
+        fatal_error("Unknown addressing mode in load operand.");
+      }
+
+      inst->value[ix] = value;
+
+    }
+    else {  /* modeform_Store */
+      switch (mode) {
+
+      case 0: /* discard value */
+        inst->desttype = 0;
+        inst->value[ix] = 0;
+        break;
+
+      case 8: /* push on stack */
+        inst->desttype = 3;
+        inst->value[ix] = 0;
+        break;
+
+      case 15: /* main memory RAM, four-byte address */
+        addr = Mem4(pc);
+        addr += ramstart;
+        pc += 4;
+        goto WrMainMemAddr; 
+
+      case 14: /* main memory RAM, two-byte address */
+        addr = (glui32)Mem2(pc);
+        addr += ramstart;
+        pc += 2;
+        goto WrMainMemAddr; 
+
+      case 13: /* main memory RAM, one-byte address */
+        addr = (glui32)(Mem1(pc));
+        addr += ramstart;
+        pc++;
+        goto WrMainMemAddr; 
+
+      case 7: /* main memory, four-byte address */
+        addr = Mem4(pc);
+        pc += 4;
+        goto WrMainMemAddr;
+
+      case 6: /* main memory, two-byte address */
+        addr = (glui32)Mem2(pc);
+        pc += 2;
+        goto WrMainMemAddr;
+
+      case 5: /* main memory, one-byte address */
+        addr = (glui32)(Mem1(pc));
+        pc++;
+        /* fall through */
+
+      WrMainMemAddr:
+        /* cases 5, 6, 7 all wind up here. */
+        inst->desttype = 1;
+        inst->value[ix] = addr;
+        break;
+
+      case 11: /* locals, four-byte address */
+        addr = Mem4(pc);
+        pc += 4;
+        goto WrLocalsAddr;
+
+      case 10: /* locals, two-byte address */
+        addr = (glui32)Mem2(pc);
+        pc += 2;
+        goto WrLocalsAddr; 
+
+      case 9: /* locals, one-byte address */
+        addr = (glui32)(Mem1(pc));
+        pc++;
+        /* fall through */
+
+      WrLocalsAddr:
+        /* cases 9, 10, 11 all wind up here. It's illegal for addr to not
+           be four-byte aligned, but we don't check this explicitly. 
+           A "strict mode" interpreter probably should. It's also illegal
+           for addr to be less than zero or greater than the size of
+           the locals segment. */
+        inst->desttype = 2;
+        /* We don't add localsbase here; the store address for desttype 2
+           is relative to the current locals segment, not an absolute
+           stack position. */
+        inst->value[ix] = addr;
+        break;
+
+      case 1:
+      case 2:
+      case 3:
+        fatal_error("Constant addressing mode in store operand.");
+
+      default:
+        fatal_error("Unknown addressing mode in store operand.");
+      }
+    }
+  }
+}
+
+/* store_operand():
+   Store a result value, according to the desttype and destaddress given.
+   This is usually used to store the result of an opcode, but it's also
+   used by any code that pulls a call-stub off the stack.
+*/
+void store_operand(glui32 desttype, glui32 destaddr, glui32 storeval)
+{
+  switch (desttype) {
+
+  case 0: /* do nothing; discard the value. */
+    return;
+
+  case 1: /* main memory. */
+    MemW4(destaddr, storeval);
+    return;
+
+  case 2: /* locals. */
+    destaddr += localsbase;
+    StkW4(destaddr, storeval);
+    return;
+
+  case 3: /* push on stack. */
+    if (stackptr+4 > stacksize) {
+      fatal_error("Stack overflow in store operand.");
+    }
+    StkW4(stackptr, storeval);
+    stackptr += 4;
+    return;
+
+  default:
+    fatal_error("Unknown destination type in store operand.");
+
+  }
+}
+
+void store_operand_s(glui32 desttype, glui32 destaddr, glui32 storeval)
+{
+  storeval &= 0xFFFF;
+
+  switch (desttype) {
+
+  case 0: /* do nothing; discard the value. */
+    return;
+
+  case 1: /* main memory. */
+    MemW2(destaddr, storeval);
+    return;
+
+  case 2: /* locals. */
+    destaddr += localsbase;
+    StkW2(destaddr, storeval);
+    return;
+
+  case 3: /* push on stack. A four-byte value is actually pushed. */
+    if (stackptr+4 > stacksize) {
+      fatal_error("Stack overflow in store operand.");
+    }
+    StkW4(stackptr, storeval);
+    stackptr += 4;
+    return;
+
+  default:
+    fatal_error("Unknown destination type in store operand.");
+
+  }
+}
+
+void store_operand_b(glui32 desttype, glui32 destaddr, glui32 storeval)
+{
+  storeval &= 0xFF;
+
+  switch (desttype) {
+
+  case 0: /* do nothing; discard the value. */
+    return;
+
+  case 1: /* main memory. */
+    MemW1(destaddr, storeval);
+    return;
+
+  case 2: /* locals. */
+    destaddr += localsbase;
+    StkW1(destaddr, storeval);
+    return;
+
+  case 3: /* push on stack. A four-byte value is actually pushed. */
+    if (stackptr+4 > stacksize) {
+      fatal_error("Stack overflow in store operand.");
+    }
+    StkW4(stackptr, storeval);
+    stackptr += 4;
+    return;
+
+  default:
+    fatal_error("Unknown destination type in store operand.");
+
+  }
+}