Skip to content

Instantly share code, notes, and snippets.

@shovon
Last active August 29, 2015 14:05
Show Gist options
  • Save shovon/ec54e8ab09c974c928b3 to your computer and use it in GitHub Desktop.
Save shovon/ec54e8ab09c974c928b3 to your computer and use it in GitHub Desktop.
A virtual machine written in Go, just for practice.
package main
import (
"fmt"
"encoding/hex"
"log"
"errors"
)
const MAX_STACK_SIZE = 2048
const (
// Print and pop the value that is at the top of the stack.
PRINTBYTE byte = iota
// Push a value to the top of the stack
PUSH
// Add two values that are at the top of the stack
ADD
// Set a value to the register A
SET_A
// Set a value to the register B
SET_B
// Push register A's value to the top of the stack
PUSH_A
// Push register B's value to the top of the stack
PUSH_B
// Jump to the specified address, if the value at register A < register B
JUMP_LESS_THAN
// Jump to the specified address, if the value at register B > register A
JUMP_GREATER_THAN
// A fatal error occurred
DIE
// End the program gracefully
HALT
)
type opcode struct {
tag string
operands byte
}
var opcodes = map[byte]opcode{
PRINTBYTE : opcode{"PRINTBYTE", 0},
PUSH : opcode{"PUSH", 1},
HALT : opcode{"HALT", 0},
ADD : opcode{"ADD", 0},
SET_A : opcode{"SET_A", 1},
SET_B : opcode{"SET_B", 1},
PUSH_A : opcode{"PUSH_A", 0},
PUSH_B : opcode{"PUSH_B", 0},
JUMP_LESS_THAN : opcode{"JUMP_LESS_THAN", 2},
JUMP_GREATER_THAN: opcode{"JUMP_GREATER_THAN", 2},
DIE : opcode{"DIE", 0} }
var (
simple = []byte{HALT}
output = []byte{
PUSH, 0x42,
PRINTBYTE,
HALT }
fatal = []byte{
PRINTBYTE,
HALT }
die = []byte{DIE}
add = []byte{
PUSH, 0x02,
PUSH, 0x02,
ADD,
PRINTBYTE,
HALT }
setASetB = []byte{
SET_A, 0x02,
SET_B, 0x03,
PUSH_A,
PUSH_B,
PRINTBYTE,
PRINTBYTE,
HALT }
comparisonLessThan = []byte{
SET_A, 0x02, // 0
SET_B, 0x03, // 2
JUMP_LESS_THAN, 0x00, 0x08, // 4
HALT, // 7
PUSH, 0x02, // 8
PUSH, 0x03, // 10
ADD, // 12
PRINTBYTE, // 13
JUMP_GREATER_THAN, 0x00, 0x18, // 14
HALT, // 17
// Force a crash; not supposed to be here.
DIE, // 18
}
comparisonGreaterThan = []byte{
SET_A, 0x03, // 0
SET_B, 0x02, // 2
JUMP_GREATER_THAN, 0x00, 0x08, // 4
HALT, // 7
PUSH, 0x02, // 8
PUSH, 0x03, // 10
ADD, // 12
PRINTBYTE, // 13
JUMP_LESS_THAN, 0x00, 0x18, // 14
HALT, // 17
// Force a crash; not supposed to be here.
DIE, // 18
}
)
var a byte = 0
var b byte = 0
var stack = [MAX_STACK_SIZE]byte{}
var sp int = -1
var pc int = -1
var trace string
var errString string
func push(v byte) error {
sp++
if sp == MAX_STACK_SIZE {
return errors.New("Maximum stack size exceeded!")
}
stack[sp] = v
return nil
}
func pop() (byte, error) {
sp--
if sp < -1 { return 0, errors.New("Nothing in stack!") }
return stack[sp+1], nil
}
const TABS = "\t\t\t"
func runtrace(instructions []byte) {
opcode := opcodes[instructions[pc]]
uintpc := uint16(pc)
switch opcode.operands {
case 1:
var operand1 byte = 0
if pc+1 < len(instructions) {
operand1 = instructions[pc+1]
}
trace = fmt.Sprintf(
"%s\t%s %s%s%s\n",
trace,
hex.EncodeToString([]byte{byte(uintpc>>8),byte(uintpc)}),
opcode.tag,
TABS,
hex.EncodeToString([]byte{operand1}) )
case 2:
var operand1 byte = 0
var operand2 byte = 0
if pc+1 < len(instructions) {
operand1 = instructions[pc+1]
}
if pc+2 < len(instructions) {
operand2 = instructions[pc+2]
}
trace = fmt.Sprintf(
"%s\t%s %s%s%s%s\n",
trace,
hex.EncodeToString([]byte{byte(uintpc>>8),byte(uintpc)}),
opcode.tag,
TABS,
hex.EncodeToString([]byte{operand1}),
hex.EncodeToString([]byte{operand2}) )
default:
trace = fmt.Sprintf(
"%s\t%s %s\n",
trace,
hex.EncodeToString([]byte{byte(uintpc>>8),byte(uintpc)}),
opcode.tag )
}
}
func exec(instructions []byte) {
fmt.Printf("Program output:\n\n")
program:
for {
pc++
if pc >= len(instructions) || pc >= 65535 {
break program
}
runtrace(instructions)
instruction := instructions[pc]
switch instruction {
case HALT:
break program
case PRINTBYTE:
value, err := pop(); if err != nil { break program }
fmt.Printf("0x%s\n", hex.EncodeToString([]byte{value}))
case PUSH:
pc++
err := push(instructions[pc]); if err != nil {
errString = err.Error(); break program
}
case ADD:
a, err := pop(); if err != nil {
errString = err.Error(); break program
}
b, err := pop(); if err != nil {
errString = err.Error(); break program
}
err = push(a + b)
case SET_A:
pc++
a = instructions[pc]
case SET_B:
pc++
b = instructions[pc]
case PUSH_A:
push(a)
case PUSH_B:
push(b)
case DIE:
errString = "Forced termination!"
break program
case JUMP_LESS_THAN:
if pc + 2 >= len(instructions) {
errString = "No more instructions left!"
break program
}
address := int(instructions[pc+1]<<8) + int(instructions[pc+2]) - 1
pc += 2
if a < b {
pc = address
}
case JUMP_GREATER_THAN:
if pc + 2 >= int(len(instructions)) {
errString = "No more instructions left!"
}
address := int(instructions[pc+1]<<8) + int(instructions[pc+2]) - 1
pc += 2
if a > b {
pc = address
}
default:
errString = "Unsupported opcode!"
break program
}
}
fmt.Printf("\nTrace:\n\n%s\n", trace)
if errString != "" {
log.Fatal(errString)
}
}
func main() {
exec(comparisonGreaterThan)
fmt.Printf("Done\n")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment