forked from TrueCloudLab/neoneo-go
compiler: reimplement for-range loops without syscalls
System.Enumerator.Next costs 1_000_000 gas which is rather big for a single iteration. Reimplement this by saving current counters on stack. This approach will use 2 PICKITEMS (270_000 gas) for maps, which is still cheaper that *.Next syscall.
This commit is contained in:
parent
da2d96d42f
commit
3729865860
1 changed files with 41 additions and 9 deletions
|
@ -900,23 +900,47 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.currentSwitch = label
|
c.currentSwitch = label
|
||||||
|
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
emit.Syscall(c.prog.BinWriter, "System.Iterator.Create")
|
|
||||||
|
|
||||||
c.pushStackLabel(label, 1)
|
// Implementation is a bit different for slices and maps:
|
||||||
|
// For slices we iterate index from 0 to len-1, storing array, len and index on stack.
|
||||||
|
// For maps we iterate index from 0 to len-1, storing map, keyarray, size and index on stack.
|
||||||
|
_, isMap := c.typeOf(n.X).Underlying().(*types.Map)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
if isMap {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.KEYS)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
}
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PUSH0)
|
||||||
|
|
||||||
|
stackSize := 3 // slice, len(slice), index
|
||||||
|
if isMap {
|
||||||
|
stackSize++ // map, keys, len(keys), index in keys
|
||||||
|
}
|
||||||
|
c.pushStackLabel(label, stackSize)
|
||||||
c.setLabel(start)
|
c.setLabel(start)
|
||||||
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
emit.Opcode(c.prog.BinWriter, opcode.OVER)
|
||||||
emit.Syscall(c.prog.BinWriter, "System.Enumerator.Next")
|
emit.Opcode(c.prog.BinWriter, opcode.OVER)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, end)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPLEL, end)
|
||||||
|
|
||||||
if n.Key != nil {
|
if n.Key != nil {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
if isMap {
|
||||||
emit.Syscall(c.prog.BinWriter, "System.Iterator.Key")
|
c.rangeLoadKey()
|
||||||
|
} else {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
}
|
||||||
c.emitStoreVar(n.Key.(*ast.Ident).Name)
|
c.emitStoreVar(n.Key.(*ast.Ident).Name)
|
||||||
}
|
}
|
||||||
if n.Value != nil {
|
if n.Value != nil {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
c.rangeLoadKey()
|
||||||
emit.Syscall(c.prog.BinWriter, "System.Enumerator.Value")
|
if isMap {
|
||||||
|
// we have loaded only key from key array, now load value
|
||||||
|
emit.Int(c.prog.BinWriter, 4)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load map itself (+1 because key was pushed)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||||
|
}
|
||||||
c.emitStoreVar(n.Value.(*ast.Ident).Name)
|
c.emitStoreVar(n.Value.(*ast.Ident).Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -924,6 +948,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
c.setLabel(post)
|
c.setLabel(post)
|
||||||
|
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.INC)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPL, start)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, start)
|
||||||
|
|
||||||
c.setLabel(end)
|
c.setLabel(end)
|
||||||
|
@ -945,6 +970,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *codegen) rangeLoadKey() {
|
||||||
|
emit.Int(c.prog.BinWriter, 2)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PICK) // load keys
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.OVER) // load index in key array
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||||
|
}
|
||||||
|
|
||||||
func isFallthroughStmt(c ast.Node) bool {
|
func isFallthroughStmt(c ast.Node) bool {
|
||||||
s, ok := c.(*ast.BranchStmt)
|
s, ok := c.(*ast.BranchStmt)
|
||||||
return ok && s.Tok == token.FALLTHROUGH
|
return ok && s.Tok == token.FALLTHROUGH
|
||||||
|
|
Loading…
Reference in a new issue