core/vm: working on peephole optimisation

This commit is contained in:
Jeffrey Wilcke 2016-10-11 17:58:41 +02:00
parent e65d1766a3
commit b48e9d7664
7 changed files with 195 additions and 44 deletions

View file

@ -34,6 +34,8 @@ type programInstruction interface {
Op() OpCode Op() OpCode
} }
//[static memory], if calldata goto jump table
type instrFn func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) type instrFn func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack)
type instruction struct { type instruction struct {
@ -49,6 +51,13 @@ type instruction struct {
returns bool returns bool
} }
func (instr instruction) String() string {
if instr.op == PUSH1 {
return fmt.Sprintf("PUSH %v", instr.data)
}
return fmt.Sprintf("%v", instr.op)
}
func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) { func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) {
if !validDest(destinations, to) { if !validDest(destinations, to) {
nop := contract.GetOp(to.Uint64()) nop := contract.GetOp(to.Uint64())

View file

@ -195,6 +195,8 @@ const (
) )
const ( const (
OPTIMISED OpCode = 0xd0 + iota
// 0xf0 range - closures // 0xf0 range - closures
CREATE OpCode = 0xf0 + iota CREATE OpCode = 0xf0 + iota
CALL CALL
@ -360,6 +362,8 @@ var opCodeToString = map[OpCode]string{
PUSH: "PUSH", PUSH: "PUSH",
DUP: "DUP", DUP: "DUP",
SWAP: "SWAP", SWAP: "SWAP",
OPTIMISED: "OPTIMISED",
} }
func (o OpCode) String() string { func (o OpCode) String() string {

View file

@ -82,8 +82,6 @@ type Program struct {
Id common.Hash // Id of the program Id common.Hash // Id of the program
status int32 // status should be accessed atomically status int32 // status should be accessed atomically
contract *Contract
instructions []programInstruction // instruction set instructions []programInstruction // instruction set
mapping map[uint64]uint64 // real PC mapping to array indices mapping map[uint64]uint64 // real PC mapping to array indices
destinations map[uint64]struct{} // cached jump destinations destinations map[uint64]struct{} // cached jump destinations
@ -288,6 +286,8 @@ func CompileProgram(program *Program) {
program.addInstr(op, pc, nil, nil) program.addInstr(op, pc, nil, nil)
} }
} }
// reset the program code. It's no longer required by the program itself.
//program.code = nil
} }
func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) { func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) {

View file

@ -17,51 +17,149 @@
package vm package vm
import ( import (
"fmt"
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
) )
type (
fnId string
fnInfo struct {
gas uint64
pos uint64
}
jumpTableInstr map[fnId]fnInfo
arithSeg struct {
value *big.Int
gas uint64
pcRange uint64
}
)
func (as arithSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
if !contract.UseGas(as.gas) {
return nil, OutOfGasError
}
stack.push(new(big.Int).Set(as.value))
*pc += as.pcRange
return nil, nil
}
func (as arithSeg) halts() bool { return false }
func (as arithSeg) Op() OpCode { return OPTIMISED }
func (as arithSeg) String() string { return fmt.Sprintf("ARITH_SEG(%v: %v)", EXP, as.value) }
func (jti jumpTableInstr) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
fnId := fnId(stack.data[len(stack.data)-1].Bytes())
fn, ok := jti[fnId]
glog.V(logger.Debug).Infof("function call: 0x%x (p=%d exist=%v)", fnId, fn.pos, ok)
*pc = fn.pos
return nil, nil
}
func (jumpTableInstr) halts() bool { return false }
func (jumpTableInstr) Op() OpCode { return OPTIMISED }
func (jti jumpTableInstr) String() string { return fmt.Sprintf("FUNC_TABLE(%d)", len(jti)) }
// optimeProgram optimises a JIT program creating segments out of program // optimeProgram optimises a JIT program creating segments out of program
// instructions. Currently covered are multi-pushes and static jumps // instructions. Currently covered are multi-pushes and static jumps
func OptimiseProgram(program *Program) { func OptimiseProgram(program *Program) {
var load []instruction
var (
statsJump = 0
statsOldPush = 0
statsNewPush = 0
)
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
glog.Infof("optimising %x\n", program.Id[:4]) glog.Infof("optimising %x\n", program.Id[:4])
tstart := time.Now() tstart := time.Now()
defer func() { defer func() {
glog.Infof("optimised %x done in %v with JMP: %d PSH: %d/%d\n", program.Id[:4], time.Since(tstart), statsJump, statsNewPush, statsOldPush) glog.Infof("optimised %x done in %v\n", program.Id[:4], time.Since(tstart))
}() }()
} }
/* code := Parse(program.code)
code := Parse(program.code) MatchFn(code, []OpCode{PUSH, PUSH, EXP}, func(i int) bool {
for _, test := range [][]OpCode{ var (
[]OpCode{PUSH, PUSH, ADD}, instr arithSeg
[]OpCode{PUSH, PUSH, SUB}, instrPos uint64
[]OpCode{PUSH, PUSH, MUL}, )
[]OpCode{PUSH, PUSH, DIV},
} {
matchCount := 0
MatchFn(code, test, func(i int) bool {
matchCount++
return true
})
fmt.Printf("found %d match count on: %v\n", matchCount, test)
}
*/
pushOp := code[i]
instrPos = pushOp.pc
size := int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
instr.pcRange += uint64(size) + 1 // size + push instruction
exponent := getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))
pushOp = code[i+1]
size = int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
instr.pcRange += uint64(size) + 1 // size + push instruction
base := getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))
//instr.value = math.Exp(common.Bytes2Big(base), common.Bytes2Big(exponent))
instr.value = new(big.Int).Exp(common.Bytes2Big(base), common.Bytes2Big(exponent), Pow256)
instr.gas = GasFastestStep64*2 + GasSlowStep64 + uint64(len(exponent))*ExpByteGas64
instr.pcRange++
instr.pcRange--
instr.pcRange--
program.instructions[program.mapping[instrPos]] = instr
return true
})
funcTable, jumpStart := make(jumpTableInstr), uint64(0)
MatchFn(code, []OpCode{PUSH, DUP, EQ, PUSH, JUMPI}, func(i int) bool {
pushOp := code[i]
jumpStart = pushOp.pc
size := int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
funcId := fnId(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size)))
pushOp = code[i+3]
size = int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
position := common.Bytes2Big(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))).Uint64()
glog.V(logger.Debug).Infof("jumpTable start : %x => %d - %d\n", funcId, position, jumpStart)
// TODO set the right amount of gas
funcTable[funcId] = fnInfo{pos: program.mapping[position], gas: 0}
return true
})
MatchFn(code, []OpCode{DUP, PUSH, EQ, PUSH, JUMPI}, func(i int) bool {
pushOp := code[i+1]
size := int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
funcId := fnId(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size)))
pushOp = code[i+3]
size = int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
position := common.Bytes2Big(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))).Uint64()
glog.V(logger.Debug).Infof("jumpTable entry: %x => %d\n", funcId, position)
// TODO set the rigth amount of gas
funcTable[funcId] = fnInfo{pos: program.mapping[position], gas: 0}
return true
})
if len(funcTable) > 0 {
program.instructions[program.mapping[jumpStart]] = funcTable
}
var (
load []instruction
statsJump = 0
statsOldPush = 0
statsNewPush = 0
)
for i := 0; i < len(program.instructions); i++ { for i := 0; i < len(program.instructions); i++ {
instr := program.instructions[i].(instruction) instr, ok := program.instructions[i].(instruction)
if !ok {
continue
}
switch { switch {
case instr.op.IsPush(): case instr.op.IsPush():
@ -95,6 +193,8 @@ func OptimiseProgram(program *Program) {
load = nil load = nil
} }
} }
glog.V(logger.Debug).Infof("optimised %d pushes as %d pushes\n", statsOldPush, statsNewPush)
glog.V(logger.Debug).Infof("optimised %d static jumps\n", statsJump)
} }
// makePushSeg creates a new push segment from N amount of push instructions // makePushSeg creates a new push segment from N amount of push instructions

View file

@ -16,23 +16,28 @@
package vm package vm
type parsedOp struct {
op OpCode
pc uint64
}
// Parse parses all opcodes from the given code byte slice. This function // Parse parses all opcodes from the given code byte slice. This function
// performs no error checking and may return non-existing opcodes. // performs no error checking and may return non-existing opcodes.
func Parse(code []byte) (opcodes []OpCode) { func Parse(code []byte) (opcodes []parsedOp) {
for pc := uint64(0); pc < uint64(len(code)); pc++ { for pc := uint64(0); pc < uint64(len(code)); pc++ {
op := OpCode(code[pc]) op := OpCode(code[pc])
switch op { switch op {
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
a := uint64(op) - uint64(PUSH1) + 1 a := uint64(op) - uint64(PUSH1) + 1
opcodes = append(opcodes, parsedOp{PUSH, pc})
pc += a pc += a
opcodes = append(opcodes, PUSH)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
opcodes = append(opcodes, DUP) opcodes = append(opcodes, parsedOp{DUP, pc})
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
opcodes = append(opcodes, SWAP) opcodes = append(opcodes, parsedOp{SWAP, pc})
default: default:
opcodes = append(opcodes, op) opcodes = append(opcodes, parsedOp{op, pc})
} }
} }
@ -43,7 +48,7 @@ func Parse(code []byte) (opcodes []OpCode) {
// an appropriate match. matcherFn yields the starting position in the input. // an appropriate match. matcherFn yields the starting position in the input.
// MatchFn will continue to search for a match until it reaches the end of the // MatchFn will continue to search for a match until it reaches the end of the
// buffer or if matcherFn return false. // buffer or if matcherFn return false.
func MatchFn(input, match []OpCode, matcherFn func(int) bool) { func MatchFn(input []parsedOp, match []OpCode, matcherFn func(int) bool) {
// short circuit if either input or match is empty or if the match is // short circuit if either input or match is empty or if the match is
// greater than the input // greater than the input
if len(input) == 0 || len(match) == 0 || len(match) > len(input) { if len(input) == 0 || len(match) == 0 || len(match) > len(input) {
@ -51,11 +56,11 @@ func MatchFn(input, match []OpCode, matcherFn func(int) bool) {
} }
main: main:
for i, op := range input[:len(input)+1-len(match)] { for i, parsedOp := range input[:len(input)+1-len(match)] {
// match first opcode and continue search // match first opcode and continue search
if op == match[0] { if parsedOp.op == match[0] {
for j := 1; j < len(match); j++ { for j := 1; j < len(match); j++ {
if input[i+j] != match[j] { if input[i+j].op != match[j] {
continue main continue main
} }
} }

View file

@ -16,7 +16,10 @@
package vm package vm
import "math/big" import (
"fmt"
"math/big"
)
type jumpSeg struct { type jumpSeg struct {
pos uint64 pos uint64
@ -34,8 +37,9 @@ func (j jumpSeg) do(vm *EVM, program *Program, pc *uint64, env *Environment, con
*pc = j.pos *pc = j.pos
return nil, nil return nil, nil
} }
func (s jumpSeg) halts() bool { return false } func (s jumpSeg) halts() bool { return false }
func (s jumpSeg) Op() OpCode { return 0 } func (s jumpSeg) Op() OpCode { return OPTIMISED }
func (s jumpSeg) String() string { return fmt.Sprintf("JUMP_SEG->%d", s.pos) }
type pushSeg struct { type pushSeg struct {
data []*big.Int data []*big.Int
@ -56,5 +60,29 @@ func (s pushSeg) do(vm *EVM, program *Program, pc *uint64, env *Environment, con
return nil, nil return nil, nil
} }
func (s pushSeg) halts() bool { return false } func (s pushSeg) halts() bool { return false }
func (s pushSeg) Op() OpCode { return 0 } func (s pushSeg) Op() OpCode { return OPTIMISED }
func (s pushSeg) String() string { return fmt.Sprintf("PUSH_SEG(%d)", len(s.data)) }
//CALLDATASIZE, ISZERO, PUSH2, JUMPI
//if len(calldata) > 0 {
// *pc = T.pos
//}
//PUSH 224, PUSH 2, EXP, PUSH 0, CALLDATALOAD, DIV
//calldata[:4]
//PUSH4, DUP2, EQ, PUSH2, JUMP
/*
if calldata[:4] == (PUSH4)
else if calldata[:4] == (PUSH2) ????
else if calldata[:4] == (PUSH2) ????
type programJumpTable map[funcId]dest
if len(calldata) > 0 {
if ppc, exist := programJumpTable[string(calldata[:4])]; exit {
*pc = ppc
}
}
*/

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -116,24 +117,28 @@ func (evm *EVM) runProgram(program *Program, contract *Contract, input []byte) (
) )
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
glog.Infof("running JIT program %x\n", program.Id[:4]) glog.Infof("running EVM program %x\n", program.Id[:4])
tstart := time.Now() tstart := time.Now()
defer func() { defer func() {
glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount) glog.Infof("EVM program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount)
os.Exit(1)
}() }()
} }
fmt.Printf("%x\n", input)
homestead := env.ChainConfig().IsHomestead(env.BlockNumber) homestead := env.ChainConfig().IsHomestead(env.BlockNumber)
for pc < uint64(len(program.instructions)) { for pc < uint64(len(program.instructions)) {
instrCount++ instrCount++
instr := program.instructions[pc] instr := program.instructions[pc]
//fmt.Println(instr)
if instr.Op() == DELEGATECALL && !homestead { if instr.Op() == DELEGATECALL && !homestead {
return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op())
} }
ret, err := instr.do(evm, program, &pc, env, contract, mem, stack) ret, err := instr.do(evm, program, &pc, env, contract, mem, stack)
if err != nil { if err != nil {
glog.Infoln("error executing EVM program for: '", instr.Op(), "' with: ", err)
//gas := new(big.Int).SetUint64(contract.gas64) //gas := new(big.Int).SetUint64(contract.gas64)
//evm.cfg.Tracer.CaptureState(evm.env, pc, instr.Op(), gas, cost, mem, stack, contract, evm.env.Depth(), err) //evm.cfg.Tracer.CaptureState(evm.env, pc, instr.Op(), gas, cost, mem, stack, contract, evm.env.Depth(), err)