LARA

Register Machine Model in Scala

Contrast this to stack machine in VM for Expressions

sealed abstract class Value
  case class Immediate(const : Int) extends Value
 
  sealed abstract class Address extends Value
    case class Register(r : Int) extends Address
    case class Memory(a : Int) extends Address
    case class Indirect(r : Int) extends Address
    case class IndirectOffset(r : Int, offset : Int) extends Address
 
sealed abstract class ArithOp
  case object Mul extends ArithOp
  case object Add extends ArithOp
  case object Sub extends ArithOp
 
sealed abstract class Instruction
  case class Quad(destination : Address,
  		  op : ArithOp,
	 	  source1 : Value,
		  source2 : Value) extends Instruction
  /* Possible restrictions to above, on various architectures:
       - source1,source2 must be registers (use Move if needed)
       - destination = source1
       - indirect addressing only for some registers
       - register 0 always stores constant 0
   */
  case class Move(destination : Address,
		  source : Value) extends Instruction
  case class BranchIfZero(r : Int, pcOffset : Int) extends Instruction
  case class BranchIndirect(r : Int) extends Instruction
  case class JumpSubroutine(pcOffset : Int) extends Instruction
 
class Computer(var code : Array[Instruction],
               var pc   : Int,        // program counter (current instruction)
               var reg  : Array[Int], // registers
	       var mem  : Array[Int]  // memory
	      )
{
  def compute(op : ArithOp, x : Int, y : Int) : Int = op match {
    case Mul => x * y
    case Add => x + y
    case Sub => x - y
  }
  def load(v : Value) : Int = v match {
    case Immediate(const) => const
    case Register(r) => reg(r)
    case Memory(a) => mem(a)
    case Indirect(r) => mem(reg(r))
    case IndirectOffset(r,offset) => mem(reg(r) + offset)
  }
  def store(a : Address, x : Int) = a match {
    case Register(r) => { reg(r) = x }
    case Memory(a) => { mem(a) = x }
    case Indirect(r) => { mem(reg(r)) = x }
    case IndirectOffset(r,offset) => { mem(reg(r) + offset) = x }
  }
  def step = code(pc) match {
    case Quad(destination,op,source1,source2) => {
      store(destination, compute(op, load(source1), load(source2)))
      pc = pc + 1
    }
    case Move(destination, source) => { 
      store(destination, load(source))
      pc = pc + 1
    }
    case BranchIfZero(r, pcOffset) => {
      if (reg(r)==0) 
	pc = pc + pcOffset
      else 
	pc = pc + 1
    }
    case BranchIndirect(r) => { pc = reg(r) }
    case JumpSubroutine(pcOffset) => {
      reg(15) = pc + 1
      pc = pc + pcOffset
    }
  }
  def run = {
    while (pc < code.length)
      step
  }
}
 
object Test {
    /* Memory layout:
         0  : 
         ...
         100 : x (initially 7)
         104 : y (initially 2)
	 108 : z (initially 6)
       Execute statement: 
         x = x*y + y*z + x*z
       Result should be 68.
    */
  val xAddr = 100
  val yAddr = 104
  val zAddr = 108
 
  def initMem() : Array[Int] = {
    val memory = new Array[Int](2048)
    memory(xAddr) = 7
    memory(yAddr) = 2
    memory(zAddr) = 6
    memory
  }
 
  def main(args : Array[String]) = {
    val registers = new Array[Int](32)
 
    // heavy use of memory addressing
    val codeMemory : Array[Instruction] = List(
      Quad(Register(1), Mul, Memory(xAddr), Memory(yAddr)),
      Quad(Register(2), Mul, Memory(yAddr), Memory(zAddr)),
      Quad(Register(1), Add, Register(1), Register(2)),
      Quad(Register(2), Mul, Memory(xAddr), Memory(zAddr)),
      Quad(Memory(xAddr), Add, Register(1), Register(2))
    ).toArray
 
    var computer = new Computer(codeMemory,0,registers, initMem)
    computer.run
    println(computer.mem(xAddr))
 
    // all arithmetic operations use registers,
    // always fresh register unless value stored previously
    val codeManyReg : Array[Instruction] = List(
      Move(Register(1), Memory(xAddr)), 
      Move(Register(2), Memory(yAddr)),
      Move(Register(3), Memory(zAddr)),
      Quad(Register(4), Mul, Register(1), Register(2)),
      Quad(Register(5), Mul, Register(2), Register(3)),
      Quad(Register(6), Mul, Register(1), Register(3)),
      Quad(Register(7), Add, Register(4), Register(5)),
      Quad(Register(8), Add, Register(7), Register(6)),
      Move(Memory(xAddr), Register(8))
    ).toArray
 
    computer = new Computer(codeManyReg,0,registers, initMem)
    computer.run
    println(computer.mem(xAddr))
 
    // all arithmetic operations use registers,
    // minimizing memory traffic and registers
    val codeSmart : Array[Instruction] = List(
      Move(Register(1), Memory(xAddr)), 
      Move(Register(2), Memory(yAddr)),
      Move(Register(3), Memory(zAddr)),
      Quad(Register(4), Mul, Register(1), Register(2)),
      Quad(Register(5), Mul, Register(2), Register(3)),
      Quad(Register(4), Add, Register(4), Register(5)),
      Quad(Register(5), Mul, Register(1), Register(3)),
      Quad(Register(4), Add, Register(4), Register(5)),
      Move(Memory(xAddr), Register(4))
    ).toArray
 
    computer = new Computer(codeSmart,0,registers, initMem)
    computer.run
    println(computer.mem(xAddr))
 
    // Question: can we do it with only one memory load for each x,y,z
    // but with even fewer registers used?
    val codeFourReg : Array[Instruction] = List(
      Move(Register(1), Memory(xAddr)), 
      Move(Register(2), Memory(yAddr)),
      Move(Register(3), Memory(zAddr)),
      Quad(Register(4), Mul, Register(1), Register(2)),
      Quad(Register(2), Mul, Register(2), Register(3)),
      Quad(Register(4), Add, Register(4), Register(2)),
      Quad(Register(1), Mul, Register(1), Register(3)),
      Quad(Register(4), Add, Register(4), Register(1)),
      Move(Memory(xAddr), Register(4))
    ).toArray
 
    computer = new Computer(codeFourReg,0,registers, initMem)
    computer.run
    println(computer.mem(xAddr))
  }
}