2: Dictionary of Opcodes

Opcodes are written here in the format:

opname L1 L2 S1

...where "L1" and "L2" are operands using the load addressing modes, and "S1" is an operand using the store addressing modes. (See section 1.5, "Instruction Format".)

The table of opcodes:

2.1: Math

add L1 L2 S1

Add L1 and L2, using standard 32-bit addition. Truncate the result to 32 bits if necessary. Store the result in S1.

sub L1 L2 S1

Compute (L1 - L2), and store the result in S1.

mul L1 L2 S1

Compute (L1 * L2), and store the result in S1. Truncate the result to 32 bits if necessary.

div L1 L2 S1

Compute (L1 / L2), and store the result in S1. This is signed integer division.

mod L1 L2 S1

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):

-11 /  2 = -5       
-11 / -2 =  5        
 11 / -2 = -5
-13 %  5 = -3        
 13 % -5 =  3       
-13 % -5 = -3

neg L1 S1

Compute the negative of L1.

bitand L1 L2 S1

Compute the bitwise AND of L1 and L2.

bitor L1 L2 S1

Compute the bitwise OR of L1 and L2.

bitxor L1 L2 S1

Compute the bitwise XOR of L1 and L2.

bitnot L1 S1

Compute the bitwise negation of L1.

shiftl L1 L2 S1

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.

ushiftr L1 L2 S1

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.

sshiftr L1 L2 S1

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.

Notes on the shift opcodes: If L2 is zero, the result is always equal to L1. L2 is considered unsigned, so 80000000 or greater is "more than 32".

2.2: Branches

All branches (except jumpabs) specify their destinations with an offset value. The actual destination address of the branch is computed as (Addr + Offset - 2), where Addr is the address of the instruction after the branch opcode, and offset is the branch's operand. The special offset values 0 and 1 are interpreted as "return 0" and "return 1" respectively. [This odd hiccup is inherited from the Z-machine. Inform uses it heavily for code optimization.]

It is illegal to branch from one function to another.

jump L1

Branch unconditionally to offset L1.

jz L1 L2

If L1 is equal to zero, branch to L2.

jnz L1 L2

If L1 is not equal to zero, branch to L2.

jeq L1 L2 L3

If L1 is equal to L2, branch to L3.

jne L1 L2 L3

If L1 is not equal to L2, branch to L3.

jlt L1 L2 L3
jle L1 L2 L3
jgt L1 L2 L3
jge L1 L2 L3

Branch is L1 is less than, less than or equal to, greater than, greater than or equal to L2. The values are compared as signed 32-bit values.

jltu L1 L2 L3
jleu L1 L2 L3
jgtu L1 L2 L3
jgeu L1 L2 L3

The same, except that the values are compared as unsigned 32-bit values.

[Since the address space can span the full 32-bit range, it is wiser to compare addresses with the unsigned comparison operators.]

jumpabs L1

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.

Note that it is still illegal to branch from one function to another, even with jumpabs.

2.3: Moving Data

copy L1 S1

Read L1 and store it at S1, without change.

copys L1 S1

Read a 16-bit value from L1 and store it at S1.

copyb L1 S1

Read an 8-bit value from L1 and store it at S1.

Since copys and copyb can access chunks smaller than the usual four bytes, they require some comment. When reading from main memory or the call-frame locals, they access two or one bytes, instead of four. However, when popping or pushing values on the stack, these opcodes pull or push a full 32-bit value.

Therefore, if copyb (for example) copies a byte from main memory to the stack, a 32-bit value will be pushed, whose value will be from 0 to 255. Sign-extension does not occur. Conversely, if copyb copies a byte from the stack to memory, a 32-bit value is popped, and the bottom 8 bits are written at the given address. The upper 24 bits are lost. Constant values are truncated as well.

If copy, copys, or copyb are used with both L1 and S1 in pop/push mode, the 32-bit value is popped, truncated, and pushed.

sexs L1 S1

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.

sexb L1 S1

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.

Note that these opcodes, like most, work on 32-bit values. Although (for example) sexb is commonly used in conjunction with copyb, it does not share copyb's behavior of reading a single byte from memory or the locals.

Also note that the upper bits, 16 or 24 of them, are entirely ignored and overwritten with ones or zeroes.

2.4: Array Data

astore L1 L2 L3

Store L3 into the 32-bit field at main memory address (L1+4*L2).

aload L1 L2 S1

Load a 32-bit value from main memory address (L1+4*L2), and store it in S1.

astores L1 L2 L3

Store L3 into the 16-bit field at main memory address (L1+2*L2).

aloads L1 L2 S1

Load an 16-bit value from main memory address (L1+2*L2), and store it in S1.

astoreb L1 L2 L3

Store L3 into the 8-bit field at main memory address (L1+L2).

aloadb L1 L2 S1

Load an 8-bit value from main memory address (L1+L2), and store it in S1.

Note that these opcodes cannot access call-frame locals, or the stack. (Not with the L1 and L2 opcodes, that is.) L1 and L2 provide a main-memory address. Be not confused by the fact that L1 and L2 can be any addressing mode, including call-frame or stack-pop modes. That controls where the values come from which are used to compute the main-memory address.

The other end of the transfer (S1 or L3) is always a 32-bit value. The "store" opcodes truncate L3 to 8 or 16 bits if necessary. The "load" opcodes expand 8-bit or 16-bit values without sign extension. (If signed values are appropriate, you can follow aloads/aloadb with sexs/sexb.)

L2 is considered signed, so you can access addresses before L1 as well as after.

astorebit L1 L2 L3

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.

aloadbit L1 L2 S1

Test a single bit, similarly. If it is set, 1 is stored at S1; if clear, 0 is stored.

For these two opcodes, bits are effectively numbered sequentially, starting with the least significant bit of address L1. L2 is considered signed, so this numbering extends both positively and negatively. For example:

astorebit  1002  0  1:  Set bit 0 of address 1002. (The 1's place.)
astorebit  1002  7  1:  Set bit 7 of address 1002. (The 128's place.)
astorebit  1002  8  1:  Set bit 0 of address 1003.
astorebit  1002  9  1:  Set bit 1 of address 1003.
astorebit  1002 -1  1:  Set bit 7 of address 1001.
astorebit  1002 -3  1:  Set bit 5 of address 1001.
astorebit  1002 -8  1:  Set bit 0 of address 1001.
astorebit  1002 -9  1:  Set bit 7 of address 1000.

Like the other aload and astore opcodes, these opcodes cannot access call-frame locals, or the stack.

2.5: The Stack

stkcount S1

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.)

stkpeek L1 S1

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.)

stkswap

Swap the top two values on the stack. The current stack-count must be at least two.

stkcopy L1

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:

5 4 3 2 1 0 <top>
stkcopy 3
5 4 3 2 1 0 2 1 0 <top>

stkroll L1 L2

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:

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.

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.

2.6: Functions

call L1 L2 S1

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.

callf L1 S1
callfi L1 L2 S1
callfii L1 L2 L3 S1
callfiii L1 L2 L3 L4 S1

Call function whose address is L1, passing zero, one, two, or three arguments. Store the return result at S1.

These opcodes behave the same as call, except that the arguments are given in the usual opcode format instead of being found on the stack. (If L2, L3, etc. all use the stack pop mode, then the behavior is exactly the same as call.)

return L1

Return from the current function, with the given return value. If this is the top-level function, Glulx execution is over.

Note that all the branch opcodes (jump, jz, jeq, and so on) have an option to return 0 or 1 instead of branching. These behave exactly as if the return opcode had been executed.

tailcall L1 L2

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.

[This opcode can be used to implement tail recursion, without forcing the stack to grow with every call.]

2.7: Continuations

catch S1 L1

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 that a branch value can be 0 to return 0, or 1 to return 1. (These return from the function that contains the catch opcode.) Otherwise, it branches to (Addr + L1 - 2), where Addr is the address of the instruction after the catch.

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).

throw L1 L2

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.)

[Why is the catch branch taken at catch time, and ignored after a throw? Because it's easier to write the interpreter that way, that's why. If it had to branch after a throw, either the call stub would have to contain the branch offset, or the terp would have to re-parse the catch instruction. Both are ugly.]

2.8: Memory Map

getmemsize S1

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. It will always be greater than or equal to ENDMEM.

setmemsize L1 S1

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.

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.

2.9: Game State

quit

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.

restart

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.

save L1 S1

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.)

restore L1 S1

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.

saveundo S1

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.

restoreundo S1

Restore the VM state from temporary storage. S1 is set to 1 if the operation failed.

protect L1 L2

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.

verify S1

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.)

Notes:

All the save and restore opcodes can generate diagnostic information on the current output stream.

A terp may support several levels of temporary storage. You should not make any assumptions about how many times restoreundo can be called. If the player so requests, you should keep calling it until it fails.

Glk opaque objects (windows, streams, filespecs) are not part of the saved game state. Therefore, when you restore a game, all the object IDs you have in Glulx memory must be considered invalid. (This includes both IDs in main memory and on the stack.) You must use the Glk iteration calls to go through all the opaque objects in existence, and recognize them by their rocks.

The same applies after restoreundo, to a lesser extent. Since saveundo/restoreundo only operate within a single play session, you can rely on the IDs of objects created before the first saveundo. However, if you have created any objects since then, you must iterate and recognize them.

The restart opcode is a similar case. You must do an iteration as soon as your program starts, to find objects created in an earlier incarnation. Alternatively, you can be careful to close all opaque objects before invoking restart.

[Another approach is to use the protect opcode, to preserve global variables containing your object IDs. This will work within a play session -- that is, with saveundo, restoreundo, and restart. You must still deal with save and restore.]

2.10: Output

getiosys S1 S2

Return the current I/O system mode and rock.

setiosys L1 L2

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:

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.)

streamchar L1

Send L1 to the current stream. This sends a single character; the value L1 is truncated to eight bits.

streamnum L1

Send L1 to the current stream, represented as a signed decimal number in ASCII.

streamstr L1

Send a string object to the current stream. L1 must be the address of a Glulx string object (type E0 or E1.) 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 a Glulx string object is not a C-style string, so it cannot be passed to glk_put_string().

Note that it is illegal to decode a compressed string (E1) if there is no string-decoding table set.

getstringtbl S1

Return the address the terp is currently using for its string-decoding table. If there is no table, set, this returns zero.

setstringtbl L1

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.

[This does not change the value in the header field at address 001C. The header is in ROM, and never changes. To determine the current table address, use the getstringtbl opcode.]

A string-decoding table may be in RAM or ROM, but there may be speed penalties if it is in RAM. See section 1.6.1.3, "The String-Decoding Table".

2.11: Random Number Generator

random L1 S1

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.)

setrandom L1

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.

2.12: Searching

Perform a generic linear, binary, or linked-list search.

[These are outrageously CISC for an hardware CPU, but easy enough to add to a software terp; and taking advantage of them can speed up a program considerably. Advent, under the Inform library, runs 15-20% faster when property-table lookup is handled with a binary-search opcode instead of Inform code. A similar change in the dictionary lookup trims another percent or so.]

All three of these opcodes operate on a collection of fixed-size data structures in memory. A key, which is a fixed-length array of bytes, is found at a known position within each data structure. The opcodes search the collection of structures, and find one whose key matches a given key.

The following flags may be set in the Options argument. Note that not all flags can be used with all types of searches.

linearsearch L1 L2 L3 L4 L5 L6 L7 S1

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 KeyIndirect, ZeroKeyTerminates, and ReturnIndex options may be used.

binarysearch L1 L2 L3 L4 L5 L6 L7 S1

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.

linkedsearch L1 L2 L3 L4 L5 L6 S1

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.

2.13: Miscellaneous

nop

Do nothing.

gestalt L1 L2 S1

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 reasoning behind the design of a Gestalt system is, I hope, too obvious to explain.

[This list of Gestalt selectors has nothing to do with the list in the Glk library.]

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. [This will ensure future compatibility, in case the selector definition is extended.]

debugtrap L1

Interrupt execution to do something interpreter-specific with L1. If the interpreter has nothing in mind, it should halt with a visible error message.

[This is intended for use by debugging interpreters. The program might be sprinkled with consistency tests, set to call debugtrap if an assertion failed. The interpreter could then be set to halt, display a warning, or ignore the debugtrap.]

This should not be used as an arbitrary interpreter trap-door in a finished (non-debugging) program. If you really want to add interpreter functionality to your program, and you're willing to support an alternate interpreter to run it, you should add an entirely new opcode. There are still 2^28 of them available, give or take.

glk L1 L2 S1

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 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.

[The difference between strings and character arrays is somewhat confusing. These are the same type in the C Glk API, but different in Glulx. Calls such as glk_put_buffer() and glk_request_line_event() take character arrays; this is the address of a byte array containing character values, followed by an integer array length. The byte array itself has neither a length field or a terminator. In contrast, calls such as glk_put_string() and glk_fileref_create_by_name() take string arguments, which must be Glulx string objects (either encoded or unencoded.) An unencoded Glulx string object is nearly a byte array, but not quite; it has an E0 byte at the beginning and a zero byte at the end.]

[The convention that "address" -1 refers to the stack is a feature of the Glk invocation mechanism; it applies only to Glk arguments. It is not part of the general Glulx definition. When instruction operands are being evaluated, -1 has no special meaning. This includes the L1, L2, and S1 arguments of the glk opcode.]

2.14: Assembly Language

The format used by Inform is acceptable for now:

@opcode [ op op op ... ] ;

Where each "op" is a constant, the name of a local variable, the name of a global variable, or "sp" (for stack push/pop modes).

It would be convenient to have a one-line form for the opcodes that pass arguments on the stack (call and glk).

It would be convenient if Inform supported the syntax to build an entirely new opcode. (It can do this in Z-code.)


Up to top Previous chapter Next chapter