Compiler optimization first iteration (#165)
* dead code elimination phase 1 * combine dead code elimination with return fix code * remove last instruction tracking from compiler code (not needed) * fix a symbol table block scope bug * add some more tests
This commit is contained in:
parent
01fe30f02a
commit
b9c1c92d2d
9 changed files with 415 additions and 97 deletions
|
@ -5,8 +5,7 @@ import "github.com/d5/tengo/compiler/source"
|
||||||
// CompilationScope represents a compiled instructions
|
// CompilationScope represents a compiled instructions
|
||||||
// and the last two instructions that were emitted.
|
// and the last two instructions that were emitted.
|
||||||
type CompilationScope struct {
|
type CompilationScope struct {
|
||||||
instructions []byte
|
instructions []byte
|
||||||
lastInstructions [2]EmittedInstruction
|
symbolInit map[string]bool
|
||||||
symbolInit map[string]bool
|
sourceMap map[int]source.Pos
|
||||||
sourceMap map[int]source.Pos
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/d5/tengo"
|
"github.com/d5/tengo"
|
||||||
|
@ -292,6 +293,15 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case *ast.BlockStmt:
|
case *ast.BlockStmt:
|
||||||
|
if len(node.Stmts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.symbolTable = c.symbolTable.Fork(true)
|
||||||
|
defer func() {
|
||||||
|
c.symbolTable = c.symbolTable.Parent(false)
|
||||||
|
}()
|
||||||
|
|
||||||
for _, stmt := range node.Stmts {
|
for _, stmt := range node.Stmts {
|
||||||
if err := c.Compile(stmt); err != nil {
|
if err := c.Compile(stmt); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -404,8 +414,8 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add OpReturn if function returns nothing
|
// code optimization
|
||||||
c.fixReturn(node)
|
c.optimizeFunc(node)
|
||||||
|
|
||||||
freeSymbols := c.symbolTable.FreeSymbols()
|
freeSymbols := c.symbolTable.FreeSymbols()
|
||||||
numLocals := c.symbolTable.MaxSymbols()
|
numLocals := c.symbolTable.MaxSymbols()
|
||||||
|
@ -688,33 +698,6 @@ func (c *Compiler) addInstruction(b []byte) int {
|
||||||
return posNewIns
|
return posNewIns
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) setLastInstruction(op Opcode, pos int) {
|
|
||||||
c.scopes[c.scopeIndex].lastInstructions[1] = c.scopes[c.scopeIndex].lastInstructions[0]
|
|
||||||
|
|
||||||
c.scopes[c.scopeIndex].lastInstructions[0].Opcode = op
|
|
||||||
c.scopes[c.scopeIndex].lastInstructions[0].Position = pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) lastInstructionIs(op Opcode) bool {
|
|
||||||
if len(c.currentInstructions()) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.scopes[c.scopeIndex].lastInstructions[0].Opcode == op
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) removeLastInstruction() {
|
|
||||||
lastPos := c.scopes[c.scopeIndex].lastInstructions[0].Position
|
|
||||||
|
|
||||||
if c.trace != nil {
|
|
||||||
c.printTrace(fmt.Sprintf("DELET %s",
|
|
||||||
FormatInstructions(c.scopes[c.scopeIndex].instructions[lastPos:], lastPos)[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.scopes[c.scopeIndex].instructions = c.currentInstructions()[:lastPos]
|
|
||||||
c.scopes[c.scopeIndex].lastInstructions[0] = c.scopes[c.scopeIndex].lastInstructions[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) replaceInstruction(pos int, inst []byte) {
|
func (c *Compiler) replaceInstruction(pos int, inst []byte) {
|
||||||
copy(c.currentInstructions()[pos:], inst)
|
copy(c.currentInstructions()[pos:], inst)
|
||||||
|
|
||||||
|
@ -731,36 +714,88 @@ func (c *Compiler) changeOperand(opPos int, operand ...int) {
|
||||||
c.replaceInstruction(opPos, inst)
|
c.replaceInstruction(opPos, inst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixReturn appends "return" statement at the end of the function if
|
// optimizeFunc performs some code-level optimization for the current function instructions
|
||||||
// 1) the function does not have a "return" statement at the end.
|
// it removes unreachable (dead code) instructions and adds "returns" instruction if needed.
|
||||||
// 2) or, there are jump instructions that jump to the end of the function.
|
func (c *Compiler) optimizeFunc(node ast.Node) {
|
||||||
func (c *Compiler) fixReturn(node ast.Node) {
|
// any instructions between RETURN and the function end
|
||||||
var appendReturn bool
|
// or instructions between RETURN and jump target position
|
||||||
|
// are considered as unreachable.
|
||||||
|
|
||||||
if !c.lastInstructionIs(OpReturn) {
|
// pass 1. identify all jump destinations
|
||||||
appendReturn = true
|
var dsts []int
|
||||||
} else {
|
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
||||||
var lastOp Opcode
|
switch opcode {
|
||||||
insts := c.scopes[c.scopeIndex].instructions
|
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
||||||
endPos := len(insts)
|
dsts = append(dsts, operands[0])
|
||||||
iterateInstructions(insts, func(pos int, opcode Opcode, operands []int) bool {
|
}
|
||||||
defer func() { lastOp = opcode }()
|
|
||||||
|
|
||||||
switch opcode {
|
return true
|
||||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
})
|
||||||
dst := operands[0]
|
sort.Ints(dsts) // sort jump positions
|
||||||
if dst == endPos && lastOp != OpReturn {
|
|
||||||
appendReturn = true
|
var newInsts []byte
|
||||||
return false
|
|
||||||
} else if dst > endPos {
|
// pass 2. eliminate dead code
|
||||||
panic(fmt.Errorf("wrong jump position: %d (end: %d)", dst, endPos))
|
posMap := make(map[int]int) // old position to new position
|
||||||
}
|
var dstIdx int
|
||||||
|
var deadCode bool
|
||||||
|
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
||||||
|
switch {
|
||||||
|
case opcode == OpReturn:
|
||||||
|
if deadCode {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
deadCode = true
|
||||||
|
case dstIdx < len(dsts) && pos == dsts[dstIdx]:
|
||||||
|
dstIdx++
|
||||||
|
deadCode = false
|
||||||
|
case deadCode:
|
||||||
return true
|
return true
|
||||||
})
|
}
|
||||||
|
|
||||||
|
posMap[pos] = len(newInsts)
|
||||||
|
newInsts = append(newInsts, MakeInstruction(opcode, operands...)...)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// pass 3. update jump positions
|
||||||
|
var lastOp Opcode
|
||||||
|
var appendReturn bool
|
||||||
|
endPos := len(newInsts)
|
||||||
|
iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool {
|
||||||
|
switch opcode {
|
||||||
|
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
||||||
|
newDst, ok := posMap[operands[0]]
|
||||||
|
if ok {
|
||||||
|
copy(newInsts[pos:], MakeInstruction(opcode, newDst))
|
||||||
|
} else if endPos == operands[0] {
|
||||||
|
// there's a jump instruction that jumps to the end of function
|
||||||
|
// compiler should append "return".
|
||||||
|
appendReturn = true
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("invalid jump position: %d", newDst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastOp = opcode
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if lastOp != OpReturn {
|
||||||
|
appendReturn = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pass 4. update source map
|
||||||
|
newSourceMap := make(map[int]source.Pos)
|
||||||
|
for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap {
|
||||||
|
newPos, ok := posMap[pos]
|
||||||
|
if ok {
|
||||||
|
newSourceMap[newPos] = srcPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.scopes[c.scopeIndex].instructions = newInsts
|
||||||
|
c.scopes[c.scopeIndex].sourceMap = newSourceMap
|
||||||
|
|
||||||
|
// append "return"
|
||||||
if appendReturn {
|
if appendReturn {
|
||||||
c.emit(node, OpReturn, 0)
|
c.emit(node, OpReturn, 0)
|
||||||
}
|
}
|
||||||
|
@ -775,7 +810,6 @@ func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int {
|
||||||
inst := MakeInstruction(opcode, operands...)
|
inst := MakeInstruction(opcode, operands...)
|
||||||
pos := c.addInstruction(inst)
|
pos := c.addInstruction(inst)
|
||||||
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
|
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
|
||||||
c.setLastInstruction(opcode, pos)
|
|
||||||
|
|
||||||
if c.trace != nil {
|
if c.trace != nil {
|
||||||
c.printTrace(fmt.Sprintf("EMIT %s",
|
c.printTrace(fmt.Sprintf("EMIT %s",
|
||||||
|
|
|
@ -49,7 +49,8 @@ func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, s
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleCompiler.fixReturn(node)
|
// code optimization
|
||||||
|
moduleCompiler.optimizeFunc(node)
|
||||||
|
|
||||||
compiledFunc := moduleCompiler.Bytecode().MainFunction
|
compiledFunc := moduleCompiler.Bytecode().MainFunction
|
||||||
compiledFunc.NumLocals = symbolTable.MaxSymbols()
|
compiledFunc.NumLocals = symbolTable.MaxSymbols()
|
||||||
|
|
124
compiler/compiler_optimize_test.go
Normal file
124
compiler/compiler_optimize_test.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/d5/tengo/compiler"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompilerDeadCode(t *testing.T) {
|
||||||
|
expect(t, `
|
||||||
|
func() {
|
||||||
|
a := 4
|
||||||
|
return a
|
||||||
|
|
||||||
|
b := 5 // dead code from here
|
||||||
|
c := a
|
||||||
|
return b
|
||||||
|
}`,
|
||||||
|
bytecode(
|
||||||
|
concat(
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
|
objectsArray(
|
||||||
|
intObject(4),
|
||||||
|
intObject(5),
|
||||||
|
compiledFunction(0, 0,
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
||||||
|
|
||||||
|
expect(t, `
|
||||||
|
func() {
|
||||||
|
if true {
|
||||||
|
return 5
|
||||||
|
a := 4 // dead code from here
|
||||||
|
b := a
|
||||||
|
return b
|
||||||
|
} else {
|
||||||
|
return 4
|
||||||
|
c := 5 // dead code from here
|
||||||
|
d := c
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}`, bytecode(
|
||||||
|
concat(
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
|
objectsArray(
|
||||||
|
intObject(5),
|
||||||
|
intObject(4),
|
||||||
|
compiledFunction(0, 0,
|
||||||
|
compiler.MakeInstruction(compiler.OpTrue),
|
||||||
|
compiler.MakeInstruction(compiler.OpJumpFalsy, 9),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
||||||
|
|
||||||
|
expect(t, `
|
||||||
|
func() {
|
||||||
|
a := 1
|
||||||
|
for {
|
||||||
|
if a == 5 {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
5 + 5
|
||||||
|
return 20
|
||||||
|
b := a
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}`, bytecode(
|
||||||
|
concat(
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 4),
|
||||||
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
|
objectsArray(
|
||||||
|
intObject(1),
|
||||||
|
intObject(5),
|
||||||
|
intObject(10),
|
||||||
|
intObject(20),
|
||||||
|
compiledFunction(0, 0,
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpEqual),
|
||||||
|
compiler.MakeInstruction(compiler.OpJumpFalsy, 19),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
||||||
|
compiler.MakeInstruction(compiler.OpPop),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
||||||
|
|
||||||
|
expect(t, `
|
||||||
|
func() {
|
||||||
|
if true {
|
||||||
|
return 5
|
||||||
|
a := 4 // dead code from here
|
||||||
|
b := a
|
||||||
|
return b
|
||||||
|
} else {
|
||||||
|
return 4
|
||||||
|
c := 5 // dead code from here
|
||||||
|
d := c
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}`, bytecode(
|
||||||
|
concat(
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
|
objectsArray(
|
||||||
|
intObject(5),
|
||||||
|
intObject(4),
|
||||||
|
compiledFunction(0, 0,
|
||||||
|
compiler.MakeInstruction(compiler.OpTrue),
|
||||||
|
compiler.MakeInstruction(compiler.OpJumpFalsy, 9),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 1)))))
|
||||||
|
}
|
69
compiler/compiler_scopes_test.go
Normal file
69
compiler/compiler_scopes_test.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/d5/tengo/compiler"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompilerScopes(t *testing.T) {
|
||||||
|
expect(t, `
|
||||||
|
if a := 1; a {
|
||||||
|
a = 2
|
||||||
|
b := a
|
||||||
|
} else {
|
||||||
|
a = 3
|
||||||
|
b := a
|
||||||
|
}`, bytecode(
|
||||||
|
concat(
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpJumpFalsy, 27),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetGlobal, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpJump, 39),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetGlobal, 1)),
|
||||||
|
objectsArray(
|
||||||
|
intObject(1),
|
||||||
|
intObject(2),
|
||||||
|
intObject(3))))
|
||||||
|
|
||||||
|
expect(t, `
|
||||||
|
func() {
|
||||||
|
if a := 1; a {
|
||||||
|
a = 2
|
||||||
|
b := a
|
||||||
|
} else {
|
||||||
|
a = 3
|
||||||
|
b := a
|
||||||
|
}
|
||||||
|
}`, bytecode(
|
||||||
|
concat(
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||||
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
|
objectsArray(
|
||||||
|
intObject(1),
|
||||||
|
intObject(2),
|
||||||
|
intObject(3),
|
||||||
|
compiledFunction(0, 0,
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpJumpFalsy, 22),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpDefineLocal, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpJump, 31),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpSetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
||||||
|
compiler.MakeInstruction(compiler.OpDefineLocal, 1),
|
||||||
|
compiler.MakeInstruction(compiler.OpReturn, 0)))))
|
||||||
|
}
|
|
@ -358,17 +358,15 @@ func TestCompiler_Compile(t *testing.T) {
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
compiler.MakeInstruction(compiler.OpArray, 3),
|
compiler.MakeInstruction(compiler.OpArray, 3),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
||||||
compiler.MakeInstruction(compiler.OpIndex),
|
compiler.MakeInstruction(compiler.OpIndex),
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
objectsArray(
|
objectsArray(
|
||||||
intObject(1),
|
intObject(1),
|
||||||
intObject(2),
|
intObject(2),
|
||||||
intObject(3),
|
intObject(3))))
|
||||||
intObject(1),
|
|
||||||
intObject(1))))
|
|
||||||
|
|
||||||
expect(t, `{a: 2}[2 - 1]`,
|
expect(t, `{a: 2}[2 - 1]`,
|
||||||
bytecode(
|
bytecode(
|
||||||
|
@ -376,15 +374,14 @@ func TestCompiler_Compile(t *testing.T) {
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpMap, 2),
|
compiler.MakeInstruction(compiler.OpMap, 2),
|
||||||
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 12),
|
compiler.MakeInstruction(compiler.OpBinaryOp, 12),
|
||||||
compiler.MakeInstruction(compiler.OpIndex),
|
compiler.MakeInstruction(compiler.OpIndex),
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
objectsArray(
|
objectsArray(
|
||||||
stringObject("a"),
|
stringObject("a"),
|
||||||
intObject(2),
|
intObject(2),
|
||||||
intObject(2),
|
|
||||||
intObject(1))))
|
intObject(1))))
|
||||||
|
|
||||||
expect(t, `[1, 2, 3][:]`,
|
expect(t, `[1, 2, 3][:]`,
|
||||||
|
@ -411,15 +408,14 @@ func TestCompiler_Compile(t *testing.T) {
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
compiler.MakeInstruction(compiler.OpArray, 3),
|
compiler.MakeInstruction(compiler.OpArray, 3),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpSliceIndex),
|
compiler.MakeInstruction(compiler.OpSliceIndex),
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
objectsArray(
|
objectsArray(
|
||||||
intObject(1),
|
intObject(1),
|
||||||
intObject(2),
|
intObject(2),
|
||||||
intObject(3),
|
intObject(3),
|
||||||
intObject(0),
|
intObject(0))))
|
||||||
intObject(2))))
|
|
||||||
|
|
||||||
expect(t, `[1, 2, 3][:2]`,
|
expect(t, `[1, 2, 3][:2]`,
|
||||||
bytecode(
|
bytecode(
|
||||||
|
@ -429,14 +425,13 @@ func TestCompiler_Compile(t *testing.T) {
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||||
compiler.MakeInstruction(compiler.OpArray, 3),
|
compiler.MakeInstruction(compiler.OpArray, 3),
|
||||||
compiler.MakeInstruction(compiler.OpNull),
|
compiler.MakeInstruction(compiler.OpNull),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpSliceIndex),
|
compiler.MakeInstruction(compiler.OpSliceIndex),
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
objectsArray(
|
objectsArray(
|
||||||
intObject(1),
|
intObject(1),
|
||||||
intObject(2),
|
intObject(2),
|
||||||
intObject(3),
|
intObject(3))))
|
||||||
intObject(2))))
|
|
||||||
|
|
||||||
expect(t, `[1, 2, 3][0:]`,
|
expect(t, `[1, 2, 3][0:]`,
|
||||||
bytecode(
|
bytecode(
|
||||||
|
@ -522,13 +517,12 @@ func TestCompiler_Compile(t *testing.T) {
|
||||||
intObject(1),
|
intObject(1),
|
||||||
intObject(2),
|
intObject(2),
|
||||||
compiledFunction(0, 0,
|
compiledFunction(0, 0,
|
||||||
compiler.MakeInstruction(compiler.OpTrue), // 0000
|
compiler.MakeInstruction(compiler.OpTrue), // 0000
|
||||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 12), // 0001
|
compiler.MakeInstruction(compiler.OpJumpFalsy, 9), // 0001
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0), // 0004
|
compiler.MakeInstruction(compiler.OpConstant, 0), // 0004
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1), // 0007
|
compiler.MakeInstruction(compiler.OpReturn, 1), // 0007
|
||||||
compiler.MakeInstruction(compiler.OpJump, 17), // 0008
|
compiler.MakeInstruction(compiler.OpConstant, 1), // 0009
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1), // 0011
|
compiler.MakeInstruction(compiler.OpReturn, 1))))) // 0012
|
||||||
compiler.MakeInstruction(compiler.OpReturn, 1))))) // 0014
|
|
||||||
|
|
||||||
expect(t, `func() { 1; if(true) { 2 } else { 3 }; 4 }`,
|
expect(t, `func() { 1; if(true) { 2 } else { 3 }; 4 }`,
|
||||||
bytecode(
|
bytecode(
|
||||||
|
@ -879,21 +873,19 @@ func() {
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||||
compiler.MakeInstruction(compiler.OpEqual),
|
compiler.MakeInstruction(compiler.OpEqual),
|
||||||
compiler.MakeInstruction(compiler.OpAndJump, 23),
|
compiler.MakeInstruction(compiler.OpAndJump, 23),
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpNotEqual),
|
compiler.MakeInstruction(compiler.OpNotEqual),
|
||||||
compiler.MakeInstruction(compiler.OpOrJump, 34),
|
compiler.MakeInstruction(compiler.OpOrJump, 34),
|
||||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||||
compiler.MakeInstruction(compiler.OpBinaryOp, 39),
|
compiler.MakeInstruction(compiler.OpBinaryOp, 39),
|
||||||
compiler.MakeInstruction(compiler.OpPop)),
|
compiler.MakeInstruction(compiler.OpPop)),
|
||||||
objectsArray(
|
objectsArray(
|
||||||
intObject(0),
|
intObject(0),
|
||||||
intObject(0),
|
|
||||||
intObject(1),
|
|
||||||
intObject(1))))
|
intObject(1))))
|
||||||
|
|
||||||
expectError(t, `import("user1")`, "module 'user1' not found") // unknown module name
|
expectError(t, `import("user1")`, "module 'user1' not found") // unknown module name
|
||||||
|
@ -1028,19 +1020,17 @@ func traceCompile(input string, symbols map[string]objects.Object) (res *compile
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Compile(parsed)
|
err = c.Compile(parsed)
|
||||||
|
res = c.Bytecode()
|
||||||
|
res.RemoveDuplicates()
|
||||||
{
|
{
|
||||||
trace = append(trace, fmt.Sprintf("Compiler Trace:\n%s", strings.Join(tr.Out, "")))
|
trace = append(trace, fmt.Sprintf("Compiler Trace:\n%s", strings.Join(tr.Out, "")))
|
||||||
|
trace = append(trace, fmt.Sprintf("Compiled Constants:\n%s", strings.Join(res.FormatConstants(), "\n")))
|
||||||
bytecode := c.Bytecode()
|
trace = append(trace, fmt.Sprintf("Compiled Instructions:\n%s\n", strings.Join(res.FormatInstructions(), "\n")))
|
||||||
trace = append(trace, fmt.Sprintf("Compiled Constants:\n%s", strings.Join(bytecode.FormatConstants(), "\n")))
|
|
||||||
trace = append(trace, fmt.Sprintf("Compiled Instructions:\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n")))
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res = c.Bytecode()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,9 +64,7 @@ func (t *SymbolTable) Resolve(name string) (symbol *Symbol, depth int, ok bool)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !t.block {
|
depth++
|
||||||
depth++
|
|
||||||
}
|
|
||||||
|
|
||||||
// if symbol is defined in parent table and if it's not global/builtin
|
// if symbol is defined in parent table and if it's not global/builtin
|
||||||
// then it's free variable.
|
// then it's free variable.
|
||||||
|
|
|
@ -83,12 +83,12 @@ func TestSymbolTable(t *testing.T) {
|
||||||
resolveExpect(t, global, "a", globalSymbol("a", 0), 0)
|
resolveExpect(t, global, "a", globalSymbol("a", 0), 0)
|
||||||
resolveExpect(t, local1, "d", localSymbol("d", 0), 0)
|
resolveExpect(t, local1, "d", localSymbol("d", 0), 0)
|
||||||
resolveExpect(t, local1, "a", globalSymbol("a", 0), 1)
|
resolveExpect(t, local1, "a", globalSymbol("a", 0), 1)
|
||||||
resolveExpect(t, local3, "a", globalSymbol("a", 0), 2)
|
resolveExpect(t, local3, "a", globalSymbol("a", 0), 3)
|
||||||
resolveExpect(t, local3, "d", freeSymbol("d", 0), 1)
|
resolveExpect(t, local3, "d", freeSymbol("d", 0), 2)
|
||||||
resolveExpect(t, local3, "r", localSymbol("r", 1), 0)
|
resolveExpect(t, local3, "r", localSymbol("r", 1), 0)
|
||||||
resolveExpect(t, local2Block2, "k", localSymbol("k", 4), 0)
|
resolveExpect(t, local2Block2, "k", localSymbol("k", 4), 0)
|
||||||
resolveExpect(t, local2Block2, "e", localSymbol("e", 0), 0)
|
resolveExpect(t, local2Block2, "e", localSymbol("e", 0), 1)
|
||||||
resolveExpect(t, local2Block2, "b", globalSymbol("b", 1), 2)
|
resolveExpect(t, local2Block2, "b", globalSymbol("b", 1), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func symbol(name string, scope compiler.SymbolScope, index int) *compiler.Symbol {
|
func symbol(name string, scope compiler.SymbolScope, index int) *compiler.Symbol {
|
||||||
|
|
103
runtime/vm_scopes_test.go
Normal file
103
runtime/vm_scopes_test.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package runtime_test
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestVMScopes(t *testing.T) {
|
||||||
|
// shadowed global variable
|
||||||
|
expect(t, `
|
||||||
|
c := 5
|
||||||
|
if a := 3; a {
|
||||||
|
c := 6
|
||||||
|
} else {
|
||||||
|
c := 7
|
||||||
|
}
|
||||||
|
out = c
|
||||||
|
`, nil, 5)
|
||||||
|
|
||||||
|
// shadowed local variable
|
||||||
|
expect(t, `
|
||||||
|
func() {
|
||||||
|
c := 5
|
||||||
|
if a := 3; a {
|
||||||
|
c := 6
|
||||||
|
} else {
|
||||||
|
c := 7
|
||||||
|
}
|
||||||
|
out = c
|
||||||
|
}()
|
||||||
|
`, nil, 5)
|
||||||
|
|
||||||
|
// 'b' is declared in 2 separate blocks
|
||||||
|
expect(t, `
|
||||||
|
c := 5
|
||||||
|
if a := 3; a {
|
||||||
|
b := 8
|
||||||
|
c = b
|
||||||
|
} else {
|
||||||
|
b := 9
|
||||||
|
c = b
|
||||||
|
}
|
||||||
|
out = c
|
||||||
|
`, nil, 8)
|
||||||
|
|
||||||
|
// shadowing inside for statement
|
||||||
|
expect(t, `
|
||||||
|
a := 4
|
||||||
|
b := 5
|
||||||
|
for i:=0;i<3;i++ {
|
||||||
|
b := 6
|
||||||
|
for j:=0;j<2;j++ {
|
||||||
|
b := 7
|
||||||
|
a = i*j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = a`, nil, 2)
|
||||||
|
|
||||||
|
// shadowing variable declared in init statement
|
||||||
|
expect(t, `
|
||||||
|
if a := 5; a {
|
||||||
|
a := 6
|
||||||
|
out = a
|
||||||
|
}`, nil, 6)
|
||||||
|
expect(t, `
|
||||||
|
a := 4
|
||||||
|
if a := 5; a {
|
||||||
|
a := 6
|
||||||
|
out = a
|
||||||
|
}`, nil, 6)
|
||||||
|
expect(t, `
|
||||||
|
a := 4
|
||||||
|
if a := 0; a {
|
||||||
|
a := 6
|
||||||
|
out = a
|
||||||
|
} else {
|
||||||
|
a := 7
|
||||||
|
out = a
|
||||||
|
}`, nil, 7)
|
||||||
|
expect(t, `
|
||||||
|
a := 4
|
||||||
|
if a := 0; a {
|
||||||
|
out = a
|
||||||
|
} else {
|
||||||
|
out = a
|
||||||
|
}`, nil, 0)
|
||||||
|
|
||||||
|
// shadowing function level
|
||||||
|
expect(t, `
|
||||||
|
a := 5
|
||||||
|
func() {
|
||||||
|
a := 6
|
||||||
|
a = 7
|
||||||
|
}()
|
||||||
|
out = a
|
||||||
|
`, nil, 5)
|
||||||
|
expect(t, `
|
||||||
|
a := 5
|
||||||
|
func() {
|
||||||
|
if a := 7; true {
|
||||||
|
a = 8
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
out = a
|
||||||
|
`, nil, 5)
|
||||||
|
}
|
Loading…
Reference in a new issue