X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=interpreters%2Fglulxe%2Foperand.c;fp=interpreters%2Fglulxe%2Foperand.c;h=bb5a7a3b261fb4a5813a97fd9ac904db55b3fba9;hb=147a8cbf17f2b3379277bf7d37cda9866510f16c;hp=0000000000000000000000000000000000000000;hpb=7de488aa6a1709a4d5c59b5ff59862105c1748c5;p=rodin%2Fchimara.git diff --git a/interpreters/glulxe/operand.c b/interpreters/glulxe/operand.c new file mode 100644 index 0000000..bb5a7a3 --- /dev/null +++ b/interpreters/glulxe/operand.c @@ -0,0 +1,580 @@ +/* operand.c: Glulxe code for instruction operands, reading and writing. + Designed by Andrew Plotkin + 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> 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."); + + } +}