mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 21:26:42 +00:00
cmd/vm: code segmenting
This commit is contained in:
parent
be935bff84
commit
cb7cd07ed0
9 changed files with 237 additions and 81 deletions
134
cmd/evm/main.go
134
cmd/evm/main.go
|
|
@ -22,79 +22,153 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
code = flag.String("code", "", "evm code")
|
||||
loglevel = flag.Int("log", 4, "log level")
|
||||
gas = flag.String("gas", "1000000000", "gas amount")
|
||||
price = flag.String("price", "0", "gas price")
|
||||
value = flag.String("value", "0", "tx value")
|
||||
dump = flag.Bool("dump", false, "dump state after run")
|
||||
data = flag.String("data", "", "data")
|
||||
app *cli.App
|
||||
DebugFlag = cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "output full trace logs",
|
||||
}
|
||||
CodeFlag = cli.StringFlag{
|
||||
Name: "code",
|
||||
Usage: "EVM code",
|
||||
}
|
||||
GasFlag = cli.StringFlag{
|
||||
Name: "gas",
|
||||
Usage: "gas limit for the evm",
|
||||
Value: "10000000000",
|
||||
}
|
||||
PriceFlag = cli.StringFlag{
|
||||
Name: "price",
|
||||
Usage: "price set for the evm",
|
||||
Value: "0",
|
||||
}
|
||||
ValueFlag = cli.StringFlag{
|
||||
Name: "value",
|
||||
Usage: "value set for the evm",
|
||||
Value: "0",
|
||||
}
|
||||
DumpFlag = cli.BoolFlag{
|
||||
Name: "dump",
|
||||
Usage: "dumps the state after the run",
|
||||
}
|
||||
InputFlag = cli.StringFlag{
|
||||
Name: "input",
|
||||
Usage: "input for the EVM",
|
||||
}
|
||||
SysStatFlag = cli.BoolFlag{
|
||||
Name: "sysstat",
|
||||
Usage: "display system stats",
|
||||
}
|
||||
)
|
||||
|
||||
func perr(v ...interface{}) {
|
||||
fmt.Println(v...)
|
||||
//os.Exit(1)
|
||||
func init() {
|
||||
app = utils.NewApp("0.2", "the evm command line interface")
|
||||
app.Flags = []cli.Flag{
|
||||
DebugFlag,
|
||||
SysStatFlag,
|
||||
CodeFlag,
|
||||
GasFlag,
|
||||
PriceFlag,
|
||||
ValueFlag,
|
||||
DumpFlag,
|
||||
InputFlag,
|
||||
}
|
||||
app.Action = run
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
func run(ctx *cli.Context) {
|
||||
vm.Debug = ctx.GlobalBool(DebugFlag.Name)
|
||||
|
||||
logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel)))
|
||||
|
||||
vm.Debug = true
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb := state.New(common.Hash{}, db)
|
||||
sender := statedb.CreateAccount(common.StringToAddress("sender"))
|
||||
receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
|
||||
receiver.SetCode(common.Hex2Bytes(*code))
|
||||
receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)))
|
||||
|
||||
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(*value))
|
||||
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)))
|
||||
|
||||
tstart := time.Now()
|
||||
|
||||
ret, e := vmenv.Call(sender, receiver.Address(), common.Hex2Bytes(*data), common.Big(*gas), common.Big(*price), common.Big(*value))
|
||||
vmstart := time.Now()
|
||||
ret, e := vmenv.Call(
|
||||
sender,
|
||||
receiver.Address(),
|
||||
common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
|
||||
common.Big(ctx.GlobalString(GasFlag.Name)),
|
||||
common.Big(ctx.GlobalString(PriceFlag.Name)),
|
||||
common.Big(ctx.GlobalString(ValueFlag.Name)),
|
||||
)
|
||||
|
||||
logger.Flush()
|
||||
if e != nil {
|
||||
perr(e)
|
||||
fmt.Println(e)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("no analysis", time.Since(vmstart))
|
||||
|
||||
if *dump {
|
||||
vmstart = time.Now()
|
||||
ret, e = vmenv.Call(
|
||||
sender,
|
||||
receiver.Address(),
|
||||
common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
|
||||
common.Big(ctx.GlobalString(GasFlag.Name)),
|
||||
common.Big(ctx.GlobalString(PriceFlag.Name)),
|
||||
common.Big(ctx.GlobalString(ValueFlag.Name)),
|
||||
)
|
||||
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("with analysis", time.Since(vmstart))
|
||||
|
||||
if ctx.GlobalBool(DumpFlag.Name) {
|
||||
fmt.Println(string(statedb.Dump()))
|
||||
}
|
||||
|
||||
vm.StdErrFormat(vmenv.StructLogs())
|
||||
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
fmt.Printf("vm took %v\n", time.Since(tstart))
|
||||
fmt.Printf(`alloc: %d
|
||||
if ctx.GlobalBool(SysStatFlag.Name) {
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
fmt.Printf("vm took %v\n", time.Since(tstart))
|
||||
fmt.Printf(`alloc: %d
|
||||
tot alloc: %d
|
||||
no. malloc: %d
|
||||
heap alloc: %d
|
||||
heap objs: %d
|
||||
num gc: %d
|
||||
`, mem.Alloc, mem.TotalAlloc, mem.Mallocs, mem.HeapAlloc, mem.HeapObjects, mem.NumGC)
|
||||
}
|
||||
|
||||
fmt.Printf("%x\n", ret)
|
||||
fmt.Printf("OUT: 0x%x\n", ret)
|
||||
|
||||
for codehash, chunks := range vm.Chunks {
|
||||
for _, chunk := range chunks {
|
||||
fmt.Printf("%x => %v\n", codehash, chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type VMEnv struct {
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ func (self *StateObject) AddGas(gas, price *big.Int) {
|
|||
}
|
||||
|
||||
func (self *StateObject) Copy() *StateObject {
|
||||
stateObject := NewStateObject(self.Address(), self.db)
|
||||
stateObject := &StateObject{db: self.db, address: self.address, balance: new(big.Int), gasPool: new(big.Int), dirty: true}
|
||||
stateObject.balance.Set(self.balance)
|
||||
stateObject.codeHash = common.CopyBytes(self.codeHash)
|
||||
stateObject.nonce = self.nonce
|
||||
|
|
|
|||
|
|
@ -26,16 +26,25 @@ type Context struct {
|
|||
Args []byte
|
||||
}
|
||||
|
||||
var dests destinations
|
||||
|
||||
func init() {
|
||||
dests = make(destinations)
|
||||
}
|
||||
|
||||
// Create a new context for the given data items.
|
||||
func NewContext(caller ContextRef, object ContextRef, value, gas, price *big.Int) *Context {
|
||||
c := &Context{caller: caller, self: object, Args: nil}
|
||||
|
||||
if parent, ok := caller.(*Context); ok {
|
||||
// Reuse JUMPDEST analysis from parent context if available.
|
||||
c.jumpdests = parent.jumpdests
|
||||
} else {
|
||||
c.jumpdests = make(destinations)
|
||||
}
|
||||
c.jumpdests = dests
|
||||
/*
|
||||
if parent, ok := caller.(*Context); ok {
|
||||
// Reuse JUMPDEST analysis from parent context if available.
|
||||
c.jumpdests = parent.jumpdests
|
||||
} else {
|
||||
c.jumpdests = make(destinations)
|
||||
}
|
||||
*/
|
||||
|
||||
// Gas should be a pointer so it can safely be reduced through the run
|
||||
// This pointer will be off the state transition
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ func baseCheck(op OpCode, stack *stack, gas *big.Int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if r.stackPush > 0 && len(stack.data)-r.stackPop+r.stackPush > int(params.StackLimit.Int64())+1 {
|
||||
return fmt.Errorf("stack limit reached %d (%d)", len(stack.data), params.StackLimit.Int64())
|
||||
if r.stackPush > 0 && stack.len()-r.stackPop+r.stackPush > int(params.StackLimit.Int64()) {
|
||||
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64())
|
||||
}
|
||||
|
||||
gas.Add(gas, r.gas)
|
||||
|
|
|
|||
|
|
@ -332,3 +332,15 @@ func (o OpCode) String() string {
|
|||
|
||||
return str
|
||||
}
|
||||
|
||||
func isDynamic(op OpCode) bool {
|
||||
switch op {
|
||||
case CREATE, CALL, CALLCODE, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8:
|
||||
return true
|
||||
}
|
||||
if _, ok := opCodeToString[op]; !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,32 +11,26 @@ func newstack() *stack {
|
|||
|
||||
type stack struct {
|
||||
data []*big.Int
|
||||
ptr int
|
||||
}
|
||||
|
||||
func (st *stack) Data() []*big.Int {
|
||||
return st.data[:st.ptr]
|
||||
return st.data
|
||||
}
|
||||
|
||||
func (st *stack) push(d *big.Int) {
|
||||
// NOTE push limit (1024) is checked in baseCheck
|
||||
stackItem := new(big.Int).Set(d)
|
||||
if len(st.data) > st.ptr {
|
||||
st.data[st.ptr] = stackItem
|
||||
} else {
|
||||
st.data = append(st.data, stackItem)
|
||||
}
|
||||
st.ptr++
|
||||
st.data = append(st.data, stackItem)
|
||||
}
|
||||
|
||||
func (st *stack) pop() (ret *big.Int) {
|
||||
st.ptr--
|
||||
ret = st.data[st.ptr]
|
||||
ret = st.data[len(st.data)-1]
|
||||
st.data = st.data[:len(st.data)-1]
|
||||
return
|
||||
}
|
||||
|
||||
func (st *stack) len() int {
|
||||
return st.ptr
|
||||
return len(st.data)
|
||||
}
|
||||
|
||||
func (st *stack) swap(n int) {
|
||||
|
|
|
|||
113
core/vm/vm.go
113
core/vm/vm.go
|
|
@ -8,8 +8,27 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
type segment struct {
|
||||
cstart, cend uint64
|
||||
msize uint64
|
||||
gas *big.Int
|
||||
}
|
||||
|
||||
func (c *segment) String() string {
|
||||
return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas)
|
||||
}
|
||||
|
||||
type codeSegments map[uint64]*segment
|
||||
|
||||
var segments *lru.Cache
|
||||
|
||||
func init() {
|
||||
segments, _ = lru.New(256)
|
||||
}
|
||||
|
||||
// Vm implements VirtualMachine
|
||||
type Vm struct {
|
||||
env Environment
|
||||
|
|
@ -44,14 +63,19 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
|||
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
|
||||
codehash = crypto.FnvHash(code) // codehash is used when doing jump dest caching
|
||||
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
|
||||
pc = uint64(0) // program counter
|
||||
ppc = uint64(0) // previous program counter
|
||||
csegment *segment // current code segment
|
||||
csegments codeSegments // program segments
|
||||
nopay uint64 = 0
|
||||
disableOptim bool
|
||||
|
||||
// jump evaluates and checks whether the given jump destination is a valid one
|
||||
// if valid move the `pc` otherwise return an error.
|
||||
|
|
@ -69,13 +93,15 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
|||
newMemSize *big.Int
|
||||
cost *big.Int
|
||||
)
|
||||
if c, ok := segments.Get(codehash); ok {
|
||||
csegments = c.(codeSegments)
|
||||
} else {
|
||||
csegments = make(codeSegments)
|
||||
segments.Add(codehash, csegments)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
|
|
@ -84,6 +110,13 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
|||
|
||||
ret = context.Return(nil)
|
||||
}
|
||||
|
||||
if csegment != nil && err != nil && csegment.cend == 0 {
|
||||
// delete this tracking segment. We couldn't fully determine the stats
|
||||
delete(csegments, csegment.cstart)
|
||||
} else if csegment != nil && err == nil && csegment.cend == 0 {
|
||||
csegment.cend = pc
|
||||
}
|
||||
}()
|
||||
|
||||
if context.CodeAddr != nil {
|
||||
|
|
@ -98,31 +131,57 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
|||
}
|
||||
|
||||
for {
|
||||
// The base for all big integer arithmetic
|
||||
base := new(big.Int)
|
||||
|
||||
// Get the memory location of pc
|
||||
// Get the current operation location of pc
|
||||
op = context.GetOp(pc)
|
||||
if isDynamic(op) && csegment != nil {
|
||||
csegment.cend = ppc
|
||||
csegment = nil
|
||||
nopay = 0
|
||||
} else if csegment == nil && pc != 0 {
|
||||
// allocate a new segment for tracking information
|
||||
csegment = &segment{pc, 0, 0, new(big.Int)}
|
||||
csegments[pc] = csegment
|
||||
} else {
|
||||
if segment, ok := csegments[pc]; ok && !disableOptim {
|
||||
nopay = segment.cend
|
||||
if !context.UseGas(segment.gas) {
|
||||
context.UseGas(context.Gas)
|
||||
|
||||
// calculate the new memory size and gas price for the current executing opcode
|
||||
newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return context.Return(nil), OutOfGasError{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the calculated gas. When insufficient gas is present, use all gas and return an
|
||||
// Out Of Gas error
|
||||
if !context.UseGas(cost) {
|
||||
if pc > nopay || pc == 0 || disableOptim {
|
||||
// calculate the new memory size and gas price for the current executing opcode
|
||||
newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
context.UseGas(context.Gas)
|
||||
// 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 context.Return(nil), OutOfGasError{}
|
||||
}
|
||||
|
||||
// Resize the memory calculated previously
|
||||
mem.Resize(newMemSize.Uint64())
|
||||
|
||||
if csegment != nil {
|
||||
csegment.msize = uint64(len(mem.store))
|
||||
csegment.gas.Add(csegment.gas, cost)
|
||||
}
|
||||
}
|
||||
// Resize the memory calculated previously
|
||||
mem.Resize(newMemSize.Uint64())
|
||||
// Add a log message
|
||||
self.log(pc, op, context.Gas, cost, mem, stack, context, nil)
|
||||
|
||||
ppc = pc
|
||||
// The base for all big integer arithmetic
|
||||
base := new(big.Int)
|
||||
|
||||
switch op {
|
||||
case ADD:
|
||||
x, y := stack.pop(), stack.pop()
|
||||
|
|
@ -348,7 +407,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
|||
|
||||
case SHA3:
|
||||
offset, size := stack.pop(), stack.pop()
|
||||
data := crypto.Sha3(mem.Get(offset.Int64(), size.Int64()))
|
||||
data := crypto.Sha3(mem.GetPtr(offset.Int64(), size.Int64()))
|
||||
|
||||
stack.push(common.BigD(data))
|
||||
|
||||
|
|
@ -491,7 +550,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
|||
|
||||
case MLOAD:
|
||||
offset := stack.pop()
|
||||
val := common.BigD(mem.Get(offset.Int64(), 32))
|
||||
val := common.BigD(mem.GetPtr(offset.Int64(), 32))
|
||||
stack.push(val)
|
||||
|
||||
case MSTORE:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
|
|
@ -35,6 +36,15 @@ func init() {
|
|||
secp256k1n = common.String2Big("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
|
||||
}
|
||||
|
||||
func FnvHash(data ...[]byte) (h common.Hash) {
|
||||
fnvHash := fnv.New32()
|
||||
for _, b := range data {
|
||||
fnvHash.Write(b)
|
||||
}
|
||||
fnvHash.Sum(h[:0])
|
||||
return h
|
||||
}
|
||||
|
||||
func Sha3(data ...[]byte) []byte {
|
||||
d := sha3.NewKeccak256()
|
||||
for _, b := range data {
|
||||
|
|
|
|||
|
|
@ -91,9 +91,7 @@ func runStateTest(test VmTest) error {
|
|||
}
|
||||
|
||||
var (
|
||||
ret []byte
|
||||
// gas *big.Int
|
||||
// err error
|
||||
ret []byte
|
||||
logs state.Logs
|
||||
)
|
||||
|
||||
|
|
@ -113,7 +111,7 @@ func runStateTest(test VmTest) error {
|
|||
}
|
||||
|
||||
if obj.Balance().Cmp(common.Big(account.Balance)) != 0 {
|
||||
return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], account.Balance, obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance()))
|
||||
return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], common.Big(account.Balance), obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance()))
|
||||
}
|
||||
|
||||
if obj.Nonce() != common.String2Big(account.Nonce).Uint64() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue