[Z-MACHINE] Question on Argument Details to Call Opcode

I’ve been playing around with a pedagogical Z-Machine emulator and interpreter, which will be heavily commented. As part of this I’m trying to include bits of disassembly from txd to drive a few points home. (I’m posting this in the “Interpreters” category for this reason; maybe that’s wrong.)

A question reared its head as I got started. Here’s an example of the first opcode to be executed in Zork 1:

Main routine 4f04, 0 locals ()

 4f05:  e0 03 2a 39 80 10 ff ff 00 
                               call            5472 #8010 #ffff -> sp

I believe it’s accurate to say that this call is pushing the address 5472 onto the stack pointer (sp).

What I don’t know is what the #8010 #ffff refer to. They are arguments to the routine, I guess, thus forming part of the set of operands. But does it really matter what they represent?

The specifications category is probably a little better, so I moved it there.

From section 4.1, an instruction can have

  Opcode               1 or 2 bytes
  (Types of operands)  1 or 2 bytes: 4 or 8 2-bit fields
  Operands             Between 0 and 8 of these: each 1 or 2 bytes
  (Store variable)     1 byte
  (Branch offset)      1 or 2 bytes
  (Text to print)      An encoded string (of unlimited length)

@call (e0) is a VAR form instruction, so the next byte specifies the operands, as in 4.2. 03 is 00 00 00 11: three long (2 byte) operands, with no fourth operand.

For @call, the first operand is a padded address to the routine. 2a39 must be multiplied by 2 to get 5472 or 21618 in decimal.

The next two operands are the arguments to the routine. They could be anything. I’d guess that 8010 is another packed address, maybe to a string, and ffff is probably -1. You’d have to look up the routine to be sure. Considering that the first opcode is conventionally a call to a real routine so that it can have local variables, both of these operands are a bit odd.

The final byte is the storer. The return value of the routine is stored in the stack pointer once it’s completed.

Perfect explanation! Thank you. You anticipated my issue. Currently my decoder produces this:

Operand Types: ['LARGE', 'LARGE', 'LARGE']
Operands: [10809, 32784, 65535]

The latter two make sense:

int(0x8010) == 32784
int(0xffff) == 65535

But I was struggling with the 5472 and the 10809. But now that makes complete sense.

Thanks again.

Not necessarily. The Z-machine call stack isn’t part of the evaluation stack exposed to the game, so whatever data call pushes can’t be read back via sp.

Since we know the game is Zork I, we can see what it’s doing:

The first instruction is a call to the QUEUE routine, passing another routine address and the constant -1.

Inform automatically wraps all of Main in another function so it can have local variables, but Infocom generally didn’t do that with GO.

1 Like

A quick source-dive implies that routine $5472 is called QUEUE in the source. This has to do with daemons. The code starts by setting up daemons for combat, the sword glow, the thief, light source expiration, and so on.

Heh. I’m just a bit slow in my research…

This is fantastic! Thanks to all of you!

I’m going to be incorporating your comments into the learning documentation for this interpreter. This is exactly the kind of stuff I want to provide as part of helping people understand how everything works and how the interpreter is constructed.

One other thing: any address, variable, etc after a -> in Inform-style assembly indicates the store destination for the instruction. In other words, the instruction produces a result of some sort, and the bit after -> indicates where that result goes.

In this case, the result of the call—that is, its return value—is stored to sp, which means it’s pushed onto the stack. But that doesn’t happen until the call is over and has returned a value.