From 9d61d78de6ad20822b2b48d6c4e3779369ea7331 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 6 Oct 2015 18:16:03 +0200 Subject: [PATCH] core/vm: abstracted instruction execution away from JIT Moved the execution of instructions to the instruction it self. This will allow for specialised instructions (e.g. segments) to be execution in the same manner as regular instructions. --- core/vm/instructions.go | 82 +++++++++++++++++++++++++++++++++++++---- core/vm/jit.go | 82 +++++++---------------------------------- 2 files changed, 88 insertions(+), 76 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 6c6039f746..5dcab6e08c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,7 @@ package vm import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -25,16 +26,16 @@ import ( ) type programInstruction interface { - Do(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) + // executes the program instruction and allows the instruction to modify the state of the program + do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) + // returns whether the program instruction halts the execution of the JIT + halts() bool + // Returns the current op code (debugging purposes) + Op() OpCode } type instrFn func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) -// Do executes the function. This implements programInstruction -func (fn instrFn) Do(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { - fn(instr, pc, env, contract, memory, stack) -} - type instruction struct { op OpCode pc uint64 @@ -44,6 +45,73 @@ type instruction struct { gas *big.Int spop int spush int + + returns bool +} + +func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) { + if !validDest(destinations, to) { + nop := contract.GetOp(to.Uint64()) + return 0, fmt.Errorf("invalid jump destination (%v) %v", nop, to) + } + + return mapping[to.Uint64()], nil +} + +func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) { + // calculate the new memory size and gas price for the current executing opcode + newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, env.Db(), memory, stack) + if err != nil { + return nil, err + } + + // Use the calculated gas. When insufficient gas is present, use all gas and return an + // Out Of Gas error + if !contract.UseGas(cost) { + return nil, OutOfGasError + } + // Resize the memory calculated previously + memory.Resize(newMemSize.Uint64()) + + // These opcodes return an argument and are thefor handled + // differently from the rest of the opcodes + switch instr.op { + case JUMP: + if pos, err := jump(program.mapping, program.destinations, contract, stack.pop()); err != nil { + return nil, err + } else { + *pc = pos + return nil, nil + } + case JUMPI: + pos, cond := stack.pop(), stack.pop() + if cond.Cmp(common.BigTrue) >= 0 { + if pos, err := jump(program.mapping, program.destinations, contract, pos); err != nil { + return nil, err + } else { + *pc = pos + return nil, nil + } + } + case RETURN: + offset, size := stack.pop(), stack.pop() + return memory.GetPtr(offset.Int64(), size.Int64()), nil + default: + if instr.fn == nil { + return nil, fmt.Errorf("Invalid opcode 0x%x", instr.op) + } + instr.fn(instr, pc, env, contract, memory, stack) + } + *pc++ + return nil, nil +} + +func (instr instruction) halts() bool { + return instr.returns +} + +func (instr instruction) Op() OpCode { + return instr.op } func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env Environment, contract *Contract, memory *Memory, stack *stack) { @@ -536,8 +604,6 @@ func opStop(instr instruction, pc *uint64, env Environment, contract *Contract, } func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { - //receiver := env.Db().GetOrNewStateObject(common.BigToAddress(stack.pop())) - //receiver.AddBalance(balance) balance := env.Db().GetBalance(contract.Address()) env.Db().AddBalance(common.BigToAddress(stack.pop()), balance) diff --git a/core/vm/jit.go b/core/vm/jit.go index 6ad5749174..8cb95b860b 100644 --- a/core/vm/jit.go +++ b/core/vm/jit.go @@ -86,9 +86,9 @@ type Program struct { contract *Contract - instructions []instruction // instruction set - mapping map[uint64]int // real PC mapping to array indices - destinations map[uint64]struct{} // cached jump destinations + instructions []programInstruction // instruction set + mapping map[uint64]uint64 // real PC mapping to array indices + destinations map[uint64]struct{} // cached jump destinations code []byte } @@ -97,7 +97,7 @@ type Program struct { func NewProgram(code []byte) *Program { program := &Program{ Id: crypto.Sha3Hash(code), - mapping: make(map[uint64]int), + mapping: make(map[uint64]uint64), destinations: make(map[uint64]struct{}), code: code, } @@ -118,10 +118,12 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) { baseOp = DUP1 } base := _baseCheck[baseOp] - instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush} + + returns := op == RETURN || op == SUICIDE || op == STOP + instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns} p.instructions = append(p.instructions, instr) - p.mapping[pc] = len(p.instructions) - 1 + p.mapping[pc] = uint64(len(p.instructions) - 1) } // CompileProgram compiles the given program and return an error when it fails @@ -301,21 +303,8 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env contract.Input = input var ( - caller = contract.caller - statedb = env.Db() - pc int = program.mapping[pcstart] - instrCount = 0 - - jump = func(to *big.Int) error { - if !validDest(program.destinations, to) { - nop := contract.GetOp(to.Uint64()) - return fmt.Errorf("invalid jump destination (%v) %v", nop, to) - } - - pc = program.mapping[to.Uint64()] - - return nil - } + pc uint64 = program.mapping[pcstart] + instrCount = 0 ) if glog.V(logger.Debug) { @@ -326,62 +315,19 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env }() } - for pc < len(program.instructions) { + for pc < uint64(len(program.instructions)) { instrCount++ instr := program.instructions[pc] - // calculate the new memory size and gas price for the current executing opcode - newMemSize, cost, err := jitCalculateGasAndSize(env, contract, caller, instr, statedb, mem, stack) + ret, err := instr.do(program, &pc, env, contract, mem, stack) if err != nil { return nil, err } - // Use the calculated gas. When insufficient gas is present, use all gas and return an - // Out Of Gas error - if !contract.UseGas(cost) { - return nil, OutOfGasError - } - // Resize the memory calculated previously - mem.Resize(newMemSize.Uint64()) - - // These opcodes return an argument and are thefor handled - // differently from the rest of the opcodes - switch instr.op { - case JUMP: - if err := jump(stack.pop()); err != nil { - return nil, err - } - continue - case JUMPI: - pos, cond := stack.pop(), stack.pop() - - if cond.Cmp(common.BigTrue) >= 0 { - if err := jump(pos); err != nil { - return nil, err - } - continue - } - case RETURN: - offset, size := stack.pop(), stack.pop() - ret := mem.GetPtr(offset.Int64(), size.Int64()) - + if instr.halts() { return contract.Return(ret), nil - case SUICIDE: - instr.fn(instr, nil, env, contract, mem, stack) - - return contract.Return(nil), nil - case STOP: - return contract.Return(nil), nil - default: - if instr.fn == nil { - return nil, fmt.Errorf("Invalid opcode %x", instr.op) - } - - instr.fn(instr, nil, env, contract, mem, stack) } - - pc++ } contract.Input = nil @@ -403,7 +349,7 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool { // jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // the operation. This does not reduce gas or resizes the memory. -func jitCalculateGasAndSize(env Environment, contract *Contract, caller ContractRef, instr instruction, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) { +func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) { var ( gas = new(big.Int) newMemSize *big.Int = new(big.Int)