Tuesday, October 20, 2009

Basic operations and branches

  Now that we have some pieces in place to run operations, let's look at a simple operation for the Z-Machine that is used in all of its versions:

///
/// Operation 20 (ADD).
///
protected override void Operation20()
{
  var num1 = (short)this.Operands[0];
  var num2 = (short)this.Operands[1];
  this.Store((ushort)(num1 + num2));
}

  This obviously performs a simple signed addition and stores the result. Math and comparison operations in the Z-machine use signed numbers, but many other operations treat operands and memory values as unsigned.

Here's another:

///
/// Operation 3 (JG).
///
protected override void Operation3()
{
  var num1 = (short)this.Operands[0];
  var num2 = (short)this.Operands[1];
  this.Branch(num1 > num2);
}

  This operation compares two operands and branches if the first is greater than the second. I mentioned branch instructions before but now we will see exactly how they work.

protected void Branch(bool condition)
{
  byte branchData = this.Memory.ReadByte(this.CallStack.ProgramCounter++);
  var offset = (ushort)(branchData & 63);
  if ((branchData & 64) != 64)
  {
    offset = (ushort)((offset << 8) + this.Memory.ReadByte(this.CallStack.ProgramCounter++));
  }

  if (condition ^ ((branchData & 128) != 128))
  {
    if (offset > 1)
    {
      this.Jump((short)(offset > 8191 ? offset - 16384 : offset));
    }
    else
    {
      this.Return(offset, this.CallStack.EndRoutine());
    }
  }
}

  We begin by reading a byte pointed to by the program counter. The top two bits are used as flags, while the remaining six bits are the offset value. If the second highest bit is not set, we need to read another byte and combine this byte with the current offset to produce a single 14-bit signed offset. If the top bit is not set, then the branch condition is inverted and we branch on false instead of true. In any case, if the branch is taken, the program counter jumps based on the offset. There is a special case where if the offset is one or zero we return that value from the current routine immediately instead of jumping.

  The actual jump is implemented as another method. This is simply because it is identical to the behavior of the unconditional jump operation (opcode #140), so I factored it out into a separate method even though it is just a one-liner:

protected void Jump(short offset)
{
  this.CallStack.ProgramCounter += offset - 2;
}

  The other math and comparison operators are implemented similarly. They are:

#1 Jump if equal (Sometimes used with more than two operands, in which case the branch is taken if the first operand is equal to any of the others)
#2 Jump if less than
#3 Jump if greater than
#20 Add
#21 Subtract
#22 Multiply
#23 Divide
#24 Modulus
#128 Jump if zero

  Next time I'll look at some other seemingly simple operations that can be a source of problems for first time interpreter writers.

No comments:

Post a Comment