diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7b20dbcde0..1204c8d39a 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -34,6 +34,8 @@ type programInstruction interface { 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 instruction struct { @@ -49,6 +51,13 @@ type instruction struct { 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) { if !validDest(destinations, to) { nop := contract.GetOp(to.Uint64()) diff --git a/core/vm/program.go b/core/vm/program.go index 0fadfe15c4..b3cf3e3306 100644 --- a/core/vm/program.go +++ b/core/vm/program.go @@ -82,8 +82,6 @@ type Program struct { Id common.Hash // Id of the program status int32 // status should be accessed atomically - contract *Contract - instructions []programInstruction // instruction set mapping map[uint64]uint64 // real PC mapping to array indices destinations map[uint64]struct{} // cached jump destinations @@ -288,6 +286,8 @@ func CompileProgram(program *Program) { 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) { diff --git a/core/vm/program_optimiser.go b/core/vm/program_optimiser.go index 79c91df3e6..197e77a39d 100644 --- a/core/vm/program_optimiser.go +++ b/core/vm/program_optimiser.go @@ -17,13 +17,17 @@ package vm import ( + "fmt" "math/big" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" ) +type fnId string + // optimeProgram optimises a JIT program creating segments out of program // instructions. Currently covered are multi-pushes and static jumps func OptimiseProgram(program *Program) { @@ -43,22 +47,34 @@ func OptimiseProgram(program *Program) { }() } - /* - code := Parse(program.code) - for _, test := range [][]OpCode{ - []OpCode{PUSH, PUSH, ADD}, - []OpCode{PUSH, PUSH, SUB}, - []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) - } - */ + code := Parse(program.code) + for _, test := range [][]OpCode{ + []OpCode{PUSH, DUP, EQ, PUSH, JUMPI}, + } { + matchCount := 0 + fmt.Printf("found %d match count on: %v\n", matchCount, test) + } + + MatchFn(code, []OpCode{PUSH, PUSH, EXP}, func(i int) bool { + // TODO optimise this instruction + return true + }) + + funcTable := make(map[fnId]uint64) + 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.Infof("jumpTable entry: %x => %d\n", funcId, position) + + funcTable[funcId] = position + + return true + }) for i := 0; i < len(program.instructions); i++ { instr := program.instructions[i].(instruction) diff --git a/core/vm/program_util.go b/core/vm/program_util.go index 947dae88f4..067061abe9 100644 --- a/core/vm/program_util.go +++ b/core/vm/program_util.go @@ -16,23 +16,28 @@ package vm +type parsedOp struct { + op OpCode + pc uint64 +} + // Parse parses all opcodes from the given code byte slice. This function // 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++ { op := OpCode(code[pc]) 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: a := uint64(op) - uint64(PUSH1) + 1 + opcodes = append(opcodes, parsedOp{PUSH, pc}) pc += a - opcodes = append(opcodes, PUSH) 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: - opcodes = append(opcodes, SWAP) + opcodes = append(opcodes, parsedOp{SWAP, pc}) 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. // MatchFn will continue to search for a match until it reaches the end of the // 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 // greater than the 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: - 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 - if op == match[0] { + if parsedOp.op == match[0] { for j := 1; j < len(match); j++ { - if input[i+j] != match[j] { + if input[i+j].op != match[j] { continue main } } diff --git a/core/vm/segments.go b/core/vm/segments.go index ed223d82f2..feb71fafe3 100644 --- a/core/vm/segments.go +++ b/core/vm/segments.go @@ -16,7 +16,10 @@ package vm -import "math/big" +import ( + "fmt" + "math/big" +) type jumpSeg struct { pos uint64 @@ -24,6 +27,10 @@ type jumpSeg struct { gas uint64 } +func (js jumpSeg) String() string { + return fmt.Sprintf("[JUMP SEG: %d]", js.pos) +} + func (j jumpSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { if !contract.UseGas(j.gas) { return nil, OutOfGasError @@ -42,6 +49,14 @@ type pushSeg struct { gas uint64 } +func (ps pushSeg) String() string { + var ret string + for _, num := range ps.data { + ret += fmt.Sprintf("PUSH %v\n", num) + } + return ret +} + func (s pushSeg) 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 @@ -58,3 +73,26 @@ func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Co func (s pushSeg) halts() bool { return false } func (s pushSeg) Op() OpCode { return 0 } + +//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 + } +} +*/ diff --git a/core/vm/vm.go b/core/vm/vm.go index 5b85211438..b13b2ecc4b 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -18,6 +18,7 @@ package vm import ( "fmt" + "os" "time" "github.com/ethereum/go-ethereum/common" @@ -120,11 +121,21 @@ func (evm *EVM) runProgram(program *Program, contract *Contract, input []byte) ( }() } + var ops []OpCode + defer func() { + if len(ops) > 0 { + fmt.Printf("%x\n", input) + fmt.Printf("%d\n", ops) + os.Exit(1) + } + }() homestead := env.RuleSet().IsHomestead(env.BlockNumber) for pc < uint64(len(program.instructions)) { instrCount++ instr := program.instructions[pc] + //fmt.Println(instr) + ops = append(ops, instr.Op()) if instr.Op() == DELEGATECALL && !homestead { return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) }