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
|
||||
// and the last two instructions that were emitted.
|
||||
type CompilationScope struct {
|
||||
instructions []byte
|
||||
lastInstructions [2]EmittedInstruction
|
||||
symbolInit map[string]bool
|
||||
sourceMap map[int]source.Pos
|
||||
instructions []byte
|
||||
symbolInit map[string]bool
|
||||
sourceMap map[int]source.Pos
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
|
@ -292,6 +293,15 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
}
|
||||
|
||||
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 {
|
||||
if err := c.Compile(stmt); err != nil {
|
||||
return err
|
||||
|
@ -404,8 +414,8 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// add OpReturn if function returns nothing
|
||||
c.fixReturn(node)
|
||||
// code optimization
|
||||
c.optimizeFunc(node)
|
||||
|
||||
freeSymbols := c.symbolTable.FreeSymbols()
|
||||
numLocals := c.symbolTable.MaxSymbols()
|
||||
|
@ -688,33 +698,6 @@ func (c *Compiler) addInstruction(b []byte) int {
|
|||
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) {
|
||||
copy(c.currentInstructions()[pos:], inst)
|
||||
|
||||
|
@ -731,36 +714,88 @@ func (c *Compiler) changeOperand(opPos int, operand ...int) {
|
|||
c.replaceInstruction(opPos, inst)
|
||||
}
|
||||
|
||||
// fixReturn appends "return" statement at the end of the function if
|
||||
// 1) the function does not have a "return" statement at the end.
|
||||
// 2) or, there are jump instructions that jump to the end of the function.
|
||||
func (c *Compiler) fixReturn(node ast.Node) {
|
||||
var appendReturn bool
|
||||
// optimizeFunc performs some code-level optimization for the current function instructions
|
||||
// it removes unreachable (dead code) instructions and adds "returns" instruction if needed.
|
||||
func (c *Compiler) optimizeFunc(node ast.Node) {
|
||||
// any instructions between RETURN and the function end
|
||||
// or instructions between RETURN and jump target position
|
||||
// are considered as unreachable.
|
||||
|
||||
if !c.lastInstructionIs(OpReturn) {
|
||||
appendReturn = true
|
||||
} else {
|
||||
var lastOp Opcode
|
||||
insts := c.scopes[c.scopeIndex].instructions
|
||||
endPos := len(insts)
|
||||
iterateInstructions(insts, func(pos int, opcode Opcode, operands []int) bool {
|
||||
defer func() { lastOp = opcode }()
|
||||
// pass 1. identify all jump destinations
|
||||
var dsts []int
|
||||
iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
|
||||
switch opcode {
|
||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
||||
dsts = append(dsts, operands[0])
|
||||
}
|
||||
|
||||
switch opcode {
|
||||
case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
|
||||
dst := operands[0]
|
||||
if dst == endPos && lastOp != OpReturn {
|
||||
appendReturn = true
|
||||
return false
|
||||
} else if dst > endPos {
|
||||
panic(fmt.Errorf("wrong jump position: %d (end: %d)", dst, endPos))
|
||||
}
|
||||
return true
|
||||
})
|
||||
sort.Ints(dsts) // sort jump positions
|
||||
|
||||
var newInsts []byte
|
||||
|
||||
// pass 2. eliminate dead code
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
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...)
|
||||
pos := c.addInstruction(inst)
|
||||
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
|
||||
c.setLastInstruction(opcode, pos)
|
||||
|
||||
if c.trace != nil {
|
||||
c.printTrace(fmt.Sprintf("EMIT %s",
|
||||
|
|
|
@ -49,7 +49,8 @@ func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, s
|
|||
return nil, err
|
||||
}
|
||||
|
||||
moduleCompiler.fixReturn(node)
|
||||
// code optimization
|
||||
moduleCompiler.optimizeFunc(node)
|
||||
|
||||
compiledFunc := moduleCompiler.Bytecode().MainFunction
|
||||
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, 2),
|
||||
compiler.MakeInstruction(compiler.OpArray, 3),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||
compiler.MakeInstruction(compiler.OpBinaryOp, 11),
|
||||
compiler.MakeInstruction(compiler.OpIndex),
|
||||
compiler.MakeInstruction(compiler.OpPop)),
|
||||
objectsArray(
|
||||
intObject(1),
|
||||
intObject(2),
|
||||
intObject(3),
|
||||
intObject(1),
|
||||
intObject(1))))
|
||||
intObject(3))))
|
||||
|
||||
expect(t, `{a: 2}[2 - 1]`,
|
||||
bytecode(
|
||||
|
@ -376,15 +374,14 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpMap, 2),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||
compiler.MakeInstruction(compiler.OpBinaryOp, 12),
|
||||
compiler.MakeInstruction(compiler.OpIndex),
|
||||
compiler.MakeInstruction(compiler.OpPop)),
|
||||
objectsArray(
|
||||
stringObject("a"),
|
||||
intObject(2),
|
||||
intObject(2),
|
||||
intObject(1))))
|
||||
|
||||
expect(t, `[1, 2, 3][:]`,
|
||||
|
@ -411,15 +408,14 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||
compiler.MakeInstruction(compiler.OpArray, 3),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 4),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpSliceIndex),
|
||||
compiler.MakeInstruction(compiler.OpPop)),
|
||||
objectsArray(
|
||||
intObject(1),
|
||||
intObject(2),
|
||||
intObject(3),
|
||||
intObject(0),
|
||||
intObject(2))))
|
||||
intObject(0))))
|
||||
|
||||
expect(t, `[1, 2, 3][:2]`,
|
||||
bytecode(
|
||||
|
@ -429,14 +425,13 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||
compiler.MakeInstruction(compiler.OpArray, 3),
|
||||
compiler.MakeInstruction(compiler.OpNull),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpSliceIndex),
|
||||
compiler.MakeInstruction(compiler.OpPop)),
|
||||
objectsArray(
|
||||
intObject(1),
|
||||
intObject(2),
|
||||
intObject(3),
|
||||
intObject(2))))
|
||||
intObject(3))))
|
||||
|
||||
expect(t, `[1, 2, 3][0:]`,
|
||||
bytecode(
|
||||
|
@ -522,13 +517,12 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
intObject(1),
|
||||
intObject(2),
|
||||
compiledFunction(0, 0,
|
||||
compiler.MakeInstruction(compiler.OpTrue), // 0000
|
||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 12), // 0001
|
||||
compiler.MakeInstruction(compiler.OpConstant, 0), // 0004
|
||||
compiler.MakeInstruction(compiler.OpReturn, 1), // 0007
|
||||
compiler.MakeInstruction(compiler.OpJump, 17), // 0008
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1), // 0011
|
||||
compiler.MakeInstruction(compiler.OpReturn, 1))))) // 0014
|
||||
compiler.MakeInstruction(compiler.OpTrue), // 0000
|
||||
compiler.MakeInstruction(compiler.OpJumpFalsy, 9), // 0001
|
||||
compiler.MakeInstruction(compiler.OpConstant, 0), // 0004
|
||||
compiler.MakeInstruction(compiler.OpReturn, 1), // 0007
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1), // 0009
|
||||
compiler.MakeInstruction(compiler.OpReturn, 1))))) // 0012
|
||||
|
||||
expect(t, `func() { 1; if(true) { 2 } else { 3 }; 4 }`,
|
||||
bytecode(
|
||||
|
@ -879,21 +873,19 @@ func() {
|
|||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||
compiler.MakeInstruction(compiler.OpEqual),
|
||||
compiler.MakeInstruction(compiler.OpAndJump, 23),
|
||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpNotEqual),
|
||||
compiler.MakeInstruction(compiler.OpOrJump, 34),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||
compiler.MakeInstruction(compiler.OpBinaryOp, 39),
|
||||
compiler.MakeInstruction(compiler.OpPop)),
|
||||
objectsArray(
|
||||
intObject(0),
|
||||
intObject(0),
|
||||
intObject(1),
|
||||
intObject(1))))
|
||||
|
||||
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)
|
||||
res = c.Bytecode()
|
||||
res.RemoveDuplicates()
|
||||
{
|
||||
trace = append(trace, fmt.Sprintf("Compiler Trace:\n%s", strings.Join(tr.Out, "")))
|
||||
|
||||
bytecode := c.Bytecode()
|
||||
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")))
|
||||
trace = append(trace, fmt.Sprintf("Compiled Constants:\n%s", strings.Join(res.FormatConstants(), "\n")))
|
||||
trace = append(trace, fmt.Sprintf("Compiled Instructions:\n%s\n", strings.Join(res.FormatInstructions(), "\n")))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = c.Bytecode()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -64,9 +64,7 @@ func (t *SymbolTable) Resolve(name string) (symbol *Symbol, depth int, ok bool)
|
|||
return
|
||||
}
|
||||
|
||||
if !t.block {
|
||||
depth++
|
||||
}
|
||||
depth++
|
||||
|
||||
// if symbol is defined in parent table and if it's not global/builtin
|
||||
// then it's free variable.
|
||||
|
|
|
@ -83,12 +83,12 @@ func TestSymbolTable(t *testing.T) {
|
|||
resolveExpect(t, global, "a", globalSymbol("a", 0), 0)
|
||||
resolveExpect(t, local1, "d", localSymbol("d", 0), 0)
|
||||
resolveExpect(t, local1, "a", globalSymbol("a", 0), 1)
|
||||
resolveExpect(t, local3, "a", globalSymbol("a", 0), 2)
|
||||
resolveExpect(t, local3, "d", freeSymbol("d", 0), 1)
|
||||
resolveExpect(t, local3, "a", globalSymbol("a", 0), 3)
|
||||
resolveExpect(t, local3, "d", freeSymbol("d", 0), 2)
|
||||
resolveExpect(t, local3, "r", localSymbol("r", 1), 0)
|
||||
resolveExpect(t, local2Block2, "k", localSymbol("k", 4), 0)
|
||||
resolveExpect(t, local2Block2, "e", localSymbol("e", 0), 0)
|
||||
resolveExpect(t, local2Block2, "b", globalSymbol("b", 1), 2)
|
||||
resolveExpect(t, local2Block2, "e", localSymbol("e", 0), 1)
|
||||
resolveExpect(t, local2Block2, "b", globalSymbol("b", 1), 3)
|
||||
}
|
||||
|
||||
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