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 classtable_t *new_classtable(glui32 firstid);
124 static void *classes_get(int classid, glui32 objid);
125 static classref_t *classes_put(int classid, void *obj);
126 static void classes_remove(int classid, void *obj);
128 static gidispatch_rock_t glulxe_classtable_register(void *obj,
130 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
131 gidispatch_rock_t objrock);
132 static gidispatch_rock_t glulxe_retained_register(void *array,
133 glui32 len, char *typecode);
134 static void glulxe_retained_unregister(void *array, glui32 len,
135 char *typecode, gidispatch_rock_t objrock);
137 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin);
138 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout);
140 static void prepare_glk_args(char *proto, dispatch_splot_t *splot);
141 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
142 int *argnumptr, glui32 subaddress, int subpassin);
143 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
144 int *argnumptr, glui32 subaddress, int subpassout);
147 Set up the class hash tables and other startup-time stuff.
153 /* Allocate the class hash tables. */
154 num_classes = gidispatch_count_classes();
155 classes = (classtable_t **)glulx_malloc(num_classes
156 * sizeof(classtable_t *));
160 for (ix=0; ix<num_classes; ix++) {
161 classes[ix] = new_classtable((glulx_random() % (glui32)(101)) + 1);
166 /* Set up the two callbacks. */
167 gidispatch_set_object_registry(&glulxe_classtable_register,
168 &glulxe_classtable_unregister);
169 gidispatch_set_retained_registry(&glulxe_retained_register,
170 &glulxe_retained_unregister);
176 Turn a list of Glulx arguments into a list of Glk arguments,
177 dispatch the function call, and return the result.
179 glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
184 /* To speed life up, we implement commonly-used Glk functions
185 directly -- instead of bothering with the whole prototype
188 case 0x0080: /* put_char */
191 glk_put_char(arglist[0] & 0xFF);
193 case 0x0081: /* put_char_stream */
196 glk_put_char_stream(find_stream_by_id(arglist[0]), arglist[1] & 0xFF);
198 case 0x00A0: /* char_to_lower */
201 retval = glk_char_to_lower(arglist[0] & 0xFF);
203 case 0x00A1: /* char_to_upper */
206 retval = glk_char_to_upper(arglist[0] & 0xFF);
210 fatal_error("Wrong number of arguments to Glk function.");
214 /* Go through the full dispatcher prototype foo. */
216 dispatch_splot_t splot;
219 /* Grab the string. */
220 proto = gidispatch_prototype(funcnum);
222 fatal_error("Unknown Glk function.");
224 splot.varglist = arglist;
225 splot.numvargs = numargs;
226 splot.retval = &retval;
228 /* The work goes in four phases. First, we figure out how many
229 arguments we want, and allocate space for the Glk argument
230 list. Then we go through the Glulxe arguments and load them
231 into the Glk list. Then we call. Then we go through the
232 arguments again, unloading the data back into Glulx memory. */
235 prepare_glk_args(proto, &splot);
240 parse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
243 gidispatch_call(funcnum, argnum, splot.garglist);
248 unparse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
258 Read the prefixes of an argument string -- the "<>&+:#!" chars.
260 static char *read_prefix(char *cx, int *isref, int *isarray,
261 int *passin, int *passout, int *nullok, int *isretained,
276 else if (*cx == '>') {
280 else if (*cx == '&') {
285 else if (*cx == '+') {
288 else if (*cx == ':') {
294 else if (*cx == '#') {
297 else if (*cx == '!') {
308 /* prepare_glk_args():
309 This reads through the prototype string, and pulls Floo objects off the
310 stack. It also works out the maximal number of gluniversal_t objects
311 which could be used by the Glk call in question. It then allocates
314 static void prepare_glk_args(char *proto, dispatch_splot_t *splot)
316 static gluniversal_t *garglist = NULL;
317 static int garglist_size = 0;
320 int numwanted, numvargswanted, maxargs;
325 while (*cx >= '0' && *cx <= '9') {
326 numwanted = 10 * numwanted + (*cx - '0');
329 splot->numwanted = numwanted;
333 for (ix = 0; ix < numwanted; ix++) {
334 int isref, passin, passout, nullok, isarray, isretained, isreturn;
335 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
336 &isretained, &isreturn);
352 if (*cx == 'I' || *cx == 'C') {
355 else if (*cx == 'Q') {
358 else if (*cx == 'S' || *cx == 'U') {
361 else if (*cx == '[') {
365 while (*cx >= '0' && *cx <= '9') {
366 nwx = 10 * nwx + (*cx - '0');
369 maxargs += nwx; /* This is *only* correct because all structs contain
372 while (refdepth > 0) {
381 fatal_error("Illegal format string.");
385 if (*cx != ':' && *cx != '\0')
386 fatal_error("Illegal format string.");
388 splot->maxargs = maxargs;
390 if (splot->numvargs != numvargswanted)
391 fatal_error("Wrong number of arguments to Glk function.");
393 if (garglist && garglist_size < maxargs) {
394 glulx_free(garglist);
399 garglist_size = maxargs + 16;
400 garglist = (gluniversal_t *)glulx_malloc(garglist_size
401 * sizeof(gluniversal_t));
404 fatal_error("Unable to allocate storage for Glk arguments.");
406 splot->garglist = garglist;
410 This long and unpleasant function translates a set of Floo objects into
411 a gluniversal_t array. It's recursive, too, to deal with structures.
413 static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
414 int *argnumptr, glui32 subaddress, int subpassin)
418 int gargnum, numwanted;
420 gluniversal_t *garglist;
423 garglist = splot->garglist;
424 varglist = splot->varglist;
425 gargnum = *argnumptr;
429 while (*cx >= '0' && *cx <= '9') {
430 numwanted = 10 * numwanted + (*cx - '0');
434 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
437 int isref, passin, passout, nullok, isarray, isretained, isreturn;
438 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
439 &isretained, &isreturn);
446 if (!isreturn && varglist[ix] == 0) {
448 fatal_error("Zero passed invalidly to Glk function.");
449 garglist[gargnum].ptrflag = FALSE;
454 garglist[gargnum].ptrflag = TRUE;
461 if (typeclass == '[') {
463 parse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passin);
467 /* definitely isref */
471 garglist[gargnum].array = AddressOfArray(varglist[ix]);
474 garglist[gargnum].uint = varglist[ix];
479 garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin);
482 garglist[gargnum].uint = varglist[ix];
487 fatal_error("Illegal format string.");
492 /* a plain value or a reference to one. */
497 else if (depth > 0) {
498 /* Definitely not isref or isarray. */
500 thisval = ReadStructField(subaddress, ix);
506 thisval = ReadMemory(varglist[ix]);
511 thisval = varglist[ix];
517 garglist[gargnum].uint = (glui32)(thisval);
519 garglist[gargnum].sint = (glsi32)(thisval);
521 fatal_error("Illegal format string.");
527 opref = classes_get(*cx-'a', thisval);
529 fatal_error("Reference to nonexistent Glk object.");
535 garglist[gargnum].opaqueref = opref;
541 garglist[gargnum].uch = (unsigned char)(thisval);
543 garglist[gargnum].sch = (signed char)(thisval);
545 garglist[gargnum].ch = (char)(thisval);
547 fatal_error("Illegal format string.");
552 garglist[gargnum].charstr = DecodeVMString(thisval);
555 #ifdef GLK_MODULE_UNICODE
557 garglist[gargnum].unicharstr = DecodeVMUstring(thisval);
560 #endif /* GLK_MODULE_UNICODE */
562 fatal_error("Illegal format string.");
568 /* We got a null reference, so we have to skip the format element. */
569 if (typeclass == '[') {
570 int numsubwanted, refdepth;
572 while (*cx >= '0' && *cx <= '9') {
573 numsubwanted = 10 * numsubwanted + (*cx - '0');
577 while (refdepth > 0) {
585 else if (typeclass == 'S' || typeclass == 'U') {
596 fatal_error("Illegal format string.");
600 if (*cx != ':' && *cx != '\0')
601 fatal_error("Illegal format string.");
605 *argnumptr = gargnum;
608 /* unparse_glk_args():
609 This is about the reverse of parse_glk_args().
611 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
612 int *argnumptr, glui32 subaddress, int subpassout)
616 int gargnum, numwanted;
618 gluniversal_t *garglist;
621 garglist = splot->garglist;
622 varglist = splot->varglist;
623 gargnum = *argnumptr;
627 while (*cx >= '0' && *cx <= '9') {
628 numwanted = 10 * numwanted + (*cx - '0');
632 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
635 int isref, passin, passout, nullok, isarray, isretained, isreturn;
636 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
637 &isretained, &isreturn);
644 if (!isreturn && varglist[ix] == 0) {
646 fatal_error("Zero passed invalidly to Glk function.");
647 garglist[gargnum].ptrflag = FALSE;
652 garglist[gargnum].ptrflag = TRUE;
659 if (typeclass == '[') {
661 unparse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passout);
665 /* definitely isref */
675 ReleaseIArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout);
682 fatal_error("Illegal format string.");
687 /* a plain value or a reference to one. */
689 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
700 thisval = (glui32)garglist[gargnum].uint;
702 thisval = (glui32)garglist[gargnum].sint;
704 fatal_error("Illegal format string.");
711 opref = garglist[gargnum].opaqueref;
713 gidispatch_rock_t objrock =
714 gidispatch_get_objrock(opref, *cx-'a');
715 thisval = ((classref_t *)objrock.ptr)->id;
727 thisval = (glui32)garglist[gargnum].uch;
729 thisval = (glui32)garglist[gargnum].sch;
731 thisval = (glui32)garglist[gargnum].ch;
733 fatal_error("Illegal format string.");
739 if (garglist[gargnum].charstr)
740 ReleaseVMString(garglist[gargnum].charstr);
743 #ifdef GLK_MODULE_UNICODE
745 if (garglist[gargnum].unicharstr)
746 ReleaseVMUstring(garglist[gargnum].unicharstr);
749 #endif /* GLK_MODULE_UNICODE */
751 fatal_error("Illegal format string.");
756 *(splot->retval) = thisval;
758 else if (depth > 0) {
759 /* Definitely not isref or isarray. */
761 WriteStructField(subaddress, ix, thisval);
765 WriteMemory(varglist[ix], thisval);
770 /* We got a null reference, so we have to skip the format element. */
771 if (typeclass == '[') {
772 int numsubwanted, refdepth;
774 while (*cx >= '0' && *cx <= '9') {
775 numsubwanted = 10 * numsubwanted + (*cx - '0');
779 while (refdepth > 0) {
787 else if (typeclass == 'S' || typeclass == 'U') {
798 fatal_error("Illegal format string.");
802 if (*cx != ':' && *cx != '\0')
803 fatal_error("Illegal format string.");
807 *argnumptr = gargnum;
810 /* find_stream_by_id():
811 This is used by some interpreter code which has to, well, find a Glk
814 strid_t find_stream_by_id(glui32 objid)
819 /* Recall that class 1 ("b") is streams. */
820 return classes_get(1, objid);
823 /* Build a hash table to hold a set of Glk objects. */
824 static classtable_t *new_classtable(glui32 firstid)
827 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
831 for (ix=0; ix<CLASSHASH_SIZE; ix++)
832 ctab->bucket[ix] = NULL;
834 ctab->lastid = firstid;
839 /* Find a Glk object in the appropriate hash table. */
840 static void *classes_get(int classid, glui32 objid)
844 if (classid < 0 || classid >= num_classes)
846 ctab = classes[classid];
847 cref = ctab->bucket[objid % CLASSHASH_SIZE];
848 for (; cref; cref = cref->next) {
849 if (cref->id == objid)
855 /* Put a Glk object in the appropriate hash table. */
856 static classref_t *classes_put(int classid, void *obj)
861 if (classid < 0 || classid >= num_classes)
863 ctab = classes[classid];
864 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
868 cref->id = ctab->lastid;
870 bucknum = cref->id % CLASSHASH_SIZE;
871 cref->bucknum = bucknum;
872 cref->next = ctab->bucket[bucknum];
873 ctab->bucket[bucknum] = cref;
877 /* Delete a Glk object from the appropriate hash table. */
878 static void classes_remove(int classid, void *obj)
883 gidispatch_rock_t objrock;
884 if (classid < 0 || classid >= num_classes)
886 ctab = classes[classid];
887 objrock = gidispatch_get_objrock(obj, classid);
891 crefp = &(ctab->bucket[cref->bucknum]);
892 for (; *crefp; crefp = &((*crefp)->next)) {
893 if ((*crefp) == cref) {
896 nonfatal_warning("attempt to free NULL object!");
908 /* The object registration/unregistration callbacks that the library calls
909 to keep the hash tables up to date. */
911 static gidispatch_rock_t glulxe_classtable_register(void *obj,
915 gidispatch_rock_t objrock;
916 cref = classes_put(objclass, obj);
921 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
922 gidispatch_rock_t objrock)
924 classes_remove(objclass, obj);
927 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin)
929 arrayref_t *arref = NULL;
934 arr = (glui32 *)glulx_malloc(len * sizeof(glui32));
935 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
937 fatal_error("Unable to allocate space for array argument to Glk call.");
942 arref->retained = FALSE;
944 arref->next = arrays;
948 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
949 arr[ix] = Mem4(addr2);
957 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout)
959 arrayref_t *arref = NULL;
961 glui32 ix, val, addr2;
964 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
965 if ((*aptr)->array == arr)
970 fatal_error("Unable to re-find array argument in Glk call.");
971 if (arref->addr != addr || arref->len != len)
972 fatal_error("Mismatched array argument in Glk call.");
974 if (arref->retained) {
982 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
992 gidispatch_rock_t glulxe_retained_register(void *array,
993 glui32 len, char *typecode)
995 gidispatch_rock_t rock;
996 arrayref_t *arref = NULL;
999 if (typecode[4] != 'I' || array == NULL) {
1000 /* We only retain integer arrays. */
1005 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1006 if ((*aptr)->array == array)
1011 fatal_error("Unable to re-find array argument in Glk call.");
1012 if (arref->elemsize != 4 || arref->len != len)
1013 fatal_error("Mismatched array argument in Glk call.");
1015 arref->retained = TRUE;
1021 void glulxe_retained_unregister(void *array, glui32 len,
1022 char *typecode, gidispatch_rock_t objrock)
1024 arrayref_t *arref = NULL;
1026 glui32 ix, addr2, val;
1028 if (typecode[4] != 'I' || array == NULL) {
1029 /* We only retain integer arrays. */
1033 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1034 if ((*aptr)->array == array)
1039 fatal_error("Unable to re-find array argument in Glk call.");
1040 if (arref != objrock.ptr)
1041 fatal_error("Mismatched array reference in Glk call.");
1042 if (!arref->retained)
1043 fatal_error("Unretained array reference in Glk call.");
1044 if (arref->elemsize != 4 || arref->len != len)
1045 fatal_error("Mismatched array argument in Glk call.");
1047 *aptr = arref->next;
1050 for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) {
1051 val = ((glui32 *)array)[ix];