• Jeffrey Wilcke's avatar
    core/vm: improved EVM run loop & instruction calling (#3378) · bbc4ea4a
    Jeffrey Wilcke authored
    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.
    bbc4ea4a
vm_jit.go 10.5 KB
// 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/>.

// +build evmjit

package vm

/*

void* evmjit_create();
int   evmjit_run(void* _jit, void* _data, void* _env);
void  evmjit_destroy(void* _jit);

// Shared library evmjit (e.g. libevmjit.so) is expected to be installed in /usr/local/lib
// More: https://github.com/ethereum/evmjit
#cgo LDFLAGS: -levmjit
*/
import "C"

/*
import (
	"bytes"
	"errors"
	"fmt"
	"math/big"
	"unsafe"

	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/params"
)

type JitVm struct {
	env        EVM
	me         ContextRef
	callerAddr []byte
	price      *big.Int
	data       RuntimeData
}

type i256 [32]byte

type RuntimeData struct {
	gas          int64
	gasPrice     int64
	callData     *byte
	callDataSize uint64
	address      i256
	caller       i256
	origin       i256
	callValue    i256
	coinBase     i256
	difficulty   i256
	gasLimit     i256
	number       uint64
	timestamp    int64
	code         *byte
	codeSize     uint64
	codeHash     i256
}

func hash2llvm(h []byte) i256 {
	var m i256
	copy(m[len(m)-len(h):], h) // right aligned copy
	return m
}

func llvm2hash(m *i256) []byte {
	return C.GoBytes(unsafe.Pointer(m), C.int(len(m)))
}

func llvm2hashRef(m *i256) []byte {
	return (*[1 << 30]byte)(unsafe.Pointer(m))[:len(m):len(m)]
}

func address2llvm(addr []byte) i256 {
	n := hash2llvm(addr)
	bswap(&n)
	return n
}

// bswap swap bytes of the 256-bit integer on LLVM side
// TODO: Do not change memory on LLVM side, that can conflict with memory access optimizations
func bswap(m *i256) *i256 {
	for i, l := 0, len(m); i < l/2; i++ {
		m[i], m[l-i-1] = m[l-i-1], m[i]
	}
	return m
}

func trim(m []byte) []byte {
	skip := 0
	for i := 0; i < len(m); i++ {
		if m[i] == 0 {
			skip++
		} else {
			break
		}
	}
	return m[skip:]
}

func getDataPtr(m []byte) *byte {
	var p *byte
	if len(m) > 0 {
		p = &m[0]
	}
	return p
}

func big2llvm(n *big.Int) i256 {
	m := hash2llvm(n.Bytes())
	bswap(&m)
	return m
}

func llvm2big(m *i256) *big.Int {
	n := big.NewInt(0)
	for i := 0; i < len(m); i++ {
		b := big.NewInt(int64(m[i]))
		b.Lsh(b, uint(i)*8)
		n.Add(n, b)
	}
	return n
}

// llvm2bytesRef creates a []byte slice that references byte buffer on LLVM side (as of that not controller by GC)
// User must ensure that referenced memory is available to Go until the data is copied or not needed any more
func llvm2bytesRef(data *byte, length uint64) []byte {
	if length == 0 {
		return nil
	}
	if data == nil {
		panic("Unexpected nil data pointer")
	}
	return (*[1 << 30]byte)(unsafe.Pointer(data))[:length:length]
}

func untested(condition bool, message string) {
	if condition {
		panic("Condition `" + message + "` tested. Remove assert.")
	}
}

func assert(condition bool, message string) {
	if !condition {
		panic("Assert `" + message + "` failed!")
	}
}

func NewJitVm(env EVM) *JitVm {
	return &JitVm{env: env}
}

func (self *JitVm) Run(me, caller ContextRef, code []byte, value, gas, price *big.Int, callData []byte) (ret []byte, err error) {
	// TODO: depth is increased but never checked by VM. VM should not know about it at all.
	self.env.SetDepth(self.env.Depth() + 1)

	// TODO: Move it to Env.Call() or sth
	if Precompiled[string(me.Address())] != nil {
		// if it's address of precompiled contract
		// fallback to standard VM
		stdVm := New(self.env)
		return stdVm.Run(me, caller, code, value, gas, price, callData)
	}

	if self.me != nil {
		panic("JitVm.Run() can be called only once per JitVm instance")
	}

	self.me = me
	self.callerAddr = caller.Address()
	self.price = price

	self.data.gas = gas.Int64()
	self.data.gasPrice = price.Int64()
	self.data.callData = getDataPtr(callData)
	self.data.callDataSize = uint64(len(callData))
	self.data.address = address2llvm(self.me.Address())
	self.data.caller = address2llvm(caller.Address())
	self.data.origin = address2llvm(self.env.Origin())
	self.data.callValue = big2llvm(value)
	self.data.coinBase = address2llvm(self.env.Coinbase())
	self.data.difficulty = big2llvm(self.env.Difficulty())
	self.data.gasLimit = big2llvm(self.env.GasLimit())
	self.data.number = self.env.BlockNumber().Uint64()
	self.data.timestamp = self.env.Time()
	self.data.code = getDataPtr(code)
	self.data.codeSize = uint64(len(code))
	self.data.codeHash = hash2llvm(crypto.Keccak256(code)) // TODO: Get already computed hash?

	jit := C.evmjit_create()
	retCode := C.evmjit_run(jit, unsafe.Pointer(&self.data), unsafe.Pointer(self))

	if retCode < 0 {
		err = errors.New("OOG from JIT")
		gas.SetInt64(0) // Set gas to 0, JIT does not bother
	} else {
		gas.SetInt64(self.data.gas)
		if retCode == 1 { // RETURN
			ret = C.GoBytes(unsafe.Pointer(self.data.callData), C.int(self.data.callDataSize))
		} else if retCode == 2 { // SUICIDE
			// TODO: Suicide support logic should be moved to Env to be shared by VM implementations
			state := self.Env().State()
			receiverAddr := llvm2hashRef(bswap(&self.data.address))
			receiver := state.GetOrNewStateObject(receiverAddr)
			balance := state.GetBalance(me.Address())
			receiver.AddBalance(balance)
			state.Delete(me.Address())
		}
	}

	C.evmjit_destroy(jit)
	return
}

func (self *JitVm) Printf(format string, v ...interface{}) VirtualMachine {
	return self
}

func (self *JitVm) Endl() VirtualMachine {
	return self
}

func (self *JitVm) Env() EVM {
	return self.env
}

//export env_sha3
func env_sha3(dataPtr *byte, length uint64, resultPtr unsafe.Pointer) {
	data := llvm2bytesRef(dataPtr, length)
	hash := crypto.Keccak256(data)
	result := (*i256)(resultPtr)
	*result = hash2llvm(hash)
}

//export env_sstore
func env_sstore(vmPtr unsafe.Pointer, indexPtr unsafe.Pointer, valuePtr unsafe.Pointer) {
	vm := (*JitVm)(vmPtr)
	index := llvm2hash(bswap((*i256)(indexPtr)))
	value := llvm2hash(bswap((*i256)(valuePtr)))
	value = trim(value)
	if len(value) == 0 {
		prevValue := vm.env.State().GetState(vm.me.Address(), index)
		if len(prevValue) != 0 {
			vm.Env().State().Refund(vm.callerAddr, GasSStoreRefund)
		}
	}

	vm.env.State().SetState(vm.me.Address(), index, value)
}

//export env_sload
func env_sload(vmPtr unsafe.Pointer, indexPtr unsafe.Pointer, resultPtr unsafe.Pointer) {
	vm := (*JitVm)(vmPtr)
	index := llvm2hash(bswap((*i256)(indexPtr)))
	value := vm.env.State().GetState(vm.me.Address(), index)
	result := (*i256)(resultPtr)
	*result = hash2llvm(value)
	bswap(result)
}

//export env_balance
func env_balance(_vm unsafe.Pointer, _addr unsafe.Pointer, _result unsafe.Pointer) {
	vm := (*JitVm)(_vm)
	addr := llvm2hash((*i256)(_addr))
	balance := vm.Env().State().GetBalance(addr)
	result := (*i256)(_result)
	*result = big2llvm(balance)
}

//export env_blockhash
func env_blockhash(_vm unsafe.Pointer, _number unsafe.Pointer, _result unsafe.Pointer) {
	vm := (*JitVm)(_vm)
	number := llvm2big((*i256)(_number))
	result := (*i256)(_result)

	currNumber := vm.Env().BlockNumber()
	limit := big.NewInt(0).Sub(currNumber, big.NewInt(256))
	if number.Cmp(limit) >= 0 && number.Cmp(currNumber) < 0 {
		hash := vm.Env().GetHash(uint64(number.Int64()))
		*result = hash2llvm(hash)
	} else {
		*result = i256{}
	}
}

//export env_call
func env_call(_vm unsafe.Pointer, _gas *int64, _receiveAddr unsafe.Pointer, _value unsafe.Pointer, inDataPtr unsafe.Pointer, inDataLen uint64, outDataPtr *byte, outDataLen uint64, _codeAddr unsafe.Pointer) bool {
	vm := (*JitVm)(_vm)

	//fmt.Printf("env_call (depth %d)\n", vm.Env().Depth())

	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("Recovered in env_call (depth %d, out %p %d): %s\n", vm.Env().Depth(), outDataPtr, outDataLen, r)
		}
	}()

	balance := vm.Env().State().GetBalance(vm.me.Address())
	value := llvm2big((*i256)(_value))

	if balance.Cmp(value) >= 0 {
		receiveAddr := llvm2hash((*i256)(_receiveAddr))
		inData := C.GoBytes(inDataPtr, C.int(inDataLen))
		outData := llvm2bytesRef(outDataPtr, outDataLen)
		codeAddr := llvm2hash((*i256)(_codeAddr))
		gas := big.NewInt(*_gas)
		var out []byte
		var err error
		if bytes.Equal(codeAddr, receiveAddr) {
			out, err = vm.env.Call(vm.me, codeAddr, inData, gas, vm.price, value)
		} else {
			out, err = vm.env.CallCode(vm.me, codeAddr, inData, gas, vm.price, value)
		}
		*_gas = gas.Int64()
		if err == nil {
			copy(outData, out)
			return true
		}
	}

	return false
}

//export env_create
func env_create(_vm unsafe.Pointer, _gas *int64, _value unsafe.Pointer, initDataPtr unsafe.Pointer, initDataLen uint64, _result unsafe.Pointer) {
	vm := (*JitVm)(_vm)

	value := llvm2big((*i256)(_value))
	initData := C.GoBytes(initDataPtr, C.int(initDataLen)) // TODO: Unnecessary if low balance
	result := (*i256)(_result)
	*result = i256{}

	gas := big.NewInt(*_gas)
	ret, suberr, ref := vm.env.Create(vm.me, nil, initData, gas, vm.price, value)
	if suberr == nil {
		dataGas := big.NewInt(int64(len(ret))) // TODO: Not the best design. env.Create can do it, it has the reference to gas counter
		dataGas.Mul(dataGas, params.CreateDataGas)
		gas.Sub(gas, dataGas)
		*result = hash2llvm(ref.Address())
	}
	*_gas = gas.Int64()
}

//export env_log
func env_log(_vm unsafe.Pointer, dataPtr unsafe.Pointer, dataLen uint64, _topic1 unsafe.Pointer, _topic2 unsafe.Pointer, _topic3 unsafe.Pointer, _topic4 unsafe.Pointer) {
	vm := (*JitVm)(_vm)

	data := C.GoBytes(dataPtr, C.int(dataLen))

	topics := make([][]byte, 0, 4)
	if _topic1 != nil {
		topics = append(topics, llvm2hash((*i256)(_topic1)))
	}
	if _topic2 != nil {
		topics = append(topics, llvm2hash((*i256)(_topic2)))
	}
	if _topic3 != nil {
		topics = append(topics, llvm2hash((*i256)(_topic3)))
	}
	if _topic4 != nil {
		topics = append(topics, llvm2hash((*i256)(_topic4)))
	}

	vm.Env().AddLog(state.NewLog(vm.me.Address(), topics, data, vm.env.BlockNumber().Uint64()))
}

//export env_extcode
func env_extcode(_vm unsafe.Pointer, _addr unsafe.Pointer, o_size *uint64) *byte {
	vm := (*JitVm)(_vm)
	addr := llvm2hash((*i256)(_addr))
	code := vm.Env().State().GetCode(addr)
	*o_size = uint64(len(code))
	return getDataPtr(code)
}*/