mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Add coroutine queue to scope context and add Spawn opcode to add coroutine/generator to the queue
This commit is contained in:
parent
3f40e65c48
commit
e959d6cee1
7 changed files with 209 additions and 0 deletions
109
core/vm/coroutine.go
Normal file
109
core/vm/coroutine.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
//TODO: Use references where possible
|
||||
type Coroutine struct {
|
||||
PC uint64
|
||||
Stack Stack
|
||||
}
|
||||
|
||||
func NewCoroutine(pc uint64, stack Stack) Coroutine {
|
||||
newStack := deepCopyStack(&stack)
|
||||
return Coroutine{pc, *newStack}
|
||||
}
|
||||
|
||||
func (co *Coroutine) ExecuteCoroutine(interpreter *EVMInterpreter, scope *ScopeContext) (ret []byte, err error) {
|
||||
log.Println("ExecuteCoroutine: ", co.PC, co.Stack)
|
||||
|
||||
// Don't bother with the execution if there's no code.
|
||||
if len(scope.Contract.Code) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
op OpCode // current opcode
|
||||
mem = scope.Memory // memory of the contract
|
||||
stack = &co.Stack // stack of the contract
|
||||
contract = scope.Contract
|
||||
callContext = scope
|
||||
pc = co.PC
|
||||
cost uint64 // TODO: Bake in with other run gas
|
||||
// copies used by tracer
|
||||
res []byte // result of the opcode execution function
|
||||
)
|
||||
// Don't move this deferred function, it's placed before the capturestate-deferred method,
|
||||
// so that it get's executed _after_: the capturestate needs the stacks before
|
||||
// they are returned to the pools
|
||||
defer func() {
|
||||
// TODO: Think more about this
|
||||
// callContext.AwaitTermination(interpreter)
|
||||
returnStack(stack)
|
||||
}()
|
||||
callContext.Stack = stack
|
||||
|
||||
//TODO : Debug and other removed stuff?
|
||||
// parent context.
|
||||
for {
|
||||
// Get the operation from the jump table and validate the stack to ensure there are
|
||||
// enough stack items available to perform the operation.
|
||||
op = contract.GetOp(pc)
|
||||
operation := interpreter.table[op]
|
||||
cost = operation.constantGas // For tracing
|
||||
// Validate stack
|
||||
if sLen := stack.len(); sLen < operation.minStack {
|
||||
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
|
||||
} else if sLen > operation.maxStack {
|
||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
if !contract.UseGas(cost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
if operation.dynamicGas != nil {
|
||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||
var memorySize uint64
|
||||
// calculate the new memory size and expand the memory to fit
|
||||
// the operation
|
||||
// Memory check needs to be done prior to evaluating the dynamic gas portion,
|
||||
// to detect calculation overflows
|
||||
if operation.memorySize != nil {
|
||||
memSize, overflow := operation.memorySize(stack)
|
||||
if overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
// memory is expanded in words of 32 bytes. Gas
|
||||
// is also calculated in words.
|
||||
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
// Consume the gas and return an error if not enough gas is available.
|
||||
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||
var dynamicCost uint64
|
||||
dynamicCost, err = operation.dynamicGas(interpreter.evm, contract, stack, mem, memorySize)
|
||||
cost += dynamicCost // for tracing
|
||||
if err != nil || !contract.UseGas(dynamicCost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
}
|
||||
// execute the operation
|
||||
res, err = operation.execute(&pc, interpreter, callContext)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
pc++
|
||||
}
|
||||
|
||||
if err == errStopToken {
|
||||
err = nil // clear stop token error
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
|
@ -501,6 +503,9 @@ func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
|||
// pop value of the stack
|
||||
mStart, val := scope.Stack.pop(), scope.Stack.pop()
|
||||
scope.Memory.Set32(mStart.Uint64(), &val)
|
||||
|
||||
// Temporary : Log the opMstore to easily see execution order
|
||||
log.Printf("opMstore: mStart=%v, val=%v", mStart, val)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -558,6 +563,27 @@ func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: Push end of program to stack before executing spawn and pop off after w/ other args?
|
||||
func opSpawn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.evm.abort.Load() {
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
||||
pos := scope.Stack.pop()
|
||||
if !scope.Contract.validJumpdest(&pos) {
|
||||
return nil, ErrInvalidJump
|
||||
}
|
||||
// Save coroutine to memory ( Stack, PC )
|
||||
coroutine := NewCoroutine(pos.Uint64(), *scope.Stack)
|
||||
|
||||
// Add coroutine to queue
|
||||
scope.PushCoroutine(coroutine)
|
||||
|
||||
log.Printf("Spawned coroutine %v", coroutine)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
scope.Stack.push(new(uint256.Int).SetUint64(*pc))
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log2 "log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
|
@ -37,6 +40,38 @@ type ScopeContext struct {
|
|||
Memory *Memory
|
||||
Stack *Stack
|
||||
Contract *Contract
|
||||
|
||||
CoroutineQueue []Coroutine
|
||||
}
|
||||
|
||||
func (scope *ScopeContext) PushCoroutine(coroutine Coroutine) {
|
||||
scope.CoroutineQueue = append(scope.CoroutineQueue, coroutine)
|
||||
}
|
||||
|
||||
func (scope *ScopeContext) PopCoroutine() (Coroutine, error) {
|
||||
if len(scope.CoroutineQueue) == 0 {
|
||||
return Coroutine{}, errors.New("Coroutine queue is empty")
|
||||
}
|
||||
coroutine := scope.CoroutineQueue[0]
|
||||
scope.CoroutineQueue = scope.CoroutineQueue[1:]
|
||||
return coroutine, nil
|
||||
}
|
||||
|
||||
func (scope *ScopeContext) AwaitTermination(interpreter *EVMInterpreter) {
|
||||
coroutine, err := scope.PopCoroutine()
|
||||
for err == nil {
|
||||
// TODO: Propogate returns and errors?
|
||||
ret, err2 := coroutine.ExecuteCoroutine(interpreter, scope)
|
||||
if err2 != nil {
|
||||
log2.Println("Coroutine execution failed", "error", err2)
|
||||
}
|
||||
if ret != nil {
|
||||
log2.Println("Coroutine returned", "ret", ret)
|
||||
}
|
||||
|
||||
coroutine, err = scope.PopCoroutine()
|
||||
log2.Println("Awaiting coroutine termination", "coroutine", coroutine, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// EVMInterpreter represents an EVM interpreter
|
||||
|
|
@ -133,6 +168,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
Memory: mem,
|
||||
Stack: stack,
|
||||
Contract: contract,
|
||||
CoroutineQueue: []Coroutine{},
|
||||
}
|
||||
// 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
|
||||
|
|
@ -150,6 +186,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
// so that it get's executed _after_: the capturestate needs the stacks before
|
||||
// they are returned to the pools
|
||||
defer func() {
|
||||
callContext.AwaitTermination(in)
|
||||
returnStack(stack)
|
||||
}()
|
||||
contract.Input = input
|
||||
|
|
@ -238,5 +275,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
err = nil // clear stop token error
|
||||
}
|
||||
|
||||
log2.Println("returning from run with", res, err)
|
||||
// Res is array of bytes located in scope.Memory
|
||||
return res, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,6 +595,12 @@ func newFrontierInstructionSet() JumpTable {
|
|||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
},
|
||||
SPAWN: {
|
||||
execute: opSpawn,
|
||||
constantGas: GasSlowStep, // TODO
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
},
|
||||
PUSH1: {
|
||||
execute: opPush1,
|
||||
constantGas: GasFastestStep,
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@ const (
|
|||
CREATE2 OpCode = 0xf5
|
||||
|
||||
STATICCALL OpCode = 0xfa
|
||||
SPAWN OpCode = 0xfb
|
||||
REVERT OpCode = 0xfd
|
||||
INVALID OpCode = 0xfe
|
||||
SELFDESTRUCT OpCode = 0xff
|
||||
|
|
@ -304,6 +305,7 @@ var opCodeToString = map[OpCode]string{
|
|||
TLOAD: "TLOAD",
|
||||
TSTORE: "TSTORE",
|
||||
MCOPY: "MCOPY",
|
||||
SPAWN: "SPAWN",
|
||||
PUSH0: "PUSH0",
|
||||
|
||||
// 0x60 range - pushes.
|
||||
|
|
@ -476,6 +478,7 @@ var stringToOp = map[string]OpCode{
|
|||
"TLOAD": TLOAD,
|
||||
"TSTORE": TSTORE,
|
||||
"MCOPY": MCOPY,
|
||||
"SPAWN": SPAWN,
|
||||
"PUSH0": PUSH0,
|
||||
"PUSH1": PUSH1,
|
||||
"PUSH2": PUSH2,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ func returnStack(s *Stack) {
|
|||
stackPool.Put(s)
|
||||
}
|
||||
|
||||
func deepCopyStack(s *Stack) *Stack {
|
||||
ret := newstack()
|
||||
ret.data = append(ret.data, s.data...)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Data returns the underlying uint256.Int array.
|
||||
func (st *Stack) Data() []uint256.Int {
|
||||
return st.data
|
||||
|
|
|
|||
20
functions.yul
Normal file
20
functions.yul
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
// TODO: return vals, storage
|
||||
function basic() {
|
||||
mstore(0x80, 0x42)
|
||||
}
|
||||
|
||||
function func1(val) {
|
||||
mstore(0xa0, val)
|
||||
}
|
||||
|
||||
function func2(val1, val2, str1) {
|
||||
mstore(0xc0, add(val1, val2))
|
||||
mstore(0xe0, str1)
|
||||
}
|
||||
|
||||
function main() {
|
||||
basic()
|
||||
func1(0x32)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue