Skip to content

Commit

Permalink
feat: add vm engine implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
dikaeinstein committed May 25, 2021
1 parent 42c7766 commit 66e28d2
Show file tree
Hide file tree
Showing 27 changed files with 3,450 additions and 233 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_STORE
monkey
fibonacci
7 changes: 4 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ linters-settings:
exhaustive:
default-signifies-exhaustive: true
funlen:
lines: 150
statements: 75
lines: 200
statements: 150
gci:
local-prefixes: github.com/golangci/golangci-lint
goconst:
Expand Down Expand Up @@ -73,6 +73,7 @@ linters:
- dupl
- errcheck
- exhaustive
- exportloopref
- funlen
- gochecknoinits
- goconst
Expand All @@ -93,7 +94,6 @@ linters:
- noctx
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
Expand All @@ -113,6 +113,7 @@ issues:
- path: _test\.go
linters:
- gomnd
- dupl
- path: eval_test\.go
linters:
- goconst
Expand Down
2 changes: 1 addition & 1 deletion .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ blocks:
- name: Build Binary
commands:
- checkout
- sem-version go 1.15
- sem-version go 1.16
- make build
- mkdir bin
- mv monkey bin
Expand Down
31 changes: 31 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": 99800
},
{
"name": "Launch Benchmark",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/benchmark",
"args": ["-engine", "eval"]
},
{
"name": "Launch Main",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/cmd",

}
]
}
7 changes: 6 additions & 1 deletion ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ast

import (
"bytes"
"fmt"
"strings"

"github.com/dikaeinstein/monkey/token"
Expand Down Expand Up @@ -235,6 +236,7 @@ type FunctionLiteral struct {
Token token.Token // The 'fn' token
Parameters []*Identifier
Body *BlockStatement
Name string
}

func (fl *FunctionLiteral) expressionNode() {}
Expand All @@ -246,7 +248,10 @@ func (fl *FunctionLiteral) String() string {
for _, p := range fl.Parameters {
params = append(params, p.String())
}
out.WriteString("fn")
out.WriteString(fl.TokenLiteral())
if fl.Name != "" {
out.WriteString(fmt.Sprintf("<%s>", fl.Name))
}
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(")")
Expand Down
79 changes: 79 additions & 0 deletions benchmark/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"flag"
"fmt"
"time"

"github.com/dikaeinstein/monkey/compile"
"github.com/dikaeinstein/monkey/eval"
"github.com/dikaeinstein/monkey/lexer"
"github.com/dikaeinstein/monkey/object"
"github.com/dikaeinstein/monkey/parser"
"github.com/dikaeinstein/monkey/vm"
)

func main() {
engine := flag.String("engine", "vm", "use 'vm' or 'eval'")
flag.Parse()

var input = `
let fibonacci = fn(x) {
if (x == 0) {
0
} else {
if (x == 1) {
return 1;
} else {
fibonacci(x - 1) + fibonacci(x - 2);
}
}
};
fibonacci(35);
`

l := lexer.New(input)
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
fmt.Println("parser errors:", p.Errors())
return
}

var duration time.Duration
var result object.Object

if *engine == "vm" {
compiler := compile.NewCompilerWithBuiltins([]object.Object{})
err := compiler.Compile(program)
if err != nil {
fmt.Printf("compiler error: %s", err)
return
}

machine := vm.New(compiler.Bytecode())

start := time.Now()

err = machine.Run()
if err != nil {
fmt.Printf("vm error: %s", err)
return
}

duration = time.Since(start)
result = machine.LastPoppedStackElem()
} else {
env := object.NewEnvironment()
start := time.Now()
result = eval.Eval(program, env)
duration = time.Since(start)
}

fmt.Printf(
"engine=%s, result=%s, duration=%s\n",
*engine,
result.Inspect(),
duration)
}
106 changes: 88 additions & 18 deletions code/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,54 @@ import (
"fmt"
)

type Opcode byte

const (
OpConstant Opcode = iota
OpPop
OpAdd
OpSub
OpMul
OpDiv
OpTrue
OpFalse
OpEqual
OpNotEqual
OpGreaterThan
OpBang
OpMinus
OpJump
OpJumpNotTruthy
OpNull
OpGetGlobal
OpSetGlobal
OpArray
OpHash
OpIndex
OpCall
OpReturn
OpReturnValue
OpGetLocal
OpSetLocal
OpGetBuiltin
OpClosure
OpGetFree
OpCurrentClosure
)

// OperandWidth is the number of bytes an operand takes up
const (
_ = iota
OperandWidth1
OperandWidth2
)

const (
_ = iota
operandCount1
operandCount2
)

type Instructions []byte

func (ins Instructions) String() string {
Expand Down Expand Up @@ -39,27 +87,15 @@ func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
switch operandCount {
case 0:
return def.Name
case 1:
case operandCount1:
return fmt.Sprintf("%s %d", def.Name, operands[0])
case operandCount2:
return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1])
default:
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
}
}

type Opcode byte

const (
OpConstant Opcode = iota
OpAdd
)

// OperandWidth is the number of bytes an operand takes up
const (
_ = iota
_
OperandWidth2
)

// Definition helps make an Opcode readable
type Definition struct {
// Name of Opcode
Expand All @@ -69,8 +105,36 @@ type Definition struct {
}

var definitions = map[Opcode]*Definition{
OpConstant: {Name: "OpConstant", OperandWidths: []uint{OperandWidth2}},
OpAdd: {Name: "OpAdd"},
OpConstant: {Name: "OpConstant", OperandWidths: []uint{OperandWidth2}},
OpPop: {Name: "OpPop"},
OpAdd: {Name: "OpAdd"},
OpSub: {Name: "OpSub"},
OpMul: {Name: "OpMul"},
OpDiv: {Name: "OpDiv"},
OpTrue: {Name: "OpTrue"},
OpFalse: {Name: "OpFalse"},
OpEqual: {Name: "OpEqual"},
OpNotEqual: {Name: "OpNotEqual"},
OpGreaterThan: {Name: "OpGreaterThan"},
OpBang: {Name: "OpBang"},
OpMinus: {Name: "OpMinus"},
OpJump: {Name: "OpJump", OperandWidths: []uint{OperandWidth2}},
OpJumpNotTruthy: {Name: "OpJumpNotTruthy", OperandWidths: []uint{OperandWidth2}},
OpNull: {Name: "OpNull"},
OpGetGlobal: {Name: "OpGetGlobal", OperandWidths: []uint{OperandWidth2}},
OpSetGlobal: {Name: "OpSetGlobal", OperandWidths: []uint{OperandWidth2}},
OpArray: {Name: "OpArray", OperandWidths: []uint{OperandWidth2}},
OpHash: {Name: "OpHash", OperandWidths: []uint{OperandWidth2}},
OpIndex: {Name: "OpIndex"},
OpCall: {Name: "OpCall", OperandWidths: []uint{OperandWidth1}},
OpReturnValue: {Name: "OpReturnValue"},
OpReturn: {Name: "OpReturn"},
OpGetLocal: {Name: "OpGetLocal", OperandWidths: []uint{OperandWidth1}},
OpSetLocal: {Name: "OpSetLocal", OperandWidths: []uint{OperandWidth1}},
OpGetBuiltin: {Name: "OpGetBuiltin", OperandWidths: []uint{OperandWidth1}},
OpClosure: {Name: "OpClosure", OperandWidths: []uint{OperandWidth2, OperandWidth1}},
OpGetFree: {"OpGetFree", []uint{OperandWidth1}},
OpCurrentClosure: {Name: "OpCurrentClosure"},
}

func Lookup(op Opcode) (*Definition, error) {
Expand Down Expand Up @@ -105,6 +169,8 @@ func Make(op Opcode, operands ...int) []byte {
switch width {
case OperandWidth2:
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
case OperandWidth1:
instruction[offset] = byte(o)
}

offset += width
Expand All @@ -118,11 +184,13 @@ func Make(op Opcode, operands ...int) []byte {
func ReadOperands(def *Definition, ins []byte) (operandsRead []int, n uint) {
operandsRead = make([]int, len(def.OperandWidths))

var offset uint = 0
var offset uint
for i, width := range def.OperandWidths {
switch width {
case OperandWidth2:
operandsRead[i] = int(ReadUint16(ins[offset:]))
case OperandWidth1:
operandsRead[i] = int(ReadUint8(ins[offset:]))
}
offset += width
}
Expand All @@ -134,3 +202,5 @@ func ReadOperands(def *Definition, ins []byte) (operandsRead []int, n uint) {
func ReadUint16(ins Instructions) uint16 {
return binary.BigEndian.Uint16(ins)
}

func ReadUint8(ins Instructions) uint8 { return ins[0] }
30 changes: 27 additions & 3 deletions code/code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ func TestMake(t *testing.T) {
}{
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
{OpAdd, []int{}, []byte{byte(OpAdd)}},
{OpPop, []int{}, []byte{byte(OpPop)}},
{OpSub, []int{}, []byte{byte(OpSub)}},
{OpMul, []int{}, []byte{byte(OpMul)}},
{OpDiv, []int{}, []byte{byte(OpDiv)}},
{OpTrue, []int{}, []byte{byte(OpTrue)}},
{OpFalse, []int{}, []byte{byte(OpFalse)}},
{OpEqual, []int{}, []byte{byte(OpEqual)}},
{OpNotEqual, []int{}, []byte{byte(OpNotEqual)}},
{OpGreaterThan, []int{}, []byte{byte(OpGreaterThan)}},
{OpBang, []int{}, []byte{byte(OpBang)}},
{OpMinus, []int{}, []byte{byte(OpMinus)}},
{OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}},
{OpSetLocal, []int{255}, []byte{byte(OpSetLocal), 255}},
{OpSetLocal, []int{255}, []byte{byte(OpSetLocal), 255}},
{OpClosure, []int{65534, 255}, []byte{byte(OpClosure), 255, 254, 255}},
}

for _, tC := range testCases {
Expand All @@ -34,15 +49,23 @@ func TestMake(t *testing.T) {
func TestInstructionsString(t *testing.T) {
instructions := []Instructions{
Make(OpAdd),
Make(OpPop),
Make(OpConstant, 1),
Make(OpConstant, 2),
Make(OpConstant, 65535),
Make(OpGetLocal, 255),
Make(OpSetLocal, 255),
Make(OpClosure, 65535, 255),
}

expected := `0000 OpAdd
0001 OpConstant 1
0004 OpConstant 2
0007 OpConstant 65535
0001 OpPop
0002 OpConstant 1
0005 OpConstant 2
0008 OpConstant 65535
0011 OpGetLocal 255
0013 OpSetLocal 255
0015 OpClosure 65535 255
`

concatted := Instructions{}
Expand All @@ -63,6 +86,7 @@ func TestReadOperands(t *testing.T) {
bytesRead uint
}{
{OpConstant, []int{65535}, 2},
{OpGetLocal, []int{255}, 1},
}

for _, tC := range testCases {
Expand Down
Loading

0 comments on commit 66e28d2

Please sign in to comment.