OSDN Git Service

Merge pull request #41 from Bytom/dev
[bytom/vapor.git] / protocol / vm / vmutil / builder.go
1 package vmutil
2
3 import (
4         "encoding/binary"
5
6         "github.com/vapor/errors"
7         "github.com/vapor/protocol/vm"
8 )
9
10 type Builder struct {
11         program     []byte
12         jumpCounter int
13
14         // Maps a jump target number to its absolute address.
15         jumpAddr map[int]uint32
16
17         // Maps a jump target number to the list of places where its
18         // absolute address must be filled in once known.
19         jumpPlaceholders map[int][]int
20 }
21
22 func NewBuilder() *Builder {
23         return &Builder{
24                 jumpAddr:         make(map[int]uint32),
25                 jumpPlaceholders: make(map[int][]int),
26         }
27 }
28
29 // AddInt64 adds a pushdata instruction for an integer value.
30 func (b *Builder) AddInt64(n int64) *Builder {
31         b.program = append(b.program, vm.PushdataInt64(n)...)
32         return b
33 }
34
35 // AddData adds a pushdata instruction for a given byte string.
36 func (b *Builder) AddData(data []byte) *Builder {
37         b.program = append(b.program, vm.PushdataBytes(data)...)
38         return b
39 }
40
41 // AddRawBytes simply appends the given bytes to the program. (It does
42 // not introduce a pushdata opcode.)
43 func (b *Builder) AddRawBytes(data []byte) *Builder {
44         b.program = append(b.program, data...)
45         return b
46 }
47
48 // AddOp adds the given opcode to the program.
49 func (b *Builder) AddOp(op vm.Op) *Builder {
50         b.program = append(b.program, byte(op))
51         return b
52 }
53
54 // NewJumpTarget allocates a number that can be used as a jump target
55 // in AddJump and AddJumpIf. Call SetJumpTarget to associate the
56 // number with a program location.
57 func (b *Builder) NewJumpTarget() int {
58         b.jumpCounter++
59         return b.jumpCounter
60 }
61
62 // AddJump adds a JUMP opcode whose target is the given target
63 // number. The actual program location of the target does not need to
64 // be known yet, as long as SetJumpTarget is called before Build.
65 func (b *Builder) AddJump(target int) *Builder {
66         return b.addJump(vm.OP_JUMP, target)
67 }
68
69 // AddJump adds a JUMPIF opcode whose target is the given target
70 // number. The actual program location of the target does not need to
71 // be known yet, as long as SetJumpTarget is called before Build.
72 func (b *Builder) AddJumpIf(target int) *Builder {
73         return b.addJump(vm.OP_JUMPIF, target)
74 }
75
76 func (b *Builder) addJump(op vm.Op, target int) *Builder {
77         b.AddOp(op)
78         b.jumpPlaceholders[target] = append(b.jumpPlaceholders[target], len(b.program))
79         b.AddRawBytes([]byte{0, 0, 0, 0})
80         return b
81 }
82
83 // SetJumpTarget associates the given jump-target number with the
84 // current position in the program - namely, the program's length,
85 // such that the first instruction executed by a jump using this
86 // target will be whatever instruction is added next. It is legal for
87 // SetJumpTarget to be called at the end of the program, causing jumps
88 // using that target to fall off the end. There must be a call to
89 // SetJumpTarget for every jump target used before any call to Build.
90 func (b *Builder) SetJumpTarget(target int) *Builder {
91         b.jumpAddr[target] = uint32(len(b.program))
92         return b
93 }
94
95 var ErrUnresolvedJump = errors.New("unresolved jump target")
96
97 // Build produces the bytecode of the program. It first resolves any
98 // jumps in the program by filling in the addresses of their
99 // targets. This requires SetJumpTarget to be called prior to Build
100 // for each jump target used (in a call to AddJump or AddJumpIf). If
101 // any target's address hasn't been set in this way, this function
102 // produces ErrUnresolvedJump. There are no other error conditions.
103 func (b *Builder) Build() ([]byte, error) {
104         for target, placeholders := range b.jumpPlaceholders {
105                 addr, ok := b.jumpAddr[target]
106                 if !ok {
107                         return nil, errors.Wrapf(ErrUnresolvedJump, "target %d", target)
108                 }
109                 for _, placeholder := range placeholders {
110                         binary.LittleEndian.PutUint32(b.program[placeholder:placeholder+4], addr)
111                 }
112         }
113         return b.program, nil
114 }