core/vm: abstracted instruction execution away from JIT

Moved the execution of instructions to the instruction it self. This
will allow for specialised instructions (e.g. segments) to be execution
in the same manner as regular instructions.
This commit is contained in:
Jeffrey Wilcke 2015-10-06 18:16:03 +02:00
parent 10ed107ba2
commit 9d61d78de6
2 changed files with 88 additions and 76 deletions

View File

@ -17,6 +17,7 @@
package vm package vm
import ( import (
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -25,16 +26,16 @@ import (
) )
type programInstruction interface { 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) 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 { type instruction struct {
op OpCode op OpCode
pc uint64 pc uint64
@ -44,6 +45,73 @@ type instruction struct {
gas *big.Int gas *big.Int
spop int spop int
spush 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 thefor 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) { 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) { 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()) balance := env.Db().GetBalance(contract.Address())
env.Db().AddBalance(common.BigToAddress(stack.pop()), balance) env.Db().AddBalance(common.BigToAddress(stack.pop()), balance)

View File

@ -86,9 +86,9 @@ type Program struct {
contract *Contract contract *Contract
instructions []instruction // instruction set instructions []programInstruction // instruction set
mapping map[uint64]int // 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
code []byte code []byte
} }
@ -97,7 +97,7 @@ type Program struct {
func NewProgram(code []byte) *Program { func NewProgram(code []byte) *Program {
program := &Program{ program := &Program{
Id: crypto.Sha3Hash(code), Id: crypto.Sha3Hash(code),
mapping: make(map[uint64]int), mapping: make(map[uint64]uint64),
destinations: make(map[uint64]struct{}), destinations: make(map[uint64]struct{}),
code: code, code: code,
} }
@ -118,10 +118,12 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) {
baseOp = DUP1 baseOp = DUP1
} }
base := _baseCheck[baseOp] 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.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 // CompileProgram compiles the given program and return an error when it fails
@ -301,21 +303,8 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env
contract.Input = input contract.Input = input
var ( var (
caller = contract.caller pc uint64 = program.mapping[pcstart]
statedb = env.Db() instrCount = 0
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
}
) )
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
@ -326,62 +315,19 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env
}() }()
} }
for pc < len(program.instructions) { for pc < uint64(len(program.instructions)) {
instrCount++ instrCount++
instr := program.instructions[pc] instr := program.instructions[pc]
// calculate the new memory size and gas price for the current executing opcode ret, err := instr.do(program, &pc, env, contract, mem, stack)
newMemSize, cost, err := jitCalculateGasAndSize(env, contract, caller, instr, statedb, mem, stack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Use the calculated gas. When insufficient gas is present, use all gas and return an if instr.halts() {
// 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())
return contract.Return(ret), nil 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 contract.Input = nil
@ -403,7 +349,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 // 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. // 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 ( var (
gas = new(big.Int) gas = new(big.Int)
newMemSize *big.Int = new(big.Int) newMemSize *big.Int = new(big.Int)