core/vm, tests: implemented semi-jit vm

* changed stack and removed stack ptr. Let go decide on slice reuse.
This commit is contained in:
Jeffrey Wilcke
2015-07-17 23:09:36 +02:00
parent 698e98d981
commit 846f34f78b
19 changed files with 1572 additions and 79 deletions

View File

@ -24,30 +24,19 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
)
// Vm implements VirtualMachine
type Vm struct {
env Environment
err error
// For logging
debug bool
BreakPoints []int64
Stepping bool
Fn string
Recoverable bool
// Will be called before the vm returns
After func(*Context, error)
}
// New returns a new Virtual Machine
func New(env Environment) *Vm {
return &Vm{env: env, debug: Debug, Recoverable: true}
return &Vm{env: env}
}
// Run loops and evaluates the contract's code with the given input data
@ -55,17 +44,67 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
self.env.SetDepth(self.env.Depth() + 1)
defer self.env.SetDepth(self.env.Depth() - 1)
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
defer func() {
if err != nil {
// In case of a VM exception (known exceptions) all gas consumed (panics NOT included).
context.UseGas(context.Gas)
ret = context.Return(nil)
}
}()
if context.CodeAddr != nil {
if p := Precompiled[context.CodeAddr.Str()]; p != nil {
return self.RunPrecompiled(p, input, context)
}
}
var (
codehash = crypto.Sha3Hash(context.Code) // codehash is used when doing jump dest caching
program *Program
)
if !DisableJit {
// Fetch program status.
// * If ready run using JIT
// * If unknown, compile in a seperate goroutine
// * If forced wait for compilation and run once done
if status := GetProgramStatus(codehash); status == progReady {
return RunProgram(GetProgram(codehash), self.env, context, input)
} else if status == progUnknown {
if ForceJit {
// Create and compile program
program = NewProgram(context.Code)
perr := CompileProgram(program)
if perr == nil {
return RunProgram(program, self.env, context, input)
}
glog.V(logger.Info).Infoln("error compiling program", err)
} else {
// create and compile the program. Compilation
// is done in a seperate goroutine
program = NewProgram(context.Code)
go func() {
err := CompileProgram(program)
if err != nil {
glog.V(logger.Info).Infoln("error compiling program", err)
return
}
}()
}
}
}
var (
caller = context.caller
code = context.Code
value = context.value
price = context.Price
op OpCode // current opcode
codehash = crypto.Sha3Hash(code) // codehash is used when doing jump dest caching
mem = NewMemory() // bound memory
stack = newstack() // local stack
statedb = self.env.State() // current state
op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
statedb = self.env.State() // current state
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Pratically much less so feasible.
pc = uint64(0) // program counter
@ -89,32 +128,25 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
defer func() {
if self.After != nil {
self.After(context, err)
}
if err != nil {
self.log(pc, op, context.Gas, cost, mem, stack, context, err)
// In case of a VM exception (known exceptions) all gas consumed (panics NOT included).
context.UseGas(context.Gas)
ret = context.Return(nil)
}
}()
if context.CodeAddr != nil {
if p := Precompiled[context.CodeAddr.Str()]; p != nil {
return self.RunPrecompiled(p, input, context)
}
}
// Don't bother with the execution if there's no code.
if len(code) == 0 {
return context.Return(nil), nil
}
for {
// Overhead of the atomic read might not be worth it
/* TODO this still causes a few issues in the tests
if program != nil && progStatus(atomic.LoadInt32(&program.status)) == progReady {
// move execution
glog.V(logger.Info).Infoln("Moved execution to JIT")
return runProgram(program, pc, mem, stack, self.env, context, input)
}
*/
// The base for all big integer arithmetic
base := new(big.Int)
@ -122,7 +154,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
op = context.GetOp(pc)
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack)
newMemSize, cost, err = calculateGasAndSize(self.env, context, caller, op, statedb, mem, stack)
if err != nil {
return nil, err
}
@ -130,11 +162,9 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
if !context.UseGas(cost) {
context.UseGas(context.Gas)
return context.Return(nil), OutOfGasError
return nil, OutOfGasError
}
// Resize the memory calculated previously
mem.Resize(newMemSize.Uint64())
// Add a log message
@ -376,7 +406,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
addr := common.BigToAddress(stack.pop())
balance := statedb.GetBalance(addr)
stack.push(balance)
stack.push(new(big.Int).Set(balance))
case ORIGIN:
origin := self.env.Origin()
@ -388,7 +418,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
stack.push(common.Bytes2Big(caller.Bytes()))
case CALLVALUE:
stack.push(value)
stack.push(new(big.Int).Set(value))
case CALLDATALOAD:
data := getData(input, stack.pop(), common.Big32)
@ -441,7 +471,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
mem.Set(mOff.Uint64(), l.Uint64(), codeCopy)
case GASPRICE:
stack.push(context.Price)
stack.push(new(big.Int).Set(context.Price))
case BLOCKHASH:
num := stack.pop()
@ -471,11 +501,11 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
case DIFFICULTY:
difficulty := self.env.Difficulty()
stack.push(difficulty)
stack.push(new(big.Int).Set(difficulty))
case GASLIMIT:
stack.push(self.env.GasLimit())
stack.push(new(big.Int).Set(self.env.GasLimit()))
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:
size := uint64(op - PUSH1 + 1)
@ -555,8 +585,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
case MSIZE:
stack.push(big.NewInt(int64(mem.Len())))
case GAS:
stack.push(context.Gas)
stack.push(new(big.Int).Set(context.Gas))
case CREATE:
var (
@ -652,7 +681,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
// calculateGasAndSize 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 (self *Vm) calculateGasAndSize(context *Context, caller ContextRef, op OpCode, statedb *state.StateDB, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
func calculateGasAndSize(env Environment, context *Context, caller ContextRef, op OpCode, statedb *state.StateDB, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
@ -759,7 +788,7 @@ func (self *Vm) calculateGasAndSize(context *Context, caller ContextRef, op OpCo
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
if self.env.State().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
if env.State().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
gas.Add(gas, params.CallNewAccountGas)
}
}