scummvm/engines/tinsel/pcode.cpp
Max Horn 1dbf8d73d5 TINSEL: Mark all (?) global vars with a FIXME comment
Use of global vars is what prevents RTL from working in Tinsel (and
probably in other engines). More specifically, the fact that many
global vars are not explicitly inited when the engine is (re)launched.

svn-id: r54262
2010-11-16 09:53:55 +00:00

965 lines
29 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
* Virtual processor.
*/
#include "tinsel/dw.h"
#include "tinsel/drives.h"
#include "tinsel/events.h" // 'POINTED' etc.
#include "tinsel/handle.h" // LockMem()
#include "tinsel/dialogs.h" // for inventory id's
#include "tinsel/pcode.h" // opcodes etc.
#include "tinsel/scn.h" // FindChunk()
#include "common/serializer.h"
#include "tinsel/timers.h"
#include "tinsel/tinlib.h" // Library routines
#include "tinsel/tinsel.h"
#include "common/util.h"
namespace Tinsel {
//----------------- EXTERN FUNCTIONS --------------------
extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState);
//----------------- LOCAL DEFINES --------------------
#define GLOBALS_FILENAME "gdata" // name of globals file
/** list of all opcodes */
enum OPCODE {
OP_HALT = 0, ///< end of program
OP_IMM = 1, ///< loads signed immediate onto stack
OP_ZERO = 2, ///< loads zero onto stack
OP_ONE = 3, ///< loads one onto stack
OP_MINUSONE = 4, ///< loads minus one onto stack
OP_STR = 5, ///< loads string offset onto stack
OP_FILM = 6, ///< loads film offset onto stack
OP_FONT = 7, ///< loads font offset onto stack
OP_PAL = 8, ///< loads palette offset onto stack
OP_LOAD = 9, ///< loads local variable onto stack
OP_GLOAD = 10, ///< loads global variable onto stack - long offset to variable
OP_STORE = 11, ///< pops stack and stores in local variable - long offset to variable
OP_GSTORE = 12, ///< pops stack and stores in global variable - long offset to variable
OP_CALL = 13, ///< procedure call
OP_LIBCALL = 14, ///< library procedure call - long offset to procedure
OP_RET = 15, ///< procedure return
OP_ALLOC = 16, ///< allocate storage on stack
OP_JUMP = 17, ///< unconditional jump - signed word offset
OP_JMPFALSE = 18, ///< conditional jump - signed word offset
OP_JMPTRUE = 19, ///< conditional jump - signed word offset
OP_EQUAL = 20, ///< tests top two items on stack for equality
OP_LESS, ///< tests top two items on stack
OP_LEQUAL, ///< tests top two items on stack
OP_NEQUAL, ///< tests top two items on stack
OP_GEQUAL, ///< tests top two items on stack
OP_GREAT = 25, ///< tests top two items on stack
OP_PLUS, ///< adds top two items on stack and replaces with result
OP_MINUS, ///< subs top two items on stack and replaces with result
OP_LOR, ///< logical or of top two items on stack and replaces with result
OP_MULT, ///< multiplies top two items on stack and replaces with result
OP_DIV = 30, ///< divides top two items on stack and replaces with result
OP_MOD, ///< divides top two items on stack and replaces with modulus
OP_AND, ///< bitwise ands top two items on stack and replaces with result
OP_OR, ///< bitwise ors top two items on stack and replaces with result
OP_EOR, ///< bitwise exclusive ors top two items on stack and replaces with result
OP_LAND = 35, ///< logical ands top two items on stack and replaces with result
OP_NOT, ///< logical nots top item on stack
OP_COMP, ///< complements top item on stack
OP_NEG, ///< negates top item on stack
OP_DUP, ///< duplicates top item on stack
OP_ESCON = 40, ///< start of escapable sequence
OP_ESCOFF = 41, ///< end of escapable sequence
OP_CIMM, ///< loads signed immediate onto stack (special to case statements)
OP_CDFILM ///< loads film offset onto stack but not in current scene
};
// modifiers for the above opcodes
#define OPSIZE8 0x40 ///< when this bit is set - the operand size is 8 bits
#define OPSIZE16 0x80 ///< when this bit is set - the operand size is 16 bits
#define OPMASK 0x3F ///< mask to isolate the opcode
bool bNoPause = false;
//----------------- LOCAL GLOBAL DATA --------------------
// FIXME: Avoid non-const global vars
static int32 *pGlobals = 0; // global vars
static int numGlobals = 0; // How many global variables to save/restore
static INT_CONTEXT *icList = 0;
static uint32 hMasterScript;
//----------------- SCRIPT BUGS WORKAROUNDS --------------
/**
* This structure is used to introduce bug fixes into the scripts used by the games.
*/
struct WorkaroundEntry {
TinselEngineVersion version; ///< Engine version this workaround applies to
bool scnFlag; ///< Only applicable for Tinsel 1 (DW 1)
SCNHANDLE hCode; ///< Script to apply fragment to
int ip; ///< Script offset to run this fragment before
int numBytes; ///< Number of bytes in the script
const byte *script; ///< Instruction(s) to execute
};
#define FRAGMENT_WORD(x) (byte)(x & 0xFF), (byte)(x >> 8)
static const byte fragment1[] = {OP_ZERO, OP_GSTORE | OPSIZE16, 206, 0};
static const byte fragment2[] = {OP_LIBCALL | OPSIZE8, 110};
static const byte fragment3[] = {OP_ZERO, OP_GSTORE | OPSIZE16, FRAGMENT_WORD(490)};
static const byte fragment4[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(900), OP_JUMP | OPSIZE16, FRAGMENT_WORD(466)};
static const byte fragment5[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(901), OP_JUMP | OPSIZE16, FRAGMENT_WORD(488)};
static const byte fragment6[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(903), OP_JUMP | OPSIZE16, FRAGMENT_WORD(516)};
static const byte fragment7[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(908), OP_JUMP | OPSIZE16, FRAGMENT_WORD(616)};
static const byte fragment8[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(910), OP_JUMP | OPSIZE16, FRAGMENT_WORD(644)};
static const byte fragment9[] = {OP_JUMP | OPSIZE8, 123};
static const byte fragment10[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(160), OP_JUMP | OPSIZE16, FRAGMENT_WORD(136)};
static const byte fragment11[] = {OP_JMPTRUE | OPSIZE16, FRAGMENT_WORD(1572),
OP_ONE, OP_LIBCALL | OPSIZE8, 14, // Re-show the cursor
OP_IMM | OPSIZE16, FRAGMENT_WORD(322), OP_LIBCALL | OPSIZE8, 46, // Give back the whistle
OP_JUMP | OPSIZE16, FRAGMENT_WORD(1661)};
static const byte fragment12[] = {OP_JMPTRUE | OPSIZE16, FRAGMENT_WORD(1491),
OP_ONE, OP_LIBCALL | OPSIZE8, 14, // Re-show the cursor
OP_IMM | OPSIZE16, FRAGMENT_WORD(322), OP_LIBCALL | OPSIZE8, 46, // Give back the whistle
OP_JUMP | OPSIZE16, FRAGMENT_WORD(1568)};
static const byte fragment13[] = {OP_ZERO, OP_GSTORE | OPSIZE16, FRAGMENT_WORD(306)};
#undef FRAGMENT_WORD
const WorkaroundEntry workaroundList[] = {
// DW1-SCN: Global 206 is whether Rincewind is trying to take the
// book back to the present. In the GRA version, it was global 373,
// and was reset when he is returned to the past, but was forgotten
// in the SCN version, so this ensures the flag is properly reset.
{TINSEL_V1, true, 427942095, 1, sizeof(fragment1), fragment1},
// DW1-GRA: Rincewind exiting the Inn is blocked by the luggage.
// Whilst you can then move into walkable areas, saving and
// restoring the game, it will error if you try to move. This
// fragment turns off NPC blocking for the Outside Inn rooms so that
// the luggage won't block Past Outside Inn.
// See bug report #2525010.
{TINSEL_V1, false, 444622076, 0, sizeof(fragment2), fragment2},
// Present Outside Inn
{TINSEL_V1, false, 352600876, 0, sizeof(fragment2), fragment2},
// DW1-GRA: Talking to palace guards in Act 2 gives !!!HIGH
// STRING||| - this happens if you initiate dialog with one of the
// guards, but not the other. So these fragments provide the correct
// talk parameters where needed.
// See bug report #2831159.
{TINSEL_V1, false, 310506872, 463, sizeof(fragment4), fragment4},
{TINSEL_V1, false, 310506872, 485, sizeof(fragment5), fragment5},
{TINSEL_V1, false, 310506872, 513, sizeof(fragment6), fragment6},
{TINSEL_V1, false, 310506872, 613, sizeof(fragment7), fragment7},
{TINSEL_V1, false, 310506872, 641, sizeof(fragment8), fragment8},
// DW1-SCN: The script for the lovable street-Starfish does a
// 'StopSample' after flicking the coin to ensure it's sound is
// stopped, but which also accidentally can stop any active
// conversation with the Amazon.
{TINSEL_V1, true, 394640351, 121, sizeof(fragment9), fragment9},
// DW2: In the garden, global #490 is set when the bees begin their
// 'out of hive' animation, and reset when done. But if the game is
// saved/restored during it, the animation sequence is reset without
// the global being cleared. This causes bugs in several actions
// which try to disable the bees animation, since they wait
// indefinitely for the global to be cleared, incorrectly believing
// the animation is currently playing. This includes:
// * Giving the brochure to the beekeeper (bug #2680397)
// * Stealing the mallets from the wizards (bug #2820788).
// This fix ensures that the global is reset when the Garden scene
// is loaded (both entering and restoring a game).
{TINSEL_V2, true, 2888147476U, 0, sizeof(fragment3), fragment3},
// DW1-GRA: Corrects text being drawn partially off-screen during
// the blackboard description of the Librarian.
{TINSEL_V1, false, 293831402, 133, sizeof(fragment10), fragment10},
// DW1-GRA/SCN: Corrects the dead-end of being able to give the
// whistle back to the pirate before giving him the parrot.
// See bug report #2934211.
{TINSEL_V1, true, 352601285, 1569, sizeof(fragment11), fragment11},
{TINSEL_V1, false, 352602304, 1488, sizeof(fragment12), fragment12},
// DW2: Corrects a bug with global 306 not being cleared if you leave
// the marketplace scene whilst D'Blah is talking (even if it's not
// actually audible); returning to the scene and clicking on him multiple
// times would cause the game to crash
{TINSEL_V2, true, 1109294728, 0, sizeof(fragment13), fragment13},
{TINSEL_V0, false, 0, 0, 0, NULL}
};
//----------------- LOCAL GLOBAL DATA --------------------
/**
* Keeps the code array pointer up to date.
*/
void LockCode(INT_CONTEXT *ic) {
if (ic->GSort == GS_MASTER) {
if (TinselV2)
// Get the srcipt handle from a specific global chunk
ic->code = (byte *)LockMem(hMasterScript);
else
ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE);
} else
ic->code = (byte *)LockMem(ic->hCode);
}
/**
* Find a free interpret context and allocate it to the calling process.
*/
static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) {
INT_CONTEXT *pic;
int i;
for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
if (pic->GSort == GS_NONE) {
pic->pProc = g_scheduler->getCurrentProcess();
pic->GSort = gsort;
return pic;
}
#ifdef DEBUG
else {
if (pic->pProc == g_scheduler->getCurrentProcess())
error("Found unreleased interpret context");
}
#endif
}
error("Out of interpret contexts");
}
static void FreeWaitCheck(PINT_CONTEXT pic, bool bVoluntary) {
int i;
// Is this waiting for something?
if (pic->waitNumber1) {
for (i = 0; i < NUM_INTERPRET; i++) {
if ((icList + i)->waitNumber2 == pic->waitNumber1) {
(icList + i)->waitNumber2 = 0;
break;
}
}
}
// Is someone waiting for this?
if (pic->waitNumber2) {
for (i = 0; i < NUM_INTERPRET; i++) {
if ((icList + i)->waitNumber1 == pic->waitNumber2) {
(icList + i)->waitNumber1 = 0;
(icList + i)->resumeCode = bVoluntary ? RES_FINISHED : RES_CUTSHORT;
g_scheduler->reschedule((icList + i)->pProc);
break;
}
}
assert(i < NUM_INTERPRET);
}
}
/**
* Normal release of an interpret context.
* Called from the end of Interpret().
*/
static void FreeInterpretContextPi(INT_CONTEXT *pic) {
FreeWaitCheck(pic, true);
if (TinselV2)
memset(pic, 0, sizeof(INT_CONTEXT));
pic->GSort = GS_NONE;
}
/**
* Free interpret context owned by a dying process.
* Ensures that interpret contexts don't get lost when an Interpret()
* call doesn't complete.
*/
void FreeInterpretContextPr(PROCESS *pProc) {
INT_CONTEXT *pic;
int i;
for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
if (pic->GSort != GS_NONE && pic->pProc == pProc) {
FreeWaitCheck(pic, false);
if (TinselV2)
memset(pic, 0, sizeof(INT_CONTEXT));
pic->GSort = GS_NONE;
break;
}
}
}
/**
* Free all interpret contexts except for the master script's
*/
void FreeMostInterpretContexts() {
INT_CONTEXT *pic;
int i;
for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
if ((pic->GSort != GS_MASTER) && (pic->GSort != GS_GPROCESS)) {
memset(pic, 0, sizeof(INT_CONTEXT));
pic->GSort = GS_NONE;
}
}
}
/**
* Free the master script's interpret context.
*/
void FreeMasterInterpretContext() {
INT_CONTEXT *pic;
int i;
for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
if ((pic->GSort == GS_MASTER) || (pic->GSort == GS_GPROCESS)) {
memset(pic, 0, sizeof(INT_CONTEXT));
pic->GSort = GS_NONE;
return;
}
}
}
/**
* Allocate and initialise an interpret context.
* Called from a process prior to Interpret().
* @param gsort which sort of code
* @param hCode Handle to code to execute
* @param event Causal event
* @param hpoly Associated polygon (if any)
* @param actorId Associated actor (if any)
* @param pinvo Associated inventory object
*/
INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, TINSEL_EVENT event,
HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo, int myEscape) {
INT_CONTEXT *ic;
ic = AllocateInterpretContext(gsort);
// Previously parameters to Interpret()
ic->hCode = hCode;
LockCode(ic);
ic->event = event;
ic->hPoly = hpoly;
ic->idActor = actorid;
ic->pinvo = pinvo;
// Previously local variables in Interpret()
ic->bHalt = false; // set to exit interpeter
ic->escOn = myEscape > 0;
ic->myEscape = myEscape;
ic->sp = 0;
ic->bp = ic->sp + 1;
ic->ip = 0; // start of code
ic->resumeState = RES_NOT;
return ic;
}
/**
* Allocate and initialise an interpret context with restored data.
*/
INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) {
INT_CONTEXT *ic;
ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden
memcpy(ic, ric, sizeof(INT_CONTEXT));
ic->pProc = g_scheduler->getCurrentProcess();
ic->resumeState = RES_1;
LockCode(ic);
return ic;
}
/**
* Allocates enough RAM to hold the global Glitter variables.
*/
void RegisterGlobals(int num) {
if (pGlobals == NULL) {
numGlobals = num;
hMasterScript = !TinselV2 ? 0 :
READ_LE_UINT32(FindChunk(MASTER_SCNHANDLE, CHUNK_MASTER_SCRIPT));
// Allocate RAM for pGlobals and make sure it's allocated
pGlobals = (int32 *)calloc(numGlobals, sizeof(int32));
if (pGlobals == NULL) {
error("Cannot allocate memory for global data");
}
// Allocate RAM for interpret contexts and make sure it's allocated
icList = (INT_CONTEXT *)calloc(NUM_INTERPRET, sizeof(INT_CONTEXT));
if (icList == NULL) {
error("Cannot allocate memory for interpret contexts");
}
g_scheduler->setResourceCallback(FreeInterpretContextPr);
} else {
// Check size is still the same
assert(numGlobals == num);
memset(pGlobals, 0, numGlobals * sizeof(int32));
memset(icList, 0, NUM_INTERPRET * sizeof(INT_CONTEXT));
}
if (TinselV2) {
// read initial values
CdCD(nullContext);
Common::File f;
if (!f.open(GLOBALS_FILENAME))
error(CANNOT_FIND_FILE, GLOBALS_FILENAME);
int32 length = f.readSint32LE();
if (length != num)
error(FILE_IS_CORRUPT, GLOBALS_FILENAME);
for (int i = 0; i < length; ++i)
pGlobals[i] = f.readSint32LE();
if (f.eos() || f.err())
error(FILE_IS_CORRUPT, GLOBALS_FILENAME);
f.close();
}
}
void FreeGlobals() {
free(pGlobals);
pGlobals = NULL;
free(icList);
icList = NULL;
}
/**
* (Un)serialize the global data for save/restore game.
*/
void syncGlobInfo(Common::Serializer &s) {
for (int i = 0; i < numGlobals; i++) {
s.syncAsSint32LE(pGlobals[i]);
}
}
/**
* (Un)serialize an interpreter context for save/restore game.
*/
void INT_CONTEXT::syncWithSerializer(Common::Serializer &s) {
if (s.isLoading()) {
// Null out the pointer fields
pProc = NULL;
code = NULL;
pinvo = NULL;
}
// Write out used fields
s.syncAsUint32LE(GSort);
s.syncAsUint32LE(hCode);
s.syncAsUint32LE(event);
s.syncAsSint32LE(hPoly);
s.syncAsSint32LE(idActor);
for (int i = 0; i < PCODE_STACK_SIZE; ++i)
s.syncAsSint32LE(stack[i]);
s.syncAsSint32LE(sp);
s.syncAsSint32LE(bp);
s.syncAsSint32LE(ip);
s.syncAsUint32LE(bHalt);
s.syncAsUint32LE(escOn);
s.syncAsSint32LE(myEscape);
}
/**
* Return pointer to and size of global data for save/restore game.
*/
void SaveInterpretContexts(INT_CONTEXT *sICInfo) {
memcpy(sICInfo, icList, NUM_INTERPRET * sizeof(INT_CONTEXT));
}
/**
* Fetches up to 4 bytes from the code script
*/
static int32 GetBytes(const byte *scriptCode, const WorkaroundEntry* &wkEntry, int &ip, uint numBytes) {
assert(numBytes <= 4 && numBytes != 3);
const byte *code = scriptCode;
if (wkEntry != NULL) {
if (ip >= wkEntry->numBytes) {
// Finished the workaround
ip = wkEntry->ip;
wkEntry = NULL;
} else {
code = wkEntry->script;
}
}
uint32 tmp;
switch (numBytes) {
case 0:
// Instruction byte
tmp = code[ip++ * (TinselV0 ? 4 : 1)];
break;
case 1:
// Fetch and sign extend a 8 bit value to 32 bits.
tmp = (int8)code[ip++];
break;
case 2:
// Fetch and sign extend a 16 bit value to 32 bits.
tmp = (int16)READ_LE_UINT16(code + ip);
ip += 2;
break;
default:
if (TinselV0)
tmp = (int32)READ_LE_UINT32(code + ip++ * 4);
else {
tmp = (int32)READ_LE_UINT32(code + ip);
ip += 4;
}
break;
}
return tmp;
}
/**
* Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code
* stream and advance the instruction pointer accordingly.
*/
static int32 Fetch(byte opcode, const byte *code, const WorkaroundEntry* &wkEntry, int &ip) {
if (TinselV0)
// Fetch a 32 bit value.
return GetBytes(code, wkEntry, ip, 4);
else if (opcode & OPSIZE8)
// Fetch and sign extend a 8 bit value to 32 bits.
return GetBytes(code, wkEntry, ip, 1);
else if (opcode & OPSIZE16)
return GetBytes(code, wkEntry, ip, 2);
return GetBytes(code, wkEntry, ip, 4);
}
/**
* Interprets the PCODE instructions in the code array.
*/
void Interpret(CORO_PARAM, INT_CONTEXT *ic) {
do {
int tmp, tmp2;
int ip = ic->ip;
const WorkaroundEntry *wkEntry = ic->fragmentPtr;
if (wkEntry == NULL) {
// Check to see if a workaround fragment needs to be executed
for (wkEntry = workaroundList; wkEntry->script != NULL; ++wkEntry) {
if ((wkEntry->version == TinselVersion) &&
(wkEntry->hCode == ic->hCode) &&
(wkEntry->ip == ip) &&
(!TinselV1 || (wkEntry->scnFlag == ((_vm->getFeatures() & GF_SCNFILES) != 0)))) {
// Point to start of workaround fragment
ip = 0;
break;
}
}
if (wkEntry->script == NULL)
wkEntry = NULL;
}
byte opcode = (byte)GetBytes(ic->code, wkEntry, ip, 0);
if (TinselV0 && ((opcode & OPMASK) > OP_IMM))
opcode += 3;
debug(7, "ip=%d Opcode %d (-> %d)", ic->ip, opcode, opcode & OPMASK);
switch (opcode & OPMASK) {
case OP_HALT: // end of program
ic->bHalt = true;
break;
case OP_IMM: // loads immediate data onto stack
case OP_STR: // loads string handle onto stack
case OP_FILM: // loads film handle onto stack
case OP_CDFILM: // loads film handle onto stack
case OP_FONT: // loads font handle onto stack
case OP_PAL: // loads palette handle onto stack
ic->stack[++ic->sp] = Fetch(opcode, ic->code, wkEntry, ip);
break;
case OP_ZERO: // loads zero onto stack
ic->stack[++ic->sp] = 0;
break;
case OP_ONE: // loads one onto stack
ic->stack[++ic->sp] = 1;
break;
case OP_MINUSONE: // loads minus one onto stack
ic->stack[++ic->sp] = -1;
break;
case OP_LOAD: // loads local variable onto stack
ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, wkEntry, ip)];
break;
case OP_GLOAD: // loads global variable onto stack
tmp = Fetch(opcode, ic->code, wkEntry, ip);
assert(0 <= tmp && tmp < numGlobals);
ic->stack[++ic->sp] = pGlobals[tmp];
break;
case OP_STORE: // pops stack and stores in local variable
ic->stack[ic->bp + Fetch(opcode, ic->code, wkEntry, ip)] = ic->stack[ic->sp--];
break;
case OP_GSTORE: // pops stack and stores in global variable
tmp = Fetch(opcode, ic->code, wkEntry, ip);
assert(0 <= tmp && tmp < numGlobals);
pGlobals[tmp] = ic->stack[ic->sp--];
break;
case OP_CALL: // procedure call
tmp = Fetch(opcode, ic->code, wkEntry, ip);
//assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds
ic->stack[ic->sp + 1] = 0; // static link
ic->stack[ic->sp + 2] = ic->bp; // dynamic link
ic->stack[ic->sp + 3] = ip; // return address
ic->bp = ic->sp + 1; // set new base pointer
ip = tmp; // set ip to procedure address
break;
case OP_LIBCALL: // library procedure or function call
tmp = Fetch(opcode, ic->code, wkEntry, ip);
// NOTE: Interpret() itself is not using the coroutine facilities,
// but still accepts a CORO_PARAM, so from the outside it looks
// like a coroutine. In fact it may still acts as a kind of "proxy"
// for some underlying coroutine. To enable this, we just pass on
// 'coroParam' to CallLibraryRoutine(). If we then detect that
// coroParam was set to a non-zero value, this means that some
// coroutine code did run at some point, and we are now supposed
// to sleep or die -- hence, we 'return' if coroParam != 0.
//
// This works because Interpret() is fully re-entrant: If we return
// now and are later called again, then we will end up in the very
// same spot (i.e. here).
//
// The reasons we do it this way, instead of turning Interpret into
// a 'proper' coroutine are (1) we avoid implementation problems
// (CORO_INVOKE involves adding 'case' statements, but Interpret
// already has a huge switch/case, so that would not work out of the
// box), (2) we incurr less overhead, (3) it's easier to debug,
// (4) it's simply cool ;).
tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState);
if (coroParam)
return;
if (!TinselV0)
ic->sp += tmp2;
LockCode(ic);
if (TinselV2 && (ic->resumeState == RES_1))
ic->resumeState = RES_NOT;
break;
case OP_RET: // procedure return
ic->sp = ic->bp - 1; // restore stack
ip = ic->stack[ic->sp + 3]; // return address
ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer
break;
case OP_ALLOC: // allocate storage on stack
ic->sp += (int32)Fetch(opcode, ic->code, wkEntry, ip);
break;
case OP_JUMP: // unconditional jump
ip = Fetch(opcode, ic->code, wkEntry, ip);
wkEntry = NULL; // In case a jump occurs from a workaround
break;
case OP_JMPFALSE: // conditional jump
tmp = Fetch(opcode, ic->code, wkEntry, ip);
if (ic->stack[ic->sp--] == 0) {
// condition satisfied - do the jump
ip = tmp;
wkEntry = NULL; // In case a jump occurs from a workaround
}
break;
case OP_JMPTRUE: // conditional jump
tmp = Fetch(opcode, ic->code, wkEntry, ip);
if (ic->stack[ic->sp--] != 0) {
// condition satisfied - do the jump
ip = tmp;
wkEntry = NULL; // In case a jump occurs from a workaround
}
break;
case OP_EQUAL: // tests top two items on stack for equality
case OP_LESS: // tests top two items on stack
case OP_LEQUAL: // tests top two items on stack
case OP_NEQUAL: // tests top two items on stack
case OP_GEQUAL: // tests top two items on stack
case OP_GREAT: // tests top two items on stack
case OP_LOR: // logical or of top two items on stack and replaces with result
case OP_LAND: // logical ands top two items on stack and replaces with result
// pop one operand
ic->sp--;
assert(ic->sp >= 0);
tmp = ic->stack[ic->sp];
tmp2 = ic->stack[ic->sp + 1];
// replace other operand with result of operation
switch (opcode) {
case OP_EQUAL: tmp = (tmp == tmp2); break;
case OP_LESS: tmp = (tmp < tmp2); break;
case OP_LEQUAL: tmp = (tmp <= tmp2); break;
case OP_NEQUAL: tmp = (tmp != tmp2); break;
case OP_GEQUAL: tmp = (tmp >= tmp2); break;
case OP_GREAT: tmp = (tmp > tmp2); break;
case OP_LOR: tmp = (tmp || tmp2); break;
case OP_LAND: tmp = (tmp && tmp2); break;
}
ic->stack[ic->sp] = tmp;
break;
case OP_PLUS: // adds top two items on stack and replaces with result
case OP_MINUS: // subs top two items on stack and replaces with result
case OP_MULT: // multiplies top two items on stack and replaces with result
case OP_DIV: // divides top two items on stack and replaces with result
case OP_MOD: // divides top two items on stack and replaces with modulus
case OP_AND: // bitwise ands top two items on stack and replaces with result
case OP_OR: // bitwise ors top two items on stack and replaces with result
case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result
// pop one operand
ic->sp--;
assert(ic->sp >= 0);
tmp = ic->stack[ic->sp];
tmp2 = ic->stack[ic->sp + 1];
// replace other operand with result of operation
switch (opcode) {
case OP_PLUS: tmp += tmp2; break;
case OP_MINUS: tmp -= tmp2; break;
case OP_MULT: tmp *= tmp2; break;
case OP_DIV: tmp /= tmp2; break;
case OP_MOD: tmp %= tmp2; break;
case OP_AND: tmp &= tmp2; break;
case OP_OR: tmp |= tmp2; break;
case OP_EOR: tmp ^= tmp2; break;
}
ic->stack[ic->sp] = tmp;
break;
case OP_NOT: // logical nots top item on stack
ic->stack[ic->sp] = !ic->stack[ic->sp];
break;
case OP_COMP: // complements top item on stack
ic->stack[ic->sp] = ~ic->stack[ic->sp];
break;
case OP_NEG: // negates top item on stack
ic->stack[ic->sp] = -ic->stack[ic->sp];
break;
case OP_DUP: // duplicates top item on stack
ic->stack[ic->sp + 1] = ic->stack[ic->sp];
ic->sp++;
break;
case OP_ESCON:
bNoPause = true;
ic->escOn = true;
ic->myEscape = GetEscEvents();
break;
case OP_ESCOFF:
ic->escOn = false;
ic->myEscape = 0;
break;
default:
error("Interpret() - Unknown opcode");
}
// check for stack under-overflow
assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE);
ic->ip = ip;
ic->fragmentPtr = wkEntry;
} while (!ic->bHalt);
// make sure stack is unwound
assert(ic->sp == 0);
FreeInterpretContextPi(ic);
}
/**
* Associates an interpret context with the
* process that will run it.
*/
void AttachInterpret(INT_CONTEXT *pic, PROCESS *pProc) {
// Attach the process which is using this context
pic->pProc = pProc;
}
/**
* Generate a number that isn't being used.
*/
static uint32 UniqueWaitNumber() {
uint32 retval;
int i;
for (retval = DwGetCurrentTime(); 1; retval--) {
if (retval == 0)
retval = (uint32)-1;
for (i = 0; i < NUM_INTERPRET; i++) {
if ((icList+i)->waitNumber1 == retval
|| (icList+i)->waitNumber2 == retval)
break;
}
if (i == NUM_INTERPRET)
return retval;
}
}
/**
* WaitInterpret
*/
void WaitInterpret(CORO_PARAM, PPROCESS pWaitProc, bool *result) {
int i;
PPROCESS currentProcess = g_scheduler->getCurrentProcess();
assert(currentProcess);
assert(currentProcess != pWaitProc);
if (result) *result = false;
/*
* Calling process is the waiter, find its interpret context.
*/
CORO_BEGIN_CONTEXT;
PINT_CONTEXT picWaiter, picWaitee;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
for (i = 0, _ctx->picWaiter = icList; i < NUM_INTERPRET; i++, _ctx->picWaiter++) {
if (_ctx->picWaiter->GSort != GS_NONE && _ctx->picWaiter->pProc == currentProcess) {
break;
}
}
/*
* Find the interpret context of the process we're waiting for
*/
for (i = 0, _ctx->picWaitee = icList; i < NUM_INTERPRET; i++, _ctx->picWaitee++) {
if (_ctx->picWaitee->GSort != GS_NONE && _ctx->picWaitee->pProc == pWaitProc) {
break;
}
}
/*
* Set the first as waiting for the second
*/
assert(_ctx->picWaitee->waitNumber2 == 0);
_ctx->picWaiter->waitNumber1 = _ctx->picWaitee->waitNumber2 = UniqueWaitNumber();
_ctx->picWaiter->resumeCode = RES_WAITING;
/*
* Wait for it
*/
CORO_GIVE_WAY;
while (_ctx->picWaiter->resumeCode == RES_WAITING) {
CORO_SLEEP(1);
}
if (result)
*result = (_ctx->picWaiter->resumeCode == RES_FINISHED);
CORO_END_CODE;
}
/**
* CheckOutWaiters
*/
void CheckOutWaiters() {
int i, j;
// Check all waited for have someone waiting
for (i = 0; i < NUM_INTERPRET; i++) {
// If someone is supposedly waiting for this one
if ((icList + i)->GSort != GS_NONE && (icList + i)->waitNumber2) {
// Someone really must be waiting for this one
for (j = 0; j < NUM_INTERPRET; j++) {
if ((icList + j)->GSort != GS_NONE
&& (icList + j)->waitNumber1 == (icList + i)->waitNumber2) {
break;
}
}
assert(j < NUM_INTERPRET);
}
}
// Check waiting for someone to wait for
for (i = 0; i < NUM_INTERPRET; i++) {
// If someone is supposedly waiting for this one
if ((icList + i)->GSort != GS_NONE && (icList + i)->waitNumber1) {
// Someone really must be waiting for this one
for (j = 0; j < NUM_INTERPRET; j++) {
if ((icList + j)->GSort != GS_NONE
&& (icList + j)->waitNumber2 == (icList + i)->waitNumber1) {
break;
}
}
assert(j < NUM_INTERPRET);
}
}
}
} // End of namespace Tinsel