fbec8f49ae/vm/golang/ngaro.go

User picture

Commiter: Charles Childers

Author: Charles Childers

Revision: fbec8f49ae


File Size: 8.58 KB

(February 15, 2010 01:11 UTC) Over 2 years ago

sync against hg repo for golang vm

 
Show/hide line numbers
// Original Ngaro Virtual Machine and Uki framework:
//	Copyright (C) 2008, 2009, Charles Childers
// Go port
//	Copyright 2009 JGL
// Public Domain or LICENSE file

/*
	Ngaro virtual machines.

	Ngaro is a portable virtual machine / emulator for a dual
	stack processor and various I/O devices. The instruction set
	is concise (31 core instructions), and the basic I/O devices
	are kept minimal. For more information see
		http://github.com/crcx/ngaro

	Communication with the virtual machine is done through
	int chanels for input (port 1) and output (port 2). The
	port 4 can be used to save the current image to a file,
	while port 5 is used to get information about the virtual
	machine.

	In addition to normal ngaro features, this Go version
	allows to launch new cores writing 1 to the port 13. The
	new core will start running the same (shared) image
	from the address at the top of the stack. Cores communicate
	writing and id to port 1 (to receive) and 2 (to send). To
	remove a channel write its id to ports 1 and 2 and wait.

	Some useful words:

	: :go ( a- ) 1 13 out wait ; ( start new core with ip set to a )
	: go ( "-  ) ' :go ; ( parse a word and run it on a new core )
	: ->c ( xy- ) 2 out wait ; ( send x to channel y )
	: c-> ( x-y ) 1 out wait 1 in ; ( receive y from channel x )
	: delchan ( x- ) dup 1 out 2 out wait ; ( delete channel )
	: cores ( -x ) -7 5 out wait 5 in ; ( number of running cores )
	: chans ( -x ) -8 5 out wait 5 in ; ( number of channels in use )

	Example:

	create ch1 ( It is not needed to create channels, but a )
	create ch2 ( good way to get a semi-random new id )
	: odd ( - ) 1 repeat dup ch1 ->c 2 + again ;
	: even ( - ) 2 repeat dup ch2 ->c 2 + again ;
	go odd
	go even
	ch1 c-> . ch1 c-> . ch1 c-> . ( prints: 1 3 5 )
	ch2 c-> . ch2 c-> . ch2 c-> . ( prints: 2 4 6 )
	ch1 delchan ( clean up )
	ch2 delchan

	Usage from go: See gonga.go

*/

package ngaro

import (
	"fmt"
	"os"
	"bufio"
	B "encoding/binary"
)

const (
	// Instruction set
	Nop = iota
	Lit
	Dup
	Drop
	Swap
	Push
	Pop
	Call
	Jump
	Return
	GtJump
	LtJump
	NeJump
	EqJump
	Fetch
	Store
	Add
	Sub
	Mul
	Dinod
	And
	Or
	Xor
	ShL
	ShR
	ZeroExit
	Inc
	Dec
	In
	Out
	Wait

	stackDepth = 100
	chanBuffer = 128
	nports     = 64
)

type NgaroVM struct {
	cores      int
	size       int
	img        []int
	dump       string
	channel    map[int]chan int
	EOI        chan bool
	OutputDone chan bool
	Off        chan bool
}

var Verbose bool = false

var ClearScreen func() = func() {}

func perror(s ...interface{}) {
	if Verbose {
		fmt.Fprint(os.Stderr, s)
	}
}

func (vm *NgaroVM) Read(p []byte) (n int, err os.Error) {
	for i, _ := range p {
		if x := <-vm.channel[1]; x < 0 {
			ClearScreen()
		} else {
			p[i] = byte(x)
			n++
		}
	}
	return
}

func (vm *NgaroVM) Write(p []byte) (n int, err os.Error) {
	for _, b := range p {
		vm.channel[0] <- int(b)
		n++
	}
	return
}

func LoadDump(filename string, img []int, start int) int {
	r, err := os.Open(filename, os.O_RDONLY, 0)
	if err != nil {
		return 0
	}
	br := bufio.NewReader(r)
	// Skip header (shebang and lines starting with #)
	for c, err := br.ReadByte(); err == nil && c == '#'; c, err = br.ReadByte() {
		br.ReadBytes('\n')
	}
	br.UnreadByte()
	var ui uint32
	var i int
	for i, _ = range img[start:] {
		if err := B.Read(br, B.LittleEndian, &ui); err != nil {
			break
		}
		img[i] = int(ui)
	}
	perror(" [ Ngaro: loaded image ", filename, " ] ")
	return i
}

func (vm *NgaroVM) WriteDump(filename string) {
	w, err := os.Open(filename, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		return
	}
	for _, i := range vm.img {
		if err = B.Write(w, B.LittleEndian, uint32(i)); err != nil {
			perror(" [ Ngaro ERROR: writing ", filename, "] ")
		}
	}
	perror(" [ Ngaro: saved image to ", filename, " ( size:", vm.size, " ) ] ")
}

func (vm *NgaroVM) Channel(id int) chan int {
	// Returns a channel that can be used by any core. Channels 0 and
	// 1 are reserved as input and output, any other id can be used
	if c, ok := vm.channel[id]; ok {
		return c
	}
	vm.channel[id] = make(chan int)
	return vm.channel[id]
}

func (vm *NgaroVM) wait(port *[nports]int, tos int) (spdec int) {
	if port[0] == 1 {
		return
	}
	if port[1] > 1 {
		if port[2] == port[1] {
			vm.channel[port[1]] = nil, false
			port[1] = 0
			port[2] = 0
		} else {
			port[1] = <-vm.Channel(port[1])
		}
		port[0] = 1
	} else if port[2] > 1 {
		vm.Channel(port[2]) <- tos
		port[2] = 0
		port[0] = 1
		spdec = 1
	}
	switch 1 {
	case port[0]:
		return
	case port[1]: // Input (Port 1)
		var p int
		var eoi bool
		if !eoi {
			select {
			case p = <-vm.channel[0]:
				port[1] = p
			case <-vm.EOI:
				eoi = true
			}
		}
		if eoi {
			var ok bool
			port[1], ok = <-vm.channel[0]
			if !ok {
				vm.Off <- true
			}
		}
		port[0] = 1

	case port[2]: // Output (Port 2)
		vm.channel[1] <- tos
		<-vm.OutputDone
		port[2] = 0
		port[0] = 1
		spdec = 1

	case port[4]: // Save Image (Port 4)
		vm.WriteDump(vm.dump)
		port[4] = 0
		port[0] = 1

	case port[13]: // New core (Port 13)
		go vm.core(tos)
		port[13] = 0
		port[0] = 1
		spdec = 1
	}

	switch port[5] { // Capabilities (Port 5)
	case 0:
		return

	case -1: // Image size
		port[5] = vm.size
		port[0] = 1

	case -5: // Stack depth
		port[5] = stackDepth
		port[0] = 1

	case -6: // Address stack depth
		port[5] = stackDepth
		port[0] = 1

	case -7: // Number of cores
		port[5] = vm.cores
		port[0] = 1

	case -8: // Number of channels
		port[5] = len(vm.channel)
		port[0] = 1

	default:
		port[5] = 0
		port[0] = 1
	}
	return
}

func (vm *NgaroVM) core(ip int) {
	var x, y int
	var port [nports]int
	var sp, rsp int
	var tos int
	var data, addr [stackDepth]int
	current := vm.cores
	vm.cores++
	for ; ip < vm.size; ip++ {
		switch vm.img[ip] {
		case Nop:
		case Lit:
			sp++
			ip++
			data[sp] = vm.img[ip]
		case Dup:
			sp++
			data[sp] = data[sp-1]
		case Drop:
			data[sp] = 0
			sp--
		case Swap:
			data[sp], data[sp-1] = data[sp-1], data[sp]
		case Push:
			rsp++
			addr[rsp] = data[sp]
			sp--
		case Pop:
			sp++
			data[sp] = addr[rsp]
			rsp--
		case Call:
			ip++
			rsp++
			addr[rsp] = ip
			ip = vm.img[ip] - 1
		case Jump:
			ip++
			ip = vm.img[ip] - 1
		case Return:
			ip = addr[rsp]
			rsp--
		case GtJump:
			ip++
			if data[sp-1] > data[sp] {
				ip = vm.img[ip] - 1
			}
			sp = sp - 2
		case LtJump:
			ip++
			if data[sp-1] < data[sp] {
				ip = vm.img[ip] - 1
			}
			sp = sp - 2
		case NeJump:
			ip++
			if data[sp-1] != data[sp] {
				ip = vm.img[ip] - 1
			}
			sp = sp - 2
		case EqJump:
			ip++
			if data[sp-1] == data[sp] {
				ip = vm.img[ip] - 1
			}
			sp = sp - 2
		case Fetch:
			data[sp] = vm.img[data[sp]]
		case Store:
			vm.img[data[sp]] = data[sp-1]
			sp = sp - 2
		case Add:
			data[sp-1] += data[sp]
			data[sp] = 0
			sp--
		case Sub:
			data[sp-1] -= data[sp]
			data[sp] = 0
			sp--
		case Mul:
			data[sp-1] *= data[sp]
			data[sp] = 0
			sp--
		case Dinod:
			x = data[sp]
			y = data[sp-1]
			data[sp] = y / x
			data[sp-1] = y % x
		case And:
			x = data[sp]
			y = data[sp-1]
			sp--
			data[sp] = x & y
		case Or:
			x = data[sp]
			y = data[sp-1]
			sp--
			data[sp] = x | y
		case Xor:
			x = data[sp]
			y = data[sp-1]
			sp--
			data[sp] = x ^ y
		case ShL:
			x = data[sp]
			y = data[sp-1]
			sp--
			data[sp] = y << uint(x)
		case ShR:
			x = data[sp]
			y = data[sp-1]
			sp--
			data[sp] = y >> uint(x)
		case ZeroExit:
			if data[sp] == 0 {
				sp--
				ip = addr[rsp]
				rsp--
			}
		case Inc:
			data[sp]++
		case Dec:
			data[sp]--
		case In:
			if data[sp] < 0 || data[sp] > nports-1 {
				perror(" [ Ngaro ERROR: Invalid port ] ")
				break
			}
			x = data[sp]
			data[sp] = port[x]
			port[x] = 0
		case Out:
			if data[sp] < 0 || data[sp] > nports-1 {
				perror(" [ Ngaro ERROR: Invalid port ] ")
				break
			}
			port[0] = 0
			port[data[sp]] = data[sp-1]
			sp = sp - 2
		case Wait:
			sp -= vm.wait(&port, tos)
		default:
			ip = vm.size
		}
		// to avoid segfaults:
		if sp < 0 {
			perror(" [ Ngaro ERROR: Stack underflow ] ")
			sp = 0
		} else if stackDepth-sp < 2 {
			perror(" [ Ngaro ERROR: Stack overflow (2 elements droped) ] ")
			sp -= 2
		}
		tos = data[sp]
	}
	vm.cores--
	if current == 0 {
		vm.Off <- true
	}
}

func NewVM(image []int, size int, dump string) *NgaroVM {
	if len(image) > size {
		perror(" [ Ngaro ERROR: image too large ] ")
		return nil
	}
	vm := new(NgaroVM)
	vm.size = size
	vm.dump = dump
	vm.channel = make(map[int]chan int)
	vm.channel[0] = make(chan int, chanBuffer)
	vm.channel[1] = make(chan int, chanBuffer)
	vm.EOI = make(chan bool)
	vm.OutputDone = make(chan bool)
	vm.Off = make(chan bool)
	vm.img = make([]int, vm.size)
	for i, x := range image {
		vm.img[i] = x
	}
	perror(" [ Ngaro: image loaded ( size:", size, ") ] ")
	go vm.core(0)
	return vm
}