Merge pull request #1883 from obscuren/jit-vm-optimisations
core/vm: JIT segmentation
This commit is contained in:
commit
58d0752fdd
|
@ -475,6 +475,9 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
|
|||
cfg.TestNet = true
|
||||
}
|
||||
|
||||
if ctx.GlobalBool(VMEnableJitFlag.Name) {
|
||||
cfg.Name += "/JIT"
|
||||
}
|
||||
if ctx.GlobalBool(DevModeFlag.Name) {
|
||||
if !ctx.GlobalIsSet(VMDebugFlag.Name) {
|
||||
cfg.VmDebug = true
|
||||
|
|
|
@ -24,9 +24,12 @@ invokes the JIT VM in a seperate goroutine and compiles the byte code in JIT
|
|||
instructions.
|
||||
|
||||
The JIT VM, when invoked, loops around a set of pre-defined instructions until
|
||||
it either runs of gas, causes an internal error, returns or stops. At a later
|
||||
stage the JIT VM will see some additional features that will cause sets of
|
||||
instructions to be compiled down to segments. Segments are sets of instructions
|
||||
that can be run in one go saving precious time during execution.
|
||||
it either runs of gas, causes an internal error, returns or stops.
|
||||
|
||||
The JIT optimiser attempts to pre-compile instructions in to chunks or segments
|
||||
such as multiple PUSH operations and static JUMPs. It does this by analysing the
|
||||
opcodes and attempts to match certain regions to known sets. Whenever the
|
||||
optimiser finds said segments it creates a new instruction and replaces the
|
||||
first occurrence in the sequence.
|
||||
*/
|
||||
package vm
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
@ -25,16 +26,16 @@ import (
|
|||
)
|
||||
|
||||
type programInstruction interface {
|
||||
Do(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack)
|
||||
// executes the program instruction and allows the instruction to modify the state of the program
|
||||
do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error)
|
||||
// returns whether the program instruction halts the execution of the JIT
|
||||
halts() bool
|
||||
// Returns the current op code (debugging purposes)
|
||||
Op() OpCode
|
||||
}
|
||||
|
||||
type instrFn func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack)
|
||||
|
||||
// Do executes the function. This implements programInstruction
|
||||
func (fn instrFn) Do(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
|
||||
fn(instr, pc, env, contract, memory, stack)
|
||||
}
|
||||
|
||||
type instruction struct {
|
||||
op OpCode
|
||||
pc uint64
|
||||
|
@ -44,6 +45,73 @@ type instruction struct {
|
|||
gas *big.Int
|
||||
spop int
|
||||
spush int
|
||||
|
||||
returns bool
|
||||
}
|
||||
|
||||
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())
|
||||
return 0, fmt.Errorf("invalid jump destination (%v) %v", nop, to)
|
||||
}
|
||||
|
||||
return mapping[to.Uint64()], nil
|
||||
}
|
||||
|
||||
func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
|
||||
// calculate the new memory size and gas price for the current executing opcode
|
||||
newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, env.Db(), memory, stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use the calculated gas. When insufficient gas is present, use all gas and return an
|
||||
// Out Of Gas error
|
||||
if !contract.UseGas(cost) {
|
||||
return nil, OutOfGasError
|
||||
}
|
||||
// Resize the memory calculated previously
|
||||
memory.Resize(newMemSize.Uint64())
|
||||
|
||||
// These opcodes return an argument and are therefor handled
|
||||
// differently from the rest of the opcodes
|
||||
switch instr.op {
|
||||
case JUMP:
|
||||
if pos, err := jump(program.mapping, program.destinations, contract, stack.pop()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
*pc = pos
|
||||
return nil, nil
|
||||
}
|
||||
case JUMPI:
|
||||
pos, cond := stack.pop(), stack.pop()
|
||||
if cond.Cmp(common.BigTrue) >= 0 {
|
||||
if pos, err := jump(program.mapping, program.destinations, contract, pos); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
*pc = pos
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
case RETURN:
|
||||
offset, size := stack.pop(), stack.pop()
|
||||
return memory.GetPtr(offset.Int64(), size.Int64()), nil
|
||||
default:
|
||||
if instr.fn == nil {
|
||||
return nil, fmt.Errorf("Invalid opcode 0x%x", instr.op)
|
||||
}
|
||||
instr.fn(instr, pc, env, contract, memory, stack)
|
||||
}
|
||||
*pc++
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (instr instruction) halts() bool {
|
||||
return instr.returns
|
||||
}
|
||||
|
||||
func (instr instruction) Op() OpCode {
|
||||
return instr.op
|
||||
}
|
||||
|
||||
func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env Environment, contract *Contract, memory *Memory, stack *stack) {
|
||||
|
@ -536,8 +604,6 @@ func opStop(instr instruction, pc *uint64, env Environment, contract *Contract,
|
|||
}
|
||||
|
||||
func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
|
||||
//receiver := env.Db().GetOrNewStateObject(common.BigToAddress(stack.pop()))
|
||||
//receiver.AddBalance(balance)
|
||||
balance := env.Db().GetBalance(contract.Address())
|
||||
env.Db().AddBalance(common.BigToAddress(stack.pop()), balance)
|
||||
|
||||
|
|
|
@ -86,9 +86,9 @@ type Program struct {
|
|||
|
||||
contract *Contract
|
||||
|
||||
instructions []instruction // instruction set
|
||||
mapping map[uint64]int // real PC mapping to array indices
|
||||
destinations map[uint64]struct{} // cached jump destinations
|
||||
instructions []programInstruction // instruction set
|
||||
mapping map[uint64]uint64 // real PC mapping to array indices
|
||||
destinations map[uint64]struct{} // cached jump destinations
|
||||
|
||||
code []byte
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ type Program struct {
|
|||
func NewProgram(code []byte) *Program {
|
||||
program := &Program{
|
||||
Id: crypto.Sha3Hash(code),
|
||||
mapping: make(map[uint64]int),
|
||||
mapping: make(map[uint64]uint64),
|
||||
destinations: make(map[uint64]struct{}),
|
||||
code: code,
|
||||
}
|
||||
|
@ -118,10 +118,12 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) {
|
|||
baseOp = DUP1
|
||||
}
|
||||
base := _baseCheck[baseOp]
|
||||
instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush}
|
||||
|
||||
returns := op == RETURN || op == SUICIDE || op == STOP
|
||||
instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns}
|
||||
|
||||
p.instructions = append(p.instructions, instr)
|
||||
p.mapping[pc] = len(p.instructions) - 1
|
||||
p.mapping[pc] = uint64(len(p.instructions) - 1)
|
||||
}
|
||||
|
||||
// CompileProgram compiles the given program and return an error when it fails
|
||||
|
@ -288,6 +290,8 @@ func CompileProgram(program *Program) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
optimiseProgram(program)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -301,21 +305,8 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env
|
|||
contract.Input = input
|
||||
|
||||
var (
|
||||
caller = contract.caller
|
||||
statedb = env.Db()
|
||||
pc int = program.mapping[pcstart]
|
||||
instrCount = 0
|
||||
|
||||
jump = func(to *big.Int) error {
|
||||
if !validDest(program.destinations, to) {
|
||||
nop := contract.GetOp(to.Uint64())
|
||||
return fmt.Errorf("invalid jump destination (%v) %v", nop, to)
|
||||
}
|
||||
|
||||
pc = program.mapping[to.Uint64()]
|
||||
|
||||
return nil
|
||||
}
|
||||
pc uint64 = program.mapping[pcstart]
|
||||
instrCount = 0
|
||||
)
|
||||
|
||||
if glog.V(logger.Debug) {
|
||||
|
@ -326,62 +317,19 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env
|
|||
}()
|
||||
}
|
||||
|
||||
for pc < len(program.instructions) {
|
||||
for pc < uint64(len(program.instructions)) {
|
||||
instrCount++
|
||||
|
||||
instr := program.instructions[pc]
|
||||
|
||||
// calculate the new memory size and gas price for the current executing opcode
|
||||
newMemSize, cost, err := jitCalculateGasAndSize(env, contract, caller, instr, statedb, mem, stack)
|
||||
ret, err := instr.do(program, &pc, env, contract, mem, stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use the calculated gas. When insufficient gas is present, use all gas and return an
|
||||
// Out Of Gas error
|
||||
if !contract.UseGas(cost) {
|
||||
return nil, OutOfGasError
|
||||
}
|
||||
// Resize the memory calculated previously
|
||||
mem.Resize(newMemSize.Uint64())
|
||||
|
||||
// These opcodes return an argument and are thefor handled
|
||||
// differently from the rest of the opcodes
|
||||
switch instr.op {
|
||||
case JUMP:
|
||||
if err := jump(stack.pop()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
case JUMPI:
|
||||
pos, cond := stack.pop(), stack.pop()
|
||||
|
||||
if cond.Cmp(common.BigTrue) >= 0 {
|
||||
if err := jump(pos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
case RETURN:
|
||||
offset, size := stack.pop(), stack.pop()
|
||||
ret := mem.GetPtr(offset.Int64(), size.Int64())
|
||||
|
||||
if instr.halts() {
|
||||
return contract.Return(ret), nil
|
||||
case SUICIDE:
|
||||
instr.fn(instr, nil, env, contract, mem, stack)
|
||||
|
||||
return contract.Return(nil), nil
|
||||
case STOP:
|
||||
return contract.Return(nil), nil
|
||||
default:
|
||||
if instr.fn == nil {
|
||||
return nil, fmt.Errorf("Invalid opcode %x", instr.op)
|
||||
}
|
||||
|
||||
instr.fn(instr, nil, env, contract, mem, stack)
|
||||
}
|
||||
|
||||
pc++
|
||||
}
|
||||
|
||||
contract.Input = nil
|
||||
|
@ -403,7 +351,7 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool {
|
|||
|
||||
// jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
|
||||
// the operation. This does not reduce gas or resizes the memory.
|
||||
func jitCalculateGasAndSize(env Environment, contract *Contract, caller ContractRef, instr instruction, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
|
||||
func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
|
||||
var (
|
||||
gas = new(big.Int)
|
||||
newMemSize *big.Int = new(big.Int)
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// optimeProgram optimises a JIT program creating segments out of program
|
||||
// instructions. Currently covered are multi-pushes and static jumps
|
||||
func optimiseProgram(program *Program) {
|
||||
var load []instruction
|
||||
|
||||
var (
|
||||
statsJump = 0
|
||||
statsPush = 0
|
||||
)
|
||||
|
||||
if glog.V(logger.Debug) {
|
||||
glog.Infof("optimising %x\n", program.Id[:4])
|
||||
tstart := time.Now()
|
||||
defer func() {
|
||||
glog.Infof("optimised %x done in %v with JMP: %d PSH: %d\n", program.Id[:4], time.Since(tstart), statsJump, statsPush)
|
||||
}()
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
}
|
||||
*/
|
||||
|
||||
for i := 0; i < len(program.instructions); i++ {
|
||||
instr := program.instructions[i].(instruction)
|
||||
|
||||
switch {
|
||||
case instr.op.IsPush():
|
||||
load = append(load, instr)
|
||||
case instr.op.IsStaticJump():
|
||||
if len(load) == 0 {
|
||||
continue
|
||||
}
|
||||
// if the push load is greater than 1, finalise that
|
||||
// segment first
|
||||
if len(load) > 2 {
|
||||
seg, size := makePushSeg(load[:len(load)-1])
|
||||
program.instructions[i-size-1] = seg
|
||||
statsPush++
|
||||
}
|
||||
// create a segment consisting of a pre determined
|
||||
// jump, destination and validity.
|
||||
seg := makeStaticJumpSeg(load[len(load)-1].data, program)
|
||||
program.instructions[i-1] = seg
|
||||
statsJump++
|
||||
|
||||
load = nil
|
||||
default:
|
||||
// create a new N pushes segment
|
||||
if len(load) > 1 {
|
||||
seg, size := makePushSeg(load)
|
||||
program.instructions[i-size] = seg
|
||||
statsPush++
|
||||
}
|
||||
load = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// makePushSeg creates a new push segment from N amount of push instructions
|
||||
func makePushSeg(instrs []instruction) (pushSeg, int) {
|
||||
var (
|
||||
data []*big.Int
|
||||
gas = new(big.Int)
|
||||
)
|
||||
|
||||
for _, instr := range instrs {
|
||||
data = append(data, instr.data)
|
||||
gas.Add(gas, instr.gas)
|
||||
}
|
||||
|
||||
return pushSeg{data, gas}, len(instrs)
|
||||
}
|
||||
|
||||
// makeStaticJumpSeg creates a new static jump segment from a predefined
|
||||
// destination (PUSH, JUMP).
|
||||
func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg {
|
||||
gas := new(big.Int)
|
||||
gas.Add(gas, _baseCheck[PUSH1].gas)
|
||||
gas.Add(gas, _baseCheck[JUMP].gas)
|
||||
|
||||
contract := &Contract{Code: program.code}
|
||||
pos, err := jump(program.mapping, program.destinations, contract, to)
|
||||
return jumpSeg{pos, err, gas}
|
||||
}
|
|
@ -26,6 +26,49 @@ import (
|
|||
|
||||
const maxRun = 1000
|
||||
|
||||
func TestSegmenting(t *testing.T) {
|
||||
prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0})
|
||||
err := CompileProgram(prog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if instr, ok := prog.instructions[0].(pushSeg); ok {
|
||||
if len(instr.data) != 2 {
|
||||
t.Error("expected 2 element width pushSegment, got", len(instr.data))
|
||||
}
|
||||
} else {
|
||||
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
|
||||
}
|
||||
|
||||
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
|
||||
err = CompileProgram(prog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := prog.instructions[1].(jumpSeg); ok {
|
||||
} else {
|
||||
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
|
||||
}
|
||||
|
||||
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
|
||||
err = CompileProgram(prog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if instr, ok := prog.instructions[0].(pushSeg); ok {
|
||||
if len(instr.data) != 2 {
|
||||
t.Error("expected 2 element width pushSegment, got", len(instr.data))
|
||||
}
|
||||
} else {
|
||||
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
|
||||
}
|
||||
if _, ok := prog.instructions[2].(jumpSeg); ok {
|
||||
} else {
|
||||
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompiling(t *testing.T) {
|
||||
prog := NewProgram([]byte{0x60, 0x10})
|
||||
err := CompileProgram(prog)
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
// 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) {
|
||||
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
|
||||
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)
|
||||
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
|
||||
opcodes = append(opcodes, SWAP)
|
||||
default:
|
||||
opcodes = append(opcodes, op)
|
||||
}
|
||||
}
|
||||
|
||||
return opcodes
|
||||
}
|
||||
|
||||
// MatchFn searcher for match in the given input and calls matcheFn if it finds
|
||||
// an appropriate match. matcherFn yields the starting position in the input.
|
||||
// MatchFn will continue to search for a match until it reacher the end of the
|
||||
// buffer or if matcherFn return false.
|
||||
func MatchFn(input, 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) {
|
||||
return
|
||||
}
|
||||
|
||||
main:
|
||||
for i, op := range input[:len(input)+1-len(match)] {
|
||||
// match first opcode and continue search
|
||||
if op == match[0] {
|
||||
for j := 1; j < len(match); j++ {
|
||||
if input[i+j] != match[j] {
|
||||
continue main
|
||||
}
|
||||
}
|
||||
// check for abort instruction
|
||||
if !matcherFn(i) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import "testing"
|
||||
|
||||
type matchTest struct {
|
||||
input []OpCode
|
||||
match []OpCode
|
||||
matches int
|
||||
}
|
||||
|
||||
func TestMatchFn(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
matchTest{
|
||||
[]OpCode{PUSH1, PUSH1, MSTORE, JUMP},
|
||||
[]OpCode{PUSH1, MSTORE},
|
||||
1,
|
||||
},
|
||||
matchTest{
|
||||
[]OpCode{PUSH1, PUSH1, MSTORE, JUMP},
|
||||
[]OpCode{PUSH1, MSTORE, PUSH1},
|
||||
0,
|
||||
},
|
||||
matchTest{
|
||||
[]OpCode{},
|
||||
[]OpCode{PUSH1},
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
var matchCount int
|
||||
MatchFn(test.input, test.match, func(i int) bool {
|
||||
matchCount++
|
||||
return true
|
||||
})
|
||||
if matchCount != test.matches {
|
||||
t.Errorf("match count failed on test[%d]: expected %d matches, got %d", i, test.matches, matchCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type parseTest struct {
|
||||
base OpCode
|
||||
size int
|
||||
output OpCode
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
tests := []parseTest{
|
||||
parseTest{PUSH1, 32, PUSH},
|
||||
parseTest{DUP1, 16, DUP},
|
||||
parseTest{SWAP1, 16, SWAP},
|
||||
parseTest{MSTORE, 1, MSTORE},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for i := 0; i < test.size; i++ {
|
||||
code := append([]byte{byte(byte(test.base) + byte(i))}, make([]byte, i+1)...)
|
||||
output := Parse(code)
|
||||
if len(output) == 0 {
|
||||
t.Fatal("empty output")
|
||||
}
|
||||
if output[0] != test.output {
|
||||
t.Error("%v failed: expected %v but got %v", test.base+OpCode(i), output[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,18 @@ import (
|
|||
// OpCode is an EVM opcode
|
||||
type OpCode byte
|
||||
|
||||
func (op OpCode) IsPush() bool {
|
||||
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:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (op OpCode) IsStaticJump() bool {
|
||||
return op == JUMP
|
||||
}
|
||||
|
||||
const (
|
||||
// 0x0 range - arithmetic ops
|
||||
STOP OpCode = iota
|
||||
|
@ -175,6 +187,13 @@ const (
|
|||
LOG4
|
||||
)
|
||||
|
||||
// unofficial opcodes used for parsing
|
||||
const (
|
||||
PUSH OpCode = 0xb0 + iota
|
||||
DUP
|
||||
SWAP
|
||||
)
|
||||
|
||||
const (
|
||||
// 0xf0 range - closures
|
||||
CREATE OpCode = 0xf0 + iota
|
||||
|
@ -182,7 +201,6 @@ const (
|
|||
CALLCODE
|
||||
RETURN
|
||||
|
||||
// 0x70 range - other
|
||||
SUICIDE = 0xff
|
||||
)
|
||||
|
||||
|
@ -335,9 +353,11 @@ var opCodeToString = map[OpCode]string{
|
|||
CALL: "CALL",
|
||||
RETURN: "RETURN",
|
||||
CALLCODE: "CALLCODE",
|
||||
SUICIDE: "SUICIDE",
|
||||
|
||||
// 0x70 range - other
|
||||
SUICIDE: "SUICIDE",
|
||||
PUSH: "PUSH",
|
||||
DUP: "DUP",
|
||||
SWAP: "SWAP",
|
||||
}
|
||||
|
||||
func (o OpCode) String() string {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package vm
|
||||
|
||||
import "math/big"
|
||||
|
||||
type jumpSeg struct {
|
||||
pos uint64
|
||||
err error
|
||||
gas *big.Int
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if j.err != nil {
|
||||
return nil, j.err
|
||||
}
|
||||
*pc = j.pos
|
||||
return nil, nil
|
||||
}
|
||||
func (s jumpSeg) halts() bool { return false }
|
||||
func (s jumpSeg) Op() OpCode { return 0 }
|
||||
|
||||
type pushSeg struct {
|
||||
data []*big.Int
|
||||
gas *big.Int
|
||||
}
|
||||
|
||||
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
|
||||
if !contract.UseGas(s.gas) {
|
||||
return nil, OutOfGasError
|
||||
}
|
||||
|
||||
for _, d := range s.data {
|
||||
stack.push(new(big.Int).Set(d))
|
||||
}
|
||||
*pc += uint64(len(s.data))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s pushSeg) halts() bool { return false }
|
||||
func (s pushSeg) Op() OpCode { return 0 }
|
|
@ -42,6 +42,9 @@ func (st *stack) push(d *big.Int) {
|
|||
//st.data = append(st.data, stackItem)
|
||||
st.data = append(st.data, d)
|
||||
}
|
||||
func (st *stack) pushN(ds ...*big.Int) {
|
||||
st.data = append(st.data, ds...)
|
||||
}
|
||||
|
||||
func (st *stack) pop() (ret *big.Int) {
|
||||
ret = st.data[len(st.data)-1]
|
||||
|
|
Loading…
Reference in New Issue