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 /* This test checks for a giant array length, which is
472 deprecated. It displays a warning and cuts it down to
473 something reasonable. Future releases of this interpreter
474 may remove this test and go on to verify_array_addresses(),
475 which treats this case as a fatal error. */
476 if (varglist[ix+1] > endmem
477 || varglist[ix]+varglist[ix+1] > endmem) {
478 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix+1]);
479 varglist[ix+1] = endmem - varglist[ix];
481 verify_array_addresses(varglist[ix], varglist[ix+1], 1);
482 garglist[gargnum].array = AddressOfArray(varglist[ix]);
485 garglist[gargnum].uint = varglist[ix];
490 /* See comment above. */
491 if (varglist[ix+1] > endmem/4
492 || varglist[ix+1] > (endmem-varglist[ix])/4) {
493 nonfatal_warning_i("Memory access was much too long -- perhaps a print_to_array call with only one argument", varglist[ix+1]);
494 varglist[ix+1] = (endmem - varglist[ix]) / 4;
496 verify_array_addresses(varglist[ix], varglist[ix+1], 4);
497 garglist[gargnum].array = CaptureIArray(varglist[ix], varglist[ix+1], passin);
500 garglist[gargnum].uint = varglist[ix];
505 fatal_error("Illegal format string.");
510 /* a plain value or a reference to one. */
515 else if (depth > 0) {
516 /* Definitely not isref or isarray. */
518 thisval = ReadStructField(subaddress, ix);
524 thisval = ReadMemory(varglist[ix]);
529 thisval = varglist[ix];
535 garglist[gargnum].uint = (glui32)(thisval);
537 garglist[gargnum].sint = (glsi32)(thisval);
539 fatal_error("Illegal format string.");
545 opref = classes_get(*cx-'a', thisval);
547 fatal_error("Reference to nonexistent Glk object.");
553 garglist[gargnum].opaqueref = opref;
559 garglist[gargnum].uch = (unsigned char)(thisval);
561 garglist[gargnum].sch = (signed char)(thisval);
563 garglist[gargnum].ch = (char)(thisval);
565 fatal_error("Illegal format string.");
570 garglist[gargnum].charstr = DecodeVMString(thisval);
573 #ifdef GLK_MODULE_UNICODE
575 garglist[gargnum].unicharstr = DecodeVMUstring(thisval);
578 #endif /* GLK_MODULE_UNICODE */
580 fatal_error("Illegal format string.");
586 /* We got a null reference, so we have to skip the format element. */
587 if (typeclass == '[') {
588 int numsubwanted, refdepth;
590 while (*cx >= '0' && *cx <= '9') {
591 numsubwanted = 10 * numsubwanted + (*cx - '0');
595 while (refdepth > 0) {
603 else if (typeclass == 'S' || typeclass == 'U') {
614 fatal_error("Illegal format string.");
618 if (*cx != ':' && *cx != '\0')
619 fatal_error("Illegal format string.");
623 *argnumptr = gargnum;
626 /* unparse_glk_args():
627 This is about the reverse of parse_glk_args().
629 static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
630 int *argnumptr, glui32 subaddress, int subpassout)
634 int gargnum, numwanted;
636 gluniversal_t *garglist;
639 garglist = splot->garglist;
640 varglist = splot->varglist;
641 gargnum = *argnumptr;
645 while (*cx >= '0' && *cx <= '9') {
646 numwanted = 10 * numwanted + (*cx - '0');
650 for (argx = 0, ix = 0; argx < numwanted; argx++, ix++) {
653 int isref, passin, passout, nullok, isarray, isretained, isreturn;
654 cx = read_prefix(cx, &isref, &isarray, &passin, &passout, &nullok,
655 &isretained, &isreturn);
662 if (!isreturn && varglist[ix] == 0) {
664 fatal_error("Zero passed invalidly to Glk function.");
665 garglist[gargnum].ptrflag = FALSE;
670 garglist[gargnum].ptrflag = TRUE;
677 if (typeclass == '[') {
679 unparse_glk_args(splot, &cx, depth+1, &gargnum, varglist[ix], passout);
683 /* definitely isref */
693 ReleaseIArray(garglist[gargnum].array, varglist[ix], varglist[ix+1], passout);
700 fatal_error("Illegal format string.");
705 /* a plain value or a reference to one. */
707 if (isreturn || (depth > 0 && subpassout) || (isref && passout)) {
718 thisval = (glui32)garglist[gargnum].uint;
720 thisval = (glui32)garglist[gargnum].sint;
722 fatal_error("Illegal format string.");
729 opref = garglist[gargnum].opaqueref;
731 gidispatch_rock_t objrock =
732 gidispatch_get_objrock(opref, *cx-'a');
733 thisval = ((classref_t *)objrock.ptr)->id;
745 thisval = (glui32)garglist[gargnum].uch;
747 thisval = (glui32)garglist[gargnum].sch;
749 thisval = (glui32)garglist[gargnum].ch;
751 fatal_error("Illegal format string.");
757 if (garglist[gargnum].charstr)
758 ReleaseVMString(garglist[gargnum].charstr);
761 #ifdef GLK_MODULE_UNICODE
763 if (garglist[gargnum].unicharstr)
764 ReleaseVMUstring(garglist[gargnum].unicharstr);
767 #endif /* GLK_MODULE_UNICODE */
769 fatal_error("Illegal format string.");
774 *(splot->retval) = thisval;
776 else if (depth > 0) {
777 /* Definitely not isref or isarray. */
779 WriteStructField(subaddress, ix, thisval);
783 WriteMemory(varglist[ix], thisval);
788 /* We got a null reference, so we have to skip the format element. */
789 if (typeclass == '[') {
790 int numsubwanted, refdepth;
792 while (*cx >= '0' && *cx <= '9') {
793 numsubwanted = 10 * numsubwanted + (*cx - '0');
797 while (refdepth > 0) {
805 else if (typeclass == 'S' || typeclass == 'U') {
816 fatal_error("Illegal format string.");
820 if (*cx != ':' && *cx != '\0')
821 fatal_error("Illegal format string.");
825 *argnumptr = gargnum;
828 /* find_stream_by_id():
829 This is used by some interpreter code which has to, well, find a Glk
832 strid_t find_stream_by_id(glui32 objid)
837 /* Recall that class 1 ("b") is streams. */
838 return classes_get(1, objid);
841 /* Build a hash table to hold a set of Glk objects. */
842 static classtable_t *new_classtable(glui32 firstid)
845 classtable_t *ctab = (classtable_t *)glulx_malloc(sizeof(classtable_t));
849 for (ix=0; ix<CLASSHASH_SIZE; ix++)
850 ctab->bucket[ix] = NULL;
852 ctab->lastid = firstid;
857 /* Find a Glk object in the appropriate hash table. */
858 static void *classes_get(int classid, glui32 objid)
862 if (classid < 0 || classid >= num_classes)
864 ctab = classes[classid];
865 cref = ctab->bucket[objid % CLASSHASH_SIZE];
866 for (; cref; cref = cref->next) {
867 if (cref->id == objid)
873 /* Put a Glk object in the appropriate hash table. */
874 static classref_t *classes_put(int classid, void *obj)
879 if (classid < 0 || classid >= num_classes)
881 ctab = classes[classid];
882 cref = (classref_t *)glulx_malloc(sizeof(classref_t));
886 cref->id = ctab->lastid;
888 bucknum = cref->id % CLASSHASH_SIZE;
889 cref->bucknum = bucknum;
890 cref->next = ctab->bucket[bucknum];
891 ctab->bucket[bucknum] = cref;
895 /* Delete a Glk object from the appropriate hash table. */
896 static void classes_remove(int classid, void *obj)
901 gidispatch_rock_t objrock;
902 if (classid < 0 || classid >= num_classes)
904 ctab = classes[classid];
905 objrock = gidispatch_get_objrock(obj, classid);
909 crefp = &(ctab->bucket[cref->bucknum]);
910 for (; *crefp; crefp = &((*crefp)->next)) {
911 if ((*crefp) == cref) {
914 nonfatal_warning("attempt to free NULL object!");
926 /* The object registration/unregistration callbacks that the library calls
927 to keep the hash tables up to date. */
929 static gidispatch_rock_t glulxe_classtable_register(void *obj,
933 gidispatch_rock_t objrock;
934 cref = classes_put(objclass, obj);
939 static void glulxe_classtable_unregister(void *obj, glui32 objclass,
940 gidispatch_rock_t objrock)
942 classes_remove(objclass, obj);
945 static glui32 *grab_temp_array(glui32 addr, glui32 len, int passin)
947 arrayref_t *arref = NULL;
952 arr = (glui32 *)glulx_malloc(len * sizeof(glui32));
953 arref = (arrayref_t *)glulx_malloc(sizeof(arrayref_t));
955 fatal_error("Unable to allocate space for array argument to Glk call.");
960 arref->retained = FALSE;
962 arref->next = arrays;
966 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
967 arr[ix] = Mem4(addr2);
975 static void release_temp_array(glui32 *arr, glui32 addr, glui32 len, int passout)
977 arrayref_t *arref = NULL;
979 glui32 ix, val, addr2;
982 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
983 if ((*aptr)->array == arr)
988 fatal_error("Unable to re-find array argument in Glk call.");
989 if (arref->addr != addr || arref->len != len)
990 fatal_error("Mismatched array argument in Glk call.");
992 if (arref->retained) {
1000 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
1010 gidispatch_rock_t glulxe_retained_register(void *array,
1011 glui32 len, char *typecode)
1013 gidispatch_rock_t rock;
1014 arrayref_t *arref = NULL;
1017 if (typecode[4] != 'I' || array == NULL) {
1018 /* We only retain integer arrays. */
1023 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1024 if ((*aptr)->array == array)
1029 fatal_error("Unable to re-find array argument in Glk call.");
1030 if (arref->elemsize != 4 || arref->len != len)
1031 fatal_error("Mismatched array argument in Glk call.");
1033 arref->retained = TRUE;
1039 void glulxe_retained_unregister(void *array, glui32 len,
1040 char *typecode, gidispatch_rock_t objrock)
1042 arrayref_t *arref = NULL;
1044 glui32 ix, addr2, val;
1046 if (typecode[4] != 'I' || array == NULL) {
1047 /* We only retain integer arrays. */
1051 for (aptr=(&arrays); (*aptr); aptr=(&((*aptr)->next))) {
1052 if ((*aptr)->array == array)
1057 fatal_error("Unable to re-find array argument in Glk call.");
1058 if (arref != objrock.ptr)
1059 fatal_error("Mismatched array reference in Glk call.");
1060 if (!arref->retained)
1061 fatal_error("Unretained array reference in Glk call.");
1062 if (arref->elemsize != 4 || arref->len != len)
1063 fatal_error("Mismatched array argument in Glk call.");
1065 *aptr = arref->next;
1068 for (ix=0, addr2=arref->addr; ix<arref->len; ix++, addr2+=4) {
1069 val = ((glui32 *)array)[ix];