1 /* glkop.c: Glulxe code for Glk API dispatching.
2 Designed by Andrew Plotkin <erkyrath@eblong.com>
3 http://eblong.com/zarf/glulx/index.html
6 /* This code is actually very general; it could work for almost any
7 32-bit VM which remotely resembles Glulxe or the Z-machine in design.
9 To be precise, we make the following assumptions:
11 - An argument list is an array of 32-bit values, which can represent
12 either integers or addresses.
13 - We can read or write to a 32-bit integer in VM memory using the macros
14 ReadMemory(addr) and WriteMemory(addr), where addr is an address
15 taken from the argument list.
16 - A character array is an actual array of bytes somewhere in terp
17 memory, whose actual address can be computed by the macro
18 AddressOfArray(addr). Again, addr is a VM address from the argument
20 - An integer array is a sequence of integers somewhere in VM memory.
21 The array can be turned into a C integer array by the macro
22 CaptureIArray(addr, len), and released by ReleaseIArray().
23 These macros are responsible for fixing byte-order and alignment
24 (if the C ABI does not match the VM's). The passin, passout hints
25 may be used to avoid unnecessary copying.
26 - A Glk structure (such as event_t) is a set of integers somewhere
27 in VM memory, which can be read and written with the macros
28 ReadStructField(addr, fieldnum) and WriteStructField(addr, fieldnum).
29 The fieldnum is an integer (from 0 to 3, for event_t.)
30 - A VM string can be turned into a C-style string with the macro
31 ptr = DecodeVMString(addr). After the string is used, this code
32 calls ReleaseVMString(ptr), which should free any memory that
33 DecodeVMString allocates.
34 - A VM Unicode string can be turned into a zero-terminated array
35 of 32-bit integers, in the same way, with DecodeVMUstring
38 To work this code over for a new VM, just diddle the macros.
41 #define ReadMemory(addr) \
42 (((addr) == 0xffffffff) \
43 ? (stackptr -= 4, Stk4(stackptr)) \
45 #define WriteMemory(addr, val) \
46 (((addr) == 0xffffffff) \
47 ? (StkW4(stackptr, (val)), stackptr += 4) \
48 : (MemW4((addr), (val))))
49 #define AddressOfArray(addr) \
51 #define CaptureIArray(addr, len, passin) \
52 (grab_temp_array(addr, len, passin))
53 #define ReleaseIArray(ptr, addr, len, passout) \
54 (release_temp_array(ptr, addr, len, passout))
55 #define ReadStructField(addr, fieldnum) \
56 (((addr) == 0xffffffff) \
57 ? (stackptr -= 4, Stk4(stackptr)) \
58 : (Mem4((addr)+(fieldnum)*4)))
59 #define WriteStructField(addr, fieldnum, val) \
60 (((addr) == 0xffffffff) \
61 ? (StkW4(stackptr, (val)), stackptr += 4) \
62 : (MemW4((addr)+(fieldnum)*4, (val))))
63 #define DecodeVMString(addr) \
64 (make_temp_string(addr))
65 #define ReleaseVMString(ptr) \
66 (free_temp_string(ptr))
67 #define DecodeVMUstring(addr) \
68 (make_temp_ustring(addr))
69 #define ReleaseVMUstring(ptr) \
70 (free_temp_ustring(ptr))
76 typedef struct dispatch_splot_struct {
79 gluniversal_t *garglist;
85 /* We maintain a linked list of arrays being used for Glk calls. It is
86 only used for integer (glui32) arrays -- char arrays are handled in
87 place. It's not worth bothering with a hash table, since most
88 arrays appear here only momentarily. */
90 typedef struct arrayref_struct arrayref_t;
91 struct arrayref_struct {
95 glui32 len; /* elements */
100 static arrayref_t *arrays = NULL;
102 /* We maintain a hash table for each opaque Glk class. classref_t are the
103 nodes of the table, and classtable_t are the tables themselves. */
105 typedef struct classref_struct classref_t;
106 struct classref_struct {
113 #define CLASSHASH_SIZE (31)
114 typedef struct classtable_struct {
116 classref_t *bucket[CLASSHASH_SIZE];
119 /* The list of hash tables, for the classes. */
120 static int num_classes = 0;
121 classtable_t **classes = NULL;
123 static glui32 find_id_for_stream(strid_t str);
125 static classtable_t *new_classtable(glui32 firstid);
126 static void *classes_get(int classid, glui32 objid);
127 static classref_t *classes_put(int classid, void *obj);
128 static void classes_remove(int classid, void *obj);
130 static gidispatch_rock_t glulxe_classtable_register(void *obj,
132 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
133 gidispatch_rock_t objrock);
134 static gidispatch_rock_t glulxe_retained_register(void *array,
135 glui32 len, char *typecode);
136 static void glulxe_retained_unregister(void *array, glui32 len,
137 char *typecode, gidispatch_rock_t objrock);
139 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin);
140 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout);
142 static void prepare_glk_args(char *proto, dispatch_splot_t *splot);
143 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
144 int *argnumptr, glui32 subaddress, int subpassin);
145 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
146 int *argnumptr, glui32 subaddress, int subpassout);
149 Set up the class hash tables and other startup-time stuff.
155 /* Allocate the class hash tables. */
156 num_classes = gidispatch_count_classes();
157 classes = (classtable_t **)glulx_malloc(num_classes
158 * sizeof(classtable_t *));
162 for (ix=0; ix<num_classes; ix++) {
163 classes[ix] = new_classtable((glulx_random() % (glui32)(101)) + 1);
168 /* Set up the two callbacks. */
169 gidispatch_set_object_registry(&glulxe_classtable_register,
170 &glulxe_classtable_unregister);
171 gidispatch_set_retained_registry(&glulxe_retained_register,
172 &glulxe_retained_unregister);
178 Turn a list of Glulx arguments into a list of Glk arguments,
179 dispatch the function call, and return the result.
181 glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
186 /* To speed life up, we implement commonly-used Glk functions
187 directly -- instead of bothering with the whole prototype
190 case 0x0047: /* stream_set_current */
193 glk_stream_set_current(find_stream_by_id(arglist[0]));
195 case 0x0048: /* stream_get_current */
198 retval = find_id_for_stream(glk_stream_get_current());
200 case 0x0080: /* put_char */
203 glk_put_char(arglist[0] & 0xFF);
205 case 0x0081: /* put_char_stream */
208 glk_put_char_stream(find_stream_by_id(arglist[0]), arglist[1] & 0xFF);
210 case 0x00A0: /* char_to_lower */
213 retval = glk_char_to_lower(arglist[0] & 0xFF);
215 case 0x00A1: /* char_to_upper */
218 retval = glk_char_to_upper(arglist[0] & 0xFF);
220 case 0x0128: /* put_char_uni */
223 glk_put_char_uni(arglist[0]);
225 case 0x012B: /* put_char_stream_uni */
228 glk_put_char_stream_uni(find_stream_by_id(arglist[0]), arglist[1]);
232 fatal_error("Wrong number of arguments to Glk function.");
236 /* Go through the full dispatcher prototype foo. */
238 dispatch_splot_t splot;
241 /* Grab the string. */
242 proto = gidispatch_prototype(funcnum);
244 fatal_error("Unknown Glk function.");
246 splot.varglist = arglist;
247 splot.numvargs = numargs;
248 splot.retval = &retval;
250 /* The work goes in four phases. First, we figure out how many
251 arguments we want, and allocate space for the Glk argument
252 list. Then we go through the Glulxe arguments and load them
253 into the Glk list. Then we call. Then we go through the
254 arguments again, unloading the data back into Glulx memory. */
257 prepare_glk_args(proto, &splot);
262 parse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
265 gidispatch_call(funcnum, argnum, splot.garglist);
270 unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0);
271 if (argnum != argnum2)
272 fatal_error("Argument counts did not match.");
282 Read the prefixes of an argument string -- the "<>&+:#!" chars.
284 static char *read_prefix(char *cx, int *isref, int *isarray,
285 int *passin, int *passout, int *nullok, int *isretained,
300 else if (*cx == '>') {
304 else if (*cx == '&') {
309 else if (*cx == '+') {
312 else if (*cx == ':') {
318 else if (*cx == '#') {
321 else if (*cx == '!') {
332 /* prepare_glk_args():
333 This reads through the prototype string, and pulls Floo objects off the
334 stack. It also works out the maximal number of gluniversal_t objects
335 which could be used by the Glk call in question. It then allocates
338 static void prepare_glk_args(char *proto, dispatch_splot_t *splot)
340 static gluniversal_t *garglist = NULL;
341 static int garglist_size = 0;
344 int numwanted, numvargswanted, maxargs;
349 while (*cx >= '0' && *cx <= '9') {
350 numwanted = 10 * numwanted + (*cx - '0');
353 splot->numwanted = numwanted;
357 for (ix = 0; ix < numwanted; ix++) {
358 int isref, passin, passout, nullok, isarray, isretained, isreturn;
359 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
360 &isretained, &isreturn);
376 if (*cx == 'I' || *cx == 'C') {
379 else if (*cx == 'Q') {
382 else if (*cx == 'S' || *cx == 'U') {
385 else if (*cx == '[') {
389 while (*cx >= '0' && *cx <= '9') {
390 nwx = 10 * nwx + (*cx - '0');
393 maxargs += nwx; /* This is *only* correct because all structs contain
396 while (refdepth > 0) {
405 fatal_error("Illegal format string.");
409 if (*cx != ':' && *cx != '\0')
410 fatal_error("Illegal format string.");
412 splot->maxargs = maxargs;
414 if (splot->numvargs != numvargswanted)
415 fatal_error("Wrong number of arguments to Glk function.");
417 if (garglist && garglist_size < maxargs) {
418 glulx_free(garglist);
423 garglist_size = maxargs + 16;
424 garglist = (gluniversal_t *)glulx_malloc(garglist_size
425 * sizeof(gluniversal_t));
428 fatal_error("Unable to allocate storage for Glk arguments.");
430 splot->garglist = garglist;
434 This long and unpleasant function translates a set of Floo objects into
435 a gluniversal_t array. It's recursive, too, to deal with structures.
437 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
438 int *argnumptr, glui32 subaddress, int subpassin)
442 int gargnum, numwanted;
444 gluniversal_t *garglist;
447 garglist = splot->garglist;
448 varglist = splot->varglist;
449 gargnum = *argnumptr;
453 while (*cx >= '0' && *cx <= '9') {
454 numwanted = 10 * numwanted + (*cx - '0');
458 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
461 int isref, passin, passout, nullok, isarray, isretained, isreturn;
462 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
463 &isretained, &isreturn);
470 if (!isreturn && varglist[ix] == 0) {
472 fatal_error("Zero passed invalidly to Glk function.");
473 garglist[gargnum].ptrflag = FALSE;
478 garglist[gargnum].ptrflag = TRUE;
485 if (typeclass == '[') {
487 parse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passin);
491 /* definitely isref */
495 /* This test checks for a giant array length, which is
496 deprecated. It displays a warning and cuts it down to
497 something reasonable. Future releases of this interpreter
498 may remove this test and go on to verify_array_addresses(),
499 which treats this case as a fatal error. */
500 if (varglist[ix+1] > endmem
501 || varglist[ix]+varglist[ix+1] > endmem) {
502 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix+1]);
503 varglist[ix+1] = endmem - varglist[ix];
505 verify_array_addresses(varglist[ix], varglist[ix+1], 1);
506 garglist[gargnum].array = AddressOfArray(varglist[ix]);
509 garglist[gargnum].uint = varglist[ix];
514 /* See comment above. */
515 if (varglist[ix+1] > endmem/4
516 || varglist[ix+1] > (endmem-varglist[ix])/4) {
517 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix+1]);
518 varglist[ix+1] = (endmem - varglist[ix]) / 4;
520 verify_array_addresses(varglist[ix], varglist[ix+1], 4);
521 garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin);
524 garglist[gargnum].uint = varglist[ix];
529 fatal_error("Illegal format string.");
534 /* a plain value or a reference to one. */
539 else if (depth > 0) {
540 /* Definitely not isref or isarray. */
542 thisval = ReadStructField(subaddress, ix);
548 thisval = ReadMemory(varglist[ix]);
553 thisval = varglist[ix];
559 garglist[gargnum].uint = (glui32)(thisval);
561 garglist[gargnum].sint = (glsi32)(thisval);
563 fatal_error("Illegal format string.");
569 opref = classes_get(*cx-'a', thisval);
571 fatal_error("Reference to nonexistent Glk object.");
577 garglist[gargnum].opaqueref = opref;
583 garglist[gargnum].uch = (unsigned char)(thisval);
585 garglist[gargnum].sch = (signed char)(thisval);
587 garglist[gargnum].ch = (char)(thisval);
589 fatal_error("Illegal format string.");
594 garglist[gargnum].charstr = DecodeVMString(thisval);
597 #ifdef GLK_MODULE_UNICODE
599 garglist[gargnum].unicharstr = DecodeVMUstring(thisval);
602 #endif /* GLK_MODULE_UNICODE */
604 fatal_error("Illegal format string.");
610 /* We got a null reference, so we have to skip the format element. */
611 if (typeclass == '[') {
612 int numsubwanted, refdepth;
614 while (*cx >= '0' && *cx <= '9') {
615 numsubwanted = 10 * numsubwanted + (*cx - '0');
619 while (refdepth > 0) {
627 else if (typeclass == 'S' || typeclass == 'U') {
640 fatal_error("Illegal format string.");
644 if (*cx != ':' && *cx != '\0')
645 fatal_error("Illegal format string.");
649 *argnumptr = gargnum;
652 /* unparse_glk_args():
653 This is about the reverse of parse_glk_args().
655 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
656 int *argnumptr, glui32 subaddress, int subpassout)
660 int gargnum, numwanted;
662 gluniversal_t *garglist;
665 garglist = splot->garglist;
666 varglist = splot->varglist;
667 gargnum = *argnumptr;
671 while (*cx >= '0' && *cx <= '9') {
672 numwanted = 10 * numwanted + (*cx - '0');
676 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
679 int isref, passin, passout, nullok, isarray, isretained, isreturn;
680 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
681 &isretained, &isreturn);
688 if (!isreturn && varglist[ix] == 0) {
690 fatal_error("Zero passed invalidly to Glk function.");
691 garglist[gargnum].ptrflag = FALSE;
696 garglist[gargnum].ptrflag = TRUE;
703 if (typeclass == '[') {
705 unparse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passout);
709 /* definitely isref */
719 ReleaseIArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout);
726 fatal_error("Illegal format string.");
731 /* a plain value or a reference to one. */
733 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
744 thisval = (glui32)garglist[gargnum].uint;
746 thisval = (glui32)garglist[gargnum].sint;
748 fatal_error("Illegal format string.");
755 opref = garglist[gargnum].opaqueref;
757 gidispatch_rock_t objrock =
758 gidispatch_get_objrock(opref, *cx-'a');
759 thisval = ((classref_t *)objrock.ptr)->id;
771 thisval = (glui32)garglist[gargnum].uch;
773 thisval = (glui32)garglist[gargnum].sch;
775 thisval = (glui32)garglist[gargnum].ch;
777 fatal_error("Illegal format string.");
783 if (garglist[gargnum].charstr)
784 ReleaseVMString(garglist[gargnum].charstr);
787 #ifdef GLK_MODULE_UNICODE
789 if (garglist[gargnum].unicharstr)
790 ReleaseVMUstring(garglist[gargnum].unicharstr);
793 #endif /* GLK_MODULE_UNICODE */
795 fatal_error("Illegal format string.");
800 *(splot->retval) = thisval;
802 else if (depth > 0) {
803 /* Definitely not isref or isarray. */
805 WriteStructField(subaddress, ix, thisval);
809 WriteMemory(varglist[ix], thisval);
814 /* We got a null reference, so we have to skip the format element. */
815 if (typeclass == '[') {
816 int numsubwanted, refdepth;
818 while (*cx >= '0' && *cx <= '9') {
819 numsubwanted = 10 * numsubwanted + (*cx - '0');
823 while (refdepth > 0) {
831 else if (typeclass == 'S' || typeclass == 'U') {
844 fatal_error("Illegal format string.");
848 if (*cx != ':' && *cx != '\0')
849 fatal_error("Illegal format string.");
853 *argnumptr = gargnum;
856 /* find_stream_by_id():
857 This is used by some interpreter code which has to, well, find a Glk
860 strid_t find_stream_by_id(glui32 objid)
865 /* Recall that class 1 ("b") is streams. */
866 return classes_get(1, objid);
869 /* find_id_for_stream():
870 The converse of find_stream_by_id().
871 This is only needed in this file, so it's static.
873 static glui32 find_id_for_stream(strid_t str)
875 gidispatch_rock_t objrock;
880 objrock = gidispatch_get_objrock(str, 1);
881 return ((classref_t *)objrock.ptr)->id;
884 /* Build a hash table to hold a set of Glk objects. */
885 static classtable_t *new_classtable(glui32 firstid)
888 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
892 for (ix=0; ix<CLASSHASH_SIZE; ix++)
893 ctab->bucket[ix] = NULL;
895 ctab->lastid = firstid;
900 /* Find a Glk object in the appropriate hash table. */
901 static void *classes_get(int classid, glui32 objid)
905 if (classid < 0 || classid >= num_classes)
907 ctab = classes[classid];
908 cref = ctab->bucket[objid % CLASSHASH_SIZE];
909 for (; cref; cref = cref->next) {
910 if (cref->id == objid)
916 /* Put a Glk object in the appropriate hash table. */
917 static classref_t *classes_put(int classid, void *obj)
922 if (classid < 0 || classid >= num_classes)
924 ctab = classes[classid];
925 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
929 cref->id = ctab->lastid;
931 bucknum = cref->id % CLASSHASH_SIZE;
932 cref->bucknum = bucknum;
933 cref->next = ctab->bucket[bucknum];
934 ctab->bucket[bucknum] = cref;
938 /* Delete a Glk object from the appropriate hash table. */
939 static void classes_remove(int classid, void *obj)
944 gidispatch_rock_t objrock;
945 if (classid < 0 || classid >= num_classes)
947 ctab = classes[classid];
948 objrock = gidispatch_get_objrock(obj, classid);
952 crefp = &(ctab->bucket[cref->bucknum]);
953 for (; *crefp; crefp = &((*crefp)->next)) {
954 if ((*crefp) == cref) {
957 nonfatal_warning("attempt to free NULL object!");
969 /* The object registration/unregistration callbacks that the library calls
970 to keep the hash tables up to date. */
972 static gidispatch_rock_t glulxe_classtable_register(void *obj,
976 gidispatch_rock_t objrock;
977 cref = classes_put(objclass, obj);
982 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
983 gidispatch_rock_t objrock)
985 classes_remove(objclass, obj);
988 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin)
990 arrayref_t *arref = NULL;
995 arr = (glui32 *)glulx_malloc(len * sizeof(glui32));
996 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
998 fatal_error("Unable to allocate space for array argument to Glk call.");
1002 arref->elemsize = 4;
1003 arref->retained = FALSE;
1005 arref->next = arrays;
1009 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1010 arr[ix] = Mem4(addr2);
1018 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout)
1020 arrayref_t *arref = NULL;
1022 glui32 ix, val, addr2;
1025 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1026 if ((*aptr)->array == arr)
1031 fatal_error("Unable to re-find array argument in Glk call.");
1032 if (arref->addr != addr || arref->len != len)
1033 fatal_error("Mismatched array argument in Glk call.");
1035 if (arref->retained) {
1039 *aptr = arref->next;
1043 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1053 gidispatch_rock_t glulxe_retained_register(void *array,
1054 glui32 len, char *typecode)
1056 gidispatch_rock_t rock;
1057 arrayref_t *arref = NULL;
1060 if (typecode[4] != 'I' || array == NULL) {
1061 /* We only retain integer arrays. */
1066 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1067 if ((*aptr)->array == array)
1072 fatal_error("Unable to re-find array argument in Glk call.");
1073 if (arref->elemsize != 4 || arref->len != len)
1074 fatal_error("Mismatched array argument in Glk call.");
1076 arref->retained = TRUE;
1082 void glulxe_retained_unregister(void *array, glui32 len,
1083 char *typecode, gidispatch_rock_t objrock)
1085 arrayref_t *arref = NULL;
1087 glui32 ix, addr2, val;
1089 if (typecode[4] != 'I' || array == NULL) {
1090 /* We only retain integer arrays. */
1094 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1095 if ((*aptr)->array == array)
1100 fatal_error("Unable to re-find array argument in Glk call.");
1101 if (arref != objrock.ptr)
1102 fatal_error("Mismatched array reference in Glk call.");
1103 if (!arref->retained)
1104 fatal_error("Unretained array reference in Glk call.");
1105 if (arref->elemsize != 4 || arref->len != len)
1106 fatal_error("Mismatched array argument in Glk call.");
1108 *aptr = arref->next;
1111 for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) {
1112 val = ((glui32 *)array)[ix];