forked from cerc-io/plugeth
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:
parent
10ed107ba2
commit
9d61d78de6
@ -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,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
|
||||
@ -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]
|
||||
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 +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…
Reference in New Issue
Block a user