Updated interpreters
[projects/chimara/chimara.git] / interpreters / glulxe / vm.c
1 /* vm.c: Glulxe code related to the VM overall. Also miscellaneous stuff.
2     Designed by Andrew Plotkin <erkyrath@eblong.com>
3     http://eblong.com/zarf/glulx/index.html
4 */
5
6 #include "glk.h"
7 #include "glulxe.h"
8
9 /* The memory blocks which contain VM main memory and the stack. */
10 unsigned char *memmap = NULL;
11 unsigned char *stack = NULL;
12
13 /* Various memory addresses which are useful. These are loaded in from
14    the game file header. */
15 glui32 ramstart;
16 glui32 endgamefile;
17 glui32 origendmem;
18 glui32 stacksize;
19 glui32 startfuncaddr;
20 glui32 origstringtable;
21 glui32 checksum;
22
23 /* The VM registers. */
24 glui32 stackptr;
25 glui32 frameptr;
26 glui32 pc;
27 glui32 stringtable;
28 glui32 valstackbase;
29 glui32 localsbase;
30 glui32 endmem;
31 glui32 protectstart, protectend;
32
33 void (*stream_char_handler)(unsigned char ch);
34 void (*stream_unichar_handler)(glui32 ch);
35
36 /* setup_vm():
37    Read in the game file and build the machine, allocating all the memory
38    necessary.
39 */
40 void setup_vm()
41 {
42   unsigned char buf[4 * 7];
43   int res;
44
45   pc = 0; /* Clear this, so that error messages are cleaner. */
46
47   /* Read in all the size constants from the game file header. */
48
49   stream_char_handler = NULL;
50   stream_unichar_handler = NULL;
51
52   glk_stream_set_position(gamefile, gamefile_start+8, seekmode_Start);
53   res = glk_get_buffer_stream(gamefile, (char *)buf, 4 * 7);
54   
55   ramstart = Read4(buf+0);
56   endgamefile = Read4(buf+4);
57   origendmem = Read4(buf+8);
58   stacksize = Read4(buf+12);
59   startfuncaddr = Read4(buf+16);
60   origstringtable = Read4(buf+20);
61   checksum = Read4(buf+24);
62
63   /* Set the protection range to (0, 0), meaning "off". */
64   protectstart = 0;
65   protectend = 0;
66
67   /* Do a few sanity checks. */
68
69   if ((ramstart & 0xFF)
70     || (endgamefile & 0xFF) 
71     || (origendmem & 0xFF)
72     || (stacksize & 0xFF)) {
73     nonfatal_warning("One of the segment boundaries in the header is not "
74       "256-byte aligned.");
75   }
76
77   if (ramstart < 0x100 || endgamefile < ramstart || origendmem < endgamefile) {
78     fatal_error("The segment boundaries in the header are in an impossible "
79       "order.");
80   }
81   if (stacksize < 0x100) {
82     fatal_error("The stack size in the header is too small.");
83   }
84   
85   /* Allocate main memory and the stack. This is where memory allocation
86      errors are most likely to occur. */
87   endmem = origendmem;
88   memmap = (unsigned char *)glulx_malloc(origendmem);
89   if (!memmap) {
90     fatal_error("Unable to allocate Glulx memory space.");
91   }
92   stack = (unsigned char *)glulx_malloc(stacksize);
93   if (!stack) {
94     glulx_free(memmap);
95     memmap = NULL;
96     fatal_error("Unable to allocate Glulx stack space.");
97   }
98   stringtable = 0;
99
100   /* Initialize various other things in the terp. */
101   init_operands(); 
102   init_accel();
103   init_serial();
104
105   /* Set up the initial machine state. */
106   vm_restart();
107 }
108
109 /* finalize_vm():
110    Deallocate all the memory and shut down the machine.
111 */
112 void finalize_vm()
113 {
114   if (memmap) {
115     glulx_free(memmap);
116     memmap = NULL;
117   }
118   if (stack) {
119     glulx_free(stack);
120     stack = NULL;
121   }
122 }
123
124 /* vm_restart(): 
125    Put the VM into a state where it's ready to begin executing the
126    game. This is called both at startup time, and when the machine
127    performs a "restart" opcode. 
128 */
129 void vm_restart()
130 {
131   glui32 lx;
132   int res;
133
134   /* Deactivate the heap (if it was active). */
135   heap_clear();
136
137   /* Reset memory to the original size. */
138   lx = change_memsize(origendmem, FALSE);
139   if (lx)
140     fatal_error("Memory could not be reset to its original size.");
141
142   /* Load in all of main memory */
143   glk_stream_set_position(gamefile, gamefile_start, seekmode_Start);
144   for (lx=0; lx<endgamefile; lx++) {
145     res = glk_get_char_stream(gamefile);
146     if (res == -1) {
147       fatal_error("The game file ended unexpectedly.");
148     }
149     if (lx >= protectstart && lx < protectend)
150       continue;
151     memmap[lx] = res;
152   }
153   for (lx=endgamefile; lx<origendmem; lx++) {
154     memmap[lx] = 0;
155   }
156
157   /* Reset all the registers */
158   stackptr = 0;
159   frameptr = 0;
160   pc = 0;
161   stream_set_iosys(0, 0);
162   stream_set_table(origstringtable);
163   valstackbase = 0;
164   localsbase = 0;
165
166   /* Note that we do not reset the protection range. */
167
168   /* Push the first function call. (No arguments.) */
169   enter_function(startfuncaddr, 0, NULL);
170
171   /* We're now ready to execute. */
172 }
173
174 /* change_memsize():
175    Change the size of the memory map. This may not be available at
176    all; #define FIXED_MEMSIZE if you want the interpreter to
177    unconditionally refuse. The internal flag should be true only when
178    the heap-allocation system is calling.
179    Returns 0 for success; otherwise, the operation failed.
180 */
181 glui32 change_memsize(glui32 newlen, int internal)
182 {
183   long lx;
184   unsigned char *newmemmap;
185
186   if (newlen == endmem)
187     return 0;
188
189 #ifdef FIXED_MEMSIZE
190   return 1;
191 #else /* FIXED_MEMSIZE */
192
193   if ((!internal) && heap_is_active())
194     fatal_error("Cannot resize Glulx memory space while heap is active.");
195
196   if (newlen < origendmem)
197     fatal_error("Cannot resize Glulx memory space smaller than it started.");
198
199   if (newlen & 0xFF)
200     fatal_error("Can only resize Glulx memory space to a 256-byte boundary.");
201   
202   newmemmap = (unsigned char *)glulx_realloc(memmap, newlen);
203   if (!newmemmap) {
204     /* The old block is still in place, unchanged. */
205     return 1;
206   }
207   memmap = newmemmap;
208
209   if (newlen > endmem) {
210     for (lx=endmem; lx<newlen; lx++) {
211       memmap[lx] = 0;
212     }
213   }
214
215   endmem = newlen;
216
217   return 0;
218
219 #endif /* FIXED_MEMSIZE */
220 }
221
222 /* pop_arguments():
223    If addr is 0, pop N arguments off the stack, and put them in an array. 
224    If non-0, take N arguments from that main memory address instead.
225    This has to dynamically allocate if there are more than 32 arguments,
226    but that shouldn't be a problem.
227 */
228 glui32 *pop_arguments(glui32 count, glui32 addr)
229 {
230   int ix;
231   glui32 argptr;
232   glui32 *array;
233
234   #define MAXARGS (32)
235   static glui32 statarray[MAXARGS];
236   static glui32 *dynarray = NULL;
237   static glui32 dynarray_size = 0;
238
239   if (count == 0)
240     return NULL;
241
242   if (count <= MAXARGS) {
243     /* Store in the static array. */
244     array = statarray;
245   }
246   else {
247     if (!dynarray) {
248       dynarray_size = count+8;
249       dynarray = glulx_malloc(sizeof(glui32) * dynarray_size);
250       if (!dynarray)
251         fatal_error("Unable to allocate function arguments.");
252       array = dynarray;
253     }
254     else {
255       if (dynarray_size >= count) {
256         /* It fits. */
257         array = dynarray;
258       }
259       else {
260         dynarray_size = count+8;
261         dynarray = glulx_realloc(dynarray, sizeof(glui32) * dynarray_size);
262         if (!dynarray)
263           fatal_error("Unable to reallocate function arguments.");
264         array = dynarray;
265       }
266     }
267   }
268
269   if (!addr) {
270     if (stackptr < valstackbase+4*count) 
271       fatal_error("Stack underflow in arguments.");
272     stackptr -= 4*count;
273     for (ix=0; ix<count; ix++) {
274       argptr = stackptr+4*((count-1)-ix);
275       array[ix] = Stk4(argptr);
276     }
277   }
278   else {
279     for (ix=0; ix<count; ix++) {
280       array[ix] = Mem4(addr);
281       addr += 4;
282     }
283   }
284
285   return array;
286 }
287
288 /* verify_address():
289    Make sure that count bytes beginning with addr all fall within the
290    current memory map. This is called at every memory (read) access if
291    VERIFY_MEMORY_ACCESS is defined in the header file.
292 */
293 void verify_address(glui32 addr, glui32 count)
294 {
295   if (addr >= endmem)
296     fatal_error_i("Memory access out of range", addr);
297   if (count > 1) {
298     addr += (count-1);
299     if (addr >= endmem)
300       fatal_error_i("Memory access out of range", addr);
301   }
302 }
303
304 /* verify_address_write():
305    Make sure that count bytes beginning with addr all fall within RAM.
306    This is called at every memory write if VERIFY_MEMORY_ACCESS is 
307    defined in the header file.
308 */
309 void verify_address_write(glui32 addr, glui32 count)
310 {
311   if (addr < ramstart)
312     fatal_error_i("Memory write to read-only address", addr);
313   if (addr >= endmem)
314     fatal_error_i("Memory access out of range", addr);
315   if (count > 1) {
316     addr += (count-1);
317     if (addr >= endmem)
318       fatal_error_i("Memory access out of range", addr);
319   }
320 }
321
322 /* verify_array_addresses():
323    Make sure that an array of count elements (size bytes each),
324    starting at addr, does not fall outside the memory map. This goes
325    to some trouble that verify_address() does not, because we need
326    to be wary of lengths near -- or beyond -- 0x7FFFFFFF.
327 */
328 void verify_array_addresses(glui32 addr, glui32 count, glui32 size)
329 {
330   glui32 bytecount;
331   if (addr >= endmem)
332     fatal_error_i("Memory access out of range", addr);
333
334   if (count == 0)
335     return;
336   bytecount = count*size;
337
338   /* If just multiplying by the element size overflows, we have trouble. */
339   if (bytecount < count)
340     fatal_error_i("Memory access way too long", addr);
341
342   /* If the byte length by itself is too long, or if its end overflows,
343      we have trouble. */
344   if (bytecount > endmem || addr+bytecount < addr)
345     fatal_error_i("Memory access much too long", addr);
346   /* The simple length test. */
347   if (addr+bytecount > endmem)
348     fatal_error_i("Memory access too long", addr);
349 }
350