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
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -501,6 +503,9 @@ func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||||
// pop value of the stack
|
// pop value of the stack
|
||||||
mStart, val := scope.Stack.pop(), scope.Stack.pop()
|
mStart, val := scope.Stack.pop(), scope.Stack.pop()
|
||||||
scope.Memory.Set32(mStart.Uint64(), &val)
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -558,6 +563,27 @@ func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
||||||
return nil, nil
|
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) {
|
func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
scope.Stack.push(new(uint256.Int).SetUint64(*pc))
|
scope.Stack.push(new(uint256.Int).SetUint64(*pc))
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
log2 "log"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -37,6 +40,38 @@ type ScopeContext struct {
|
||||||
Memory *Memory
|
Memory *Memory
|
||||||
Stack *Stack
|
Stack *Stack
|
||||||
Contract *Contract
|
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
|
// EVMInterpreter represents an EVM interpreter
|
||||||
|
|
@ -133,6 +168,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
Memory: mem,
|
Memory: mem,
|
||||||
Stack: stack,
|
Stack: stack,
|
||||||
Contract: contract,
|
Contract: contract,
|
||||||
|
CoroutineQueue: []Coroutine{},
|
||||||
}
|
}
|
||||||
// For optimisation reason we're using uint64 as the program counter.
|
// 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
|
// 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
|
// so that it get's executed _after_: the capturestate needs the stacks before
|
||||||
// they are returned to the pools
|
// they are returned to the pools
|
||||||
defer func() {
|
defer func() {
|
||||||
|
callContext.AwaitTermination(in)
|
||||||
returnStack(stack)
|
returnStack(stack)
|
||||||
}()
|
}()
|
||||||
contract.Input = input
|
contract.Input = input
|
||||||
|
|
@ -238,5 +275,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
err = nil // clear stop token error
|
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
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -595,6 +595,12 @@ func newFrontierInstructionSet() JumpTable {
|
||||||
minStack: minStack(0, 0),
|
minStack: minStack(0, 0),
|
||||||
maxStack: maxStack(0, 0),
|
maxStack: maxStack(0, 0),
|
||||||
},
|
},
|
||||||
|
SPAWN: {
|
||||||
|
execute: opSpawn,
|
||||||
|
constantGas: GasSlowStep, // TODO
|
||||||
|
minStack: minStack(1, 0),
|
||||||
|
maxStack: maxStack(1, 0),
|
||||||
|
},
|
||||||
PUSH1: {
|
PUSH1: {
|
||||||
execute: opPush1,
|
execute: opPush1,
|
||||||
constantGas: GasFastestStep,
|
constantGas: GasFastestStep,
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,7 @@ const (
|
||||||
CREATE2 OpCode = 0xf5
|
CREATE2 OpCode = 0xf5
|
||||||
|
|
||||||
STATICCALL OpCode = 0xfa
|
STATICCALL OpCode = 0xfa
|
||||||
|
SPAWN OpCode = 0xfb
|
||||||
REVERT OpCode = 0xfd
|
REVERT OpCode = 0xfd
|
||||||
INVALID OpCode = 0xfe
|
INVALID OpCode = 0xfe
|
||||||
SELFDESTRUCT OpCode = 0xff
|
SELFDESTRUCT OpCode = 0xff
|
||||||
|
|
@ -304,6 +305,7 @@ var opCodeToString = map[OpCode]string{
|
||||||
TLOAD: "TLOAD",
|
TLOAD: "TLOAD",
|
||||||
TSTORE: "TSTORE",
|
TSTORE: "TSTORE",
|
||||||
MCOPY: "MCOPY",
|
MCOPY: "MCOPY",
|
||||||
|
SPAWN: "SPAWN",
|
||||||
PUSH0: "PUSH0",
|
PUSH0: "PUSH0",
|
||||||
|
|
||||||
// 0x60 range - pushes.
|
// 0x60 range - pushes.
|
||||||
|
|
@ -476,6 +478,7 @@ var stringToOp = map[string]OpCode{
|
||||||
"TLOAD": TLOAD,
|
"TLOAD": TLOAD,
|
||||||
"TSTORE": TSTORE,
|
"TSTORE": TSTORE,
|
||||||
"MCOPY": MCOPY,
|
"MCOPY": MCOPY,
|
||||||
|
"SPAWN": SPAWN,
|
||||||
"PUSH0": PUSH0,
|
"PUSH0": PUSH0,
|
||||||
"PUSH1": PUSH1,
|
"PUSH1": PUSH1,
|
||||||
"PUSH2": PUSH2,
|
"PUSH2": PUSH2,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,12 @@ func returnStack(s *Stack) {
|
||||||
stackPool.Put(s)
|
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.
|
// Data returns the underlying uint256.Int array.
|
||||||
func (st *Stack) Data() []uint256.Int {
|
func (st *Stack) Data() []uint256.Int {
|
||||||
return st.data
|
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