1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
// SPDX-License-Identifier: CC-BY-NC-SA-4.0 AND Apache-2.0 WITH LLVM-Exception
// Copyright 2024 Daniel Fox Franke.
// Glulx specification excerpts Copyright 2020-2022 by the Interactive Fiction
// Technology Foundation.
//! Definition of [`Instr`].
//!
//! Keep this definition in its own file separate from any `impl`s, so that it's
//! easy to strip the CC-BY-NC-SA-4.0 stuff if needed.
use crate::operands::{LoadOperand, StoreOperand};
/// Representation of a Glulx instruction.
///
/// **License note**: the rustdoc comments on this enumeration incorporate
/// material from the Glulx VM Specification version 3.1.3, Copyright 2020-2022
/// by the Interactive Fiction Technology Foundation. The specification is
/// licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0
/// International License (SPDX-License-Identifier: CC-BY-NC-SA-4.0). Any
/// redistribution of this crate with this documentation intact must abide by
/// the terms of that license. Although the license is
/// "sharealike/"infectious"/"copyleft", linking against this crate or
/// distributing this crate in binary-only form generally will not bind you to
/// it, since such products generally will not incorporate the documentation and
/// so will not constitute works derived from it. Such activities are permitted
/// subject only to the Apache-2.0 WITH LLVM-Exception license under which this
/// crate's source code is provided. The specification's license does not infect
/// code which merely implements the specification, since, in the spec's own
/// words, "The virtual machine described by this document is an idea, not an
/// expression of an idea, and is therefore not copyrightable. Anyone is free to
/// write programs that run on the Glulx VM or make use of it, including
/// compilers, interpreters, debuggers, and so on".
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Instr<L> {
/// No-op
Nop,
// INTEGER MATH
/// Add L1 and L2, using standard 32-bit addition. Truncate the result to 32
/// bits if necessary. Store the result in S1.
Add(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute `(L1 - L2)`, and store the result in S1.
Sub(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute `(L1 * L2)`, and store the result in S1. Truncate the result to 32
/// bits if necessary.
Mul(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute `(L1 / L2)`, and store the result in S1. This is signed integer
/// division.
Div(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute `(L1 % L2)`, and store the result in S1. This is the remainder
/// from signed integer division.
///
/// In division and remainer, signs are annoying. Rounding is towards zero.
/// The sign of a remainder equals the sign of the dividend. It is always
/// true that `(A / B) * B + (A % B) == A`. Some examples (in decimal):
///
/// ```text
/// 11 / 2 = 5
/// -11 / 2 = -5
/// 11 / -2 = -5
/// -11 / -2 = 5
/// 13 % 5 = 3
/// -13 % 5 = -3
/// 13 % -5 = 3
/// -13 % -5 = -3
/// ```
Mod(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute the negative of L1.
Neg(LoadOperand<L>, StoreOperand<L>),
/// Compute the bitwise AND of L1 and L2.
Bitand(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute the bitwise OR of L1 and L2.
Bitor(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute the bitwise XOR of L1 and L2.
Bitxor(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Compute the bitwise negation of L1.
Bitnot(LoadOperand<L>, StoreOperand<L>),
/// Shift the bits of L1 to the left (towards more significant bits) by L2
/// places. The bottom L2 bits are filled in with zeroes. If L2 is 32 or
/// more, the result is always zero.
Shiftl(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Shift the bits of L1 to the right by L2 places. The top L2 bits are
/// filled in with zeroes. If L2 is 32 or more, the result is always zero.
Ushiftr(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Shift the bits of L1 to the right by L2 places. The top L2 bits are
/// filled in with copies of the top bit of L1. If L2 is 32 or more, the
/// result is always zero or `FFFFFFFF`, depending on the top bit of L1.
Sshiftr(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
// BRANCHING
/// Branch unconditionally to offset L1.
Jump(LoadOperand<L>),
/// If L1 is equal to zero, branch to L2.
Jz(LoadOperand<L>, LoadOperand<L>),
/// If L1 is not equal to zero, branch to L2.
Jnz(LoadOperand<L>, LoadOperand<L>),
/// If L1 is equal to L2, branch to L3.
Jeq(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is not equal to L2, branch to L3.
Jne(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is less than L2, branch to L3. The values are compared as signed
/// 32-bit values.
Jlt(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is less than or equal to L2, branch to L3. The values are compared
/// as signed 32-bit values.
Jle(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is greater than L2, branch to L3. The values are compared as
/// signed 32-bit values.
Jgt(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is greater than or equal to L2, branch to L3. The values are
/// compared as signed 32-bit values.
Jge(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is less than L2, branch to L3. The values are compared as unsigned
/// 32-bit values.
Jltu(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is less than or equal to L2, branch to L3. The values are compared
/// as unsigned 32-bit values.
Jleu(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is greater than L2, branch to L3. The values are compared as
/// unsigned 32-bit values.
Jgtu(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// If L1 is greater than or equal to L2, branch to L3. The values are
/// compared as unsigned 32-bit values.
Jgeu(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Branch unconditionally to address L1. Unlike the other branch opcodes,
/// this takes an absolute address, not an offset. The special cases 0 and 1
/// (for returning) do not apply; `jumpabs 0` would branch to memory address
/// 0, if that were ever a good idea, which it isn't.
Jumpabs(LoadOperand<L>),
// MOVING DATA
/// Read L1 and store it at S1, without change.
Copy(LoadOperand<L>, StoreOperand<L>),
/// Read a 16-bit value from L1 and store it at S1.
Copys(LoadOperand<L>, StoreOperand<L>),
/// Read an 8-bit value from L1 and store it at S1.
Copyb(LoadOperand<L>, StoreOperand<L>),
/// Sign-extend a value, considered as a 16-bit value. If the value's `8000`
/// bit is set, the upper 16 bits are all set; otherwise, the upper 16 bits
/// are all cleared.
Sexs(LoadOperand<L>, StoreOperand<L>),
/// Sign-extend a value, considered as an 8-bit value. If the value's 80 bit
/// is set, the upper 24 bits are all set; otherwise, the upper 24 bits are
/// all cleared.
Sexb(LoadOperand<L>, StoreOperand<L>),
// ARRAY DATA
/// Store L3 into the 32-bit field at main memory address `(L1+4*L2)`.
Astore(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Load a 32-bit value from main memory address `(L1+4*L2)`, and store it in S1.
Aload(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Store L3 into the 16-bit field at main memory address `(L1+4*L2)`.
Astores(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Load a 16-bit value from main memory address `(L1+4*L2)`, and store it in S1.
Aloads(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Store L3 into the 8-bit field at main memory address `(L1+4*L2)`.
Astoreb(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Load a 8-bit value from main memory address `(L1+4*L2)`, and store it in S1.
Aloadb(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Set or clear a single bit. This is bit number `(L2 mod 8)` of memory
/// address `(L1+L2/8)`. It is cleared if L3 is zero, set if nonzero.
Astorebit(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Test a single bit, similarly. If it is set, 1 is stored at S1; if clear, 0 is stored.
Aloadbit(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
// THE STACK
/// Store a count of the number of values on the stack. This counts only
/// values above the current call-frame. In other words, it is always zero
/// when a C1 function starts executing, and `(numargs+1)` when a C0
/// function starts executing. It then increases and decreases thereafter as
/// values are pushed and popped; it is always the number of values that can
/// be popped legally. (If S1 uses the stack push mode, the count is done
/// before the result is pushed.)
Stkcount(StoreOperand<L>),
/// Peek at the L1'th value on the stack, without actually popping anything.
/// If L1 is zero, this is the top value; if one, it's the value below that;
/// etc. L1 must be less than the current stack-count. (If L1 or S1 use the
/// stack pop/push modes, the peek is counted after L1 is popped, but before
/// the result is pushed.)
Stkpeek(LoadOperand<L>, StoreOperand<L>),
/// Swap the top two values on the stack. The current stack-count must be at
/// least two.
Stkswap,
/// Peek at the top L1 values in the stack, and push duplicates onto the
/// stack in the same order. If L1 is zero, nothing happens. L1 must not be
/// greater than the current stack-count. (If L1 uses the stack pop mode,
/// the stkcopy is counted after L1 is popped.)
///
/// An example of stkcopy, starting with six values on the stack:
///
/// ```text
/// 5 4 3 2 1 0 <top>
/// stkcopy 3
/// 5 4 3 2 1 0 2 1 0 <top>
/// ```
Stkcopy(LoadOperand<L>),
/// Rotate the top L1 values on the stack. They are rotated up or down L2
/// places, with positive values meaning up and negative meaning down. The
/// current stack-count must be at least L1. If either L1 or L2 is zero,
/// nothing happens. (If L1 and/or L2 use the stack pop mode, the roll
/// occurs after they are popped.)
///
/// An example of two stkrolls, starting with nine values on the stack:
///
/// ```text
/// 8 7 6 5 4 3 2 1 0 <top>
/// stkroll 5 1
/// 8 7 6 5 0 4 3 2 1 <top>
/// stkroll 9 -3
/// 5 0 4 3 2 1 8 7 6 <top>
/// ```
///
/// Note that stkswap is equivalent to stkroll 2 1, or for that matter
/// stkroll 2 -1. Also, stkcopy 1 is equivalent to stkpeek 0 sp.
///
/// These opcodes can only access the values pushed on the stack above the
/// current call-frame. It is illegal to stkswap, stkpeek, stkcopy, or
/// stkroll values below that – i.e, the locals segment or any previous
/// function call frames.
Stkroll(LoadOperand<L>, LoadOperand<L>),
// FUNCTIONS
/// Call function whose address is L1, passing in L2 arguments, and store
/// the return result at S1.
///
/// The arguments are taken from the stack. Before you execute the call
/// opcode, you must push the arguments on, in backward order (last argument
/// pushed first, first argument topmost on the stack.) The L2 arguments are
/// removed before the new function's call frame is constructed. (If L1, L2,
/// or S1 use the stack pop/push modes, the arguments are taken after L1 or
/// L2 is popped, but before the result is pushed.)
///
/// Recall that all functions in Glulx have a single 32-bit return value. If
/// you do not care about the return value, you can use operand mode 0
/// ("discard value") for operand S1.
Call(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Call function whose address is L1, passing no arguments. Store the
/// return result at S1.
Callf(LoadOperand<L>, StoreOperand<L>),
/// Call function whose address is L1, passing one argument as L2. Store the
/// return result at S1.
Callfi(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Call function whose address is L1, passing two argument as L2/L3. Store
/// the return result at S1.
Callfii(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
),
/// Call function whose address is L1, passing three argument as L2/L3/L4.
/// Store the return result at S1.
Callfiii(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
),
/// Return from the current function, with the given return value. If this
/// is the top-level function, Glulx execution is over.
Return(LoadOperand<L>),
///Call function whose address is L1, passing in L2 arguments, and pass the
///return result out to whoever called the current function.
///
/// This destroys the current call-frame, as if a return had been executed,
/// but does not touch the call stub below that. It then immediately calls
/// L1, creating a new call-frame. The effect is the same as a call
/// immediately followed by a return, but takes less stack space.
///
/// It is legal to use tailcall from the top-level function. L1 becomes the
/// top-level function.
Tailcall(LoadOperand<L>, LoadOperand<L>),
// CONTINUATIONS
/// Generates a "catch token", which can be used to jump back to this
/// execution point from a throw opcode. The token is stored in S1, and then
/// execution branches to offset L1. If execution is proceeding from this
/// point because of a throw, the thrown value is stored instead, and the
/// branch is ignored.
///
/// Remember if the branch value is not 0 or 1, the branch is to to (Addr +
/// L1 - 2), where Addr is the address of the instruction after the catch.
/// If the value is 0 or 1, the function returns immediately, invalidating
/// the catch token.
///
///If S1 or L1 uses the stack push/pop modes, note that the precise order of
///execution is: evaluate L1 (popping if appropriate); generate a call stub
///and compute the token; store S1 (pushing if appropriate).
Catch(StoreOperand<L>, LoadOperand<L>),
/// Jump back to a previously-executed catch opcode, and store the value L1.
/// L2 must be a valid catch token.
///
/// The exact catch/throw procedure is as follows:
///
/// When catch is executed, a four-value call stub is pushed on the stack –
/// result destination, PC, and FramePtr. (See section 1.3.2, "Call Stubs".
/// The PC is the address of the next instruction after the catch.) The
/// catch token is the value of the stack pointer after these are pushed.
/// The token value is stored in the result destination, and execution
/// proceeds, branching to L1.
///
/// When throw is executed, the stack is popped down until the stack pointer
/// equals the given token. Then the four values are read back off the
/// stack, the thrown value is stored in the destination, and execution
/// proceeds with the instruction after the catch.
///
/// If the call stub (or any part of it) is removed from the stack, the
/// catch token becomes invalid, and must not be used. This will certainly
/// occur when you return from the function containing the catch opcode. It
/// will also occur if you pop too many values from the stack after
/// executing the catch. (You may wish to do this to "cancel" the catch; if
/// you pop and discard those four values, the token is invalidated, and it
/// is as if you had never executed the catch at all.) The catch token is
/// also invalidated if any part of the call stub is overwritten (e.g. with
/// stkswap or stkroll).
Throw(LoadOperand<L>, LoadOperand<L>),
// MEMORY MAP
/// Store the current size of the memory map. This is originally the ENDMEM
/// value from the header, but you can change it with the setmemsize opcode.
/// (The malloc and mfree opcodes may also cause this value to change; see
/// section 2.9, "Memory Allocation Heap".) It will always be greater than
/// or equal to ENDMEM, and will always be a multiple of 256.
Getmemsize(StoreOperand<L>),
/// Set the current size of the memory map. The new value must be a multiple
/// of 256, like all memory boundaries in Glulx. It must be greater than or
/// equal to ENDMEM (the initial memory-size value which is stored in the
/// header.) It does not have to be greater than the previous memory size.
/// The memory size may grow and shrink over time, as long as it never gets
/// smaller than the initial size.
///
/// When the memory size grows, the new space is filled with zeroes. When it
/// shrinks, the contents of the old space are lost.
///
/// If the allocation heap is active (see section 2.9, "Memory Allocation
/// Heap") you may not use setmemsize – the memory map is under the control
/// of the heap system. If you free all heap objects, the heap will then no
/// longer be active, and you can use setmemsize.
///
/// Since memory allocation is never guaranteed, you must be prepared for
/// the possibility that setmemsize will fail. The opcode stores the value
/// zero if it succeeded, and 1 if it failed. If it failed, the memory size
/// is unchanged.
///
/// Some interpreters do not have the capability to resize memory at all. On
/// such interpreters, setmemsize will always fail. You can check this in
/// advance with the ResizeMem gestalt selector.
///
/// Note that the memory size is considered part of the game state. If you
/// restore a saved game, the current memory size is changed to the size
/// that was in effect when the game was saved. If you restart, the current
/// memory size is reset to its initial value.
Setmemsize(LoadOperand<L>, StoreOperand<L>),
/// Manage the memory allocation heap.
///
/// Allocate a memory block of L1 bytes. (L1 must be positive.) This stores
/// the address of the new memory block, which will be within the heap and
/// will not overlap any other extant block. The interpreter may have to
/// extend the memory map (see section 2.8, "Memory Map") to accomodate the
/// new block.
///
/// This operation does not change the contents of the memory block (or,
/// indeed, the contents of the memory map at all). If you want the memory
/// block to be initialized, you must do it yourself.
///
/// If the allocation fails, this stores zero.
///
/// Glulx is able to maintain a list of dynamically-allocated memory
/// objects. These objects exist in the memory map, above ENDMEM. The malloc
/// and mfree opcodes allow the game to request the allocation and
/// destruction of these objects.
///
/// Some interpreters do not have the capability to manage an allocation
/// heap. On such interpreters, malloc will always fail. You can check this
/// in advance with the MAlloc gestalt selector.
///
/// When you first allocate a block of memory, the heap becomes active. The
/// current end of memory – that is, the current getmemsize value – becomes
/// the beginning address of the heap. The memory map is then extended to
/// accomodate the memory block.
///
/// Subsequent memory allocations and deallocations are done within the
/// heap. The interpreter may extend or reduce the memory map, as needed,
/// when allocations and deallocations occur. While the heap is active, you
/// may not manually resize the memory map with setmemsize; the heap system
/// is responsible for doing that.
///
/// When you free the last extant memory block, the heap becomes inactive.
/// The interpreter will reduce the memory map size down to the heap-start
/// address. (That is, the getmemsize value returns to what it was before
/// you allocated the first block.) Thereafter, it is legal to call
/// setmemsize again.
///
/// It is legitimate to read or write any memory address in the heap range
/// (from ENDMEM to the end of the memory map). You are not restricted to
/// extant blocks. [The VM's heap state is not stored in its own memory map.
/// So, unlike the familiar C heap, you cannot damage it by writing outside
/// valid blocks.]
///
///The heap state (whether it is active, its starting address, and the
///addresses and sizes of all extant blocks) is part of the saved game
///state.
Malloc(LoadOperand<L>, StoreOperand<L>),
/// Free the memory block at address L1. This must be the address of an
/// extant block – that is, a value returned by malloc and not previously
/// freed.
///
/// This operation does not change the contents of the memory block (or,
/// indeed, the contents of the memory map at all).
Mfree(LoadOperand<L>),
// GAME STATE
/// Shut down the terp and exit. This is equivalent to returning from the
/// top-level function, or for that matter calling glk_exit().
///
/// Note that (in the Glk I/O system) Glk is responsible for any "hit any
/// key to exit" prompt. It is safe for you to print a bunch of final text
/// and then exit immediately.
Quit,
/// Restore the VM to its initial state (memory, stack, and registers). Note
/// that the current memory size is reset, as well as the contents of
/// memory.
Restart,
/// Save the VM state to the output stream L1. It is your responsibility to
/// prompt the player for a filespec, open the stream, and then destroy
/// these objects afterward. S1 is set to zero if the operation succeeded, 1
/// if it failed, and -1 if the VM has just been restored and is continuing
/// from this instruction.
/// (In the Glk I/O system, L1 should be the ID of a writable Glk stream. In
/// other I/O systems, it will mean something different. In the "filter" and
/// "null" I/O systems, the save opcode is illegal, as the interpreter has
/// nowhere to write the state.)
Save(LoadOperand<L>, StoreOperand<L>),
/// Restore the VM state from the input stream L1. S1 is set to 1 if the
/// operation failed. If it succeeded, of course, this instruction never
/// returns a value.
Restore(LoadOperand<L>, StoreOperand<L>),
/// Save the VM state in a temporary location. The terp will choose a
/// location appropriate for rapid access, so this may be called once per
/// turn. S1 is set to zero if the operation succeeded, 1 if it failed, and
/// -1 if the VM state has just been restored.
Saveundo(StoreOperand<L>),
/// Restore the VM state from temporary storage. S1 is set to 1 if the
/// operation failed.
Restoreundo(StoreOperand<L>),
/// Test whether a VM state is available in temporary storage. S1 is set to
/// 0 if a state is available, 1 if not. If this returns 0, then restoreundo
/// is expected to succeed.
Hasundo(StoreOperand<L>),
/// Discard a VM state (the most recently saved) from temporary storage. If
/// none is available, this does nothing.
///
/// The hasundo and discardundo opcodes were added in Glulx 3.1.3. You can
/// check for their existence with the ExtUndo gestalt selector.
Discardundo,
/// Protect a range of memory from restart, restore, restoreundo. The
/// protected range starts at address L1 and has a length of L2 bytes. This
/// memory is silently unaffected by the state-restoring operations.
/// (However, if the result-storage S1 is directed into the protected range,
/// that is not blocked.)
///
/// When the VM starts up, there is no protection range. Only one range can
/// be protected at a time. Calling protect cancels any previous range. To
/// turn off protection, call protect with L1 and L2 set to zero.
///
/// It is important to note that the protection range itself (its existence,
/// location, and length) is not part of the saved game state! If you save a
/// game, move the protection range to a new location, and then restore that
/// game, it is the new range that will be protected, and the range will
/// remain there afterwards.
Protect(LoadOperand<L>, LoadOperand<L>),
/// Perform sanity checks on the game file, using its length and checksum.
/// S1 is set to zero if everything looks good, 1 if there seems to be a
/// problem. (Many interpreters will do this automatically, before the game
/// starts executing. This opcode is provided mostly for slower
/// interpreters, where auto-verify might cause an unacceptable delay.)
Verify(StoreOperand<L>),
// OUTPUT
/// Return the current I/O system mode and rock.
///
/// Due to a long-standing bug in the reference interpreter, the two store
/// operands must be of the same general type: both main-memory/global
/// stores, both local variable stores, or both stack pushes.
Getiosys(StoreOperand<L>, StoreOperand<L>),
/// Set the I/O system mode and rock. If the system L1 is not supported by
/// the interpreter, it will default to the "null" system (0).
/// These systems are currently defined:
/// * 0: The null system. All output is discarded. (When the Glulx machine
/// starts up, this is the current system.)
///
/// * 1: The filtering system. The rock (L2) value should be the address of a
/// Glulx function. This function will be called for every character output
/// (with the character value as its sole argument). The function's return
/// value is ignored.
///
/// * 2: The Glk system. All output will be handled through Glk function
/// calls, sent to the current Glk stream.
///
/// * 20: The FyreVM channel system. See section 0.2, "Glulx and Other IF
/// Systems".
///
/// The values 140-14F are reserved for extension projects by ZZO38. These
/// are not documented here.
///
/// It is important to recall that when Glulx starts up, the Glk I/O system
/// is not set. And when Glk starts up, there are no windows and no current
/// output stream. To make anything appear to the user, you must first do
/// three things: select the Glk I/O system, open a Glk window, and set its
/// stream as the current one. (It is illegal in Glk to send output when
/// there is no stream set. Sending output to Glulx's "null" I/O system is
/// legal, but pointless.)
Setiosys(LoadOperand<L>, LoadOperand<L>),
/// Send L1 to the current stream. This sends a single character; the value L1 is truncated to eight bits.
Streamchar(LoadOperand<L>),
/// Send L1 to the current stream. This sends a single (32-bit) character.
///
/// This opcode was added in Glulx version 3.0.
Streamunichar(LoadOperand<L>),
/// Send L1 to the current stream, represented as a signed decimal number in ASCII.
Streamnum(LoadOperand<L>),
/// Send a string object to the current stream. L1 must be the address of a
/// Glulx string object (type E0, E1, or E2.) The string is decoded and sent
/// as a sequence of characters.
///
/// When the Glk I/O system is set, these opcodes are implemented using the
/// Glk API. You can bypass them and directly call glk_put_char(),
/// glk_put_buffer(), and so on. Remember, however, that glk_put_string()
/// only accepts unencoded string (E0) objects; glk_put_string_uni() only
/// accepts unencoded Unicode (E2) objects.
///
/// Note that it is illegal to decode a compressed string (E1) if there is
/// no string-decoding table set.
Streamstr(LoadOperand<L>),
/// Return the address the terp is currently using for its string-decoding
/// table. If there is no table, set, this returns zero.
Getstringtbl(StoreOperand<L>),
/// Change the address the terp is using for its string-decoding table. This
/// may be zero, indicating that there is no table (in which case it is
/// illegal to print any compressed string). Otherwise, it must be the
/// address of a valid string-decoding table.
Setstringtbl(LoadOperand<L>),
/// Convert an integer value to the closest equivalent float. (That is, if
/// L1 is 1, then 3F800000 – the float encoding of 1.0 – will be stored in
/// S1.) Integer zero is converted to (positive) float zero.
///
/// If the value is less than -1000000 or greater than 1000000 (hex), the
/// conversion may not be exact. (More specifically, it may round to a
/// nearby multiple of a power of 2.)
Numtof(LoadOperand<L>, StoreOperand<L>),
/// Convert a float value to an integer, rounding towards zero (i.e.,
/// truncating the fractional part). If the value is outside the 32-bit
/// integer range, or is NaN or infinity, the result will be 7FFFFFFF (for
/// positive values) or 80000000 (for negative values).
Ftonumz(LoadOperand<L>, StoreOperand<L>),
/// Convert a float value to an integer, rounding towards the nearest
/// integer. Again, overflows become 7FFFFFFF or 80000000.\
Ftonumn(LoadOperand<L>, StoreOperand<L>),
/// Floating point addition. Overflows produce infinite values (with the
/// appropriate sign); underflows produce zero values (ditto). 0/0 is NaN.
/// Inf/Inf, or Inf-Inf, is NaN. Any finite number added to infinity is
/// infinity. Any nonzero number divided by an infinity, or multiplied by
/// zero, is a zero. Any nonzero number multiplied by an infinity, or
/// divided by zero, is an infinity.
Fadd(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Floating pointing subtraction.
Fsub(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Floating pointing multiplication.
Fmul(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Floating pointing division.
Fdiv(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Perform a floating-point modulo operation. S1 is the remainder (or
/// modulus); S2 is the quotient.
///
/// S2 is L1/L2, rounded (towards zero) to an integral value. S1 is
/// L1-(S2*L2). Note that S1 always has the same sign as L1; S2 has the
/// appropriate sign for L1/L2.
///
/// If L2 is 1, this gives you the fractional and integer parts of L1. If L1
/// is zero, both results are zero. If L2 is infinite, S1 is L1 and S2 is
/// zero. If L1 is infinite or L2 is zero, both results are NaN.
Fmod(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Round L1 up (towards +Inf) to the nearest integral value. (The result is
/// still in float format, however.) These opcodes are idempotent.
///
/// The result keeps the sign of L1; in particular, floor(0.5) is 0 and
/// ceil(−0.5) is −0. Rounding −0 up or down gives −0. Rounding an infinite
/// value gives infinity.
Ceil(LoadOperand<L>, StoreOperand<L>),
/// Round L1 down (toward -Inf) to the nearest integral value.
Floor(LoadOperand<L>, StoreOperand<L>),
/// Compute the square root of L1.
///
/// sqrt(−0) is −0. sqrt returns NaN for all other negative values.
Sqrt(LoadOperand<L>, StoreOperand<L>),
/// Compute exp(L1).
///
/// exp(+0) and exp(−0) are 1; exp(−Inf) is +0.
Exp(LoadOperand<L>, StoreOperand<L>),
/// Compute ln(L1).
///
/// log(+0) and log(−0) are −Inf. log returns NaN for all other negative values.
Log(LoadOperand<L>, StoreOperand<L>),
/// Compute L1 raised to the L2 power.
Pow(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Standard trigonometric sine function.
Sin(LoadOperand<L>, StoreOperand<L>),
/// Standard trigonometric cosine function.
Cos(LoadOperand<L>, StoreOperand<L>),
/// Standard trigonometric tangent function.
Tan(LoadOperand<L>, StoreOperand<L>),
/// Standard trigonometric arcsine function.
Asin(LoadOperand<L>, StoreOperand<L>),
/// Standard trigonometric arccosine function.
Acos(LoadOperand<L>, StoreOperand<L>),
/// Standard trigonometric arctangent function.
Atan(LoadOperand<L>, StoreOperand<L>),
/// Computes the arctangent of L1/L2, using the signs of both arguments to
/// determine the quadrant of the return value. (Note that the Y argument is
/// first and the X argument is second.)
Atan2(LoadOperand<L>, StoreOperand<L>),
/// Convert an integer value to the closest equivalent double. Integer zero
/// is converted to (positive) double zero. The result is stored as S2:S1.
Numtod(LoadOperand<L>, StoreOperand<L>, StoreOperand<L>),
/// Convert a double value L1:L2 to an integer, rounding towards zero (i.e.,
/// truncating the fractional part). If the value is outside the 32-bit
/// integer range, or is NaN or infinity, the result will be 7FFFFFFF (for
/// positive values) or 80000000 (for negative values).
Dtonumz(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Convert a double value L1:L2 to an integer, rounding towards the nearest
/// integer. Again, overflows become 7FFFFFFF or 80000000.
Dtonumn(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Convert a float value L1 to a double value, stored as S2:S1.
Ftod(LoadOperand<L>, StoreOperand<L>, StoreOperand<L>),
/// Convert a double value L1:L2 to a float value, stored as S1.
Dtof(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Add doubles. The arguments are L1:L2 and L3:L4; the result is stored as S2:S1.
Dadd(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Subtract doubles. The arguments are L1:L2 and L3:L4; the result is stored as S2:S1.
Dsub(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Multiply doubles. The arguments are L1:L2 and L3:L4; the result is stored as S2:S1.
Dmul(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Divide doubles. The arguments are L1:L2 and L3:L4; the result is stored as S2:S1.
Ddiv(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Get the remainder of a floating point modulo operation. The arguments
/// are L1:L2 and L3:L4; the result is stored as S2:S1.
///
/// Unlike fmod, there are separate opcodes to compute the remainder and
/// modulus.
Dmodr(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Get the quotient of a float point modulo operation. The arguments are
/// L1:L2 and L3:L4; the result is stored as S2:S1.
///
/// Unlike fmod, there are separate opcodes to compute the remainder and
/// modulus.
Dmodq(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Round L1:L2 up (towards +Inf) to the nearest integral value. (The result
/// is still in double format, however.) The result is stored as S2:S1.
/// These opcodes are idempotent.
Dceil(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Round L1:L2 down (towards −Inf) to the nearest integral value. (The
/// result is still in double format, however.) The result is stored as
/// S2:S1. These opcodes are idempotent.
Dfloor(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the square root of L1:L2.
Dsqrt(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute exp(L1:L2).
Dexp(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute ln(L1:L2).
Dlog(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute L1:L2 raised to the L3:L4 power. The result is stored as S2:S1.
Dpow(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the standard trigonometric sine of (L1:L2).
Dsin(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the standard trigonometric cosine of (L1:L2).
Dcos(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the standard trigonometric tangent of (L1:L2).
Dtan(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the standard trigonometric arcsine of (L1:L2).
Dasin(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the standard trigonometric arccosine of (L1:L2).
Dacos(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Compute the standard trigonometric arctangent of (L1:L2).
Datan(
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
/// Computes the arctangent of L1:L2/L3:L4, using the signs of both
/// arguments to determine the quadrant of the return value. (Note that the
/// Y argument is first and the X argument is second.) The result is stored
/// as S2:S1.
Datan2(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
StoreOperand<L>,
),
// FLOATING POINT COMPARISONS
/// Branch to L2 if the floating-point value L1 is a NaN value.
Jisnan(LoadOperand<L>, LoadOperand<L>),
/// Branch to L2 if the floating-point value L1 is an infinity (7F800000 or FF800000).
Jisinf(LoadOperand<L>, LoadOperand<L>),
/// Branch to L4 if the difference between L1 and L2 is less than or equal
/// to (plus or minus) L3. The sign of L3 is ignored.
///
/// If any of the arguments are NaN, this will not branch. If L3 is
/// infinite, this will always branch – unless L1 and L2 are opposite
/// infinities. (Opposite infinities are never equal, regardless of L3.
/// Infinities of the same sign are always equal.)
///
/// If L3 is (plus or minus) zero, this tests for exact equality. Note that
/// +0 is considered exactly equal to −0.
Jfeq(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// The reverse of jfeq. This will branch if any of the arguments is NaN.
Jfne(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// Branch to L3 if L1 is less than L2.
Jflt(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Branch to L3 if L1 is less than or equal to L2.
Jfle(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Branch to L3 if L1 is greater than L2.
Jfgt(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Branch to L3 if L1 is greater than or equal to L2.
Jfge(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
// DOUBLE PRECISION COMPARISONS
/// Branch to L3 if the double value L1:L2 is a NaN value.
Jdisnan(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Branch to L3 if the double value L1:L2 is an infinity.
Jdisinf(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
/// Branch to L7 if the difference between L1:L2 and L3:L4 is less than or
/// equal to (plus or minus) L5:L6. The sign of L5:L6 is ignored.
Jdeq(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// The reverse of jdeq
Jdne(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// Branch to L5 if L1:L2 is less than L3:L4.
Jdlt(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// Branch to L5 if L1:L2 is less than or equal to L3:L4.
Jdle(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// Branch to L5 if L1:L2 is greater than L3:L4.
Jdgt(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
/// Branch to L5 if L1:L2 is greater than or equal to L3:L4.
Jdge(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
),
// RANDOM NUMBER GENERATOR
/// Return a random number in the range 0 to (L1-1); or, if L1 is negative,
/// the range (L1+1) to 0. If L1 is zero, return a random number in the full
/// 32-bit integer range. (Remember that this may be either positive or
/// negative.)
Random(LoadOperand<L>, StoreOperand<L>),
/// Seed the random-number generator with the value L1. If L1 is zero,
/// subsequent random numbers will be as genuinely unpredictable as the terp
/// can provide; it may include timing data or other random sources in its
/// generation. If L1 is nonzero, subsequent random numbers will follow a
/// deterministic sequence, always the same for a given nonzero seed.
///
/// The terp starts up in the "nondeterministic" mode (as if setrandom 0 had
/// been invoked.)
///
/// The random-number generator is not part of the saved-game state.
Setrandom(LoadOperand<L>),
// BLOCK COPY AND CLEAR
/// Write L1 zero bytes, starting at address L2.
Mzero(LoadOperand<L>, LoadOperand<L>),
/// Copy L1 bytes from address L2 to address L3. It is safe to copy a block to an overlapping block.
Mcopy(LoadOperand<L>, LoadOperand<L>, LoadOperand<L>),
// SEARCHING
/// Accelerated linear search.
///
/// * L1: Key
/// * L2: KeySize
/// * L3: Start
/// * L4: StructSize
/// * L5: NumStructs
/// * L6: KeyOffset
/// * L7: Options
/// * S1: Result
///
/// An array of data structures is stored in memory, beginning at `Start`,
/// each structure being `StructSize` bytes. Within each struct, there is a
/// key value `KeySize` bytes long, starting at position `KeyOffset` (from
/// the start of the structure.) Search through these in order. If one is
/// found whose key matches, return it. If `NumStructs` are searched with no
/// result, the search fails.
///
/// NumStructs may be -1 (`0xFFFFFFFF``) to indicate no upper limit to the
/// number of structures to search. The search will continue until a match
/// is found, or (if `ZeroKeyTerminates`` is used) a zero key.
///
/// The following options may be set in `L7`:
/// * KeyIndirect (`0x01`): This flag indicates that the `Key`` argument passed
/// to the opcode is the address of the actual key. If this flag is not
/// used, the Key argument is the key value itself. (In this case, the
/// KeySize must be 1, 2, or 4 – the native sizes of Glulx values. If the
/// KeySize is 1 or 2, the lower bytes of the Key are used and the upper
/// bytes ignored.)
/// * ZeroKeyTerminates (`0x02`): This flag indicates that the search should
/// stop (and return failure) if it encounters a structure whose key is
/// all zeroes. If the searched-for key happens to also be all zeroes, the
/// success takes precedence.
/// * ReturnIndex (`0x04`): This flag indicates that search should return the
/// array index of the structure that it finds, or -1 (`0xFFFFFFFF`) for
/// failure. If this flag is not used, the search returns the address of
/// the structure that it finds, or 0 for failure.
Linearsearch(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
),
/// Accelerated binary search.
///
/// * L1: Key
/// * L2: KeySize
/// * L3: Start
/// * L4: StructSize
/// * L5: NumStructs
/// * L6: KeyOffset
/// * L7: Options
/// * S1: Result
///
/// An array of data structures is in memory, as above. However, the structs
/// must be stored in forward order of their keys (taking each key to be a
/// big-endian unsigned integer.) There can be no duplicate keys. `NumStructs``
/// must indicate the exact length of the array; it cannot be -1.
///
/// The `KeyIndirect`` and `ReturnIndex`` options may be used.
Binarysearch(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
),
/// Accelerated linked-list search.
///
/// * L1: Key
/// * L2: KeySize
/// * L3: Start
/// * L4: KeyOffset
/// * L5: NextOffset
/// * L6: Options
/// * S1: Result
///
/// The structures need not be consecutive; they may be anywhere in memory,
/// in any order. They are linked by a four-byte address field, which is
/// found in each struct at position NextOffset. If this field contains
/// zero, it indicates the end of the linked list.
///
/// The KeyIndirect and ZeroKeyTerminates options may be used.
Linkedsearch(
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
LoadOperand<L>,
StoreOperand<L>,
),
// ACCELERATED FUNCTIONS
/// Request that the VM function with address L2 be replaced by the
/// accelerated function whose number is L1. If L1 is zero, the acceleration
/// for address L2 is cancelled.
///
/// If the terp does not offer accelerated function L1, this does nothing.
///
/// If you request acceleration at an address which is already accelerated,
/// the previous request is cancelled before the new one is considered. If
/// you cancel at an unaccelerated address, nothing happens.
///
/// A given accelerated function L1 may replace several VM functions (at
/// different addresses) at the same time. Each request is considered
/// separate, and must be cancelled separately.
Accelfunc(LoadOperand<L>, LoadOperand<L>),
/// Store the value L2 in the parameter table at position L1. If the terp
/// does not know about parameter L1, this does nothing.
Accelparam(LoadOperand<L>, LoadOperand<L>),
// MISCELLANEOUS
/// Test the Gestalt selector number L1, with optional extra argument L2,
/// and store the result in S1. If the selector is not known, store zero.
/// The list of L1 selectors is as follows. Note that if a selector does not
/// mention L2, you should always set that argument to zero.
/// * GlulxVersion (0): Returns the version of the Glulx spec which the
/// interpreter implements. The upper 16 bits of the value contain a major
/// version number; the next 8 bits contain a minor version number; and
/// the lowest 8 bits contain an even more minor version number, if any.
/// This specification is version 3.1.3, so a terp implementing it would
/// return 0x00030103. Future Glulx specs will try to maintain the
/// convention that minor version changes are backwards compatible, and
/// subminor version changes are backwards and forwards compatible.
/// * TerpVersion (1): Returns the version of the interpreter. The format is
/// the same as the GlulxVersion. [Each interpreter has its own version
/// numbering system, defined by its author, so this information is not
/// terribly useful. But it is convenient for the game to be able to
/// display it, in case the player is capturing version information for a
/// bug report.]
/// * ResizeMem (2): Returns 1 if the terp has the potential to resize the
/// memory map, with the setmemsize opcode. If this returns 0, setmemsize
/// will always fail. [But remember that setmemsize might fail in any
/// case.]
/// * Undo (3): Returns 1 if the terp has the potential to undo. If this
/// returns 0, saveundo, restoreundo, and hasundo will always fail.
/// * IOSystem (4): Returns 1 if the terp supports the I/O system given in
/// L2. (The constants are the same as for the setiosys opcode: 0 for
/// null, 1 for filter, 2 for Glk, 20 for FyreVM. 0 and 1 will always
/// succeed.)
/// * Unicode (5): Returns 1 if the terp supports Unicode operations. These
/// are: the E2 Unicode string type; the 04 and 05 string node types (in
/// compressed strings); the streamunichar opcode; the type-14 call stub.
/// If the Unicode selector returns 0, encountering any of these will
/// cause a fatal interpreter error.
/// * MemCopy (6): Returns 1 if the interpreter supports the mzero and mcopy
/// opcodes. (This must true for any terp supporting Glulx 3.1.)
/// * MAlloc (7): Returns 1 if the interpreter supports the malloc and mfree
/// opcodes. (If this is true, MemCopy and ResizeMem must also both be
/// true, so there is no need to check all three.)
/// * MAllocHeap (8): Returns the start address of the heap. This is the
/// value that getmemsize had when the first memory block was allocated.
/// If the heap is not active (no blocks are extant), this returns zero.
/// * Acceleration (9): Returns 1 if the interpreter supports the accelfunc
/// and accelparam opcodes. (This must true for any terp supporting Glulx
/// 3.1.1.)
/// * AccelFunc (10): Returns 1 if the terp implements the accelerated
/// function given in L2.
/// * Float (11): Returns 1 if the interpreter supports the floating-point
/// arithmetic opcodes.
/// * ExtUndo (12): Returns 1 if the interpreter supports the hasundo and
/// discardundo opcodes.
/// * Double (13): Returns 1 if the interpreter supports the
/// double-precision floating-point arithmetic opcodes.
///
/// Selectors 0x1000 to 0x10FF are reserved for use by FyreVM. Selectors
/// 0x1100 to 0x11FF are reserved for extension projects by Dannii Willis.
/// Selectors 0x1200 to 0x12FF are reserved for iOS extension features by
/// Andrew Plotkin. Selectors 0x1400 to 0x14FF are reserved for iOS
/// extension features by ZZO38.
Gestalt(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
/// Interrupt execution to do something interpreter-specific with L1. If the
/// interpreter has nothing in mind, it should halt with a visible error
/// message.
Debugtrap(LoadOperand<L>),
/// Call the Glk API function whose identifier is L1, passing in L2
/// arguments. The return value is stored at S1. (If the Glk function has no
/// return value, zero is stored at S1.)
/// The arguments are passed on the stack, last argument pushed first, just
/// as for the call opcode.
/// Arguments should be represented in the obvious way. Integers and
/// character are passed as integers. Glk opaque objects are passed as
/// integer identifiers, with zero representing NULL. Strings and Unicode
/// strings are passed as the addresses of Glulx string objects (see section
/// 1.6.1, "Strings".) References to values are passed by their addresses.
/// Arrays are passed by their addresses; note that an array argument,
/// unlike a string argument, is always followed by an array length
/// argument.
/// Reference arguments require more explanation. A reference to an integer
/// or opaque object is the address of a 32-bit value (which, being in main
/// memory, does not have to be aligned, but must be big-endian.)
/// Alternatively, the value -1 (FFFFFFFF) may be passed; this is a special
/// case, which means that the value is read from or written to the stack.
/// Arguments are always evaluated left to right, which means that input
/// arguments are popped from the stack first-topmost, but output arguments
/// are pushed on last-topmost.
/// A reference to a Glk structure is the address of an array of 32-bit
/// values in main memory. Again, -1 means that all the values are written
/// to the stack. Also again, an input structure is popped off
/// first-topmost, and an output structure is pushed on last-topmost.
/// All stack input references (-1 addresses) are popped after the Glk
/// argument list is popped. [This should be obvious, since the -1 occurs in
/// the Glk argument list.] Stack output references are pushed after the Glk
/// call, but before the S1 result value is stored.
Glk(LoadOperand<L>, LoadOperand<L>, StoreOperand<L>),
}