vm: implement new JMP* and CALL* opcodes

In compiler JMP*_L opcodes are always used, as this requires less effort.
This commit is contained in:
Evgenii Stratonikov 2020-04-23 11:56:36 +03:00
parent fba185cd99
commit d6624a92ca
10 changed files with 328 additions and 141 deletions

View file

@ -435,13 +435,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
if n.Cond != nil { if n.Cond != nil {
ast.Walk(c, n.Cond) ast.Walk(c, n.Cond)
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, lElse) emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lElse)
} }
c.setLabel(lIf) c.setLabel(lIf)
ast.Walk(c, n.Body) ast.Walk(c, n.Body)
if n.Else != nil { if n.Else != nil {
emit.Jmp(c.prog.BinWriter, opcode.JMP, lElseEnd) emit.Jmp(c.prog.BinWriter, opcode.JMPL, lElseEnd)
} }
c.setLabel(lElse) c.setLabel(lElse)
@ -476,9 +476,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, cc.List[j]) ast.Walk(c, cc.List[j])
emit.Opcode(c.prog.BinWriter, eqOpcode) emit.Opcode(c.prog.BinWriter, eqOpcode)
if j == l-1 { if j == l-1 {
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, lEnd) emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lEnd)
} else { } else {
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, lStart) emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, lStart)
} }
} }
} }
@ -487,12 +487,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
last := len(cc.Body) - 1 last := len(cc.Body) - 1
for j, stmt := range cc.Body { for j, stmt := range cc.Body {
if j == last && isFallthroughStmt(stmt) { if j == last && isFallthroughStmt(stmt) {
emit.Jmp(c.prog.BinWriter, opcode.JMP, startLabels[i+1]) emit.Jmp(c.prog.BinWriter, opcode.JMPL, startLabels[i+1])
break break
} }
ast.Walk(c, stmt) ast.Walk(c, stmt)
} }
emit.Jmp(c.prog.BinWriter, opcode.JMP, switchEnd) emit.Jmp(c.prog.BinWriter, opcode.JMPL, switchEnd)
c.setLabel(lEnd) c.setLabel(lEnd)
} }
@ -562,9 +562,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
next := c.newLabel() next := c.newLabel()
end := c.newLabel() end := c.newLabel()
ast.Walk(c, n.X) ast.Walk(c, n.X)
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, next) emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, next)
emit.Opcode(c.prog.BinWriter, opcode.PUSHF) emit.Opcode(c.prog.BinWriter, opcode.PUSHF)
emit.Jmp(c.prog.BinWriter, opcode.JMP, end) emit.Jmp(c.prog.BinWriter, opcode.JMPL, end)
c.setLabel(next) c.setLabel(next)
ast.Walk(c, n.Y) ast.Walk(c, n.Y)
c.setLabel(end) c.setLabel(end)
@ -574,9 +574,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
next := c.newLabel() next := c.newLabel()
end := c.newLabel() end := c.newLabel()
ast.Walk(c, n.X) ast.Walk(c, n.X)
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, next) emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, next)
emit.Opcode(c.prog.BinWriter, opcode.PUSHT) emit.Opcode(c.prog.BinWriter, opcode.PUSHT)
emit.Jmp(c.prog.BinWriter, opcode.JMP, end) emit.Jmp(c.prog.BinWriter, opcode.JMPL, end)
c.setLabel(next) c.setLabel(next)
ast.Walk(c, n.Y) ast.Walk(c, n.Y)
c.setLabel(end) c.setLabel(end)
@ -686,7 +686,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case isSyscall(f): case isSyscall(f):
c.convertSyscall(n, f.selector.Name, f.name) c.convertSyscall(n, f.selector.Name, f.name)
default: default:
emit.Call(c.prog.BinWriter, opcode.CALL, f.label) emit.Call(c.prog.BinWriter, opcode.CALLL, f.label)
} }
return nil return nil
@ -782,10 +782,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch n.Tok { switch n.Tok {
case token.BREAK: case token.BREAK:
end := c.getLabelOffset(labelEnd, label) end := c.getLabelOffset(labelEnd, label)
emit.Jmp(c.prog.BinWriter, opcode.JMP, end) emit.Jmp(c.prog.BinWriter, opcode.JMPL, end)
case token.CONTINUE: case token.CONTINUE:
post := c.getLabelOffset(labelPost, label) post := c.getLabelOffset(labelPost, label)
emit.Jmp(c.prog.BinWriter, opcode.JMP, post) emit.Jmp(c.prog.BinWriter, opcode.JMPL, post)
} }
return nil return nil
@ -819,7 +819,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.Cond) ast.Walk(c, n.Cond)
// Jump if the condition is false // Jump if the condition is false
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, fend) emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, fend)
} }
// Walk body followed by the iterator (post stmt). // Walk body followed by the iterator (post stmt).
@ -830,7 +830,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
} }
// Jump back to condition. // Jump back to condition.
emit.Jmp(c.prog.BinWriter, opcode.JMP, fstart) emit.Jmp(c.prog.BinWriter, opcode.JMPL, fstart)
c.setLabel(fend) c.setLabel(fend)
c.dropStackLabel() c.dropStackLabel()
@ -867,7 +867,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
emit.Opcode(c.prog.BinWriter, opcode.OVER) emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.OVER) emit.Opcode(c.prog.BinWriter, opcode.OVER)
emit.Opcode(c.prog.BinWriter, opcode.LTE) // finish if len <= i emit.Opcode(c.prog.BinWriter, opcode.LTE) // finish if len <= i
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, end) emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, end)
if n.Key != nil { if n.Key != nil {
emit.Opcode(c.prog.BinWriter, opcode.DUP) emit.Opcode(c.prog.BinWriter, opcode.DUP)
@ -881,7 +881,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.setLabel(post) c.setLabel(post)
emit.Opcode(c.prog.BinWriter, opcode.INC) emit.Opcode(c.prog.BinWriter, opcode.INC)
emit.Jmp(c.prog.BinWriter, opcode.JMP, start) emit.Jmp(c.prog.BinWriter, opcode.JMPL, start)
c.setLabel(end) c.setLabel(end)
c.dropStackLabel() c.dropStackLabel()
@ -1346,21 +1346,28 @@ func (c *codegen) writeJumps(b []byte) error {
ctx := vm.NewContext(b) ctx := vm.NewContext(b)
for op, _, err := ctx.Next(); err == nil && ctx.NextIP() < len(b); op, _, err = ctx.Next() { for op, _, err := ctx.Next(); err == nil && ctx.NextIP() < len(b); op, _, err = ctx.Next() {
switch op { switch op {
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL: case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT:
panic("short jumps are not yet supported")
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
opcode.CALLL:
// we can't use arg returned by ctx.Next() because it is copied // we can't use arg returned by ctx.Next() because it is copied
nextIP := ctx.NextIP() nextIP := ctx.NextIP()
arg := b[nextIP-2:] arg := b[nextIP-4:]
index := binary.LittleEndian.Uint16(arg) index := binary.LittleEndian.Uint16(arg)
if int(index) > len(c.l) { if int(index) > len(c.l) {
return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l)) return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l))
} }
offset := c.l[index] - nextIP + 3 offset := c.l[index] - nextIP + 5
if offset > math.MaxUint16 { if offset > math.MaxInt32 || offset < math.MinInt32 {
return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d)", return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)",
nextIP-3, offset, math.MaxUint16) nextIP-5, offset, math.MaxInt32, math.MinInt32)
} }
binary.LittleEndian.PutUint16(arg, uint16(offset)) binary.LittleEndian.PutUint32(arg, uint32(offset))
} }
} }
return nil return nil

View file

@ -48,18 +48,18 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const testContractHash = "8bb068bca226bf013e7d19400b9d85c4eb865607" const testContractHash = "6b9e88be61028590ebbb1296cbee09beed4ae75d"
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {
{ {
name: "positive", name: "positive",
params: `["66238fd4ac778326b0c151c025ee8f1c6d738e7e191820537564d2b887f3ecde"]`, params: `["4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} }, result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog) res, ok := acc.(*result.ApplicationLog)
require.True(t, ok) require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("66238fd4ac778326b0c151c025ee8f1c6d738e7e191820537564d2b887f3ecde") expectedTxHash, err := util.Uint256DecodeStringLE("4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions)) assert.Equal(t, 1, len(res.Executions))

Binary file not shown.

Binary file not shown.

View file

@ -99,9 +99,13 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
numtoread = int(n) numtoread = int(n)
c.nextip += 4 c.nextip += 4
} }
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL: case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
numtoread = 2 opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
case opcode.SYSCALL: opcode.CALL:
numtoread = 1
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
opcode.CALLL, opcode.SYSCALL:
numtoread = 4 numtoread = 4
case opcode.APPCALL, opcode.TAILCALL: case opcode.APPCALL, opcode.TAILCALL:
numtoread = 20 numtoread = 20

View file

@ -135,7 +135,7 @@ func Jmp(w *io.BinWriter, op opcode.Opcode, label uint16) {
w.Err = fmt.Errorf("opcode %s is not a jump or call type", op.String()) w.Err = fmt.Errorf("opcode %s is not a jump or call type", op.String())
return return
} }
buf := make([]byte, 2) buf := make([]byte, 4)
binary.LittleEndian.PutUint16(buf, label) binary.LittleEndian.PutUint16(buf, label)
Instruction(w, op, buf) Instruction(w, op, buf)
} }
@ -172,10 +172,7 @@ func AppCallWithOperation(w *io.BinWriter, scriptHash util.Uint160, operation st
} }
func isInstructionJmp(op opcode.Opcode) bool { func isInstructionJmp(op opcode.Opcode) bool {
if op == opcode.JMP || op == opcode.JMPIFNOT || op == opcode.JMPIF || op == opcode.CALL { return opcode.JMP <= op && op <= opcode.CALLL
return true
}
return false
} }
// InteropNameToID returns an identificator of the method based on its name. // InteropNameToID returns an identificator of the method based on its name.

View file

@ -42,14 +42,32 @@ const (
PUSH15 Opcode = 0x1F PUSH15 Opcode = 0x1F
PUSH16 Opcode = 0x20 PUSH16 Opcode = 0x20
// Flow control
NOP Opcode = 0x21
JMP Opcode = 0x22
JMPL Opcode = 0x23
JMPIF Opcode = 0x24
JMPIFL Opcode = 0x25
JMPIFNOT Opcode = 0x26
JMPIFNOTL Opcode = 0x27
JMPEQ Opcode = 0x28
JMPEQL Opcode = 0x29
JMPNE Opcode = 0x2A
JMPNEL Opcode = 0x2B
JMPGT Opcode = 0x2C
JMPGTL Opcode = 0x2D
JMPGE Opcode = 0x2E
JMPGEL Opcode = 0x2F
JMPLT Opcode = 0x30
JMPLTL Opcode = 0x31
JMPLE Opcode = 0x32
JMPLEL Opcode = 0x33
CALL Opcode = 0x34
CALLL Opcode = 0x35
// Legacy
OLDPUSH1 Opcode = 0x51 OLDPUSH1 Opcode = 0x51
// Flow control
NOP Opcode = 0x61
JMP Opcode = 0x62
JMPIF Opcode = 0x63
JMPIFNOT Opcode = 0x64
CALL Opcode = 0x65
RET Opcode = 0x66 RET Opcode = 0x66
APPCALL Opcode = 0x67 APPCALL Opcode = 0x67
SYSCALL Opcode = 0x68 SYSCALL Opcode = 0x68

View file

@ -38,12 +38,28 @@ func _() {
_ = x[PUSH14-30] _ = x[PUSH14-30]
_ = x[PUSH15-31] _ = x[PUSH15-31]
_ = x[PUSH16-32] _ = x[PUSH16-32]
_ = x[NOP-33]
_ = x[JMP-34]
_ = x[JMPL-35]
_ = x[JMPIF-36]
_ = x[JMPIFL-37]
_ = x[JMPIFNOT-38]
_ = x[JMPIFNOTL-39]
_ = x[JMPEQ-40]
_ = x[JMPEQL-41]
_ = x[JMPNE-42]
_ = x[JMPNEL-43]
_ = x[JMPGT-44]
_ = x[JMPGTL-45]
_ = x[JMPGE-46]
_ = x[JMPGEL-47]
_ = x[JMPLT-48]
_ = x[JMPLTL-49]
_ = x[JMPLE-50]
_ = x[JMPLEL-51]
_ = x[CALL-52]
_ = x[CALLL-53]
_ = x[OLDPUSH1-81] _ = x[OLDPUSH1-81]
_ = x[NOP-97]
_ = x[JMP-98]
_ = x[JMPIF-99]
_ = x[JMPIFNOT-100]
_ = x[CALL-101]
_ = x[RET-102] _ = x[RET-102]
_ = x[APPCALL-103] _ = x[APPCALL-103]
_ = x[SYSCALL-104] _ = x[SYSCALL-104]
@ -125,7 +141,7 @@ func _() {
_ = x[THROWIFNOT-241] _ = x[THROWIFNOT-241]
} }
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16OLDPUSH1NOPJMPJMPIFJMPIFNOTCALLRETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESTHROWTHROWIFNOT" const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESTHROWTHROWIFNOT"
var _Opcode_map = map[Opcode]string{ var _Opcode_map = map[Opcode]string{
0: _Opcode_name[0:8], 0: _Opcode_name[0:8],
@ -156,91 +172,107 @@ var _Opcode_map = map[Opcode]string{
30: _Opcode_name[170:176], 30: _Opcode_name[170:176],
31: _Opcode_name[176:182], 31: _Opcode_name[176:182],
32: _Opcode_name[182:188], 32: _Opcode_name[182:188],
81: _Opcode_name[188:196], 33: _Opcode_name[188:191],
97: _Opcode_name[196:199], 34: _Opcode_name[191:194],
98: _Opcode_name[199:202], 35: _Opcode_name[194:199],
99: _Opcode_name[202:207], 36: _Opcode_name[199:204],
100: _Opcode_name[207:215], 37: _Opcode_name[204:211],
101: _Opcode_name[215:219], 38: _Opcode_name[211:219],
102: _Opcode_name[219:222], 39: _Opcode_name[219:229],
103: _Opcode_name[222:229], 40: _Opcode_name[229:234],
104: _Opcode_name[229:236], 41: _Opcode_name[234:241],
105: _Opcode_name[236:244], 42: _Opcode_name[241:246],
106: _Opcode_name[244:259], 43: _Opcode_name[246:253],
107: _Opcode_name[259:269], 44: _Opcode_name[253:258],
108: _Opcode_name[269:281], 45: _Opcode_name[258:265],
109: _Opcode_name[281:286], 46: _Opcode_name[265:270],
112: _Opcode_name[286:292], 47: _Opcode_name[270:277],
114: _Opcode_name[292:297], 48: _Opcode_name[277:282],
115: _Opcode_name[297:302], 49: _Opcode_name[282:289],
116: _Opcode_name[302:307], 50: _Opcode_name[289:294],
117: _Opcode_name[307:311], 51: _Opcode_name[294:301],
118: _Opcode_name[311:314], 52: _Opcode_name[301:305],
119: _Opcode_name[314:317], 53: _Opcode_name[305:311],
120: _Opcode_name[317:321], 81: _Opcode_name[311:319],
121: _Opcode_name[321:325], 102: _Opcode_name[319:322],
122: _Opcode_name[325:329], 103: _Opcode_name[322:329],
123: _Opcode_name[329:332], 104: _Opcode_name[329:336],
124: _Opcode_name[332:336], 105: _Opcode_name[336:344],
125: _Opcode_name[336:340], 106: _Opcode_name[344:359],
126: _Opcode_name[340:343], 107: _Opcode_name[359:369],
127: _Opcode_name[343:349], 108: _Opcode_name[369:381],
128: _Opcode_name[349:353], 109: _Opcode_name[381:386],
129: _Opcode_name[353:358], 112: _Opcode_name[386:392],
130: _Opcode_name[358:362], 114: _Opcode_name[392:397],
131: _Opcode_name[362:368], 115: _Opcode_name[397:402],
132: _Opcode_name[368:371], 116: _Opcode_name[402:407],
133: _Opcode_name[371:373], 117: _Opcode_name[407:411],
134: _Opcode_name[373:376], 118: _Opcode_name[411:414],
135: _Opcode_name[376:381], 119: _Opcode_name[414:417],
139: _Opcode_name[381:384], 120: _Opcode_name[417:421],
140: _Opcode_name[384:387], 121: _Opcode_name[421:425],
141: _Opcode_name[387:391], 122: _Opcode_name[425:429],
143: _Opcode_name[391:397], 123: _Opcode_name[429:432],
144: _Opcode_name[397:400], 124: _Opcode_name[432:436],
145: _Opcode_name[400:403], 125: _Opcode_name[436:440],
146: _Opcode_name[403:405], 126: _Opcode_name[440:443],
147: _Opcode_name[405:408], 127: _Opcode_name[443:449],
148: _Opcode_name[408:411], 128: _Opcode_name[449:453],
149: _Opcode_name[411:414], 129: _Opcode_name[453:458],
150: _Opcode_name[414:417], 130: _Opcode_name[458:462],
151: _Opcode_name[417:420], 131: _Opcode_name[462:468],
152: _Opcode_name[420:423], 132: _Opcode_name[468:471],
153: _Opcode_name[423:426], 133: _Opcode_name[471:473],
154: _Opcode_name[426:433], 134: _Opcode_name[473:476],
155: _Opcode_name[433:439], 135: _Opcode_name[476:481],
156: _Opcode_name[439:447], 139: _Opcode_name[481:484],
158: _Opcode_name[447:458], 140: _Opcode_name[484:487],
159: _Opcode_name[458:460], 141: _Opcode_name[487:491],
160: _Opcode_name[460:462], 143: _Opcode_name[491:497],
161: _Opcode_name[462:465], 144: _Opcode_name[497:500],
162: _Opcode_name[465:468], 145: _Opcode_name[500:503],
163: _Opcode_name[468:471], 146: _Opcode_name[503:505],
164: _Opcode_name[471:474], 147: _Opcode_name[505:508],
165: _Opcode_name[474:480], 148: _Opcode_name[508:511],
167: _Opcode_name[480:484], 149: _Opcode_name[511:514],
168: _Opcode_name[484:490], 150: _Opcode_name[514:517],
169: _Opcode_name[490:497], 151: _Opcode_name[517:520],
170: _Opcode_name[497:504], 152: _Opcode_name[520:523],
172: _Opcode_name[504:512], 153: _Opcode_name[523:526],
173: _Opcode_name[512:518], 154: _Opcode_name[526:533],
174: _Opcode_name[518:531], 155: _Opcode_name[533:539],
192: _Opcode_name[531:540], 156: _Opcode_name[539:547],
193: _Opcode_name[540:544], 158: _Opcode_name[547:558],
194: _Opcode_name[544:550], 159: _Opcode_name[558:560],
195: _Opcode_name[550:558], 160: _Opcode_name[560:562],
196: _Opcode_name[558:565], 161: _Opcode_name[562:565],
197: _Opcode_name[565:573], 162: _Opcode_name[565:568],
198: _Opcode_name[573:582], 163: _Opcode_name[568:571],
199: _Opcode_name[582:588], 164: _Opcode_name[571:574],
200: _Opcode_name[588:594], 165: _Opcode_name[574:580],
201: _Opcode_name[594:601], 167: _Opcode_name[580:584],
202: _Opcode_name[601:607], 168: _Opcode_name[584:590],
203: _Opcode_name[607:613], 169: _Opcode_name[590:597],
204: _Opcode_name[613:617], 170: _Opcode_name[597:604],
205: _Opcode_name[617:623], 172: _Opcode_name[604:612],
240: _Opcode_name[623:628], 173: _Opcode_name[612:618],
241: _Opcode_name[628:638], 174: _Opcode_name[618:631],
192: _Opcode_name[631:640],
193: _Opcode_name[640:644],
194: _Opcode_name[644:650],
195: _Opcode_name[650:658],
196: _Opcode_name[658:665],
197: _Opcode_name[665:673],
198: _Opcode_name[673:682],
199: _Opcode_name[682:688],
200: _Opcode_name[688:694],
201: _Opcode_name[694:701],
202: _Opcode_name[701:707],
203: _Opcode_name[707:713],
204: _Opcode_name[713:717],
205: _Opcode_name[717:723],
240: _Opcode_name[723:728],
241: _Opcode_name[728:738],
} }
func (i Opcode) String() string { func (i Opcode) String() string {

View file

@ -1118,16 +1118,25 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
arr := elem.Bytes() arr := elem.Bytes()
v.estack.PushVal(len(arr)) v.estack.PushVal(len(arr))
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT: case opcode.JMP, opcode.JMPL, opcode.JMPIF, opcode.JMPIFL, opcode.JMPIFNOT, opcode.JMPIFNOTL,
opcode.JMPEQ, opcode.JMPEQL, opcode.JMPNE, opcode.JMPNEL,
opcode.JMPGT, opcode.JMPGTL, opcode.JMPGE, opcode.JMPGEL,
opcode.JMPLT, opcode.JMPLTL, opcode.JMPLE, opcode.JMPLEL:
offset := v.getJumpOffset(ctx, parameter, 0) offset := v.getJumpOffset(ctx, parameter, 0)
cond := true cond := true
if op != opcode.JMP { switch op {
cond = v.estack.Pop().Bool() == (op == opcode.JMPIF) case opcode.JMP, opcode.JMPL:
case opcode.JMPIF, opcode.JMPIFL, opcode.JMPIFNOT, opcode.JMPIFNOTL:
cond = v.estack.Pop().Bool() == (op == opcode.JMPIF || op == opcode.JMPIFL)
default:
b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt()
cond = getJumpCondition(op, a, b)
} }
v.jumpIf(ctx, offset, cond) v.jumpIf(ctx, offset, cond)
case opcode.CALL: case opcode.CALL, opcode.CALLL:
v.checkInvocationStackSize() v.checkInvocationStackSize()
newCtx := ctx.Copy() newCtx := ctx.Copy()
@ -1293,6 +1302,27 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
return return
} }
// getJumpCondition performs opcode specific comparison of a and b
func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
cmp := a.Cmp(b)
switch op {
case opcode.JMPEQ, opcode.JMPEQL:
return cmp == 0
case opcode.JMPNE, opcode.JMPNEL:
return cmp != 0
case opcode.JMPGT, opcode.JMPGTL:
return cmp > 0
case opcode.JMPGE, opcode.JMPGEL:
return cmp >= 0
case opcode.JMPLT, opcode.JMPLTL:
return cmp < 0
case opcode.JMPLE, opcode.JMPLEL:
return cmp <= 0
default:
panic(fmt.Sprintf("invalid JMP* opcode: %s", op))
}
}
// jumpIf performs jump to offset if cond is true. // jumpIf performs jump to offset if cond is true.
func (v *VM) jumpIf(ctx *Context, offset int, cond bool) { func (v *VM) jumpIf(ctx *Context, offset int, cond bool) {
if cond { if cond {
@ -1302,9 +1332,18 @@ func (v *VM) jumpIf(ctx *Context, offset int, cond bool) {
// getJumpOffset returns instruction number in a current context // getJumpOffset returns instruction number in a current context
// to a which JMP should be performed. // to a which JMP should be performed.
// parameter is interpreted as little-endian int16. // parameter should have length either 1 or 4 and
// is interpreted as little-endian.
func (v *VM) getJumpOffset(ctx *Context, parameter []byte, mod int) int { func (v *VM) getJumpOffset(ctx *Context, parameter []byte, mod int) int {
rOffset := int16(binary.LittleEndian.Uint16(parameter)) var rOffset int32
switch l := len(parameter); l {
case 1:
rOffset = int32(int8(parameter[0]))
case 4:
rOffset = int32(binary.LittleEndian.Uint32(parameter))
default:
panic(fmt.Sprintf("invalid JMP* parameter length: %d", l))
}
offset := ctx.ip + int(rOffset) + mod offset := ctx.ip + int(rOffset) + mod
if offset < 0 || offset > len(ctx.prog) { if offset < 0 || offset > len(ctx.prog) {
panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip)) panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip))

View file

@ -719,9 +719,9 @@ func callNTimes(n uint16) []byte {
return makeProgram( return makeProgram(
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
opcode.JMPIF, 0x4, 0, opcode.RET, opcode.JMPIF, 0x3, opcode.RET,
opcode.FROMALTSTACK, opcode.DEC, opcode.FROMALTSTACK, opcode.DEC,
opcode.CALL, 0xF8, 0xFF) // -8 -> JMP to TOALTSTACK) opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK)
} }
func TestInvocationLimitGood(t *testing.T) { func TestInvocationLimitGood(t *testing.T) {
@ -736,6 +736,96 @@ func TestInvocationLimitBad(t *testing.T) {
checkVMFailed(t, v) checkVMFailed(t, v)
} }
func isLongJMP(op opcode.Opcode) bool {
return op == opcode.JMPL || op == opcode.JMPIFL || op == opcode.JMPIFNOTL ||
op == opcode.JMPEQL || op == opcode.JMPNEL ||
op == opcode.JMPGEL || op == opcode.JMPGTL ||
op == opcode.JMPLEL || op == opcode.JMPLTL
}
func getJMPProgram(op opcode.Opcode) []byte {
prog := []byte{byte(op)}
if isLongJMP(op) {
prog = append(prog, 0x07, 0x00, 0x00, 0x00)
} else {
prog = append(prog, 0x04)
}
return append(prog, byte(opcode.PUSH1), byte(opcode.RET), byte(opcode.PUSH2), byte(opcode.RET))
}
func testJMP(t *testing.T, op opcode.Opcode, res interface{}, items ...interface{}) {
prog := getJMPProgram(op)
v := load(prog)
for i := range items {
v.estack.PushVal(items[i])
}
if res == nil {
checkVMFailed(t, v)
return
}
runVM(t, v)
require.EqualValues(t, res, v.estack.Pop().BigInt().Int64())
}
func TestJMPs(t *testing.T) {
testCases := []struct {
name string
items []interface{}
}{
{
name: "no condition",
},
{
name: "single item (true)",
items: []interface{}{true},
},
{
name: "single item (false)",
items: []interface{}{false},
},
{
name: "24 and 42",
items: []interface{}{24, 42},
},
{
name: "42 and 24",
items: []interface{}{42, 24},
},
{
name: "42 and 42",
items: []interface{}{42, 42},
},
}
// 2 is true, 1 is false
results := map[opcode.Opcode][]interface{}{
opcode.JMP: {2, 2, 2, 2, 2, 2},
opcode.JMPIF: {nil, 2, 1, 2, 2, 2},
opcode.JMPIFNOT: {nil, 1, 2, 1, 1, 1},
opcode.JMPEQ: {nil, nil, nil, 1, 1, 2},
opcode.JMPNE: {nil, nil, nil, 2, 2, 1},
opcode.JMPGE: {nil, nil, nil, 1, 2, 2},
opcode.JMPGT: {nil, nil, nil, 1, 2, 1},
opcode.JMPLE: {nil, nil, nil, 2, 1, 2},
opcode.JMPLT: {nil, nil, nil, 2, 1, 1},
}
for i, tc := range testCases {
i := i
t.Run(tc.name, func(t *testing.T) {
for op := opcode.JMP; op < opcode.JMPLEL; op++ {
resOp := op
if isLongJMP(op) {
resOp--
}
t.Run(op.String(), func(t *testing.T) {
testJMP(t, op, results[resOp][i], tc.items...)
})
}
})
}
}
func TestNOTNoArgument(t *testing.T) { func TestNOTNoArgument(t *testing.T) {
prog := makeProgram(opcode.NOT) prog := makeProgram(opcode.NOT)
vm := load(prog) vm := load(prog)
@ -1798,7 +1888,7 @@ func TestSimpleCall(t *testing.T) {
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()
w := buf.BinWriter w := buf.BinWriter
emit.Opcode(w, opcode.PUSH2) emit.Opcode(w, opcode.PUSH2)
emit.Instruction(w, opcode.CALL, []byte{04, 00}) emit.Instruction(w, opcode.CALL, []byte{03})
emit.Opcode(w, opcode.RET) emit.Opcode(w, opcode.RET)
emit.Opcode(w, opcode.PUSH10) emit.Opcode(w, opcode.PUSH10)
emit.Opcode(w, opcode.ADD) emit.Opcode(w, opcode.ADD)