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];