ZMachine contents
Summary.
This segment closely parallels Glulx.i6t, which provides exactly equivalent functionality (indeed, usually the same-named functions and in the same order) for the Glulx VM. This is intended to make the rest of the template code independent of the choice of VM, although that is more of an ideal than a reality, because there are so many fiddly differences in some of the grammar and dictionary tables that it is not really practical for the parser (for instance) to call VM-neutral routines to get the data it wants out of these arrays.
Variables and Arrays.
20Global top_object;
21Global xcommsdir;
22Global transcript_mode;
23
24Constant INPUT_BUFFER_LEN = 120;
25
26Array buffer -> 123;
27Array buffer2 -> 123;
28Array buffer3 -> 123;
29Array parse buffer 63;
30Array parse2 buffer 63;
31
32Global dict_start;
33Global dict_entry_size;
34Global dict_end;
Starting Up.
VM_Initialise() is almost the first routine called, except that the "starting the virtual machine" activity is allowed to go first.
41[ VM_Initialise i;
42 standard_interpreter = HDR_TERPSTANDARD-->0;
43 transcript_mode = ((HDR_GAMEFLAGS-->0) & 1);
44
45 dict_start = HDR_DICTIONARY-->0;
46 dict_entry_size = dict_start->(dict_start->0 + 1);
47 dict_start = dict_start + dict_start->0 + 4;
48 dict_end = dict_start + ((dict_start - 2)-->0) * dict_entry_size;
49
50 buffer->0 = INPUT_BUFFER_LEN;
51 buffer2->0 = INPUT_BUFFER_LEN;
52 buffer3->0 = INPUT_BUFFER_LEN;
53 parse->0 = 15;
54 parse2->0 = 15;
55
56 top_object = #largest_object-255;
57
58 #ifdef FIX_RNG;
59 @random 10000 -> i;
60 i = -i-2000;
61 print "[Random number generator seed is ", i, "]^";
62 @random i -> i;
63 #endif;
64];
Enable Acceleration.
This rule enables use of March 2009 extension to Glulx which optimises the speed of Inform-compiled story files, so for the Z-machine it has no effect.
71[ ENABLE_GLULX_ACCEL_R;
72 rfalse;
73];
Release Number.
Like all software, IF story files have release numbers to mark revised versions being circulated: unlike most software, and partly for traditional reasons, the version number is recorded not in some print statement or variable but is branded on, so to speak, in a specific memory location of the story file header.
VM_Describe_Release() describes the release and is used as part of the "banner", IF's equivalent to a title page.
86[ VM_Describe_Release i;
87 print "Release ", (HDR_GAMERELEASE-->0) & $03ff, " / Serial number ";
88 for (i=0 : i<6 : i++) print (char) HDR_GAMESERIAL->i;
89];
The VM must provide three routines for keyboard input:
(a) VM_KeyChar() waits for a key to be pressed and then returns the character chosen as a ZSCII character. (b) VM_KeyDelay(N) waits up to N/10 seconds for a key to be pressed, returning the ZSCII character if so, or 0 if not. (c) VM_ReadKeyboard(b, t) reads a whole newline-terminated command into the buffer b, then parses it into a word stream in the table t.
There are elaborations to due with mouse clicks, but this isn't the place to document all of that.
105[ VM_KeyChar win key;
106 if (win) @set_window win;
107 @read_char 1 -> key;
108 return key;
109];
110
111[ VM_KeyDelay tenths key;
112 @read_char 1 tenths VM_KeyDelay_Interrupt -> key;
113 return key;
114];
115[ VM_KeyDelay_Interrupt; rtrue; ];
116
117[ VM_ReadKeyboard a_buffer a_table i;
118 read a_buffer a_table;
119 #ifdef ECHO_COMMANDS;
120 print "** ";
121 for (i=2: i<=(a_buffer->1)+1: i++) print (char) a_buffer->i;
122 print "^";
123 #ifnot;
124 i=0;
125 #endif;
126
127 #Iftrue (#version_number == 6);
128 @output_stream -1;
129 @loadb a_buffer 1 -> sp;
130 @add a_buffer 2 -> sp;
131 @print_table sp sp;
132 new_line;
133 @output_stream 1;
134 #Endif;
135];
Buffer Functions.
A "buffer", in this sense, is an array containing a stream of characters typed from the keyboard; a "parse buffer" is an array which resolves this into individual words, pointing to the relevant entries in the dictionary structure. Because each VM has its own format for each of these arrays (not to mention the dictionary), we have to provide some standard operations needed by the rest of the template as routines for each VM.
The Z-machine buffer and parse buffer formats are documented in the DM4.
VM_CopyBuffer(to, from) copies one buffer into another.
VM_Tokenise(buff, parse_buff) takes the text in the buffer buff and produces the corresponding data in the parse buffer parse_buff – this is called tokenisation since the characters are divided into words: in traditional computing jargon, such clumps of characters treated syntactically as units are called tokens.
LTI_Insert is documented in the DM4 and the LTI prefix stands for "Language To Informese": it's used only by translations into non-English languages of play, and is not called in the template.
160[ VM_CopyBuffer bto bfrom i;
161 for (i=0: i<INPUT_BUFFER_LEN: i++) bto->i = bfrom->i;
162];
163
164[ VM_PrintToBuffer buf len a b c;
165 @output_stream 3 buf;
166 switch (metaclass(a)) {
167 String: print (string) a;
168 Routine: a(b, c);
169 Object, Class: if (b) PrintOrRun(a, b, true); else print (name) a;
170 }
171 @output_stream -3;
172 if (buf-->0 > len) print "Error: Overflow in VM_PrintToBuffer.^";
173 return buf-->0;
174];
175
176[ VM_Tokenise b p; b->(2 + b->1) = 0; @tokenise b p; ];
177
178[ LTI_Insert i ch b y;
179
180
181 b = buffer;
182
183
184
185 y = b->1;
186 if (y > b->0) y = b->0;
187
188
189 for (y=y+2 : y>i : y--) b->y = b->(y-1);
190 b->i = ch;
191
192
193 if (b->1 < b->0) (b->1)++;
194];
Dictionary Functions.
Again, the dictionary structure is differently arranged on the different VMs. This is a data structure containing, in compressed form, the text of all the words to be recognised by tokenisation (above). In I6 for Z, a dictionary word value is represented at run-time by its record number in the dictionary, 0, 1, 2, ..., in alphabetical order.
VM_InvalidDictionaryAddress(A) tests whether A is a valid record address in the dictionary data structure.
VM_DictionaryAddressToNumber(A) and VM_NumberToDictionaryAddress(N) convert between record numbers and dictionary addresses.
210[ VM_InvalidDictionaryAddress addr;
211 if ((UnsignedCompare(addr, dict_start) < 0) ||
212 (UnsignedCompare(addr, dict_end) >= 0) ||
213 ((addr - dict_start) % dict_entry_size ~= 0)) rtrue;
214 rfalse;
215];
216
217[ VM_DictionaryAddressToNumber w; return (w-(HDR_DICTIONARY-->0 + 7))/9; ];
218[ VM_NumberToDictionaryAddress n; return HDR_DICTIONARY-->0 + 7 + 9*n; ];
Command Tables.
The VM is also generated containing a data structure for the grammar produced by I6's Verb and Extend directives: this is essentially a list of command verbs such as DROP or PUSH, together with a list of synonyms, and then the grammar for the subsequent commands to be recognised by the parser.
228[ VM_CommandTableAddress i;
229 return (HDR_STATICMEMORY-->0)-->i;
230];
231
232[ VM_PrintCommandWords i da j;
233 da = HDR_DICTIONARY-->0;
234 for (j=0 : j<(da+5)-->0 : j++)
235 if (da->(j*9 + 14) == $ff-i)
236 print "", (address) VM_NumberToDictionaryAddress(j), " ";
237];
SHOWVERB support.
Further VM-specific tables cover actions and attributes, and these are used by the SHOWVERB testing command.
244#Ifdef DEBUG;
245[ DebugAction a anames;
246 if (a >= 4096) { print "<fake action ", a-4096, ">"; return; }
247 anames = #identifiers_table;
248 anames = anames + 2*(anames-->0) + 2*48;
249 print (string) anames-->a;
250];
251
252[ DebugAttribute a anames;
253 if (a < 0 || a >= 48) print "<invalid attribute ", a, ">";
254 else {
255 anames = #identifiers_table; anames = anames + 2*(anames-->0);
256 print (string) anames-->a;
257 }
258];
259#Endif;
RNG.
No routine is needed for extracting a random number, since I6's built-in random function does that, but it's useful to abstract the process of seeding the RNG so that it produces a repeatable sequence of "random" numbers from here on: the necessary opcodes are different for the two VMs.
268[ VM_Seed_RNG n;
269 if (n > 0) n = -n;
270 @random n -> n;
271];
Memory Allocation.
This is dynamic memory allocation: something which is never practicable in the Z-machine, because the whole address range is already claimed, but which is viable on recent revisions of Glulx.
279[ VM_AllocateMemory amount;
280 return 0;
281];
282
283[ VM_FreeMemory address;
284];
Audiovisual Resources.
The Z-machine only barely supports figures and sound effects. I7 only allows us to use them for Version 6 of the Z-machine, even though sound effects have a longer pedigree and Infocom used them on some version 5 and even some version 3 works: really, though, from an I7 point of view we would prefer that anyone needing figures and sounds use Glulx instead.
294[ VM_Picture resource_ID;
295 #IFTRUE #version_number == 6;
296 @draw_picture resource_ID;
297 #ENDIF;
298];
299
300[ VM_SoundEffect resource_ID;
301 #IFTRUE #version_number == 6;
302 @sound_effect resource_ID;
303 #ENDIF;
304];
Typography.
Relatively few typographic effects are available on the Z-machine, so that many of the semantic markups for text which would be distinguishable on Glulx are indistinguishable here.
312[ VM_Style sty;
313 switch (sty) {
314 NORMAL_VMSTY, NOTE_VMSTY: style roman;
315 HEADER_VMSTY, SUBHEADER_VMSTY, ALERT_VMSTY: style bold;
316 }
317];
Character Casing.
The following are the equivalent of tolower and toupper, the traditional C library functions for forcing letters into lower and upper case form, for the ZSCII character set.
325[ VM_UpperToLowerCase c;
326 switch (c) {
327 'A' to 'Z': c = c + 32;
328 202, 204, 212, 214, 221: c--;
329 217, 218: c = c - 2;
330 158 to 160, 167 to 168, 208 to 210: c = c - 3;
331 186 to 190, 196 to 200: c = c - 5 ;
332 175 to 180: c = c - 6;
333 }
334 return c;
335];
336
337[ VM_LowerToUpperCase c;
338 switch (c) {
339 'a' to 'z': c = c - 32;
340 201, 203, 211, 213, 220: c++;
341 215, 216: c = c + 2;
342 155 to 157, 164 to 165, 205 to 207: c = c + 3;
343 181 to 185, 191 to 195: c = c + 5 ;
344 169 to 174: c = c + 6;
345 }
346 return c;
347];
The Screen.
Our generic screen model is that the screen is made up of windows: we tend to refer only to two of these, the main window and the status line, but others may also exist from time to time. Windows have unique ID numbers: the special window ID -1 means "all windows" or "the entire screen", which usually amounts to the same thing.
Screen height and width are measured in characters, with respect to the fixed-pitch font used for the status line. The main window normally contains variable-pitch text which may even have been kerned, and character dimensions make little sense there.
Clearing all windows (WIN_ALL here) has the side-effect of collapsing the status line, so we need to ensure that statuswin_cursize is reduced to 0, in order to keep it accurate.
366[ VM_ClearScreen window;
367 switch (window) {
368 WIN_ALL: @erase_window -1; statuswin_cursize = 0;
369 WIN_STATUS: @erase_window 1;
370 WIN_MAIN: @erase_window 0;
371 }
372];
373
374#Iftrue (#version_number == 6);
375[ VM_ScreenWidth width charw;
376 @get_wind_prop 1 3 -> width;
377 @get_wind_prop 1 13 -> charw;
378 charw = charw & $FF;
379 return (width+charw-1) / charw;
380];
381#Ifnot;
382[ VM_ScreenWidth; return (HDR_SCREENWCHARS->0); ];
383#Endif;
384
385[ VM_ScreenHeight; return (HDR_SCREENHLINES->0); ];
Window Colours.
Each window can have its own foreground and background colours.
The colour of individual letters or words of type is not controllable in Glulx, to the frustration of many, and so the template layer of I7 has no framework for handling this (even though it is controllable on the Z-machine, which is greatly superior in this respect).
396[ VM_SetWindowColours f b window;
397 if (clr_on && f && b) {
398 if (window == 0) {
399 clr_fgstatus = b;
400 clr_bgstatus = f;
401 }
402 if (window == 1) {
403 clr_fgstatus = f;
404 clr_bgstatus = b;
405 }
406 if (window == 0 or 2) {
407 clr_fg = f;
408 clr_bg = b;
409 }
410 if (statuswin_current)
411 @set_colour clr_fgstatus clr_bgstatus;
412 else
413 @set_colour clr_fg clr_bg;
414 }
415];
416
417[ VM_RestoreWindowColours;
418 if (clr_on) {
419 VM_SetWindowColours(clr_fg, clr_bg, 2);
420 VM_SetWindowColours(clr_fgstatus, clr_bgstatus, 1, true);
421 VM_ClearScreen();
422 }
423 #Iftrue (#version_number == 6);
424 (0-->8) = (0-->8) | $$00000100;
425 #Endif;
426];
Main Window.
The part of the screen on which commands and responses are printed, which ordinarily occupies almost all of the screen area.
VM_MainWindow() switches printing back from another window, usually the status line, to the main window. Note that the Z-machine implementation emulates the Glulx model of window rather than text colours.
437[ VM_MainWindow;
438 if (statuswin_current) {
439 if (clr_on && clr_bgstatus > 1) @set_colour clr_fg clr_bg;
440 else style roman;
441 @set_window 0;
442 }
443 statuswin_current = false;
444];
Status Line.
Despite the name, the status line need not be a single line at the top of the screen: that's only the conventional default arrangement. It can expand to become the equivalent of an old-fashioned VT220 terminal, with menus and grids and mazes displayed lovingly in character graphics, or it can close up to invisibility.
VM_StatusLineHeight(n) sets the status line to have a height of n lines of type. (The width of the status line is always the width of the whole screen, and the position is always at the top, so the height is the only controllable aspect.) The n=0 case makes the status line disappear.
VM_MoveCursorInStatusLine(x, y) switches printing to the status line, positioning the "cursor" – the position at which printing will begin – at the given character grid position (x, y). Line 1 represents the top line; line 2 is underneath, and so on; columns are similarly numbered from 1 at the left.
465[ VM_MoveCursorInStatusLine line column;
466 if (~~statuswin_current) {
467 @set_window 1;
468 if (clr_on && clr_bgstatus > 1) @set_colour clr_fgstatus clr_bgstatus;
469 else style reverse;
470 }
471 if (line == 0) {
472 line = 1;
473 column = 1;
474 }
475 #Iftrue (#version_number == 6);
476 Z6_MoveCursor(line, column);
477 #Ifnot;
478 @set_cursor line column;
479 #Endif;
480 statuswin_current = true;
481];
482
483#Iftrue (#version_number == 6);
484[ Z6_MoveCursor line column charw charh;
485 @get_wind_prop 1 13 -> charw;
486 @log_shift charw $FFF8 -> charh;
487 charw = charw / $100;
488 line = 1 + charh*(line-1);
489 column = 1 + charw*(column-1);
490 @set_cursor line column;
491];
492#Endif;
493
494#Iftrue (#version_number == 6);
495[ VM_StatusLineHeight height wx wy x y charh;
496
497
498
499
500
501 @get_wind_prop 0 0 -> wy; @get_wind_prop 0 1 -> wx;
502 @get_wind_prop 0 13 -> charh; @log_shift charh $FFF8 -> charh;
503 @get_wind_prop 0 4 -> y; @get_wind_prop 0 5 -> x;
504 height = height * charh;
505 @split_window height;
506 y = y - height + wy - 1;
507 if (y < 1) y = 1;
508 x = x + wx - 1;
509 @set_cursor y x 0;
510 statuswin_cursize = height;
511];
512#Ifnot;
513[ VM_StatusLineHeight height;
514 if (statuswin_cursize ~= height)
515 @split_window height;
516 statuswin_cursize = height;
517];
518#Endif;
519
520#Iftrue (#version_number == 6);
521[ Z6_DrawStatusLine width x charw scw;
522 (0-->8) = (0-->8) &~ $$00000100;
523 @push say__p; @push say__pc;
524 BeginActivity(CONSTRUCTING_STATUS_LINE_ACT);
525 VM_StatusLineHeight(statuswin_size);
526
527
528
529
530 VM_MoveCursorInStatusLine(1, 1);
531 @set_font 4 -> x;
532 width = VM_ScreenWidth();
533 spaces width;
534 ClearParagraphing(8);
535 if (ForActivity(CONSTRUCTING_STATUS_LINE_ACT) == false) {
536
537
538
539 VM_MoveCursorInStatusLine(1, 2);
540 @set_font 1 -> x;
541 TEXT_TY_Say(left_hand_status_line);
542 @get_wind_prop 1 3 -> width;
543 @get_wind_prop 1 13 -> charw;
544 charw = charw & $FF;
545 @output_stream 3 StorageForShortName;
546 TEXT_TY_Say(right_hand_status_line);
547 @output_stream -3; scw = HDR_PIXELSTO3-->0 + charw;
548 x = 1+width-scw;
549 @set_cursor 1 x; TEXT_TY_Say(right_hand_status_line);
550 }
551
552 VM_MainWindow();
553 ClearParagraphing(8);
554 EndActivity(CONSTRUCTING_STATUS_LINE_ACT);
555 @pull say__pc; @pull say__p;
556];
557#Endif;
Quotation Boxes.
No routine is needed to produce quotation boxes: the I6 box statement generates the necessary Z-machine opcodes all by itself.
Undo.
These simply wrap the relevant opcodes.
567[ VM_Undo result_code;
568 @restore_undo result_code;
569 return result_code;
570];
571
572[ VM_Save_Undo result_code;
573 @save_undo result_code;
574 return result_code;
575];
580[ QUIT_THE_GAME_R;
581 if (actor ~= player) rfalse;
582 QUIT_THE_GAME_RM('A');
583 if (YesOrNo()~=0) quit;
584];
589[ RESTART_THE_GAME_R;
590 if (actor ~= player) rfalse;
591 RESTART_THE_GAME_RM('A');
592 if (YesOrNo()~=0) {
593 @restart;
594 RESTART_THE_GAME_RM('B'); new_line;
595 }
596];
601[ RESTORE_THE_GAME_R;
602 if (actor ~= player) rfalse;
603 restore Rmaybe;
604 RESTORE_THE_GAME_RM('A'); new_line;
605 rtrue;
606 .RMaybe; RESTORE_THE_GAME_RM('B'); new_line;
607];
612[ SAVE_THE_GAME_R flag;
613 if (actor ~= player) rfalse;
614 #IFV5;
615 @save -> flag;
616 switch (flag) {
617 0: SAVE_THE_GAME_RM('A'); new_line;
618 1: SAVE_THE_GAME_RM('B'); new_line;
619 2: RESTORE_THE_GAME_RM('B'); new_line;
620 }
621 #IFNOT;
622 save Smaybe;
623 SAVE_THE_GAME_RM('A'); new_line; rtrue;
624 .SMaybe; SAVE_THE_GAME_RM('B'); new_line;
625 #ENDIF;
626];
Verify The Story File Rule.
This is a fossil now, really, but in the days of Infocom, the 110K story file occupying an entire disc was a huge data set: floppy discs were by no means a reliable medium, and cheap hardware often used hit-and-miss components, as on the notorious Commodore 64 disc controller. If somebody experienced an apparent bug in play, it could easily be that he had a corrupt disc or was unable to read data of that density. So the VERIFY command, which took up to ten minutes on some early computers, would chug through the entire story file and compute a checksum, compare it against a known result in the header, and determine that the story file could or could not properly be read. The Z-machine provided this service as an opcode, and so Glulx followed suit.
642[ VERIFY_THE_STORY_FILE_R;
643 if (actor ~= player) rfalse;
644 @verify ?Vmaybe;
645 jump Vwrong;
646 .Vmaybe; VERIFY_THE_STORY_FILE_RM('A'); new_line; rtrue;
647 .Vwrong;
648 VERIFY_THE_STORY_FILE_RM('B'); new_line;
649];
Switch Transcript On Rule.
654[ SWITCH_TRANSCRIPT_ON_R;
655 if (actor ~= player) rfalse;
656 transcript_mode = ((0-->8) & 1);
657 if (transcript_mode) { SWITCH_TRANSCRIPT_ON_RM('A'); new_line; rtrue; }
658 @output_stream 2;
659 if (((0-->8) & 1) == 0) { SWITCH_TRANSCRIPT_ON_RM('C'); new_line; rtrue; }
660 SWITCH_TRANSCRIPT_ON_RM('B'); new_line; VersionSub();
661 transcript_mode = true;
662];
Switch Transcript Off Rule.
667[ SWITCH_TRANSCRIPT_OFF_R;
668 if (actor ~= player) rfalse;
669 transcript_mode = ((0-->8) & 1);
670 if (transcript_mode == false) { SWITCH_TRANSCRIPT_OFF_RM('A'); new_line; rtrue; }
671 SWITCH_TRANSCRIPT_OFF_RM('B'); new_line;
672 @output_stream -2;
673 if ((0-->8) & 1) { SWITCH_TRANSCRIPT_ON_RM('C'); new_line; rtrue; }
674 transcript_mode = false;
675];
Announce Story File Version Rule.
680[ ANNOUNCE_STORY_FILE_VERSION_R ix;
681 if (actor ~= player) rfalse;
682 Banner();
683 print "Identification number: ";
684 for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix;
685 print "^";
686 ix = 0;
687 if (standard_interpreter > 0) {
688 print "Standard interpreter ",
689 standard_interpreter/256, ".", standard_interpreter%256,
690 " (", HDR_TERPNUMBER->0;
691 #Iftrue (#version_number == 6);
692 print (char) '.', HDR_TERPVERSION->0;
693 #Ifnot;
694 print (char) HDR_TERPVERSION->0;
695 #Endif;
696 print ") / ";
697 } else {
698 print "Interpreter ", HDR_TERPNUMBER->0, " Version ";
699 #Iftrue (#version_number == 6);
700 print HDR_TERPVERSION->0;
701 #Ifnot;
702 print (char) HDR_TERPVERSION->0;
703 #Endif;
704 print " / ";
705 }
706 print "Library serial number ", (string) LibSerial, "^";
707 #Ifdef LanguageVersion;
708 print (string) LanguageVersion, "^";
709 #Endif;
710 #ifdef ShowExtensionVersions;
711 ShowExtensionVersions();
712 #endif;
713 say__p = 1;
714];
Descend To Specific Action Rule.
There are 100 or so actions, typically, and this rule is for efficiency's sake: rather than perform 100 or so comparisons to see which routine to call, we indirect through a jump table. The routines called are the -Sub routines: thus, for instance, if action is ##Wait then WaitSub is called. It is essential that this routine not be called for fake actions: in I7 use this is guaranteed, since fake actions are not allowed into the action machinery at all.
Strangely, Glulx's action routines table is numbered in an off-by-one way compared to the Z-machine's: hence the +1.
729[ DESCEND_TO_SPECIFIC_ACTION_R;
730 indirect(#actions_table-->action);
731 rtrue;
732];
737[ OC__Cl obj cla j a n objflag;
738
739 @jl obj 1 ?NotObj;
740 @jg obj max_z_object ?NotObj;
741 @inc objflag;
742 @je cla K1_room ?~NotRoom;
743 @test_attr obj mark_as_room ?rtrue;
744 @rfalse;
745 .NotRoom;
746 @je cla K2_thing ?~NotObj;
747 @test_attr obj mark_as_thing ?rtrue;
748 @rfalse;
749 .NotObj;
750
751 @je cla Object Class ?ObjOrClass;
752 @je cla Routine String ?RoutOrStr;
753
754 @jin cla 1 ?~Mistake;
755
756 @jz objflag ?rfalse;
757 @get_prop_addr obj 2 -> a;
758 @jz a ?rfalse;
759 @get_prop_len a -> n;
760
761 @div n 2 -> n;
762 .Loop;
763 @loadw a j -> sp;
764 @je sp cla ?rtrue;
765 @inc j;
766 @jl j n ?Loop;
767 @rfalse;
768
769 .ObjOrClass;
770 @jz objflag ?rfalse;
771 @je cla Object ?JustObj;
772
773
774 @jg obj String ?~rtrue;
775 @jin obj Class ?rtrue;
776 @rfalse;
777
778 .JustObj;
779
780 @jg obj String ?~rfalse;
781 @jin obj Class ?rfalse;
782 @rtrue;
783
784 .RoutOrStr;
785 @jz objflag ?~rfalse;
786 @call_2s Z__Region obj -> sp;
787 @inc sp;
788 @je sp cla ?rtrue;
789 @rfalse;
790
791 .Mistake;
792 RT__Err("apply ofclass for", cla, -1);
793 rfalse;
794];
795
796[ Unsigned__Compare x y u v;
797 @je x y ?rfalse;
798 @jl x 0 ?XNegative;
799
800 @jl y 0 ?XPosYNeg;
801
802
803
804 @jg x y ?rtrue;
805 @ret -1;
806
807 .XPosYNeg;
808
809 @ret -1;
810
811 .XNegative;
812 @jl y 0 ?~rtrue;
813
814
815 @jg x y ?rtrue;
816 @ret -1;
817];
818
819[ RT__ChLDW base offset;
820 @loadw base offset -> sp;
821 @ret sp;
822];