1 // $Id: glkop.c,v 1.4 2004/12/22 14:33:40 iain Exp $
3 // glkop.c: Glulxe code for Glk API dispatching.
4 // Designed by Andrew Plotkin <erkyrath@eblong.com>
5 // http://www.eblong.com/zarf/glulx/index.html
7 /* This code is actually very general; it could work for almost any
8 32-bit VM which remotely resembles Glulxe or the Z-machine in design.
10 To be precise, we make the following assumptions:
12 - An argument list is an array of 32-bit values, which can represent
13 either integers or addresses.
14 - We can read or write to a 32-bit integer in VM memory using the macros
15 ReadMemory(addr) and WriteMemory(addr), where addr is an address
16 taken from the argument list.
17 - A character array is an actual array of bytes somewhere in terp
18 memory, whose actual address can be computed by the macro
19 AddressOfArray(addr). Again, addr is a VM address from the argument
21 - An integer array is a sequence of integers somewhere in VM memory.
22 The array can be turned into a C integer array by the macro
23 CaptureIArray(addr, len), and released by ReleaseIArray().
24 These macros are responsible for fixing byte-order and alignment
25 (if the C ABI does not match the VM's). The passin, passout hints
26 may be used to avoid unnecessary copying.
27 - A Glk structure (such as event_t) is a set of integers somewhere
28 in VM memory, which can be read and written with the macros
29 ReadStructField(addr, fieldnum) and WriteStructField(addr, fieldnum).
30 The fieldnum is an integer (from 0 to 3, for event_t.)
31 - A VM string can be turned into a C-style string with the macro
32 ptr = DecodeVMString(addr). After the string is used, this code
33 calls ReleaseVMString(ptr), which should free any memory that
34 DecodeVMString allocates.
35 - A VM Unicode string can be turned into a zero-terminated array
36 of 32-bit integers, in the same way, with DecodeVMUstring
39 To work this code over for a new VM, just diddle the macros.
43 (*((unsigned char *)(sp)))
49 #define StkW1(sp, vl) \
50 (*((unsigned char *)(sp)) = (unsigned char)(vl))
51 #define StkW2(sp, vl) \
52 (*((glui16 *)(sp)) = (glui16)(vl))
53 #define StkW4(sp, vl) \
54 (*((glui32 *)(sp)) = (glui32)(vl))
57 #define ReadMemory(addr) \
58 (((addr) == 0xffffffff) \
59 ? (gStackPointer -= 1, Stk4(gStackPointer)) \
61 #define WriteMemory(addr, val) \
62 if ((addr) == 0xffffffff) \
63 { StkW4(gStackPointer, (val)); gStackPointer += 1;} \
64 else memWrite32((addr), (val))
65 #define AddressOfArray(addr) \
66 ((addr) < gRamStart ? (gRom + (addr)) : (gRam + (addr)))
67 #define AddressOfIArray(addr) \
68 ((addr) < gRamStart ? (gRom + (addr)) : (gRam + (addr)))
69 #define CaptureIArray(addr, len, passin) \
70 (grab_temp_array(addr, len, passin))
71 #define ReleaseIArray(ptr, addr, len, passout) \
72 (release_temp_array(ptr, addr, len, passout))
73 #define ReadStructField(addr, fieldnum) \
74 (((addr) == 0xffffffff) \
75 ? (gStackPointer -= 1, Stk4(gStackPointer)) \
76 : (memRead32((addr)+(fieldnum)*4)))
77 #define WriteStructField(addr, fieldnum, val) \
78 if ((addr) == 0xffffffff) \
79 { StkW4(gStackPointer, (val)); gStackPointer += 1;} \
80 else memWrite32((addr)+(fieldnum)*4, (val))
82 #define glulx_malloc malloc
83 #define glulx_free free
84 #define glulx_random rand
97 static char * DecodeVMString (git_uint32 addr)
103 // The string must be a C string.
104 if (memRead8(addr) != 0xE0)
106 fatalError ("Illegal string type passed to Glk function");
111 while (memRead8(end) != 0)
114 data = glulx_malloc (end - addr + 1);
116 fatalError ("Couldn't allocate string");
120 *c++ = memRead8(addr++);
126 static glui32 * DecodeVMUstring (git_uint32 addr)
132 // The string must be a Unicode string.
133 if (memRead8(addr) != 0xE2)
135 fatalError ("Illegal string type passed to Glk function");
140 while (memRead32(end) != 0)
143 data = glulx_malloc (end - addr + 4);
145 fatalError ("Couldn't allocate string");
150 *c++ = memRead32(addr);
158 static void ReleaseVMString (char * ptr)
163 static void ReleaseVMUstring (glui32 * ptr)
168 typedef struct dispatch_splot_struct {
171 gluniversal_t *garglist;
177 /* We maintain a linked list of arrays being used for Glk calls. It is
178 only used for integer (glui32) arrays -- char arrays are handled in
179 place. It's not worth bothering with a hash table, since most
180 arrays appear here only momentarily. */
182 typedef struct arrayref_struct arrayref_t;
183 struct arrayref_struct {
187 glui32 len; /* elements */
192 static arrayref_t *arrays = NULL;
194 /* We maintain a hash table for each opaque Glk class. classref_t are the
195 nodes of the table, and classtable_t are the tables themselves. */
197 typedef struct classref_struct classref_t;
198 struct classref_struct {
205 #define CLASSHASH_SIZE (31)
206 typedef struct classtable_struct {
208 classref_t *bucket[CLASSHASH_SIZE];
211 /* The list of hash tables, for the git_classes. */
212 static int num_classes = 0;
213 classtable_t **git_classes = NULL;
215 static classtable_t *new_classtable(glui32 firstid);
216 static void *classes_get(int classid, glui32 objid);
217 static classref_t *classes_put(int classid, void *obj);
218 static void classes_remove(int classid, void *obj);
220 static gidispatch_rock_t glulxe_classtable_register(void *obj,
222 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
223 gidispatch_rock_t objrock);
224 static gidispatch_rock_t glulxe_retained_register(void *array,
225 glui32 len, char *typecode);
226 static void glulxe_retained_unregister(void *array, glui32 len,
227 char *typecode, gidispatch_rock_t objrock);
229 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin);
230 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout);
232 static void prepare_glk_args(char *proto, dispatch_splot_t *splot);
233 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
234 int *argnumptr, glui32 subaddress, int subpassin);
235 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
236 int *argnumptr, glui32 subaddress, int subpassout);
239 Set up the class hash tables and other startup-time stuff.
241 int git_init_dispatch()
245 /* Allocate the class hash tables. */
246 num_classes = gidispatch_count_classes();
247 git_classes = (classtable_t **)glulx_malloc(num_classes
248 * sizeof(classtable_t *));
252 for (ix=0; ix<num_classes; ix++) {
253 git_classes[ix] = new_classtable((glulx_random() % (glui32)(101)) + 1);
254 if (!git_classes[ix])
258 /* Set up the two callbacks. */
259 gidispatch_set_object_registry(&glulxe_classtable_register,
260 &glulxe_classtable_unregister);
261 gidispatch_set_retained_registry(&glulxe_retained_register,
262 &glulxe_retained_unregister);
268 Turn a list of Glulx arguments into a list of Glk arguments,
269 dispatch the function call, and return the result.
271 glui32 git_perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
276 /* To speed life up, we implement commonly-used Glk functions
277 directly -- instead of bothering with the whole prototype
280 case 0x0047: /* stream_set_current */
283 glk_stream_set_current(git_find_stream_by_id(arglist[0]));
285 case 0x0048: /* stream_get_current */
288 retval = git_find_id_for_stream(glk_stream_get_current());
290 case 0x0080: /* put_char */
293 glk_put_char(arglist[0] & 0xFF);
295 case 0x0081: /* put_char_stream */
298 glk_put_char_stream(git_find_stream_by_id(arglist[0]), arglist[1] & 0xFF);
300 case 0x00A0: /* char_to_lower */
303 retval = glk_char_to_lower(arglist[0] & 0xFF);
305 case 0x00A1: /* char_to_upper */
308 retval = glk_char_to_upper(arglist[0] & 0xFF);
310 case 0x0128: /* put_char_uni */
313 glk_put_char_uni(arglist[0]);
315 case 0x012B: /* put_char_stream_uni */
318 glk_put_char_stream_uni(git_find_stream_by_id(arglist[0]), arglist[1]);
322 fatalError("Wrong number of arguments to Glk function.");
326 /* Go through the full dispatcher prototype foo. */
328 dispatch_splot_t splot;
331 /* Grab the string. */
332 proto = gidispatch_prototype(funcnum);
334 fatalError("Unknown Glk function.");
336 splot.varglist = arglist;
337 splot.numvargs = numargs;
338 splot.retval = &retval;
340 /* The work goes in four phases. First, we figure out how many
341 arguments we want, and allocate space for the Glk argument
342 list. Then we go through the Glulxe arguments and load them
343 into the Glk list. Then we call. Then we go through the
344 arguments again, unloading the data back into Glulx memory. */
347 prepare_glk_args(proto, &splot);
352 parse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
355 gidispatch_call(funcnum, argnum, splot.garglist);
360 unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0);
361 if (argnum != argnum2)
362 fatalError("Argument counts did not match.");
372 Read the prefixes of an argument string -- the "<>&+:#!" chars.
374 static char *read_prefix(char *cx, int *isref, int *isarray,
375 int *passin, int *passout, int *nullok, int *isretained,
390 else if (*cx == '>') {
394 else if (*cx == '&') {
399 else if (*cx == '+') {
402 else if (*cx == ':') {
408 else if (*cx == '#') {
411 else if (*cx == '!') {
422 /* prepare_glk_args():
423 This reads through the prototype string, and pulls Floo objects off the
424 stack. It also works out the maximal number of gluniversal_t objects
425 which could be used by the Glk call in question. It then allocates
428 static void prepare_glk_args(char *proto, dispatch_splot_t *splot)
430 static gluniversal_t *garglist = NULL;
431 static int garglist_size = 0;
434 int numwanted, numvargswanted, maxargs;
439 while (*cx >= '0' && *cx <= '9') {
440 numwanted = 10 * numwanted + (*cx - '0');
443 splot->numwanted = numwanted;
447 for (ix = 0; ix < numwanted; ix++) {
448 int isref, passin, passout, nullok, isarray, isretained, isreturn;
449 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
450 &isretained, &isreturn);
466 if (*cx == 'I' || *cx == 'C') {
469 else if (*cx == 'Q') {
472 else if (*cx == 'S' || *cx == 'U') {
475 else if (*cx == '[') {
479 while (*cx >= '0' && *cx <= '9') {
480 nwx = 10 * nwx + (*cx - '0');
483 maxargs += nwx; /* This is *only* correct because all structs contain
486 while (refdepth > 0) {
495 fatalError("Illegal format string.");
499 if (*cx != ':' && *cx != '\0')
500 fatalError("Illegal format string.");
502 splot->maxargs = maxargs;
504 if (splot->numvargs != numvargswanted)
505 fatalError("Wrong number of arguments to Glk function.");
507 if (garglist && garglist_size < maxargs) {
508 glulx_free(garglist);
513 garglist_size = maxargs + 16;
514 garglist = (gluniversal_t *)glulx_malloc(garglist_size
515 * sizeof(gluniversal_t));
518 fatalError("Unable to allocate storage for Glk arguments.");
520 splot->garglist = garglist;
524 This long and unpleasant function translates a set of Floo objects into
525 a gluniversal_t array. It's recursive, too, to deal with structures.
527 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
528 int *argnumptr, glui32 subaddress, int subpassin)
532 int gargnum, numwanted;
534 gluniversal_t *garglist;
537 garglist = splot->garglist;
538 varglist = splot->varglist;
539 gargnum = *argnumptr;
543 while (*cx >= '0' && *cx <= '9') {
544 numwanted = 10 * numwanted + (*cx - '0');
548 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
551 int isref, passin, passout, nullok, isarray, isretained, isreturn;
552 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
553 &isretained, &isreturn);
560 if (!isreturn && varglist[ix] == 0) {
562 fatalError("Zero passed invalidly to Glk function.");
563 garglist[gargnum].ptrflag = FALSE;
568 garglist[gargnum].ptrflag = TRUE;
575 if (typeclass == '[') {
577 parse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passin);
581 /* definitely isref */
585 /* This test checks for a giant array length, and cuts it down to
586 something reasonable. Future releases of this interpreter may
587 treat this case as a fatal error. */
588 if (varglist[ix+1] > gEndMem || varglist[ix]+varglist[ix+1] > gEndMem)
589 varglist[ix+1] = gEndMem - varglist[ix];
591 garglist[gargnum].array = (void*) AddressOfArray(varglist[ix]);
594 garglist[gargnum].uint = varglist[ix];
599 /* See comment above. */
600 if (varglist[ix+1] > gEndMem/4 || varglist[ix+1] > (gEndMem-varglist[ix])/4)
601 varglist[ix+1] = (gEndMem - varglist[ix]) / 4;
603 garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin);
606 garglist[gargnum].uint = varglist[ix];
611 fatalError("Illegal format string.");
616 /* a plain value or a reference to one. */
621 else if (depth > 0) {
622 /* Definitely not isref or isarray. */
624 thisval = ReadStructField(subaddress, ix);
630 thisval = ReadMemory(varglist[ix]);
635 thisval = varglist[ix];
641 garglist[gargnum].uint = (glui32)(thisval);
643 garglist[gargnum].sint = (glsi32)(thisval);
645 fatalError("Illegal format string.");
651 opref = classes_get(*cx-'a', thisval);
653 fatalError("Reference to nonexistent Glk object.");
659 garglist[gargnum].opaqueref = opref;
665 garglist[gargnum].uch = (unsigned char)(thisval);
667 garglist[gargnum].sch = (signed char)(thisval);
669 garglist[gargnum].ch = (char)(thisval);
671 fatalError("Illegal format string.");
676 garglist[gargnum].charstr = DecodeVMString(thisval);
679 #ifdef GLK_MODULE_UNICODE
681 garglist[gargnum].unicharstr = DecodeVMUstring(thisval);
686 fatalError("Illegal format string.");
692 /* We got a null reference, so we have to skip the format element. */
693 if (typeclass == '[') {
694 int numsubwanted, refdepth;
696 while (*cx >= '0' && *cx <= '9') {
697 numsubwanted = 10 * numsubwanted + (*cx - '0');
701 while (refdepth > 0) {
709 else if (typeclass == 'S' || typeclass == 'U') {
722 fatalError("Illegal format string.");
726 if (*cx != ':' && *cx != '\0')
727 fatalError("Illegal format string.");
731 *argnumptr = gargnum;
734 /* unparse_glk_args():
735 This is about the reverse of parse_glk_args().
737 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
738 int *argnumptr, glui32 subaddress, int subpassout)
742 int gargnum, numwanted;
744 gluniversal_t *garglist;
747 garglist = splot->garglist;
748 varglist = splot->varglist;
749 gargnum = *argnumptr;
753 while (*cx >= '0' && *cx <= '9') {
754 numwanted = 10 * numwanted + (*cx - '0');
758 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
761 int isref, passin, passout, nullok, isarray, isretained, isreturn;
762 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
763 &isretained, &isreturn);
770 if (!isreturn && varglist[ix] == 0) {
772 fatalError("Zero passed invalidly to Glk function.");
773 garglist[gargnum].ptrflag = FALSE;
778 garglist[gargnum].ptrflag = TRUE;
785 if (typeclass == '[') {
787 unparse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passout);
791 /* definitely isref */
801 ReleaseIArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout);
808 fatalError("Illegal format string.");
813 /* a plain value or a reference to one. */
815 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
826 thisval = (glui32)garglist[gargnum].uint;
828 thisval = (glui32)garglist[gargnum].sint;
830 fatalError("Illegal format string.");
837 opref = garglist[gargnum].opaqueref;
839 gidispatch_rock_t objrock =
840 gidispatch_get_objrock(opref, *cx-'a');
841 thisval = ((classref_t *)objrock.ptr)->id;
853 thisval = (glui32)garglist[gargnum].uch;
855 thisval = (glui32)garglist[gargnum].sch;
857 thisval = (glui32)garglist[gargnum].ch;
859 fatalError("Illegal format string.");
865 if (garglist[gargnum].charstr)
866 ReleaseVMString(garglist[gargnum].charstr);
869 #ifdef GLK_MODULE_UNICODE
871 if (garglist[gargnum].unicharstr)
872 ReleaseVMUstring(garglist[gargnum].unicharstr);
877 fatalError("Illegal format string.");
882 *(splot->retval) = thisval;
884 else if (depth > 0) {
885 /* Definitely not isref or isarray. */
888 WriteStructField(subaddress, ix, thisval);
894 WriteMemory(varglist[ix], thisval);
900 /* We got a null reference, so we have to skip the format element. */
901 if (typeclass == '[') {
902 int numsubwanted, refdepth;
904 while (*cx >= '0' && *cx <= '9') {
905 numsubwanted = 10 * numsubwanted + (*cx - '0');
909 while (refdepth > 0) {
917 else if (typeclass == 'S' || typeclass == 'U') {
930 fatalError("Illegal format string.");
934 if (*cx != ':' && *cx != '\0')
935 fatalError("Illegal format string.");
939 *argnumptr = gargnum;
942 /* find_stream_by_id():
943 This is used by some interpreter code which has to, well, find a Glk
946 strid_t git_find_stream_by_id(glui32 objid)
951 /* Recall that class 1 ("b") is streams. */
952 return classes_get(1, objid);
955 /* find_id_for_stream():
956 The converse of find_stream_by_id().
957 This is only needed in this file, so it's static.
959 glui32 git_find_id_for_stream(strid_t str)
961 gidispatch_rock_t objrock;
966 objrock = gidispatch_get_objrock(str, 1);
967 return ((classref_t *)objrock.ptr)->id;
970 /* Build a hash table to hold a set of Glk objects. */
971 static classtable_t *new_classtable(glui32 firstid)
974 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
978 for (ix=0; ix<CLASSHASH_SIZE; ix++)
979 ctab->bucket[ix] = NULL;
981 ctab->lastid = firstid;
986 /* Find a Glk object in the appropriate hash table. */
987 static void *classes_get(int classid, glui32 objid)
991 if (classid < 0 || classid >= num_classes)
993 ctab = git_classes[classid];
994 cref = ctab->bucket[objid % CLASSHASH_SIZE];
995 for (; cref; cref = cref->next) {
996 if (cref->id == objid)
1002 /* Put a Glk object in the appropriate hash table. */
1003 static classref_t *classes_put(int classid, void *obj)
1008 if (classid < 0 || classid >= num_classes)
1010 ctab = git_classes[classid];
1011 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
1015 cref->id = ctab->lastid;
1017 bucknum = cref->id % CLASSHASH_SIZE;
1018 cref->bucknum = bucknum;
1019 cref->next = ctab->bucket[bucknum];
1020 ctab->bucket[bucknum] = cref;
1024 /* Delete a Glk object from the appropriate hash table. */
1025 static void classes_remove(int classid, void *obj)
1030 gidispatch_rock_t objrock;
1031 if (classid < 0 || classid >= num_classes)
1033 ctab = git_classes[classid];
1034 objrock = gidispatch_get_objrock(obj, classid);
1038 crefp = &(ctab->bucket[cref->bucknum]);
1039 for (; *crefp; crefp = &((*crefp)->next)) {
1040 if ((*crefp) == cref) {
1041 *crefp = cref->next;
1043 fprintf(stderr, "attempt to free NULL object!\n");
1055 /* The object registration/unregistration callbacks that the library calls
1056 to keep the hash tables up to date. */
1058 static gidispatch_rock_t glulxe_classtable_register(void *obj,
1062 gidispatch_rock_t objrock;
1063 cref = classes_put(objclass, obj);
1068 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
1069 gidispatch_rock_t objrock)
1071 classes_remove(objclass, obj);
1074 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin)
1076 arrayref_t *arref = NULL;
1081 arr = (glui32 *)glulx_malloc(len * sizeof(glui32));
1082 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
1084 fatalError("Unable to allocate space for array argument to Glk call.");
1088 arref->elemsize = 4;
1089 arref->retained = FALSE;
1091 arref->next = arrays;
1095 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1096 arr[ix] = memRead32(addr2);
1104 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout)
1106 arrayref_t *arref = NULL;
1108 glui32 ix, val, addr2;
1111 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1112 if ((*aptr)->array == arr)
1117 fatalError("Unable to re-find array argument in Glk call.");
1118 if (arref->addr != addr || arref->len != len)
1119 fatalError("Mismatched array argument in Glk call.");
1121 if (arref->retained) {
1125 *aptr = arref->next;
1129 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1131 memWrite32(addr2, val);
1139 gidispatch_rock_t glulxe_retained_register(void *array,
1140 glui32 len, char *typecode)
1142 gidispatch_rock_t rock;
1143 arrayref_t *arref = NULL;
1146 if (typecode[4] != 'I' || array == NULL) {
1147 /* We only retain integer arrays. */
1152 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1153 if ((*aptr)->array == array)
1158 fatalError("Unable to re-find array argument in Glk call.");
1159 if (arref->elemsize != 4 || arref->len != len)
1160 fatalError("Mismatched array argument in Glk call.");
1162 arref->retained = TRUE;
1168 void glulxe_retained_unregister(void *array, glui32 len,
1169 char *typecode, gidispatch_rock_t objrock)
1171 arrayref_t *arref = NULL;
1173 glui32 ix, addr2, val;
1175 if (typecode[4] != 'I' || array == NULL) {
1176 /* We only retain integer arrays. */
1180 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1181 if ((*aptr)->array == array)
1186 fatalError("Unable to re-find array argument in Glk call.");
1187 if (arref != objrock.ptr)
1188 fatalError("Mismatched array reference in Glk call.");
1189 if (!arref->retained)
1190 fatalError("Unretained array reference in Glk call.");
1191 if (arref->elemsize != 4 || arref->len != len)
1192 fatalError("Mismatched array argument in Glk call.");
1194 *aptr = arref->next;
1197 for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) {
1198 val = ((glui32 *)array)[ix];
1199 memWrite32(addr2, val);