Move vm into subdir
This commit is contained in:
8
vm/go.mod
Normal file
8
vm/go.mod
Normal 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
4
vm/go.sum
Normal 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
52
vm/instruction.go
Normal 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
39
vm/memory.go
Normal 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
51
vm/opcode.go
Normal 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
15
vm/operand.go
Normal 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
149
vm/ophandler.go
Normal 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
15
vm/stackframe.go
Normal 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
165
vm/state.go
Normal 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
47
vm/state_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user