Move vm into subdir

This commit is contained in:
Ian Gulliver
2021-11-16 20:41:57 -10:00
parent cf33a52392
commit db33045391
10 changed files with 1 additions and 1 deletions

8
vm/go.mod Normal file
View File

@@ -0,0 +1,8 @@
module github.com/firestuff/subcoding/vm
go 1.17
require (
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/pkg/errors v0.9.1 // indirect
)

4
vm/go.sum Normal file
View File

@@ -0,0 +1,4 @@
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

52
vm/instruction.go Normal file
View File

@@ -0,0 +1,52 @@
package vm
import "bytes"
import "github.com/lunixbochs/struc"
import "github.com/pkg/errors"
type Instruction struct {
OpCode OpCodeType
Reserved [4]byte
Operand1 Operand
Operand2 Operand
opHandler opHandler `struc:"skip"`
}
const instructionBytes = 32
func NewInstructionFromByteCode(byteCode []byte) (*Instruction, error) {
instr := &Instruction{}
reader := bytes.NewReader(byteCode)
err := struc.Unpack(reader, instr)
if err != nil {
return nil, errors.Wrap(err, "Error decoding instruction")
}
return instr, nil
}
func NewInstructionsFromByteCode(byteCode []byte) ([]*Instruction, error) {
instrs := []*Instruction{}
for start := 0; start < len(byteCode); start += instructionBytes {
chunk := byteCode[start : start+instructionBytes]
instr, err := NewInstructionFromByteCode(chunk)
if err != nil {
return nil, errors.Wrapf(err, "At byte offset %d", start)
}
instrs = append(instrs, instr)
}
if len(instrs) == 0 || instrs[len(instrs)-1].OpCode != OpReturn {
// Add implicit return at the end of each function
instrs = append(instrs, &Instruction{
OpCode: OpReturn,
})
}
return instrs, nil
}

39
vm/memory.go Normal file
View File

@@ -0,0 +1,39 @@
package vm
import "fmt"
type memory struct {
entries []uint64
}
func newMemory(size uint64) *memory {
return &memory{
entries: make([]uint64, size),
}
}
func (mem *memory) readUnsigned(index uint64) (uint64, error) {
if index >= uint64(len(mem.entries)) {
return 0, fmt.Errorf("Invalid memory index: %016x", index)
}
return mem.entries[index], nil
}
func (mem *memory) mustReadUnsigned(index uint64) uint64 {
value, err := mem.readUnsigned(index)
if err != nil {
panic(err)
}
return value
}
func (mem *memory) writeUnsigned(index uint64, value uint64) error {
if index >= uint64(len(mem.entries)) {
return fmt.Errorf("Invalid memory index: %016x", index)
}
mem.entries[index] = value
return nil
}

51
vm/opcode.go Normal file
View File

@@ -0,0 +1,51 @@
package vm
type OpCodeType uint32
const (
OpNoOp OpCodeType = 0x00000000
OpNop = OpNoOp
OpCall = 0x00000001
OpCal = OpCall
OpReturn = 0x00000002
OpRet = OpReturn
OpMove = 0x00000100
OpMov = OpMove
OpAdd = 0x00000200
OpSubtract = 0x00000201
OpSub = OpSubtract
OpMultiply = 0x00000202
OpMul = OpMultiply
OpDivideUnsigned = 0x00000203
OpDivU = OpDivideUnsigned
OpDivideSigned = 0x00000204
OpDivS = OpDivideSigned
OpIsEqual = 0x00000300
OpEq = OpIsEqual
OpIsLessThanUnsigned = 0x00000301
OpLTU = OpIsLessThanUnsigned
OpIsLessThanSigned = 0x00000302
OpLTS = OpIsLessThanSigned
OpIsGreaterThanUnsigned = 0x00000303
OpGTU = OpIsGreaterThanUnsigned
OpIsGreaterThanSigned = 0x00000304
OpGTS = OpIsGreaterThanSigned
OpIsLessThanOrEqualUnsigned = 0x00000305
OpLTEU = OpIsLessThanOrEqualUnsigned
OpIsLessThanOrEqualSigned = 0x00000306
OpLTES = OpIsLessThanOrEqualSigned
OpIsGreaterThanOrEqualUnsigned = 0x00000307
OpGTEU = OpIsGreaterThanOrEqualUnsigned
OpIsGreaterThanOrEqualSigned = 0x00000308
OpGTES = OpIsGreaterThanOrEqualSigned
OpJump = 0x00000400
OpJmp = OpJump
OpJumpIfTrue = 0x00000401
OpJmpT = OpJumpIfTrue
OpJumpIfFalse = 0x00000402
OpJmpF = OpJumpIfFalse
)

15
vm/operand.go Normal file
View File

@@ -0,0 +1,15 @@
package vm
type OperandType uint8
const (
Literal OperandType = 0
FunctionMemoryIndex = 1
GlobalMemoryIndex = 2
)
type Operand struct {
Type OperandType
Reserved [3]byte
Value uint64
}

149
vm/ophandler.go Normal file
View File

@@ -0,0 +1,149 @@
package vm
type opHandler func(*State, *Instruction)
var opHandlers = map[OpCodeType]opHandler{
OpNoOp: (*State).handleNoOp,
OpCall: (*State).handleCall,
OpReturn: (*State).handleReturn,
OpMove: (*State).handleMove,
OpAdd: (*State).handleAdd,
OpSubtract: (*State).handleSubtract,
OpMultiply: (*State).handleMultiply,
OpDivideUnsigned: (*State).handleDivideUnsigned,
OpDivideSigned: (*State).handleDivideSigned,
OpIsEqual: (*State).handleIsEqual,
OpIsLessThanUnsigned: (*State).handleIsLessThanUnsigned,
OpIsLessThanSigned: (*State).handleIsLessThanSigned,
OpIsGreaterThanUnsigned: (*State).handleIsGreaterThanUnsigned,
OpIsGreaterThanSigned: (*State).handleIsGreaterThanSigned,
OpIsLessThanOrEqualUnsigned: (*State).handleIsLessThanOrEqualUnsigned,
OpIsLessThanOrEqualSigned: (*State).handleIsLessThanOrEqualSigned,
OpIsGreaterThanOrEqualUnsigned: (*State).handleIsGreaterThanOrEqualUnsigned,
OpIsGreaterThanOrEqualSigned: (*State).handleIsGreaterThanOrEqualSigned,
OpJump: (*State).handleJump,
OpJumpIfTrue: (*State).handleJumpIfTrue,
OpJumpIfFalse: (*State).handleJumpIfFalse,
}
func (state *State) handleNoOp(instr *Instruction) {
}
func (state *State) handleCall(instr *Instruction) {
in := state.readSigned(&instr.Operand1)
state.call(in)
}
func (state *State) handleReturn(instr *Instruction) {
state.ret()
}
func (state *State) handleMove(instr *Instruction) {
in := state.readUnsigned(&instr.Operand2)
state.writeUnsigned(&instr.Operand1, in)
}
func (state *State) handleAdd(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.writeUnsigned(&instr.Operand1, in1+in2)
}
func (state *State) handleSubtract(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.writeUnsigned(&instr.Operand1, in1-in2)
}
func (state *State) handleMultiply(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.writeUnsigned(&instr.Operand1, in1*in2)
}
func (state *State) handleDivideUnsigned(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.writeUnsigned(&instr.Operand1, in1/in2)
}
func (state *State) handleDivideSigned(instr *Instruction) {
in1 := state.readSigned(&instr.Operand1)
in2 := state.readSigned(&instr.Operand2)
state.writeSigned(&instr.Operand1, in1/in2)
}
func (state *State) handleIsEqual(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.comparisonResult = (in1 == in2)
}
func (state *State) handleIsLessThanUnsigned(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.comparisonResult = (in1 < in2)
}
func (state *State) handleIsLessThanSigned(instr *Instruction) {
in1 := state.readSigned(&instr.Operand1)
in2 := state.readSigned(&instr.Operand2)
state.comparisonResult = (in1 < in2)
}
func (state *State) handleIsGreaterThanUnsigned(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.comparisonResult = (in1 > in2)
}
func (state *State) handleIsGreaterThanSigned(instr *Instruction) {
in1 := state.readSigned(&instr.Operand1)
in2 := state.readSigned(&instr.Operand2)
state.comparisonResult = (in1 > in2)
}
func (state *State) handleIsLessThanOrEqualUnsigned(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.comparisonResult = (in1 <= in2)
}
func (state *State) handleIsLessThanOrEqualSigned(instr *Instruction) {
in1 := state.readSigned(&instr.Operand1)
in2 := state.readSigned(&instr.Operand2)
state.comparisonResult = (in1 <= in2)
}
func (state *State) handleIsGreaterThanOrEqualUnsigned(instr *Instruction) {
in1 := state.readUnsigned(&instr.Operand1)
in2 := state.readUnsigned(&instr.Operand2)
state.comparisonResult = (in1 >= in2)
}
func (state *State) handleIsGreaterThanOrEqualSigned(instr *Instruction) {
in1 := state.readSigned(&instr.Operand1)
in2 := state.readSigned(&instr.Operand2)
state.comparisonResult = (in1 >= in2)
}
func (state *State) handleJump(instr *Instruction) {
in := state.readSigned(&instr.Operand1)
state.jump(in)
}
func (state *State) handleJumpIfTrue(instr *Instruction) {
if state.comparisonResult == true {
state.handleJump(instr)
}
}
func (state *State) handleJumpIfFalse(instr *Instruction) {
if state.comparisonResult == false {
state.handleJump(instr)
}
}

15
vm/stackframe.go Normal file
View File

@@ -0,0 +1,15 @@
package vm
type stackFrame struct {
previousFunctionIndex int64
previousInstructionIndex int64
functionMemory *memory
}
func newStackFrame(state *State) *stackFrame {
return &stackFrame{
previousFunctionIndex: state.functionIndex,
previousInstructionIndex: state.instructionIndex,
functionMemory: newMemory(16),
}
}

165
vm/state.go Normal file
View File

@@ -0,0 +1,165 @@
package vm
import "fmt"
import "github.com/pkg/errors"
type State struct {
running bool
err error
functions [][]*Instruction
functionIndex int64
instructionIndex int64
comparisonResult bool
globalMemory *memory
stack []*stackFrame
}
func NewState(functions [][]*Instruction) (*State, error) {
return &State{
functions: functions,
globalMemory: newMemory(16),
}, nil
}
func NewStateFromByteCode(byteCodes [][]byte) (*State, error) {
functions := [][]*Instruction{}
for i, byteCode := range byteCodes {
instrs, err := NewInstructionsFromByteCode(byteCode)
if err != nil {
return nil, errors.Wrapf(err, "At function index %d", i)
}
functions = append(functions, instrs)
}
return NewState(functions)
}
func (state *State) Execute() {
state.setHandlers()
state.call(0)
state.running = true
for state.running {
state.processInstruction()
}
}
func (state *State) stackFrame() *stackFrame {
return state.stack[len(state.stack)-1]
}
func (state *State) function() []*Instruction {
return state.functions[state.functionIndex]
}
func (state *State) setError(err error) {
state.err = err
state.running = false
}
func (state *State) setHandlers() {
for _, fnc := range state.functions {
for _, instr := range fnc {
handler, found := opHandlers[instr.OpCode]
if !found {
state.setError(fmt.Errorf("Invalid OpCode: 0x%08x", instr.OpCode))
return
}
instr.opHandler = handler
}
}
}
func (state *State) processInstruction() {
fnc := state.function()
instr := fnc[state.instructionIndex]
state.instructionIndex += 1
instr.opHandler(state, instr)
}
func (state *State) readUnsigned(op *Operand) uint64 {
switch op.Type {
case Literal:
return op.Value
case FunctionMemoryIndex:
value, err := state.stackFrame().functionMemory.readUnsigned(op.Value)
if err != nil {
state.setError(err)
}
return value
case GlobalMemoryIndex:
value, err := state.globalMemory.readUnsigned(op.Value)
if err != nil {
state.setError(err)
}
return value
default:
state.setError(fmt.Errorf("Unknown operand type: 0x%02x", op.Type))
return 0
}
}
func (state *State) readSigned(op *Operand) int64 {
return int64(state.readUnsigned(op))
}
func (state *State) writeUnsigned(op *Operand, value uint64) {
switch op.Type {
case Literal:
state.setError(fmt.Errorf("Write to literal operand"))
case FunctionMemoryIndex:
err := state.stackFrame().functionMemory.writeUnsigned(op.Value, value)
if err != nil {
state.setError(err)
}
case GlobalMemoryIndex:
err := state.globalMemory.writeUnsigned(op.Value, value)
if err != nil {
state.setError(err)
}
default:
state.setError(fmt.Errorf("Unknown operand type: 0x%02x", op.Type))
}
}
func (state *State) writeSigned(op *Operand, value int64) {
state.writeUnsigned(op, uint64(value))
}
func (state *State) call(functionOffset int64) {
if state.functionIndex+functionOffset >= int64(len(state.functions)) {
state.setError(fmt.Errorf("Invalid function call index: %d + %d = %d", state.functionIndex, functionOffset, state.functionIndex+functionOffset))
return
}
stackFrame := newStackFrame(state)
state.stack = append(state.stack, stackFrame)
state.functionIndex += functionOffset
state.instructionIndex = 0
}
func (state *State) ret() {
state.functionIndex = state.stackFrame().previousFunctionIndex
state.instructionIndex = state.stackFrame().previousInstructionIndex
state.stack = state.stack[:len(state.stack)-1]
if len(state.stack) == 0 {
state.running = false
}
}
func (state *State) jump(instructionOffset int64) {
// -1 accounts for the +1 processInstruction()
state.instructionIndex += instructionOffset - 1
}

47
vm/state_test.go Normal file
View File

@@ -0,0 +1,47 @@
package vm
import "encoding/hex"
import "strings"
import "testing"
func TestFirst(t *testing.T) {
asm := [][]string{
[]string{
"0000020000000000010000000000000000000000000000000000000000000001",
"0000000100000000000000000000000000000001000000000000000000000000",
"0000030100000000010000000000000000000000000000000000000000000003",
"000004010000000000000000fffffffffffffffd000000000000000000000000",
},
[]string{
"0000020000000000020000000000000000000000000000000000000000000001",
},
}
functionByteCode := [][]byte{}
for _, fnc := range asm {
fncString := strings.Join(fnc, "")
byteCode, err := hex.DecodeString(fncString)
if err != nil {
t.Fatal(err)
}
functionByteCode = append(functionByteCode, byteCode)
}
state, err := NewStateFromByteCode(functionByteCode)
if err != nil {
t.Fatal(err)
}
state.Execute()
if state.err != nil {
t.Fatal(state)
}
if state.globalMemory.mustReadUnsigned(0) != 3 {
t.Fatal(state.globalMemory.mustReadUnsigned(0))
}
}