Skip to content

Commit b196278

Browse files
committed
core/vm: added JIT segmenting / optimisations
* multi-push segments * static jumps segments
1 parent 9d61d78 commit b196278

File tree

9 files changed

+205
-5
lines changed

9 files changed

+205
-5
lines changed

cmd/utils/flags.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
475475
cfg.TestNet = true
476476
}
477477

478+
if ctx.GlobalBool(VMEnableJitFlag.Name) {
479+
cfg.Name += "/JIT"
480+
}
478481
if ctx.GlobalBool(DevModeFlag.Name) {
479482
if !ctx.GlobalIsSet(VMDebugFlag.Name) {
480483
cfg.VmDebug = true

core/vm/doc.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ invokes the JIT VM in a seperate goroutine and compiles the byte code in JIT
2424
instructions.
2525
2626
The JIT VM, when invoked, loops around a set of pre-defined instructions until
27-
it either runs of gas, causes an internal error, returns or stops. At a later
28-
stage the JIT VM will see some additional features that will cause sets of
29-
instructions to be compiled down to segments. Segments are sets of instructions
30-
that can be run in one go saving precious time during execution.
27+
it either runs of gas, causes an internal error, returns or stops.
28+
29+
The JIT optimiser attempts to pre-compile instructions in to chunks or segments
30+
such as multiple PUSH operations and static JUMPs. It does this by analysing the
31+
opcodes and attempts to match certain regions to known sets. Whenever the
32+
optimiser finds said segments it creates a new instruction and replaces the
33+
first occurrence in the sequence.
3134
*/
3235
package vm

core/vm/instructions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (instr instruction) do(program *Program, pc *uint64, env Environment, contr
7373
// Resize the memory calculated previously
7474
memory.Resize(newMemSize.Uint64())
7575

76-
// These opcodes return an argument and are thefor handled
76+
// These opcodes return an argument and are therefor handled
7777
// differently from the rest of the opcodes
7878
switch instr.op {
7979
case JUMP:

core/vm/jit.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ func CompileProgram(program *Program) (err error) {
290290
}
291291
}
292292

293+
optimiseProgram(program)
294+
293295
return nil
294296
}
295297

core/vm/jit_optimiser.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package vm
2+
3+
import (
4+
"math/big"
5+
"time"
6+
7+
"github.com/ethereum/go-ethereum/logger"
8+
"github.com/ethereum/go-ethereum/logger/glog"
9+
)
10+
11+
// optimeProgram optimises a JIT program creating segments out of program
12+
// instructions. Currently covered are multi-pushes and static jumps
13+
func optimiseProgram(program *Program) {
14+
var load []instruction
15+
16+
var (
17+
statsJump = 0
18+
statsPush = 0
19+
)
20+
21+
if glog.V(logger.Debug) {
22+
glog.Infof("optimising %x\n", program.Id[:4])
23+
tstart := time.Now()
24+
defer func() {
25+
glog.Infof("optimised %x done in %v with JMP: %d PSH: %d\n", program.Id[:4], time.Since(tstart), statsJump, statsPush)
26+
}()
27+
}
28+
29+
for i := 0; i < len(program.instructions); i++ {
30+
instr := program.instructions[i].(instruction)
31+
32+
switch {
33+
case instr.op.IsPush():
34+
load = append(load, instr)
35+
case instr.op.IsStaticJump():
36+
if len(load) == 0 {
37+
continue
38+
}
39+
// if the push load is greater than 1, finalise that
40+
// segment first
41+
if len(load) > 2 {
42+
seg, size := makePushSeg(load[:len(load)-1])
43+
program.instructions[i-size-1] = seg
44+
statsPush++
45+
}
46+
// create a segment consisting of a pre determined
47+
// jump, destination and validity.
48+
seg := makeStaticJumpSeg(load[len(load)-1].data, program)
49+
program.instructions[i-1] = seg
50+
statsJump++
51+
52+
load = nil
53+
default:
54+
// create a new N pushes segment
55+
if len(load) > 1 {
56+
seg, size := makePushSeg(load)
57+
program.instructions[i-size] = seg
58+
statsPush++
59+
}
60+
load = nil
61+
}
62+
}
63+
}
64+
65+
// makePushSeg creates a new push segment from N amount of push instructions
66+
func makePushSeg(instrs []instruction) (pushSeg, int) {
67+
var (
68+
data []*big.Int
69+
gas = new(big.Int)
70+
)
71+
72+
for _, instr := range instrs {
73+
data = append(data, instr.data)
74+
gas.Add(gas, instr.gas)
75+
}
76+
77+
return pushSeg{data, gas}, len(instrs)
78+
}
79+
80+
// makeStaticJumpSeg creates a new static jump segment from a predefined
81+
// destination (PUSH, JUMP).
82+
func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg {
83+
gas := new(big.Int)
84+
gas.Add(gas, _baseCheck[PUSH1].gas)
85+
gas.Add(gas, _baseCheck[JUMP].gas)
86+
87+
contract := &Contract{Code: program.code}
88+
pos, err := jump(program.mapping, program.destinations, contract, to)
89+
return jumpSeg{pos, err, gas}
90+
}

core/vm/jit_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,49 @@ import (
2626

2727
const maxRun = 1000
2828

29+
func TestSegmenting(t *testing.T) {
30+
prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0})
31+
err := CompileProgram(prog)
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
36+
if instr, ok := prog.instructions[0].(pushSeg); ok {
37+
if len(instr.data) != 2 {
38+
t.Error("expected 2 element width pushSegment, got", len(instr.data))
39+
}
40+
} else {
41+
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
42+
}
43+
44+
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
45+
err = CompileProgram(prog)
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
if _, ok := prog.instructions[1].(jumpSeg); ok {
50+
} else {
51+
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
52+
}
53+
54+
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
55+
err = CompileProgram(prog)
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
if instr, ok := prog.instructions[0].(pushSeg); ok {
60+
if len(instr.data) != 2 {
61+
t.Error("expected 2 element width pushSegment, got", len(instr.data))
62+
}
63+
} else {
64+
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
65+
}
66+
if _, ok := prog.instructions[2].(jumpSeg); ok {
67+
} else {
68+
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
69+
}
70+
}
71+
2972
func TestCompiling(t *testing.T) {
3073
prog := NewProgram([]byte{0x60, 0x10})
3174
err := CompileProgram(prog)

core/vm/opcodes.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ import (
2323
// OpCode is an EVM opcode
2424
type OpCode byte
2525

26+
func (op OpCode) IsPush() bool {
27+
switch op {
28+
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
29+
return true
30+
}
31+
return false
32+
}
33+
34+
func (op OpCode) IsStaticJump() bool {
35+
return op == JUMP
36+
}
37+
2638
const (
2739
// 0x0 range - arithmetic ops
2840
STOP OpCode = iota

core/vm/segments.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package vm
2+
3+
import "math/big"
4+
5+
type jumpSeg struct {
6+
pos uint64
7+
err error
8+
gas *big.Int
9+
}
10+
11+
func (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
12+
if !contract.UseGas(j.gas) {
13+
return nil, OutOfGasError
14+
}
15+
if j.err != nil {
16+
return nil, j.err
17+
}
18+
*pc = j.pos
19+
return nil, nil
20+
}
21+
func (s jumpSeg) halts() bool { return false }
22+
func (s jumpSeg) Op() OpCode { return 0 }
23+
24+
type pushSeg struct {
25+
data []*big.Int
26+
gas *big.Int
27+
}
28+
29+
func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
30+
// Use the calculated gas. When insufficient gas is present, use all gas and return an
31+
// Out Of Gas error
32+
if !contract.UseGas(s.gas) {
33+
return nil, OutOfGasError
34+
}
35+
36+
for _, d := range s.data {
37+
stack.push(new(big.Int).Set(d))
38+
}
39+
*pc += uint64(len(s.data))
40+
return nil, nil
41+
}
42+
43+
func (s pushSeg) halts() bool { return false }
44+
func (s pushSeg) Op() OpCode { return 0 }

core/vm/stack.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ func (st *stack) push(d *big.Int) {
4242
//st.data = append(st.data, stackItem)
4343
st.data = append(st.data, d)
4444
}
45+
func (st *stack) pushN(ds ...*big.Int) {
46+
st.data = append(st.data, ds...)
47+
}
4548

4649
func (st *stack) pop() (ret *big.Int) {
4750
ret = st.data[len(st.data)-1]

0 commit comments

Comments
 (0)