1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/glulx/glulx.h"
24 
25 namespace Glk {
26 namespace Glulx {
27 
enter_function(uint funcaddr,uint argc,uint * argv)28 void Glulx::enter_function(uint funcaddr, uint argc, uint *argv) {
29 	uint ix, jx;
30 	acceleration_func accelFunc;
31 	int locallen;
32 	int functype;
33 	uint modeaddr, opaddr, val;
34 	int loctype, locnum;
35 	uint addr = funcaddr;
36 
37 	accelFunc = accel_get_func(addr);
38 	if (accelFunc) {
39 		profile_in(addr, stackptr, true);
40 		val = (this->*accelFunc)(argc, argv);
41 		profile_out(stackptr);
42 		pop_callstub(val);
43 		return;
44 	}
45 
46 	profile_in(addr, stackptr, false);
47 
48 	/* Check the Glulx type identifier byte. */
49 	functype = Mem1(addr);
50 	if (functype != 0xC0 && functype != 0xC1) {
51 		if (functype >= 0xC0 && functype <= 0xDF)
52 			fatal_error_i("Call to unknown type of function.", addr);
53 		else
54 			fatal_error_i("Call to non-function.", addr);
55 	}
56 	addr++;
57 
58 	/* Bump the frameptr to the top. */
59 	frameptr = stackptr;
60 
61 	/* Go through the function's locals-format list, copying it to the
62 	   call frame. At the same time, we work out how much space the locals
63 	   will actually take up. (Including padding.) */
64 	ix = 0;
65 	locallen = 0;
66 	while (1) {
67 		/* Grab two bytes from the locals-format list. These are
68 		   unsigned (0..255 range). */
69 		loctype = Mem1(addr);
70 		addr++;
71 		locnum = Mem1(addr);
72 		addr++;
73 
74 		/* Copy them into the call frame. */
75 		StkW1(frameptr + 8 + 2 * ix, loctype);
76 		StkW1(frameptr + 8 + 2 * ix + 1, locnum);
77 		ix++;
78 
79 		/* If the type is zero, we're done, except possibly for two more
80 		   zero bytes in the call frame (to ensure 4-byte alignment.) */
81 		if (loctype == 0) {
82 			/* Make sure ix is even. */
83 			if (ix & 1) {
84 				StkW1(frameptr + 8 + 2 * ix, 0);
85 				StkW1(frameptr + 8 + 2 * ix + 1, 0);
86 				ix++;
87 			}
88 			break;
89 		}
90 
91 		/* Pad to 4-byte or 2-byte alignment if these locals are 4 or 2
92 		   bytes long. */
93 		if (loctype == 4) {
94 			while (locallen & 3)
95 				locallen++;
96 		} else if (loctype == 2) {
97 			while (locallen & 1)
98 				locallen++;
99 		} else if (loctype == 1) {
100 			/* no padding */
101 		} else {
102 			fatal_error("Illegal local type in locals-format list.");
103 		}
104 
105 		/* Add the length of the locals themselves. */
106 		locallen += (loctype * locnum);
107 	}
108 
109 	/* Pad the locals to 4-byte alignment. */
110 	while (locallen & 3)
111 		locallen++;
112 
113 	/* We now know how long the locals-frame and locals segments are. */
114 	localsbase = frameptr + 8 + 2 * ix;
115 	valstackbase = localsbase + locallen;
116 
117 	/* Test for stack overflow. */
118 	/* This really isn't good enough; if the format list overflowed the
119 	   stack, we've already written outside the stack array. */
120 	if (valstackbase >= stacksize)
121 		fatal_error("Stack overflow in function call.");
122 
123 	/* Fill in the beginning of the stack frame. */
124 	StkW4(frameptr + 4, 8 + 2 * ix);
125 	StkW4(frameptr, 8 + 2 * ix + locallen);
126 
127 	/* Set the stackptr and PC. */
128 	stackptr = valstackbase;
129 	pc = addr;
130 
131 	/* Zero out all the locals. */
132 	for (jx = 0; jx < (uint)locallen; jx++)
133 		StkW1(localsbase + jx, 0);
134 
135 	if (functype == 0xC0) {
136 		/* Push the function arguments on the stack. The locals have already
137 		   been zeroed. */
138 		if (stackptr + 4 * (argc + 1) >= stacksize)
139 			fatal_error("Stack overflow in function arguments.");
140 		for (ix = 0; ix < argc; ix++) {
141 			val = argv[(argc - 1) - ix];
142 			StkW4(stackptr, val);
143 			stackptr += 4;
144 		}
145 		StkW4(stackptr, argc);
146 		stackptr += 4;
147 	} else {
148 		/* Copy in function arguments. This is a bit gross, since we have to
149 		   follow the locals format. If there are fewer arguments than locals,
150 		   that's fine -- we've already zeroed out this space. If there are
151 		   more arguments than locals, the extras are silently dropped. */
152 		modeaddr = frameptr + 8;
153 		opaddr = localsbase;
154 		ix = 0;
155 		while (ix < argc) {
156 			loctype = Stk1(modeaddr);
157 			modeaddr++;
158 			locnum = Stk1(modeaddr);
159 			modeaddr++;
160 			if (loctype == 0)
161 				break;
162 			if (loctype == 4) {
163 				while (opaddr & 3)
164 					opaddr++;
165 				while (ix < argc && locnum) {
166 					val = argv[ix];
167 					StkW4(opaddr, val);
168 					opaddr += 4;
169 					ix++;
170 					locnum--;
171 				}
172 			} else if (loctype == 2) {
173 				while (opaddr & 1)
174 					opaddr++;
175 				while (ix < argc && locnum) {
176 					val = argv[ix] & 0xFFFF;
177 					StkW2(opaddr, val);
178 					opaddr += 2;
179 					ix++;
180 					locnum--;
181 				}
182 			} else if (loctype == 1) {
183 				while (ix < argc && locnum) {
184 					val = argv[ix] & 0xFF;
185 					StkW1(opaddr, val);
186 					opaddr += 1;
187 					ix++;
188 					locnum--;
189 				}
190 			}
191 		}
192 	}
193 
194 	/* If the debugger is compiled in, check for a breakpoint on this
195 	   function. (Checking the function address, not the starting PC.) */
196 	debugger_check_func_breakpoint(funcaddr);
197 }
198 
leave_function()199 void Glulx::leave_function() {
200 	profile_out(stackptr);
201 	stackptr = frameptr;
202 }
203 
push_callstub(uint desttype,uint destaddr)204 void Glulx::push_callstub(uint desttype, uint destaddr) {
205 	if (stackptr + 16 > stacksize)
206 		fatal_error("Stack overflow in callstub.");
207 	StkW4(stackptr + 0, desttype);
208 	StkW4(stackptr + 4, destaddr);
209 	StkW4(stackptr + 8, pc);
210 	StkW4(stackptr + 12, frameptr);
211 	stackptr += 16;
212 }
213 
pop_callstub(uint returnvalue)214 void Glulx::pop_callstub(uint returnvalue) {
215 	uint desttype, destaddr;
216 	uint newpc, newframeptr;
217 
218 	if (stackptr < 16)
219 		fatal_error("Stack underflow in callstub.");
220 	stackptr -= 16;
221 
222 	newframeptr = Stk4(stackptr + 12);
223 	newpc = Stk4(stackptr + 8);
224 	destaddr = Stk4(stackptr + 4);
225 	desttype = Stk4(stackptr + 0);
226 
227 	pc = newpc;
228 	frameptr = newframeptr;
229 
230 	/* Recompute valstackbase and localsbase */
231 	valstackbase = frameptr + Stk4(frameptr);
232 	localsbase = frameptr + Stk4(frameptr + 4);
233 
234 	switch (desttype) {
235 
236 	case 0x11:
237 		fatal_error("String-terminator call stub at end of function call.");
238 		break;
239 
240 	case 0x10:
241 		/* This call stub was pushed during a string-decoding operation!
242 		   We have to restart it. (Note that the return value is discarded.) */
243 		stream_string(pc, 0xE1, destaddr);
244 		break;
245 
246 	case 0x12:
247 		/* This call stub was pushed during a number-printing operation.
248 		   Restart that. (Return value discarded.) */
249 		stream_num(pc, true, destaddr);
250 		break;
251 
252 	case 0x13:
253 		/* This call stub was pushed during a C-string printing operation.
254 		   We have to restart it. (Note that the return value is discarded.) */
255 		stream_string(pc, 0xE0, destaddr);
256 		break;
257 
258 	case 0x14:
259 		/* This call stub was pushed during a Unicode printing operation.
260 		   We have to restart it. (Note that the return value is discarded.) */
261 		stream_string(pc, 0xE2, destaddr);
262 		break;
263 
264 	default:
265 		/* We're back in the original frame, so we can store the returnvalue.
266 		   (If we tried to do this before resetting frameptr, a result
267 		   destination on the stack would go astray.) */
268 		store_operand(desttype, destaddr, returnvalue);
269 		break;
270 	}
271 }
272 
pop_callstub_string(int * bitnum)273 uint Glulx::pop_callstub_string(int *bitnum) {
274 	uint desttype, destaddr, newpc;
275 
276 	if (stackptr < 16)
277 		fatal_error("Stack underflow in callstub.");
278 	stackptr -= 16;
279 
280 	newpc = Stk4(stackptr + 8);
281 	destaddr = Stk4(stackptr + 4);
282 	desttype = Stk4(stackptr + 0);
283 
284 	pc = newpc;
285 
286 	if (desttype == 0x11) {
287 		return 0;
288 	}
289 	if (desttype == 0x10) {
290 		*bitnum = destaddr;
291 		return pc;
292 	}
293 
294 	fatal_error("Function-terminator call stub at end of string.");
295 	return 0;
296 }
297 
298 } // End of namespace Glulx
299 } // End of namespace Glk
300