Sunday, September 27, 2009

Limitations of the Z-machine standard

"It is wrong in all cases to believe on insufficient evidence; and where it is presumption to doubt and to investigate, there it is worse than presumption to believe." - The Ethics of Belief, William Kingdon Clifford


  When writing an interpreter, despite the very useful Z-machine standard, there are small areas of behavior which are undefined. There are also a very small number of errors in the standard itself. I have to say that overall the 1.0 version standard is very thorough, although I believe the 1.1 standard was not as well conceived in a number of areas. I won't go into the 1.1 standard just yet. Today I am talking about a design decision regarding instruction decoding.

  In my last post I described how to decode an instruction and execute it. I deliberately left out one part of the instruction format mentioned in the standard because I don't deal with it at that point, but rather during the execution itself. There are three special types of instructions: branch, store, and text. Branch instructions are followed by one or two bytes which contain information as to whether the branch occurs on true or false and an offset address. Store instructions are followed by a byte containing a variable number in which to store the result of the operation. Text instructions are followed by a string of text to print.

  I do not read these extra pieces of the instruction and store them during decoding although many, but not all, interpreters do. Rather I delay the reading of this information until the operation concludes. For example: We read a new instruction from the PC and the byte is 0x14 (20 decimal) which is the 'add' opcode. It is a 2-op instruction with two small constant operands. The next two bytes contain these constants. This is a store instruction therefore it is followed by a byte which contains the variable number to put the result in. We do not read this during decoding, instead we wait until the end of the operation itself. Here is the add operation (which uses signed addition):
protected override void Op20()
{
  var num1 = (short)this.Operands[0];
  var num2 = (short)this.Operands[1];
  this.Store((ushort)(num1 + num2));
}

and the store method:
protected void Store(ushort value)
{
  byte variable = this.Memory.ReadByte(this.CallStack.ProgramCounter++);
  this.WriteVariable(variable, value);
}

  Why is this important? Can't we just read the storage byte ahead of time during decoding and cache it somewhere? After all it is part of the instruction and the standard doesn't say how the instruction should be read. Well, yes, but there really are no compelling reasons to do so, yet there are some not to:

  First, caching the result causes complications for the save/restore opcodes. Versions 1-3 of the Z-machine used branch opcodes for save and restore (later versions use store instead). If we read ahead, then when we encounter a save opcode, the PC that is saved will point just past the branching information. When a restore is performed, execution picks up at this point, but cannot know what to do as the save format includes memory and the stack, but no information about the current operation, thus the branch information is lost. Because the branch information can be one or two bytes, backward parsing from the current location is ugly. To fix it, we need to save the PC from the location before we read the branch or store information in order to be able to continue after the restore. When we read these values at the end of the operation, we don't need to do these PC gymnastics and everything 'just works'.

  Second and more interestingly, if only from a theoretical point of view, is if the instruction is a 'call' instruction which starts a routine and stores the return value, then reading ahead presupposes the routine will not change the value of the storage byte during execution. We can imagine this routine sits in dynamic memory where it can rewrite itself while running. Read ahead caching of the storage location would break this code! Through testing I found that Infocom's own interpreters do not read the storage location until after a routine returns. Thus the original implementations of the Z-machine have always had this capability and a modern interpreter which does not is unnecessarily limiting possible behavior of the Z-machine. I avoid creating limitations wherever possible.

Wednesday, September 23, 2009

Decoding operations

  We know that Z-machine decodes and executes one operation at a time. Let's look at exactly how it does this.

  Remember the call stack? One piece of information stored in the call stack is the current program counter (PC). This points to an address in the Z-machine's memory (often beyond the 64k mark, so a 32-bit integer value is recommended). We begin by reading the byte in memory pointed to by the PC. We increment the counter every time we use it. The byte we just read contains the opcode number. After we have the opcode number we need to load the operands to be used during this operation. First we need to determine how many and what type they are.

  The Z-machine has three types of operands: small constants (1 byte value), large constants (2 byte value) , and variables (1 byte variable number). It is useful to define them in an enum like this:

public enum OperandType
{
  LargeConstant = 0,
  Omitted = 3,
  SmallConstant = 1,
  Variable = 2
}


  The enum values correspond to a pair of bits, where a large constant is binary 00, a small constant is 01, a variable is 10, and an omitted operand is 11.

Where do we read these values from? That depends on the opcode number.

  If the opcode number is less than 128, the operation uses 'long form'. The operation always has two operands with either small constant or variable operands. Their types are determined by the seventh and sixth bits of the opcode number. Add 1 to the bit value to determine the operand type. Example: The opcode number is 78 which in binary is 01001110. The seventh bit is 1 and the sixth bit is 0. This gives the first operand as 2 (variable) and the second as 1 (small constant).

  If the opcode is > 127 and less than 192 then it uses 'short form'. There may be zero or one operands. The type is given by the sixth and fifth bits taken together. It may be any of the three types or omitted.

  If the opcode is > 191 (but not 236 or 250, which are special) then it uses 'variable form'. The operand types are specified by an additional byte. We read the byte of memory pointed to by the PC. Each pair of bits in this byte corresponds to one operand, which can be any of the types, including omitted. This gives us 0-4 operands.

  If the opcode is 236 or 250 it uses 'double variable form'. The operands are determined by two additional bytes read in the same way as variable form. This gives us 0-7 operands.

  Once we have the number and types of operands we can then get their values. Small constants are loaded by reading the next byte pointed to by the PC. Large constants by the next word (two bytes) pointed to by the PC. Variable operands are more complicated. First the variable number is determined by reading the next byte pointed to by the PC. What happens next depends on the variable number: If it is zero, we pop a value off the temporary stack in the call stack. If it is between one and fifteen we read the value of a routine local variable (also stored in the call stack), otherwise we read from a so-called "global" variable, which is really just an offset from a known location in the Z-machine memory. In any of those three cases, the resulting 16-bit value becomes the value of the variable operand.

  Now we can execute our operation with the given operands. The following routine decodes and executes a single operation.

private void ExecuteInstruction()
{
  this.Operands.Reset();
  byte operationCode = this.Memory.ReadByte(this.CallStack.ProgramCounter++);
  if (operationCode < 128)
  {
    this.PopulateOperand((OperandType)((operationCode >> 6 & 1) + 1));
    this.PopulateOperand((OperandType)((operationCode >> 5 & 1) + 1));
  }
  else
  {
    if (operationCode < 192)
    {
      this.PopulateOperand((OperandType)(operationCode >> 4 & 3));
    }
    else
    {
      byte operandByte1 = this.Memory.ReadByte(this.CallStack.ProgramCounter++);
      byte operandByte2 = 255;
      if (operationCode == 236 || operationCode == 250)
      {
        operandByte2 = this.Memory.ReadByte(this.CallStack.ProgramCounter++);
      }

      this.PopulateOperand((OperandType)(operandByte1 >> 6 & 3));
      this.PopulateOperand((OperandType)(operandByte1 >> 4 & 3));
      this.PopulateOperand((OperandType)(operandByte1 >> 2 & 3));
      this.PopulateOperand((OperandType)(operandByte1 & 3));
      this.PopulateOperand((OperandType)(operandByte2 >> 6 & 3));
      this.PopulateOperand((OperandType)(operandByte2 >> 4 & 3));
      this.PopulateOperand((OperandType)(operandByte2 >> 2 & 3));
      this.PopulateOperand((OperandType)(operandByte2 & 3));
    }
  }

  Operations[operationCode](this);
}


We can take advantage of the fact that a byte value of of 255 corresponds to all operands being omitted to combine the variable and double variable case. The final line of the routine executes the operation by invoking a delegate stored in an immutable array called Operations and indexed by the opcode number.

Here's the code for populating the operands:

protected void PopulateOperand(OperandType type)
{
  switch (type)
  {
    case OperandType.SmallConstant:
      this.Operands.LoadOperand(this.Memory.ReadByte(this.CallStack.ProgramCounter++));
      break;
    case OperandType.Variable:
      this.Operands.LoadOperand(this.ReadVariable(this.Memory.ReadByte(this.CallStack.ProgramCounter++)));
      break;
    case OperandType.LargeConstant:
      this.Operands.LoadOperand(this.Memory.ReadWord(this.CallStack.ProgramCounter));
      this.CallStack.ProgramCounter += 2;
      break;
  }
}


and finally, here is the code for reading a variable:

protected ushort ReadVariable(ushort variableNumber)
{
  if (variableNumber > 15)
  {
    return this.ReadGlobalVariable((ushort)(variableNumber - 16));
  }

  return variableNumber > 0 ? this.CallStack.LocalVariableRead((byte)(variableNumber - 1)) : this.CallStack.Pop();
}


CallStack.Pop() pops a value off of the temporary stack within the current frame. It does not remove the entire frame. I use methods called BeginRoutine and EndRoutine for that.

That's it for today.

Tuesday, September 22, 2009

Interpreter basics

  There have been Z-machine interpreters created for most platforms and in most languages. Mine is not the first written in C# but my goals are perhaps a bit different. As I mentioned, my goal is not to create a monolithic interpreter, but rather a class library usable by a host of interpreter front-ends. I currently test my library against several front-ends including: applications using windows console, windows forms, WPF, ASP.Net, and XNA for the Xbox360.

  Infocom released almost three dozen Z-machine games which spanned six distinct versions of the Z-machine platform. Hundreds more have been written by other authors since then (many can be found at the IF Archive) and two minor version changes to the Z-machine have been added bringing the total to eight. I wish to support these new games and Infocom's full catalog as well, which includes the version 6 games that had graphics capabilities for the first time. Version 6 is really a different beast from the others (versions 7 and 8 are successors to 5 rather than 6) and requires a lot of extra effort to support.

  So what does a Z-machine do? After initializing, it executes code until hitting a read opcode (or additionally read_char in version 4 and up) then waits for input. After receiving input, execution continues with the next instruction. Most of the time is spent waiting for input from the user. Using these simple states we can 'run' one cycle of our machine like this:

public void Run()
{
  lock (this.lockObject)
  {
    switch (this.State)
    {
      case MachineState.Initializing:
        this.Initialize();
        break;
      case MachineState.Running:
        this.ExecuteInstruction();
        break;
      case MachineState.ReadingInput:
        this.ReadInput();
        break;
    }
  }
}


  Initialize() seeds the random number generator, places the first routine on the stack, and then sets the state to Running. ExecuteInstruction() decodes and executes a single operation. If that operation is 'read' or 'read_char' it then switches the state to ReadingInput. ReadInput() checks the input buffer. If input has been terminated by a carriage return or a 'terminating character' specified by a table in memory it then switches the state back to Running.

  We synchronize on a private object and only run one machine cycle at a time to allow the front-end to determine the nature of the loop between cycles as well as details like how many threads may service the machine.

Next time I'll go into detail about how to decode an operation and execute it.

Thursday, September 17, 2009

The Troll Room - Randomness in the Z-machine

This is a small room with passages to the east and south and a forbidding hole leading west. Bloodstains and deep scratches (perhaps made by an axe) mar the walls.
A nasty-looking troll, brandishing a bloody axe, blocks all passages out of the room.
Your sword has begun to glow very brightly.
The troll swings his axe, and it nicks your arm as you dodge.

>attack troll with sword

A good slash, but it misses the troll by a mile.The axe crashes against the rock, throwing sparks!

>attack troll with sword
The troll is disarmed by a subtle feint past his guard.The troll, angered and humiliated, recovers his weapon. He appears to have an axe to grind with you.

>attack troll with sword
A quick stroke, but the troll is on guard.The flat of the troll's axe hits you delicately on the head, knocking you out.Conquering his fears, the troll puts you to death.It appears that that last blow was too much for you. I'm afraid you are dead.

**** You have died **** .


  A familiar scene to anyone who has played Zork, the troll room is likely a player's first encounter with random (or pseudo-random if we are being pedantic) behavior in the Z-machine. In fact it is possible to die immediately upon entering the room! While this may not be the best possible use of randomness in a game, it serves as a good example of the Z-machine's ability to generate random outcomes. I've chosen the random number generator as the first Z-machine component to look at in detail because it is relatively simple, self-contained and remains constant throughout the different versions of Z-machine design.

  The random number generator is invoked through a single opcode which takes a single operand and stores a result, both of which are 16-bit values. The Z-machine specification outlines the expected behavior in two places: Section 2.4 and the details of the random opcode. What we need to do is look at the operand as a signed value, let's call it 'x', and do one of three things: If x is negative, we seed the generator with -x and return zero. If x is zero we seed the generator as randomly as possible and return zero. Finally, if x is positive we generate a random number 'r' such that 1<=r<=x and return it. Additionally, the author of the specification, Graham Nelson, recommends an algorithm where seed values 1 to 999 change the behavior of the generator from producing random numbers to sequential ones mainly to assist game authors with testing. For example: "random -10" would place the generator in 'counting' mode and subsequent calls to random with positive ranges will produce 1,2,3...,9,10,1,2,3... and so on until the generator is seeded with another value. We now have all the behavior we need to implement an abstract base class. An abstract class allows the actual random number generation algorithm to be replaced if need arises without rewriting our class or risk of losing our recommended behavior. Here's our class:

/// <summary>
/// The Z-machine random number generator.
/// </summary>
public abstract class RandomNumberGenerator
{
  private short count;
  private bool countingMode;
  private int seed;
  public event EventHandler<GetRandomSeedEventArgs> GetRandomSeed;

  protected int Seed
  {
    get
    {
      return this.seed;
    }

    private set
    {
      this.seed = value;
      if (value > 0 && value < 1000)
      {
        this.countingMode = true;
        this.count = 0;
        return;
      }

      if (value == 0)
      {
        var args = new GetRandomSeedEventArgs();
        this.GetRandomSeed(this, args);
        this.seed = args.Seed;
      }

      this.countingMode = false;
      this.SeedGenerator();
    }
  }

  /// <summary>
  /// Generates a random number within a given range.
  /// </summary>
  /// <param name="range">
  /// The range of possible results.
  /// </param>
  /// <returns>
  /// A random number within the given range.
  /// </returns>
  public short Generate(short range)
  {
    if (range < 1)
    {
      this.Seed = -range;
      return 0;
    }

    if (this.countingMode)
    {
      int temp = this.count % this.Seed;
      this.count = (short)(temp + 1);
      return (short)((temp % range) + 1);
    }

    return (short)(((this.GetNext() & 0x7fff) % range) + 1);
  }

  /// <summary>
  /// Initializes the generator.
  /// </summary>
  public void Initialize()
  {
    this.Seed = 0;
  }

  /// <summary>
  /// Gets the next random number.
  /// </summary>
  /// <returns>
  /// The random number.
  /// </returns>
  protected abstract short GetNext();

  /// <summary>
  /// Seeds the generator.
  /// </summary>
  protected abstract void SeedGenerator();
}

Some explanation:
  GetRandomSeedEventArgs is a simple class derived from EventArgs which contains a single read-write property of type int called Seed. This allows the generator to raise an event when it needs a randomly valued seed. This occurs whenever Random is called with a zero operand, or the generator is initialized, which happens at machine startup and during a restart.
  The reason we do this: this.GetNext() & 0x7fff is to ensure the result is within the expected range because the return value is signed and could possibly be negative if a derived class implements GetNext() badly. This looks a little ugly, but if we use an unsigned return type we lose CLS compliance.

  Now that we have a base class, we can implement a real generator. This one is my C# implementation of the Mersenne Twister - mt19937ar:

/// <summary>
/// The Z-machine random number generator using Mersenne Twister algorithm.
/// </summary>
internal sealed class MersenneTwister : RandomNumberGenerator
{
  private const uint TemperingMaskB = 0x9d2c5680;
  private const uint TemperingMaskC = 0xefc60000;
  private const uint InitializationMultipler = 0x6c078965;
  private const uint LowerMask = 0x7fffffff;
  private const uint M = 397;
  private const uint MatrixA = 0x9908b0df;
  private const uint N = 624;
  private const uint UpperMask = 0x80000000;
  private static readonly ImmutableArray<uint> mag01 = InitializeMag01();
  private readonly uint[] stateVector = new uint[N];
  private uint stateVectorIndex;

  /// <summary>
  /// Gets the next random number.
  /// </summary>
  /// <returns>
  /// The random number.
  /// </returns>
  protected override short GetNext()
  {
    uint result;
    if (this.stateVectorIndex >= N)
    {
      int kk;
      for (kk = 0; kk < N - M; kk++)
      {
        result = (this.stateVector[kk] & UpperMask) (this.stateVector[kk + 1] & LowerMask);
        this.stateVector[kk] = this.stateVector[kk + M] ^ (result >> 1) ^ mag01[(int)(result & 1)];
      }

      for (; kk < N - 1; kk++)
      {
        result = (this.stateVector[kk] & UpperMask) (this.stateVector[kk + 1] & LowerMask);
        this.stateVector[kk] = this.stateVector[kk + M - N] ^ (result >> 1) ^ mag01[(int)(result & 1)];
      }

      result = (this.stateVector[N - 1] & UpperMask) (this.stateVector[0] & LowerMask);
      this.stateVector[N - 1] = this.stateVector[M - 1] ^ (result >> 1) ^ mag01[(int)(result & 1)];
      this.stateVectorIndex = 0;
    }

    result = this.stateVector[this.stateVectorIndex++];
    result ^= result >> 11;
    result ^= (result << 7) & TemperingMaskB;
    result ^= (result << 15) & TemperingMaskC;
    result ^= result >> 18;
    return (short)result;
  }

  /// <summary>
  /// Seeds the generator.
  /// </summary>
  protected override void SeedGenerator()
  {
    this.stateVector[0] = (uint)this.Seed;
    for (this.stateVectorIndex = 1; this.stateVectorIndex < N; this.stateVectorIndex++)
    {
      var previousValue = this.stateVector[this.stateVectorIndex - 1];
      this.stateVector[this.stateVectorIndex] = (InitializationMultipler * (previousValue ^ (previousValue >> 30))) + this.stateVectorIndex;
    }
  }

  /// <summary>
  /// Initializes the mag01.
  /// </summary>
  /// <returns>
  /// The mag01.
  /// </returns>
  private static ImmutableArray<uint> InitializeMag01()
  {
    var mag01Builder = new ImmutableArray<uint>.Builder(2);
    mag01Builder[0] = 0;
    mag01Builder[1] = MatrixA;
    return mag01Builder.MakeImmutable();
  }
}


Some explanation:
  For those familiar with this algorithm, you may notice I left out a small section in the GetNext method which initializes the generator with a fixed seed value if you forget to seed the generator before calling it. I think this is a bad idea as it hides the error. Failing to seed the generator produces a string of identical results which is much easier to identify and fix.
  You might also have noticed this code contains an immutable array class. The immutable array is something I found a need for in other parts of the Z-machine and have gotten into the habit of using it everywhwere. We'll see it again...

There we go! A complete implementation of the Z-machine random number capabilities.

Monday, September 14, 2009

The Great Underground Empire

A look beneath the surface
  The Z-machine is a 16-bit virtual machine using big endian addressing. A cpu, memory, and stack make up the bulk of the machine. The cpu has no registers, but instead relies on global variables in memory, routine specific local variables and temporary values on the stack to read and write data.

  Memory is divided into dynamic, static, and high regions. Dynamic memory can be written to whereas static memory is read only. High memory is also read only, but can be swapped out of ram and loaded when needed. This allowed Infocom's games to be larger than the memory size of the computers they ran on (which in the mid-80's was typically around 64k or less). Modern interpreters needn't worry about the high memory distinction unless running on a memory constrained platform. By today's standards that would probably mean running on a wristwatch or toaster.

  The stack is interesting and I think a source of confusion for people learning the Z-machine. Technically there are two stacks. The first maintains the state of each routine that has been called, such as local variables and the program counter. Calls to routines and returns from routines push/pop frames from this stack. The second stack holds temporary 16-bit values. Various opcodes can push/pop values from this stack. Because a routine is not allowed to pop values off the second stack that an earlier routine placed there, the second stack can be imagined as a bunch of independent stacks, each one belonging to a corresponding frame on the first stack. The basic structure of the stack can be implemented like this:

class CallStack
{
    private Stack<Frame> frames;
...
}

class Frame
{
    private Stack<ushort> localStack;
...
}

  I'll go over these components in more detail in future posts. Beyond these basic components of the Z-machine, there are smaller but still important pieces, such as the random number generator which I will talk about next time.

Sunday, September 6, 2009

Rebuilding the Z-machine

Hi,

  I've created this blog to share my experiences in building a Z-machine interpreter. My name is Mike Greger and I am a C# developer and a gamer. I got started with computers in the early 1980's when I became fascinated by the worlds found within so-called 'text adventures'. Some of my favorites were written by a company called Infocom.

What is a Z-machine?
  Infocom created a clever way of making their games available for the many types of computers on the market at that time by designing a virtual machine now known as the Z-machine. Each game only needed to be written once, for the Z-machine. A Z-machine interpreter was then written for each platform to run the games.
  Fast forward to 2009: Infocom is long gone. The Z-machine format has been reverse engineered by talented people and the specifications made available. Armed with this information developers have created Z-machine interpreters for platforms big and small.

If it's already been done, then why do it?
  The short answer is because of the challenge and learning opportunities it presents. I also like to think I can improve on some things. Some time ago I set out to write my own interpreter and succeeded, sort of. My interpreter played all the infocom games just as I remembered them. However, when I first started my project I did not have a lot of programming experience. Looking back later at what I had written I found the code amateurish and not suitable for easy reuse. I decided to rebuild it and came up with a set of design criteria for a class library written in C# 3.0 which implements the Z-machine:
  • Complete. Supports all features and Z-machine versions
  • Easy to use and extend
  • Decoupled from any particular interface (console, windows forms, WPF, etc.)
  • CLS compliant
  • Well documented
  • Thread-safe
  • Memory efficient for multiple instances
  • Not overly dependent on framework types
The last requirement is to help ensure portability to platforms like Mono or others which may not implement the entire .Net framework. Thread safety and multiple instance support allows the library to be useful in a web server or other multi-user environment.

Why write a blog about it?
  In the hope that someone will find something useful, maybe for their own interpreter or other project. While the published specifications contain a lot of useful information, Z-machine behavior can be complex and seeing a working example can be beneficial. Additionally I hope to make clear why I chose certain designs, rather than just publishing source code. Code comments tend to explain how something works, not why it was written that way in the first place.

Next time: Z-machine architecture.