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 0x0080: /* put_char */
283 glk_put_char(arglist[0] & 0xFF);
285 case 0x0081: /* put_char_stream */
288 glk_put_char_stream(git_find_stream_by_id(arglist[0]), arglist[1] & 0xFF);
290 case 0x00A0: /* char_to_lower */
293 retval = glk_char_to_lower(arglist[0] & 0xFF);
295 case 0x00A1: /* char_to_upper */
298 retval = glk_char_to_upper(arglist[0] & 0xFF);
302 fatalError("Wrong number of arguments to Glk function.");
306 /* Go through the full dispatcher prototype foo. */
308 dispatch_splot_t splot;
311 /* Grab the string. */
312 proto = gidispatch_prototype(funcnum);
314 fatalError("Unknown Glk function.");
316 splot.varglist = arglist;
317 splot.numvargs = numargs;
318 splot.retval = &retval;
320 /* The work goes in four phases. First, we figure out how many
321 arguments we want, and allocate space for the Glk argument
322 list. Then we go through the Glulxe arguments and load them
323 into the Glk list. Then we call. Then we go through the
324 arguments again, unloading the data back into Glulx memory. */
327 prepare_glk_args(proto, &splot);
332 parse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
335 gidispatch_call(funcnum, argnum, splot.garglist);
340 unparse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
350 Read the prefixes of an argument string -- the "<>&+:#!" chars.
352 static char *read_prefix(char *cx, int *isref, int *isarray,
353 int *passin, int *passout, int *nullok, int *isretained,
368 else if (*cx == '>') {
372 else if (*cx == '&') {
377 else if (*cx == '+') {
380 else if (*cx == ':') {
386 else if (*cx == '#') {
389 else if (*cx == '!') {
400 /* prepare_glk_args():
401 This reads through the prototype string, and pulls Floo objects off the
402 stack. It also works out the maximal number of gluniversal_t objects
403 which could be used by the Glk call in question. It then allocates
406 static void prepare_glk_args(char *proto, dispatch_splot_t *splot)
408 static gluniversal_t *garglist = NULL;
409 static int garglist_size = 0;
412 int numwanted, numvargswanted, maxargs;
417 while (*cx >= '0' && *cx <= '9') {
418 numwanted = 10 * numwanted + (*cx - '0');
421 splot->numwanted = numwanted;
425 for (ix = 0; ix < numwanted; ix++) {
426 int isref, passin, passout, nullok, isarray, isretained, isreturn;
427 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
428 &isretained, &isreturn);
444 if (*cx == 'I' || *cx == 'C') {
447 else if (*cx == 'Q') {
450 else if (*cx == 'S' || *cx == 'U') {
453 else if (*cx == '[') {
457 while (*cx >= '0' && *cx <= '9') {
458 nwx = 10 * nwx + (*cx - '0');
461 maxargs += nwx; /* This is *only* correct because all structs contain
464 while (refdepth > 0) {
473 fatalError("Illegal format string.");
477 if (*cx != ':' && *cx != '\0')
478 fatalError("Illegal format string.");
480 splot->maxargs = maxargs;
482 if (splot->numvargs != numvargswanted)
483 fatalError("Wrong number of arguments to Glk function.");
485 if (garglist && garglist_size < maxargs) {
486 glulx_free(garglist);
491 garglist_size = maxargs + 16;
492 garglist = (gluniversal_t *)glulx_malloc(garglist_size
493 * sizeof(gluniversal_t));
496 fatalError("Unable to allocate storage for Glk arguments.");
498 splot->garglist = garglist;
502 This long and unpleasant function translates a set of Floo objects into
503 a gluniversal_t array. It's recursive, too, to deal with structures.
505 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
506 int *argnumptr, glui32 subaddress, int subpassin)
510 int gargnum, numwanted;
512 gluniversal_t *garglist;
515 garglist = splot->garglist;
516 varglist = splot->varglist;
517 gargnum = *argnumptr;
521 while (*cx >= '0' && *cx <= '9') {
522 numwanted = 10 * numwanted + (*cx - '0');
526 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
529 int isref, passin, passout, nullok, isarray, isretained, isreturn;
530 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
531 &isretained, &isreturn);
538 if (!isreturn && varglist[ix] == 0) {
540 fatalError("Zero passed invalidly to Glk function.");
541 garglist[gargnum].ptrflag = FALSE;
546 garglist[gargnum].ptrflag = TRUE;
553 if (typeclass == '[') {
555 parse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passin);
559 /* definitely isref */
563 garglist[gargnum].array = (void*) AddressOfArray(varglist[ix]);
566 garglist[gargnum].uint = varglist[ix];
571 garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin);
574 garglist[gargnum].uint = varglist[ix];
579 fatalError("Illegal format string.");
584 /* a plain value or a reference to one. */
589 else if (depth > 0) {
590 /* Definitely not isref or isarray. */
592 thisval = ReadStructField(subaddress, ix);
598 thisval = ReadMemory(varglist[ix]);
603 thisval = varglist[ix];
609 garglist[gargnum].uint = (glui32)(thisval);
611 garglist[gargnum].sint = (glsi32)(thisval);
613 fatalError("Illegal format string.");
619 opref = classes_get(*cx-'a', thisval);
621 fatalError("Reference to nonexistent Glk object.");
627 garglist[gargnum].opaqueref = opref;
633 garglist[gargnum].uch = (unsigned char)(thisval);
635 garglist[gargnum].sch = (signed char)(thisval);
637 garglist[gargnum].ch = (char)(thisval);
639 fatalError("Illegal format string.");
644 garglist[gargnum].charstr = DecodeVMString(thisval);
647 #ifdef GLK_MODULE_UNICODE
649 garglist[gargnum].unicharstr = DecodeVMUstring(thisval);
654 fatalError("Illegal format string.");
660 /* We got a null reference, so we have to skip the format element. */
661 if (typeclass == '[') {
662 int numsubwanted, refdepth;
664 while (*cx >= '0' && *cx <= '9') {
665 numsubwanted = 10 * numsubwanted + (*cx - '0');
669 while (refdepth > 0) {
677 else if (typeclass == 'S' || typeclass == 'U') {
688 fatalError("Illegal format string.");
692 if (*cx != ':' && *cx != '\0')
693 fatalError("Illegal format string.");
697 *argnumptr = gargnum;
700 /* unparse_glk_args():
701 This is about the reverse of parse_glk_args().
703 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
704 int *argnumptr, glui32 subaddress, int subpassout)
708 int gargnum, numwanted;
710 gluniversal_t *garglist;
713 garglist = splot->garglist;
714 varglist = splot->varglist;
715 gargnum = *argnumptr;
719 while (*cx >= '0' && *cx <= '9') {
720 numwanted = 10 * numwanted + (*cx - '0');
724 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
727 int isref, passin, passout, nullok, isarray, isretained, isreturn;
728 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
729 &isretained, &isreturn);
736 if (!isreturn && varglist[ix] == 0) {
738 fatalError("Zero passed invalidly to Glk function.");
739 garglist[gargnum].ptrflag = FALSE;
744 garglist[gargnum].ptrflag = TRUE;
751 if (typeclass == '[') {
753 unparse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passout);
757 /* definitely isref */
767 ReleaseIArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout);
774 fatalError("Illegal format string.");
779 /* a plain value or a reference to one. */
781 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
792 thisval = (glui32)garglist[gargnum].uint;
794 thisval = (glui32)garglist[gargnum].sint;
796 fatalError("Illegal format string.");
803 opref = garglist[gargnum].opaqueref;
805 gidispatch_rock_t objrock =
806 gidispatch_get_objrock(opref, *cx-'a');
807 thisval = ((classref_t *)objrock.ptr)->id;
819 thisval = (glui32)garglist[gargnum].uch;
821 thisval = (glui32)garglist[gargnum].sch;
823 thisval = (glui32)garglist[gargnum].ch;
825 fatalError("Illegal format string.");
831 if (garglist[gargnum].charstr)
832 ReleaseVMString(garglist[gargnum].charstr);
835 #ifdef GLK_MODULE_UNICODE
837 if (garglist[gargnum].unicharstr)
838 ReleaseVMUstring(garglist[gargnum].unicharstr);
843 fatalError("Illegal format string.");
848 *(splot->retval) = thisval;
850 else if (depth > 0) {
851 /* Definitely not isref or isarray. */
854 WriteStructField(subaddress, ix, thisval);
860 WriteMemory(varglist[ix], thisval);
866 /* We got a null reference, so we have to skip the format element. */
867 if (typeclass == '[') {
868 int numsubwanted, refdepth;
870 while (*cx >= '0' && *cx <= '9') {
871 numsubwanted = 10 * numsubwanted + (*cx - '0');
875 while (refdepth > 0) {
883 else if (typeclass == 'S' || typeclass == 'U') {
894 fatalError("Illegal format string.");
898 if (*cx != ':' && *cx != '\0')
899 fatalError("Illegal format string.");
903 *argnumptr = gargnum;
906 /* find_stream_by_id():
907 This is used by some interpreter code which has to, well, find a Glk
910 strid_t git_find_stream_by_id(glui32 objid)
915 /* Recall that class 1 ("b") is streams. */
916 return classes_get(1, objid);
919 /* Build a hash table to hold a set of Glk objects. */
920 static classtable_t *new_classtable(glui32 firstid)
923 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
927 for (ix=0; ix<CLASSHASH_SIZE; ix++)
928 ctab->bucket[ix] = NULL;
930 ctab->lastid = firstid;
935 /* Find a Glk object in the appropriate hash table. */
936 static void *classes_get(int classid, glui32 objid)
940 if (classid < 0 || classid >= num_classes)
942 ctab = git_classes[classid];
943 cref = ctab->bucket[objid % CLASSHASH_SIZE];
944 for (; cref; cref = cref->next) {
945 if (cref->id == objid)
951 /* Put a Glk object in the appropriate hash table. */
952 static classref_t *classes_put(int classid, void *obj)
957 if (classid < 0 || classid >= num_classes)
959 ctab = git_classes[classid];
960 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
964 cref->id = ctab->lastid;
966 bucknum = cref->id % CLASSHASH_SIZE;
967 cref->bucknum = bucknum;
968 cref->next = ctab->bucket[bucknum];
969 ctab->bucket[bucknum] = cref;
973 /* Delete a Glk object from the appropriate hash table. */
974 static void classes_remove(int classid, void *obj)
979 gidispatch_rock_t objrock;
980 if (classid < 0 || classid >= num_classes)
982 ctab = git_classes[classid];
983 objrock = gidispatch_get_objrock(obj, classid);
987 crefp = &(ctab->bucket[cref->bucknum]);
988 for (; *crefp; crefp = &((*crefp)->next)) {
989 if ((*crefp) == cref) {
992 fprintf(stderr, "attempt to free NULL object!\n");
1004 /* The object registration/unregistration callbacks that the library calls
1005 to keep the hash tables up to date. */
1007 static gidispatch_rock_t glulxe_classtable_register(void *obj,
1011 gidispatch_rock_t objrock;
1012 cref = classes_put(objclass, obj);
1017 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
1018 gidispatch_rock_t objrock)
1020 classes_remove(objclass, obj);
1023 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin)
1025 arrayref_t *arref = NULL;
1030 arr = (glui32 *)glulx_malloc(len * sizeof(glui32));
1031 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
1033 fatalError("Unable to allocate space for array argument to Glk call.");
1037 arref->elemsize = 4;
1038 arref->retained = FALSE;
1040 arref->next = arrays;
1044 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1045 arr[ix] = memRead32(addr2);
1053 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout)
1055 arrayref_t *arref = NULL;
1057 glui32 ix, val, addr2;
1060 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1061 if ((*aptr)->array == arr)
1066 fatalError("Unable to re-find array argument in Glk call.");
1067 if (arref->addr != addr || arref->len != len)
1068 fatalError("Mismatched array argument in Glk call.");
1070 if (arref->retained) {
1074 *aptr = arref->next;
1078 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1080 memWrite32(addr2, val);
1088 gidispatch_rock_t glulxe_retained_register(void *array,
1089 glui32 len, char *typecode)
1091 gidispatch_rock_t rock;
1092 arrayref_t *arref = NULL;
1095 if (typecode[4] != 'I' || array == NULL) {
1096 /* We only retain integer arrays. */
1101 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1102 if ((*aptr)->array == array)
1107 fatalError("Unable to re-find array argument in Glk call.");
1108 if (arref->elemsize != 4 || arref->len != len)
1109 fatalError("Mismatched array argument in Glk call.");
1111 arref->retained = TRUE;
1117 void glulxe_retained_unregister(void *array, glui32 len,
1118 char *typecode, gidispatch_rock_t objrock)
1120 arrayref_t *arref = NULL;
1122 glui32 ix, addr2, val;
1124 if (typecode[4] != 'I' || array == NULL) {
1125 /* We only retain integer arrays. */
1129 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1130 if ((*aptr)->array == array)
1135 fatalError("Unable to re-find array argument in Glk call.");
1136 if (arref != objrock.ptr)
1137 fatalError("Mismatched array reference in Glk call.");
1138 if (!arref->retained)
1139 fatalError("Unretained array reference in Glk call.");
1140 if (arref->elemsize != 4 || arref->len != len)
1141 fatalError("Mismatched array argument in Glk call.");
1143 for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) {
1144 val = ((glui32 *)array)[ix];
1145 memWrite32(addr2, val);