go-ethereum/core/vm/vm_jit.go
Jeffrey Wilcke bbc4ea4ae8 core/vm: improved EVM run loop & instruction calling (#3378)
The run loop, which previously contained custom opcode executes have been
removed and has been simplified to a few checks.

Each operation consists of 4 elements: execution function, gas cost function,
stack validation function and memory size function. The execution function
implements the operation's runtime behaviour, the gas cost function implements
the operation gas costs function and greatly depends on the memory and stack,
the stack validation function validates the stack and makes sure that enough
items can be popped off and pushed on and the memory size function calculates
the memory required for the operation and returns it.

This commit also allows the EVM to go unmetered. This is helpful for offline
operations such as contract calls.
2017-01-05 11:52:10 +01:00

430 lines
12 KiB
Go

// Copyright 2015 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
/*
#include <evmjit.h>
#include <stdlib.h>
static struct evm_instance* new_evmjit()
{
// Declare exported Go functions. The are ABI compatible with C callbacks
// but differ by type names and const pointers.
void query(void*, size_t, int, void*);
void update(size_t env, int key, void* arg1, void* arg2);
long long call(void* env, int kind, long long gas, void* address,
void* value, void* input, size_t input_size, void* output,
size_t output_size);
struct evm_factory factory = evmjit_get_factory();
return factory.create((evm_query_fn)query, (evm_update_fn)update,
(evm_call_fn)call);
}
static struct evm_result evm_execute(struct evm_instance* instance,
struct evm_env* env, enum evm_mode mode, struct evm_uint256be code_hash,
uint8_t const* code, size_t code_size, int64_t gas, uint8_t const* input,
size_t input_size, struct evm_uint256be value)
{
return instance->execute(instance, env, mode, code_hash, code, code_size,
gas, input, input_size, value);
}
static void evm_release_result(struct evm_result* result)
{
result->release(result);
}
#cgo CFLAGS: -I/home/chfast/Projects/ethereum/evmjit/include
#cgo LDFLAGS: -levmjit-standalone -lstdc++ -lm -ldl -L/home/chfast/Projects/ethereum/evmjit/build/release-llvm/libevmjit
*/
import "C"
import (
"fmt"
"math/big"
"reflect"
"sync"
"unsafe"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
type EVMJIT struct {
jit *C.struct_evm_instance
env *Environment
}
type EVMCContext struct {
contract *Contract
env *Environment
}
func NewJit(env *Environment, cfg Config) *EVMJIT {
// FIXME: Destroy the jit later.
return &EVMJIT{C.new_evmjit(), env}
}
var contextMap = make(map[uintptr]*EVMCContext)
var contextMapMu sync.Mutex
func pinCtx(ctx *EVMCContext) uintptr {
contextMapMu.Lock()
// Find empty slot in the map starting from the map length.
id := uintptr(len(contextMap))
for contextMap[id] != nil {
id++
}
contextMap[id] = ctx
contextMapMu.Unlock()
return id
}
func unpinCtx(id uintptr) {
contextMapMu.Lock()
delete(contextMap, id)
contextMapMu.Unlock()
}
func getCtx(id uintptr) *EVMCContext {
contextMapMu.Lock()
defer contextMapMu.Unlock()
return contextMap[id]
}
func HashToEvmc(hash common.Hash) C.struct_evm_uint256be {
return C.struct_evm_uint256be{bytes: *(*[32]C.uint8_t)(unsafe.Pointer(&hash[0]))}
}
func BigToEvmc(i *big.Int) C.struct_evm_uint256be {
return HashToEvmc(common.BigToHash(i))
}
func assert(cond bool, msg string) {
if !cond {
panic(fmt.Sprintf("Assertion failure! %v", msg))
}
}
type MemoryRef struct {
ptr *C.uint8_t
len int
}
func GoByteSlice(data unsafe.Pointer, size C.size_t) []byte {
var sliceHeader reflect.SliceHeader
sliceHeader.Data = uintptr(data)
sliceHeader.Len = int(size)
sliceHeader.Cap = int(size)
return *(*[]byte)(unsafe.Pointer(&sliceHeader))
}
//export query
func query(pResult unsafe.Pointer, ctxIdx uintptr, key int32, pArg unsafe.Pointer) {
// Represent the result memory as Go slice of 32 bytes.
result := GoByteSlice(pResult, 32)
// Or as pointer to int64.
pInt64Result := (*int64)(pResult)
// Get the execution context.
ctx := getCtx(ctxIdx)
switch key {
case C.EVM_SLOAD:
arg := *(*[32]byte)(pArg)
val := ctx.env.StateDB.GetState(ctx.contract.Address(), arg)
copy(result, val[:])
// fmt.Printf("EVMJIT SLOAD %x : %x\n", arg, result)
case C.EVM_ADDRESS:
addr := ctx.contract.Address()
copy(result[12:], addr[:])
case C.EVM_CALLER:
addr := ctx.contract.Caller()
copy(result[12:], addr[:])
case C.EVM_ORIGIN:
copy(result[12:], ctx.env.Origin[:])
case C.EVM_GAS_PRICE:
val := common.BigToHash(ctx.env.GasPrice)
copy(result, val[:])
case C.EVM_COINBASE:
copy(result[12:], ctx.env.Coinbase[:])
case C.EVM_DIFFICULTY:
val := common.BigToHash(ctx.env.Difficulty)
copy(result, val[:])
case C.EVM_GAS_LIMIT:
*pInt64Result = ctx.env.GasLimit.Int64()
case C.EVM_NUMBER:
*pInt64Result = ctx.env.BlockNumber.Int64()
case C.EVM_TIMESTAMP:
*pInt64Result = ctx.env.Time.Int64()
case C.EVM_CODE_BY_ADDRESS:
arg := GoByteSlice(pArg, 32)
var addr common.Address
copy(addr[:], arg[12:])
code := ctx.env.StateDB.GetCode(addr)
pResAsMemRef := (*MemoryRef)(pResult)
pResAsMemRef.ptr = ptr(code)
pResAsMemRef.len = len(code)
// fmt.Printf("EXTCODE %x : %d\n", addr, pResAsMemRef.len)
case C.EVM_CODE_SIZE:
arg := GoByteSlice(pArg, 32)
var addr common.Address
copy(addr[:], arg[12:])
pInt64Result := (*int64)(pResult)
*pInt64Result = int64(ctx.env.StateDB.GetCodeSize(addr))
// fmt.Printf("EXTCODESIZE %x : %d\n", addr, *pInt64Result)
case C.EVM_BALANCE:
arg := GoByteSlice(pArg, 32)
var addr common.Address
copy(addr[:], arg[12:])
balance := ctx.env.StateDB.GetBalance(addr)
val := common.BigToHash(balance)
copy(result, val[:])
case C.EVM_BLOCKHASH:
n := *(*int64)(pArg)
b := ctx.env.BlockNumber.Int64()
a := b - 256
var hash common.Hash
if n >= a && n < b {
hash = ctx.env.GetHash(uint64(n))
}
copy(result, hash[:])
// fmt.Printf("BLOCKHASH %x : %x (%d, %d, %d)\n", result, hash, n, a, b)
case C.EVM_ACCOUNT_EXISTS:
arg := GoByteSlice(pArg, 32)
var addr common.Address
copy(addr[:], arg[12:])
eip158 := ctx.env.ChainConfig().IsEIP158(ctx.env.BlockNumber)
var exist int64
if eip158 {
if !ctx.env.StateDB.Empty(addr) {
exist = 1
}
} else if ctx.env.StateDB.Exist(addr) {
exist = 1
}
*pInt64Result = exist
// fmt.Printf("EXISTS? %x : %v\n", addr, exist)
case C.EVM_CALL_DEPTH:
*pInt64Result = int64(ctx.env.Depth - 1)
default:
panic(fmt.Sprintf("Unhandled EVM-C query %d\n", key))
}
}
//export update
func update(ctxIdx uintptr, key int32, pArg1 unsafe.Pointer, pArg2 unsafe.Pointer) {
ctx := getCtx(ctxIdx)
switch key {
case C.EVM_SSTORE:
key := *(*[32]byte)(pArg1)
newVal := *(*[32]byte)(pArg2)
oldVal := ctx.env.StateDB.GetState(ctx.contract.Address(), key)
ctx.env.StateDB.SetState(ctx.contract.Address(), key, newVal)
if !common.EmptyHash(oldVal) && common.EmptyHash(newVal) {
ctx.env.StateDB.AddRefund(params.SstoreRefundGas)
}
// fmt.Printf("EVMJIT STORE %x : %x [%x, %d]\n", arg1, arg2, ctx.contract.Address(), int(uintptr(pEnv)))
case C.EVM_LOG:
dataRef := (*MemoryRef)(pArg1)
topicsRef := (*MemoryRef)(pArg2)
data := C.GoBytes(unsafe.Pointer(dataRef.ptr), C.int(dataRef.len))
// FIXME: Avoid double copy of topics.
tData := C.GoBytes(unsafe.Pointer(topicsRef.ptr), C.int(topicsRef.len))
nTopics := topicsRef.len / 32
topics := make([]common.Hash, nTopics)
for i := 0; i < nTopics; i++ {
copy(topics[i][:], tData[i*32:(i+1)*32])
}
log := NewLog(ctx.contract.Address(), topics, data, ctx.env.BlockNumber.Uint64())
ctx.env.StateDB.AddLog(log)
case C.EVM_SELFDESTRUCT:
arg := GoByteSlice(pArg1, 32)
db := ctx.env.StateDB
addr := ctx.contract.Address()
if !db.HasSuicided(addr) {
db.AddRefund(params.SuicideRefundGas)
}
balance := db.GetBalance(addr)
beneficiary := common.BytesToAddress(arg[12:])
db.AddBalance(beneficiary, balance)
db.Suicide(addr)
}
}
//export call
func call(
pCtx unsafe.Pointer,
kind int32,
gas int64,
pAddr unsafe.Pointer,
pValue unsafe.Pointer,
pInput unsafe.Pointer,
inputSize C.size_t,
pOutput unsafe.Pointer,
outputSize C.size_t) int64 {
ctx := getCtx(uintptr(pCtx))
address := *(*[20]byte)(pAddr)
value := (*(*common.Hash)(pValue)).Big()
input := GoByteSlice(pInput, inputSize)
output := GoByteSlice(pOutput, outputSize)
bigGas := new(big.Int).SetInt64(gas)
switch kind {
case C.EVM_CALL:
// fmt.Printf("CALL(gas %d, %x)\n", bigGas, address)
ctx.contract.Gas.SetInt64(0)
ret, err := ctx.env.Call(ctx.contract, address, input, bigGas, value)
gasLeft := ctx.contract.Gas.Int64()
assert(gasLeft <= gas, fmt.Sprintf("%d <= %d", gasLeft, gas))
// fmt.Printf("Gas left %d\n", gasLeft)
if err == nil {
copy(output, ret)
assert(gasLeft <= gas, fmt.Sprintf("%d <= %d", gasLeft, gas))
return gasLeft
}
return gasLeft | C.EVM_CALL_FAILURE
case C.EVM_CALLCODE:
// fmt.Printf("CALLCODE(gas %d, %x, value %d)\n", bigGas, address, value)
ctx.contract.Gas.SetInt64(0)
ret, err := ctx.env.CallCode(ctx.contract, address, input, bigGas, value)
gasLeft := ctx.contract.Gas.Int64()
assert(gasLeft <= gas, fmt.Sprintf("%d <= %d", gasLeft, gas))
// fmt.Printf("Gas left %d\n", gasLeft)
if err == nil {
copy(output, ret)
return gasLeft
} else {
// fmt.Printf("Error: %v\n", err)
}
return gasLeft | C.EVM_CALL_FAILURE
case C.EVM_DELEGATECALL:
// fmt.Printf("DELEGATECALL(gas %d, %x)\n", bigGas, address)
ctx.contract.Gas.SetInt64(0)
ret, err := ctx.env.DelegateCall(ctx.contract, address, input, bigGas)
gasLeft := ctx.contract.Gas.Int64()
assert(gasLeft <= gas, fmt.Sprintf("%d <= %d", gasLeft, gas))
// fmt.Printf("Gas left %d\n", gasLeft)
if err == nil {
copy(output, ret)
return gasLeft
}
return gasLeft | C.EVM_CALL_FAILURE
case C.EVM_CREATE:
// fmt.Printf("DELEGATECALL(gas %d, %x)\n", bigGas, address)
ctx.contract.Gas.SetInt64(0)
_, addr, err := ctx.env.Create(ctx.contract, input, bigGas, value)
gasLeft := ctx.contract.Gas.Int64()
assert(gasLeft <= gas, fmt.Sprintf("%d <= %d", gasLeft, gas))
if (ctx.env.ChainConfig().IsHomestead(ctx.env.BlockNumber) && err == CodeStoreOutOfGasError) ||
(err != nil && err != CodeStoreOutOfGasError) {
return C.EVM_CALL_FAILURE
} else {
copy(output, addr[:])
return gasLeft
}
}
return 0
}
func ptr(bytes []byte) *C.uint8_t {
header := *(*reflect.SliceHeader)(unsafe.Pointer(&bytes))
return (*C.uint8_t)(unsafe.Pointer(header.Data))
}
func getMode(env *Environment) C.enum_evm_mode {
n := env.BlockNumber
if env.ChainConfig().IsEIP158(n) {
return C.EVM_CLEARING
}
if env.ChainConfig().IsEIP150(n) {
return C.EVM_ANTI_DOS
}
if env.ChainConfig().IsHomestead(n) {
return C.EVM_HOMESTEAD
}
return C.EVM_FRONTIER
}
func (evm *EVMJIT) Run(contract *Contract, input []byte) (ret []byte, err error) {
evm.env.Depth++
defer func() { evm.env.Depth-- }()
if contract.CodeAddr != nil {
if p := Precompiled[contract.CodeAddr.Str()]; p != nil {
return evm.RunPrecompiled(p, input, contract)
}
}
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
code := contract.Code
codePtr := (*C.uint8_t)(unsafe.Pointer(&code[0]))
codeSize := C.size_t(len(code))
codeHash := HashToEvmc(crypto.Keccak256Hash(code))
gas := C.int64_t(contract.Gas.Int64())
inputPtr := ptr(input)
inputLen := C.size_t(len(input))
value := BigToEvmc(contract.value)
mode := getMode(evm.env)
// fmt.Printf("EVMJIT pre Run (gas %d %d mode: %d, env: %d) %x\n", contract.Gas, gas, mode, env, evm.contract.Address())
// Create context for this execution.
ctxId := pinCtx(&EVMCContext{contract, evm.env})
r := C.evm_execute(evm.jit, unsafe.Pointer(ctxId), mode, codeHash, codePtr, codeSize, gas, inputPtr, inputLen, value)
unpinCtx(ctxId)
// fmt.Printf("EVMJIT Run %d %d %x\n", r.code, r.gas_left, evm.contract.Address())
if r.gas_left > gas {
panic("OOPS")
}
contract.Gas.SetInt64(int64(r.gas_left))
// fmt.Printf("Gas left: %d\n", contract.Gas)
output := C.GoBytes(unsafe.Pointer(r.output_data), C.int(r.output_size))
if r.code != 0 {
// EVMJIT does not informs about the kind of the EVM expection.
err = OutOfGasError
}
C.evm_release_result(&r)
return output, err
}
func (evm *EVMJIT) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.Gas(len(input))
if contract.UseGas(gas) {
ret = p.Call(input)
return ret, nil
} else {
return nil, OutOfGasError
}
}