compiler: inline all expression with no side-effects, fix #1881
This has the drawback of traversing expression each time the argument is used. However this is not the case in our syscall/native wrappers. The old behaviour can be restored by explicit local assignment.
This commit is contained in:
parent
3749f38720
commit
6b3afe9131
4 changed files with 58 additions and 30 deletions
|
@ -247,8 +247,24 @@ func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
|
||||||
// emitLoadVar loads specified variable to the evaluation stack.
|
// emitLoadVar loads specified variable to the evaluation stack.
|
||||||
func (c *codegen) emitLoadVar(pkg string, name string) {
|
func (c *codegen) emitLoadVar(pkg string, name string) {
|
||||||
vi := c.getVarIndex(pkg, name)
|
vi := c.getVarIndex(pkg, name)
|
||||||
if vi.tv.Value != nil {
|
if vi.ctx != nil && c.typeAndValueOf(vi.ctx.expr).Value != nil {
|
||||||
c.emitLoadConst(vi.tv)
|
c.emitLoadConst(c.typeAndValueOf(vi.ctx.expr))
|
||||||
|
return
|
||||||
|
} else if vi.ctx != nil {
|
||||||
|
var oldScope []map[string]varInfo
|
||||||
|
oldMap := c.importMap
|
||||||
|
c.importMap = vi.ctx.importMap
|
||||||
|
if c.scope != nil {
|
||||||
|
oldScope = c.scope.vars.locals
|
||||||
|
c.scope.vars.locals = vi.ctx.scope
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.Walk(c, vi.ctx.expr)
|
||||||
|
|
||||||
|
if c.scope != nil {
|
||||||
|
c.scope.vars.locals = oldScope
|
||||||
|
}
|
||||||
|
c.importMap = oldMap
|
||||||
return
|
return
|
||||||
} else if vi.index == unspecifiedVarIndex {
|
} else if vi.index == unspecifiedVarIndex {
|
||||||
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
|
emit.Opcodes(c.prog.BinWriter, opcode.PUSHNULL)
|
||||||
|
|
|
@ -63,29 +63,18 @@ func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
name := sig.Params().At(i).Name()
|
name := sig.Params().At(i).Name()
|
||||||
if tv := c.typeAndValueOf(n.Args[i]); tv.Value != nil {
|
if !c.hasCalls(n.Args[i]) {
|
||||||
|
// If argument contains no calls, we save context and traverse the expression
|
||||||
|
// when argument is emitted.
|
||||||
c.scope.vars.locals = newScope
|
c.scope.vars.locals = newScope
|
||||||
c.scope.vars.addAlias(name, varLocal, unspecifiedVarIndex, tv)
|
c.scope.vars.addAlias(name, -1, unspecifiedVarIndex, &varContext{
|
||||||
|
importMap: c.importMap,
|
||||||
|
expr: n.Args[i],
|
||||||
|
scope: oldScope,
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if arg, ok := n.Args[i].(*ast.Ident); ok {
|
|
||||||
// When function argument is variable or const, we may avoid
|
|
||||||
// introducing additional variables for parameters.
|
|
||||||
// This is done by providing additional alias to variable.
|
|
||||||
if vi := c.scope.vars.getVarInfo(arg.Name); vi != nil {
|
|
||||||
c.scope.vars.locals = newScope
|
|
||||||
c.scope.vars.addAlias(name, vi.refType, vi.index, vi.tv)
|
|
||||||
continue
|
|
||||||
} else if arg.Name == "nil" {
|
|
||||||
c.scope.vars.locals = newScope
|
|
||||||
c.scope.vars.addAlias(name, varLocal, unspecifiedVarIndex, types.TypeAndValue{})
|
|
||||||
continue
|
|
||||||
} else if index, ok := c.globals[c.getIdentName("", arg.Name)]; ok {
|
|
||||||
c.scope.vars.locals = newScope
|
|
||||||
c.scope.vars.addAlias(name, varGlobal, index, types.TypeAndValue{})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast.Walk(c, n.Args[i])
|
ast.Walk(c, n.Args[i])
|
||||||
c.scope.vars.locals = newScope
|
c.scope.vars.locals = newScope
|
||||||
c.scope.newLocal(name)
|
c.scope.newLocal(name)
|
||||||
|
@ -144,3 +133,18 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr) {
|
||||||
c.emittedEvents[name] = append(c.emittedEvents[name], params)
|
c.emittedEvents[name] = append(c.emittedEvents[name], params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasCalls returns true if expression contains any calls.
|
||||||
|
// We uses this as a rough heuristic to determine if expression calculation
|
||||||
|
// has any side-effects.
|
||||||
|
func (c *codegen) hasCalls(expr ast.Expr) bool {
|
||||||
|
var has bool
|
||||||
|
ast.Inspect(expr, func(n ast.Node) bool {
|
||||||
|
_, ok := n.(*ast.CallExpr)
|
||||||
|
if ok {
|
||||||
|
has = true
|
||||||
|
}
|
||||||
|
return !has
|
||||||
|
})
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
|
@ -137,24 +137,24 @@ func TestInline(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("selector, global", func(t *testing.T) {
|
t.Run("selector, global", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `return inline.Sum(inline.A, 2)`)
|
src := fmt.Sprintf(srcTmpl, `return inline.Sum(inline.A, 2)`)
|
||||||
checkCallCount(t, src, 0, 1, 1)
|
checkCallCount(t, src, 0, 0, 0)
|
||||||
eval(t, src, big.NewInt(3))
|
eval(t, src, big.NewInt(3))
|
||||||
})
|
})
|
||||||
t.Run("selector, struct, simple", func(t *testing.T) {
|
t.Run("selector, struct, simple", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `x := pair{a: 1, b: 2}; return inline.Sum(x.b, 1)`)
|
src := fmt.Sprintf(srcTmpl, `x := pair{a: 1, b: 2}; return inline.Sum(x.b, 1)`)
|
||||||
checkCallCount(t, src, 0, 1, 2)
|
checkCallCount(t, src, 0, 1, 1)
|
||||||
eval(t, src, big.NewInt(3))
|
eval(t, src, big.NewInt(3))
|
||||||
})
|
})
|
||||||
t.Run("selector, struct, complex", func(t *testing.T) {
|
t.Run("selector, struct, complex", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `x := triple{a: 1, b: pair{a: 2, b: 3}}
|
src := fmt.Sprintf(srcTmpl, `x := triple{a: 1, b: pair{a: 2, b: 3}}
|
||||||
return inline.Sum(x.b.a, 1)`)
|
return inline.Sum(x.b.a, 1)`)
|
||||||
checkCallCount(t, src, 0, 1, 2)
|
checkCallCount(t, src, 0, 1, 1)
|
||||||
eval(t, src, big.NewInt(3))
|
eval(t, src, big.NewInt(3))
|
||||||
})
|
})
|
||||||
t.Run("expression", func(t *testing.T) {
|
t.Run("expression", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(srcTmpl, `x, y := 1, 2
|
src := fmt.Sprintf(srcTmpl, `x, y := 1, 2
|
||||||
return inline.Sum(x+y, y*2)`)
|
return inline.Sum(x+y, y*2)`)
|
||||||
checkCallCount(t, src, 0, 1, 4)
|
checkCallCount(t, src, 0, 1, 2)
|
||||||
eval(t, src, big.NewInt(7))
|
eval(t, src, big.NewInt(7))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/types"
|
"go/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
type varScope struct {
|
type varScope struct {
|
||||||
|
@ -10,10 +10,18 @@ type varScope struct {
|
||||||
locals []map[string]varInfo
|
locals []map[string]varInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type varContext struct {
|
||||||
|
importMap map[string]string
|
||||||
|
expr ast.Expr
|
||||||
|
scope []map[string]varInfo
|
||||||
|
}
|
||||||
|
|
||||||
type varInfo struct {
|
type varInfo struct {
|
||||||
refType varType
|
refType varType
|
||||||
index int
|
index int
|
||||||
tv types.TypeAndValue
|
// ctx is set for inline arguments and contains
|
||||||
|
// context for expression traversal.
|
||||||
|
ctx *varContext
|
||||||
}
|
}
|
||||||
|
|
||||||
const unspecifiedVarIndex = -1
|
const unspecifiedVarIndex = -1
|
||||||
|
@ -32,11 +40,11 @@ func (c *varScope) dropScope() {
|
||||||
c.locals = c.locals[:len(c.locals)-1]
|
c.locals = c.locals[:len(c.locals)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *varScope) addAlias(name string, vt varType, index int, tv types.TypeAndValue) {
|
func (c *varScope) addAlias(name string, vt varType, index int, ctx *varContext) {
|
||||||
c.locals[len(c.locals)-1][name] = varInfo{
|
c.locals[len(c.locals)-1][name] = varInfo{
|
||||||
refType: vt,
|
refType: vt,
|
||||||
index: index,
|
index: index,
|
||||||
tv: tv,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue