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.
pull/1883/head
Jeffrey Wilcke 9 years ago
parent 10ed107ba2
commit 9d61d78de6
  1. 82
      core/vm/instructions.go
  2. 78
      core/vm/jit.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 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) {
@ -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,8 +86,8 @@ type Program struct {
contract *Contract
instructions []instruction // instruction set
mapping map[uint64]int // real PC mapping to array indices
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
@ -301,21 +303,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]
pc uint64 = 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) {
@ -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++
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 +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
// 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)

Loading…
Cancel
Save