aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeffrey Wilcke <jeffrey@ethereum.org>2015-10-22 03:34:32 +0800
committerJeffrey Wilcke <jeffrey@ethereum.org>2015-10-22 03:34:32 +0800
commit58d0752fdd1c7363fb1a127cd7e0a86d7602be02 (patch)
tree84fffee930e7b6d02599f3910f284b8641c996a0
parent0467a6ceec4973b00c344d2a724f7fb01a6b0aee (diff)
parent8c855324128fa824bb6a7ea8f4ed1224e727fad0 (diff)
downloadgo-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.tar
go-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.tar.gz
go-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.tar.bz2
go-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.tar.lz
go-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.tar.xz
go-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.tar.zst
go-tangerine-58d0752fdd1c7363fb1a127cd7e0a86d7602be02.zip
Merge pull request #1883 from obscuren/jit-vm-optimisations
core/vm: JIT segmentation
-rw-r--r--cmd/utils/flags.go3
-rw-r--r--core/vm/doc.go11
-rw-r--r--core/vm/instructions.go82
-rw-r--r--core/vm/jit.go84
-rw-r--r--core/vm/jit_optimiser.go107
-rw-r--r--core/vm/jit_test.go43
-rw-r--r--core/vm/jit_util.go68
-rw-r--r--core/vm/jit_util_test.go84
-rw-r--r--core/vm/opcodes.go26
-rw-r--r--core/vm/segments.go44
-rw-r--r--core/vm/stack.go3
11 files changed, 472 insertions, 83 deletions
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 060d80b35..eb2a7d421 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -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
diff --git a/core/vm/doc.go b/core/vm/doc.go
index ab87bf934..debbdb35e 100644
--- a/core/vm/doc.go
+++ b/core/vm/doc.go
@@ -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
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 6c6039f74..2e868521e 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -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)
diff --git a/core/vm/jit.go b/core/vm/jit.go
index 6ad574917..1aa7d7ef2 100644
--- a/core/vm/jit.go
+++ b/core/vm/jit.go
@@ -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)
diff --git a/core/vm/jit_optimiser.go b/core/vm/jit_optimiser.go
new file mode 100644
index 000000000..845ffbbdf
--- /dev/null
+++ b/core/vm/jit_optimiser.go
@@ -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}
+}
diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go
index cb09e179d..aa97e5184 100644
--- a/core/vm/jit_test.go
+++ b/core/vm/jit_test.go
@@ -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)
diff --git a/core/vm/jit_util.go b/core/vm/jit_util.go
new file mode 100644
index 000000000..0d3d6d701
--- /dev/null
+++ b/core/vm/jit_util.go
@@ -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
+ }
+ }
+ }
+}
diff --git a/core/vm/jit_util_test.go b/core/vm/jit_util_test.go
new file mode 100644
index 000000000..686bee0ac
--- /dev/null
+++ b/core/vm/jit_util_test.go
@@ -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])
+ }
+ }
+ }
+}
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index 986c35ef8..dc4139092 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -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 {
diff --git a/core/vm/segments.go b/core/vm/segments.go
new file mode 100644
index 000000000..fd4065149
--- /dev/null
+++ b/core/vm/segments.go
@@ -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 }
diff --git a/core/vm/stack.go b/core/vm/stack.go
index 009ac9e1b..0046edec2 100644
--- a/core/vm/stack.go
+++ b/core/vm/stack.go
@@ -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]