300 lines
7.8 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.
*
*/
#include "glk/glulxe/glulxe.h"
namespace Glk {
namespace Glulxe {
void Glulxe::enter_function(uint funcaddr, uint argc, uint *argv) {
uint ix, jx;
acceleration_func accelFunc;
int locallen;
int functype;
uint modeaddr, opaddr, val;
int loctype, locnum;
uint addr = funcaddr;
accelFunc = accel_get_func(addr);
if (accelFunc) {
profile_in(addr, stackptr, true);
val = (this->*accelFunc)(argc, argv);
profile_out(stackptr);
pop_callstub(val);
return;
}
profile_in(addr, stackptr, false);
/* Check the Glulx type identifier byte. */
functype = Mem1(addr);
if (functype != 0xC0 && functype != 0xC1) {
if (functype >= 0xC0 && functype <= 0xDF)
fatal_error_i("Call to unknown type of function.", addr);
else
fatal_error_i("Call to non-function.", addr);
}
addr++;
/* Bump the frameptr to the top. */
frameptr = stackptr;
/* Go through the function's locals-format list, copying it to the
call frame. At the same time, we work out how much space the locals
will actually take up. (Including padding.) */
ix = 0;
locallen = 0;
while (1) {
/* Grab two bytes from the locals-format list. These are
unsigned (0..255 range). */
loctype = Mem1(addr);
addr++;
locnum = Mem1(addr);
addr++;
/* Copy them into the call frame. */
StkW1(frameptr + 8 + 2 * ix, loctype);
StkW1(frameptr + 8 + 2 * ix + 1, locnum);
ix++;
/* If the type is zero, we're done, except possibly for two more
zero bytes in the call frame (to ensure 4-byte alignment.) */
if (loctype == 0) {
/* Make sure ix is even. */
if (ix & 1) {
StkW1(frameptr + 8 + 2 * ix, 0);
StkW1(frameptr + 8 + 2 * ix + 1, 0);
ix++;
}
break;
}
/* Pad to 4-byte or 2-byte alignment if these locals are 4 or 2
bytes long. */
if (loctype == 4) {
while (locallen & 3)
locallen++;
} else if (loctype == 2) {
while (locallen & 1)
locallen++;
} else if (loctype == 1) {
/* no padding */
} else {
fatal_error("Illegal local type in locals-format list.");
}
/* Add the length of the locals themselves. */
locallen += (loctype * locnum);
}
/* Pad the locals to 4-byte alignment. */
while (locallen & 3)
locallen++;
/* We now know how long the locals-frame and locals segments are. */
localsbase = frameptr + 8 + 2 * ix;
valstackbase = localsbase + locallen;
/* Test for stack overflow. */
/* This really isn't good enough; if the format list overflowed the
stack, we've already written outside the stack array. */
if (valstackbase >= stacksize)
fatal_error("Stack overflow in function call.");
/* Fill in the beginning of the stack frame. */
StkW4(frameptr + 4, 8 + 2 * ix);
StkW4(frameptr, 8 + 2 * ix + locallen);
/* Set the stackptr and PC. */
stackptr = valstackbase;
pc = addr;
/* Zero out all the locals. */
for (jx = 0; jx < (uint)locallen; jx++)
StkW1(localsbase + jx, 0);
if (functype == 0xC0) {
/* Push the function arguments on the stack. The locals have already
been zeroed. */
if (stackptr + 4 * (argc + 1) >= stacksize)
fatal_error("Stack overflow in function arguments.");
for (ix = 0; ix < argc; ix++) {
val = argv[(argc - 1) - ix];
StkW4(stackptr, val);
stackptr += 4;
}
StkW4(stackptr, argc);
stackptr += 4;
} else {
/* Copy in function arguments. This is a bit gross, since we have to
follow the locals format. If there are fewer arguments than locals,
that's fine -- we've already zeroed out this space. If there are
more arguments than locals, the extras are silently dropped. */
modeaddr = frameptr + 8;
opaddr = localsbase;
ix = 0;
while (ix < argc) {
loctype = Stk1(modeaddr);
modeaddr++;
locnum = Stk1(modeaddr);
modeaddr++;
if (loctype == 0)
break;
if (loctype == 4) {
while (opaddr & 3)
opaddr++;
while (ix < argc && locnum) {
val = argv[ix];
StkW4(opaddr, val);
opaddr += 4;
ix++;
locnum--;
}
} else if (loctype == 2) {
while (opaddr & 1)
opaddr++;
while (ix < argc && locnum) {
val = argv[ix] & 0xFFFF;
StkW2(opaddr, val);
opaddr += 2;
ix++;
locnum--;
}
} else if (loctype == 1) {
while (ix < argc && locnum) {
val = argv[ix] & 0xFF;
StkW1(opaddr, val);
opaddr += 1;
ix++;
locnum--;
}
}
}
}
/* If the debugger is compiled in, check for a breakpoint on this
function. (Checking the function address, not the starting PC.) */
debugger_check_func_breakpoint(funcaddr);
}
void Glulxe::leave_function() {
profile_out(stackptr);
stackptr = frameptr;
}
void Glulxe::push_callstub(uint desttype, uint destaddr) {
if (stackptr + 16 > stacksize)
fatal_error("Stack overflow in callstub.");
StkW4(stackptr + 0, desttype);
StkW4(stackptr + 4, destaddr);
StkW4(stackptr + 8, pc);
StkW4(stackptr + 12, frameptr);
stackptr += 16;
}
void Glulxe::pop_callstub(uint returnvalue) {
uint desttype, destaddr;
uint newpc, newframeptr;
if (stackptr < 16)
fatal_error("Stack underflow in callstub.");
stackptr -= 16;
newframeptr = Stk4(stackptr + 12);
newpc = Stk4(stackptr + 8);
destaddr = Stk4(stackptr + 4);
desttype = Stk4(stackptr + 0);
pc = newpc;
frameptr = newframeptr;
/* Recompute valstackbase and localsbase */
valstackbase = frameptr + Stk4(frameptr);
localsbase = frameptr + Stk4(frameptr + 4);
switch (desttype) {
case 0x11:
fatal_error("String-terminator call stub at end of function call.");
break;
case 0x10:
/* This call stub was pushed during a string-decoding operation!
We have to restart it. (Note that the return value is discarded.) */
stream_string(pc, 0xE1, destaddr);
break;
case 0x12:
/* This call stub was pushed during a number-printing operation.
Restart that. (Return value discarded.) */
stream_num(pc, true, destaddr);
break;
case 0x13:
/* This call stub was pushed during a C-string printing operation.
We have to restart it. (Note that the return value is discarded.) */
stream_string(pc, 0xE0, destaddr);
break;
case 0x14:
/* This call stub was pushed during a Unicode printing operation.
We have to restart it. (Note that the return value is discarded.) */
stream_string(pc, 0xE2, destaddr);
break;
default:
/* We're back in the original frame, so we can store the returnvalue.
(If we tried to do this before resetting frameptr, a result
destination on the stack would go astray.) */
store_operand(desttype, destaddr, returnvalue);
break;
}
}
uint Glulxe::pop_callstub_string(int *bitnum) {
uint desttype, destaddr, newpc;
if (stackptr < 16)
fatal_error("Stack underflow in callstub.");
stackptr -= 16;
newpc = Stk4(stackptr + 8);
destaddr = Stk4(stackptr + 4);
desttype = Stk4(stackptr + 0);
pc = newpc;
if (desttype == 0x11) {
return 0;
}
if (desttype == 0x10) {
*bitnum = destaddr;
return pc;
}
fatal_error("Function-terminator call stub at end of string.");
return 0;
}
} // End of namespace Glulxe
} // End of namespace Glk