From 7fcd537b098cb9a0b9fd885e13bf55b596ccb5e6 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 6 May 2020 16:31:52 +0300 Subject: [PATCH 1/3] vm: implement Pointer stack item Pointer is a generic address type in VM. It is used for calling lambda-expressions. --- pkg/vm/stack_item.go | 83 +++++++++++++++++++++++++++++++++++++++ pkg/vm/stack_item_test.go | 30 ++++++++++++++ pkg/vm/vm_test.go | 6 ++- 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index bba8b8fc3..675105f6f 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -10,7 +10,9 @@ import ( "math/big" "reflect" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) @@ -857,3 +859,84 @@ func (i *InteropItem) Convert(typ StackItemType) (StackItem, error) { func (i *InteropItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) } + +// PointerItem represents VM-level instruction pointer. +type PointerItem struct { + pos int + script []byte + hash util.Uint160 +} + +// NewPointerItem returns new pointer on the specified position. +func NewPointerItem(pos int, script []byte) *PointerItem { + return &PointerItem{ + pos: pos, + script: script, + hash: hash.Hash160(script), + } +} + +// String implements StackItem interface. +func (p *PointerItem) String() string { + return "Pointer" +} + +// Value implements StackItem interface. +func (p *PointerItem) Value() interface{} { + return p.pos +} + +// Dup implements StackItem interface. +func (p *PointerItem) Dup() StackItem { + return &PointerItem{ + pos: p.pos, + script: p.script, + hash: p.hash, + } +} + +// Bool implements StackItem interface. +func (p *PointerItem) Bool() bool { + return true +} + +// TryBytes implements StackItem interface. +func (p *PointerItem) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Pointer to ByteArray") +} + +// TryInteger implements StackItem interface. +func (p *PointerItem) TryInteger() (*big.Int, error) { + return nil, errors.New("can't convert Pointer to Integer") +} + +// Equals implements StackItem interface. +func (p *PointerItem) Equals(s StackItem) bool { + if p == s { + return true + } + ptr, ok := s.(*PointerItem) + return ok && p.pos == ptr.pos && p.hash == ptr.hash +} + +// ToContractParameter implements StackItem interface. +func (p *PointerItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { + return smartcontract.NewParameter(smartcontract.AnyType) +} + +// Type implements StackItem interface. +func (p *PointerItem) Type() StackItemType { + return PointerT +} + +// Convert implements StackItem interface. +func (p *PointerItem) Convert(typ StackItemType) (StackItem, error) { + switch typ { + case PointerT: + return p, nil + case BooleanT: + return NewBoolItem(p.Bool()), nil + default: + return nil, errInvalidConversion + } +} diff --git a/pkg/vm/stack_item_test.go b/pkg/vm/stack_item_test.go index 9587e5e0a..fb2f3633f 100644 --- a/pkg/vm/stack_item_test.go +++ b/pkg/vm/stack_item_test.go @@ -127,6 +127,10 @@ var stringerTestCases = []struct { input: NewInteropItem(nil), result: "InteropItem", }, + { + input: NewPointerItem(0, nil), + result: "Pointer", + }, } func TestStringer(t *testing.T) { @@ -314,6 +318,32 @@ var equalsTestCases = map[string][]struct { result: true, }, }, + "pointer": { + { + item1: NewPointerItem(0, []byte{}), + result: false, + }, + { + item1: NewPointerItem(1, []byte{1}), + item2: NewPointerItem(1, []byte{1}), + result: true, + }, + { + item1: NewPointerItem(1, []byte{1}), + item2: NewPointerItem(2, []byte{1}), + result: false, + }, + { + item1: NewPointerItem(1, []byte{1}), + item2: NewPointerItem(1, []byte{2}), + result: false, + }, + { + item1: NewPointerItem(0, []byte{}), + item2: NewBigIntegerItem(big.NewInt(0)), + result: false, + }, + }, } func TestEquals(t *testing.T) { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index ad1386d1c..a031fc2eb 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -266,6 +266,7 @@ func TestCONVERT(t *testing.T) { NewArrayItem(arr), NewArrayItem(nil), NewStructItem(arr), NewStructItem(nil), NewMapItem(), m, NewInteropItem(struct{}{}), + NewPointerItem(0, []byte{}), } for i := range trueCases { t.Run(getName(trueCases[i], BooleanT), testBool(trueCases[i], NewBoolItem(true))) @@ -329,9 +330,12 @@ func TestCONVERT(t *testing.T) { t.Run("Map->Map", testCONVERT(MapT, m, m)) + ptr := NewPointerItem(1, []byte{1}) + t.Run("Pointer->Pointer", testCONVERT(PointerT, ptr, ptr)) + t.Run("Null->", func(t *testing.T) { types := []StackItemType{ - BooleanT, ByteArrayT, IntegerT, ArrayT, StructT, MapT, InteropT, + BooleanT, ByteArrayT, IntegerT, ArrayT, StructT, MapT, InteropT, PointerT, } for i := range types { t.Run(types[i].String(), testCONVERT(types[i], NullItem{}, NullItem{})) From 1d3fb3d6f5de2147dce4dc92fe6db323ae24c4af Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 6 May 2020 16:55:30 +0300 Subject: [PATCH 2/3] vm: implement PUSHA/CALLA opcodes --- pkg/vm/context.go | 2 +- pkg/vm/opcode/opcode.go | 2 + pkg/vm/opcode/opcode_string.go | 250 +++++++++++++++++---------------- pkg/vm/vm.go | 22 +++ pkg/vm/vm_test.go | 15 ++ 5 files changed, 167 insertions(+), 124 deletions(-) diff --git a/pkg/vm/context.go b/pkg/vm/context.go index bcc7194dd..4c610ee9e 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -111,7 +111,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) { 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: + opcode.CALLL, opcode.SYSCALL, opcode.PUSHA: numtoread = 4 default: if instr <= opcode.PUSHINT256 { diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index b6fcc13a1..b6641747f 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -15,6 +15,7 @@ const ( PUSHINT128 Opcode = 0x04 PUSHINT256 Opcode = 0x05 + PUSHA Opcode = 0x0A PUSHNULL Opcode = 0x0B PUSHDATA1 Opcode = 0x0C @@ -64,6 +65,7 @@ const ( JMPLEL Opcode = 0x33 CALL Opcode = 0x34 CALLL Opcode = 0x35 + CALLA Opcode = 0x36 // Exceptions ABORT Opcode = 0x37 diff --git a/pkg/vm/opcode/opcode_string.go b/pkg/vm/opcode/opcode_string.go index 5ff84b7b5..ad726d7e0 100644 --- a/pkg/vm/opcode/opcode_string.go +++ b/pkg/vm/opcode/opcode_string.go @@ -14,6 +14,7 @@ func _() { _ = x[PUSHINT64-3] _ = x[PUSHINT128-4] _ = x[PUSHINT256-5] + _ = x[PUSHA-10] _ = x[PUSHNULL-11] _ = x[PUSHDATA1-12] _ = x[PUSHDATA2-13] @@ -59,6 +60,7 @@ func _() { _ = x[JMPLEL-51] _ = x[CALL-52] _ = x[CALLL-53] + _ = x[CALLA-54] _ = x[ABORT-55] _ = x[ASSERT-56] _ = x[THROW-58] @@ -141,7 +143,7 @@ func _() { _ = x[CONVERT-219] } -const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" +const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPOLDPUSH1ROLLREVERSE3REVERSE4REVERSENDUPFROMALTSTACKTOALTSTACKFROMALTSTACKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT" var _Opcode_map = map[Opcode]string{ 0: _Opcode_name[0:8], @@ -150,128 +152,130 @@ var _Opcode_map = map[Opcode]string{ 3: _Opcode_name[26:35], 4: _Opcode_name[35:45], 5: _Opcode_name[45:55], - 11: _Opcode_name[55:63], - 12: _Opcode_name[63:72], - 13: _Opcode_name[72:81], - 14: _Opcode_name[81:90], - 15: _Opcode_name[90:96], - 16: _Opcode_name[96:101], - 17: _Opcode_name[101:106], - 18: _Opcode_name[106:111], - 19: _Opcode_name[111:116], - 20: _Opcode_name[116:121], - 21: _Opcode_name[121:126], - 22: _Opcode_name[126:131], - 23: _Opcode_name[131:136], - 24: _Opcode_name[136:141], - 25: _Opcode_name[141:146], - 26: _Opcode_name[146:152], - 27: _Opcode_name[152:158], - 28: _Opcode_name[158:164], - 29: _Opcode_name[164:170], - 30: _Opcode_name[170:176], - 31: _Opcode_name[176:182], - 32: _Opcode_name[182:188], - 33: _Opcode_name[188:191], - 34: _Opcode_name[191:194], - 35: _Opcode_name[194:198], - 36: _Opcode_name[198:203], - 37: _Opcode_name[203:209], - 38: _Opcode_name[209:217], - 39: _Opcode_name[217:226], - 40: _Opcode_name[226:231], - 41: _Opcode_name[231:237], - 42: _Opcode_name[237:242], - 43: _Opcode_name[242:248], - 44: _Opcode_name[248:253], - 45: _Opcode_name[253:259], - 46: _Opcode_name[259:264], - 47: _Opcode_name[264:270], - 48: _Opcode_name[270:275], - 49: _Opcode_name[275:281], - 50: _Opcode_name[281:286], - 51: _Opcode_name[286:292], - 52: _Opcode_name[292:296], - 53: _Opcode_name[296:301], - 55: _Opcode_name[301:306], - 56: _Opcode_name[306:312], - 58: _Opcode_name[312:317], - 64: _Opcode_name[317:320], - 65: _Opcode_name[320:327], - 67: _Opcode_name[327:332], - 69: _Opcode_name[332:336], - 70: _Opcode_name[336:339], - 72: _Opcode_name[339:344], - 73: _Opcode_name[344:349], - 74: _Opcode_name[349:352], - 75: _Opcode_name[352:356], - 77: _Opcode_name[356:360], - 78: _Opcode_name[360:364], - 80: _Opcode_name[364:368], - 81: _Opcode_name[368:376], - 82: _Opcode_name[376:380], - 83: _Opcode_name[380:388], - 84: _Opcode_name[388:396], - 85: _Opcode_name[396:404], - 106: _Opcode_name[404:419], - 107: _Opcode_name[419:429], - 108: _Opcode_name[429:441], - 126: _Opcode_name[441:444], - 127: _Opcode_name[444:450], - 128: _Opcode_name[450:454], - 129: _Opcode_name[454:459], - 144: _Opcode_name[459:465], - 145: _Opcode_name[465:468], - 146: _Opcode_name[468:470], - 147: _Opcode_name[470:473], - 151: _Opcode_name[473:478], - 152: _Opcode_name[478:486], - 153: _Opcode_name[486:490], - 154: _Opcode_name[490:493], - 155: _Opcode_name[493:499], - 156: _Opcode_name[499:502], - 157: _Opcode_name[502:505], - 158: _Opcode_name[505:508], - 159: _Opcode_name[508:511], - 160: _Opcode_name[511:514], - 161: _Opcode_name[514:517], - 162: _Opcode_name[517:520], - 168: _Opcode_name[520:523], - 169: _Opcode_name[523:526], - 170: _Opcode_name[526:529], - 171: _Opcode_name[529:536], - 172: _Opcode_name[536:542], - 177: _Opcode_name[542:544], - 179: _Opcode_name[544:552], - 180: _Opcode_name[552:563], - 181: _Opcode_name[563:565], - 182: _Opcode_name[565:568], - 183: _Opcode_name[568:570], - 184: _Opcode_name[570:573], - 185: _Opcode_name[573:576], - 186: _Opcode_name[576:579], - 187: _Opcode_name[579:585], - 192: _Opcode_name[585:589], - 193: _Opcode_name[589:595], - 194: _Opcode_name[595:604], - 195: _Opcode_name[604:612], - 196: _Opcode_name[612:621], - 197: _Opcode_name[621:631], - 198: _Opcode_name[631:640], - 200: _Opcode_name[640:646], - 202: _Opcode_name[646:650], - 203: _Opcode_name[650:656], - 204: _Opcode_name[656:660], - 205: _Opcode_name[660:666], - 206: _Opcode_name[666:674], - 207: _Opcode_name[674:680], - 208: _Opcode_name[680:687], - 209: _Opcode_name[687:699], - 210: _Opcode_name[699:705], - 211: _Opcode_name[705:715], - 216: _Opcode_name[715:721], - 217: _Opcode_name[721:727], - 219: _Opcode_name[727:734], + 10: _Opcode_name[55:60], + 11: _Opcode_name[60:68], + 12: _Opcode_name[68:77], + 13: _Opcode_name[77:86], + 14: _Opcode_name[86:95], + 15: _Opcode_name[95:101], + 16: _Opcode_name[101:106], + 17: _Opcode_name[106:111], + 18: _Opcode_name[111:116], + 19: _Opcode_name[116:121], + 20: _Opcode_name[121:126], + 21: _Opcode_name[126:131], + 22: _Opcode_name[131:136], + 23: _Opcode_name[136:141], + 24: _Opcode_name[141:146], + 25: _Opcode_name[146:151], + 26: _Opcode_name[151:157], + 27: _Opcode_name[157:163], + 28: _Opcode_name[163:169], + 29: _Opcode_name[169:175], + 30: _Opcode_name[175:181], + 31: _Opcode_name[181:187], + 32: _Opcode_name[187:193], + 33: _Opcode_name[193:196], + 34: _Opcode_name[196:199], + 35: _Opcode_name[199:203], + 36: _Opcode_name[203:208], + 37: _Opcode_name[208:214], + 38: _Opcode_name[214:222], + 39: _Opcode_name[222:231], + 40: _Opcode_name[231:236], + 41: _Opcode_name[236:242], + 42: _Opcode_name[242:247], + 43: _Opcode_name[247:253], + 44: _Opcode_name[253:258], + 45: _Opcode_name[258:264], + 46: _Opcode_name[264:269], + 47: _Opcode_name[269:275], + 48: _Opcode_name[275:280], + 49: _Opcode_name[280:286], + 50: _Opcode_name[286:291], + 51: _Opcode_name[291:297], + 52: _Opcode_name[297:301], + 53: _Opcode_name[301:306], + 54: _Opcode_name[306:311], + 55: _Opcode_name[311:316], + 56: _Opcode_name[316:322], + 58: _Opcode_name[322:327], + 64: _Opcode_name[327:330], + 65: _Opcode_name[330:337], + 67: _Opcode_name[337:342], + 69: _Opcode_name[342:346], + 70: _Opcode_name[346:349], + 72: _Opcode_name[349:354], + 73: _Opcode_name[354:359], + 74: _Opcode_name[359:362], + 75: _Opcode_name[362:366], + 77: _Opcode_name[366:370], + 78: _Opcode_name[370:374], + 80: _Opcode_name[374:378], + 81: _Opcode_name[378:386], + 82: _Opcode_name[386:390], + 83: _Opcode_name[390:398], + 84: _Opcode_name[398:406], + 85: _Opcode_name[406:414], + 106: _Opcode_name[414:429], + 107: _Opcode_name[429:439], + 108: _Opcode_name[439:451], + 126: _Opcode_name[451:454], + 127: _Opcode_name[454:460], + 128: _Opcode_name[460:464], + 129: _Opcode_name[464:469], + 144: _Opcode_name[469:475], + 145: _Opcode_name[475:478], + 146: _Opcode_name[478:480], + 147: _Opcode_name[480:483], + 151: _Opcode_name[483:488], + 152: _Opcode_name[488:496], + 153: _Opcode_name[496:500], + 154: _Opcode_name[500:503], + 155: _Opcode_name[503:509], + 156: _Opcode_name[509:512], + 157: _Opcode_name[512:515], + 158: _Opcode_name[515:518], + 159: _Opcode_name[518:521], + 160: _Opcode_name[521:524], + 161: _Opcode_name[524:527], + 162: _Opcode_name[527:530], + 168: _Opcode_name[530:533], + 169: _Opcode_name[533:536], + 170: _Opcode_name[536:539], + 171: _Opcode_name[539:546], + 172: _Opcode_name[546:552], + 177: _Opcode_name[552:554], + 179: _Opcode_name[554:562], + 180: _Opcode_name[562:573], + 181: _Opcode_name[573:575], + 182: _Opcode_name[575:578], + 183: _Opcode_name[578:580], + 184: _Opcode_name[580:583], + 185: _Opcode_name[583:586], + 186: _Opcode_name[586:589], + 187: _Opcode_name[589:595], + 192: _Opcode_name[595:599], + 193: _Opcode_name[599:605], + 194: _Opcode_name[605:614], + 195: _Opcode_name[614:622], + 196: _Opcode_name[622:631], + 197: _Opcode_name[631:641], + 198: _Opcode_name[641:650], + 200: _Opcode_name[650:656], + 202: _Opcode_name[656:660], + 203: _Opcode_name[660:666], + 204: _Opcode_name[666:670], + 205: _Opcode_name[670:676], + 206: _Opcode_name[676:684], + 207: _Opcode_name[684:690], + 208: _Opcode_name[690:697], + 209: _Opcode_name[697:709], + 210: _Opcode_name[709:715], + 211: _Opcode_name[715:725], + 216: _Opcode_name[725:731], + 217: _Opcode_name[731:737], + 219: _Opcode_name[737:744], } func (i Opcode) String() string { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index da444962b..694ae147c 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -198,6 +198,9 @@ func (v *VM) PrintOps() { case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL: offset := int16(binary.LittleEndian.Uint16(parameter)) desc = fmt.Sprintf("%d (%d/%x)", ctx.ip+int(offset), offset, parameter) + case opcode.PUSHA: + offset := int32(binary.LittleEndian.Uint32(parameter)) + desc = fmt.Sprintf("%d (%x)", offset, parameter) case opcode.SYSCALL: desc = fmt.Sprintf("%q", parameter) default: @@ -529,6 +532,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.PUSHDATA1, opcode.PUSHDATA2, opcode.PUSHDATA4: v.estack.PushVal(parameter) + case opcode.PUSHA: + n := int32(binary.LittleEndian.Uint32(parameter)) + if n < 0 || int(n) > len(ctx.prog) { + panic(fmt.Sprintf("invalid pointer offset (%d)", n)) + } + ptr := NewPointerItem(int(n), ctx.prog) + v.estack.PushVal(ptr) + case opcode.PUSHNULL: v.estack.PushVal(NullItem{}) @@ -1134,6 +1145,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro offset := v.getJumpOffset(newCtx, parameter, 0) v.jumpIf(newCtx, offset, true) + case opcode.CALLA: + ptr := v.estack.Pop().Item().(*PointerItem) + if ptr.hash != ctx.ScriptHash() { + panic("invalid script in pointer") + } + + newCtx := ctx.Copy() + newCtx.rvcount = -1 + v.istack.PushVal(newCtx) + v.jumpIf(newCtx, ptr.pos, true) + case opcode.SYSCALL: interopID := GetInteropID(parameter) ifunc := v.GetInteropByID(interopID) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index a031fc2eb..5a313bfa5 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -909,6 +909,21 @@ func TestJMPs(t *testing.T) { } } +func TestPUSHA(t *testing.T) { + t.Run("Negative", getTestFuncForVM(makeProgram(opcode.PUSHA, 0xFF, 0xFF, 0xFF, 0xFF), nil)) + t.Run("TooBig", getTestFuncForVM(makeProgram(opcode.PUSHA, 10, 0, 0, 0), nil)) + t.Run("Good", func(t *testing.T) { + prog := makeProgram(opcode.PUSHA, 2, 0, 0, 0) + runWithArgs(t, prog, NewPointerItem(2, prog)) + }) +} + +func TestCALLA(t *testing.T) { + prog := makeProgram(opcode.CALLA, opcode.PUSH2, opcode.ADD, opcode.RET, opcode.PUSH3, opcode.RET) + t.Run("InvalidScript", getTestFuncForVM(prog, nil, NewPointerItem(4, []byte{1}))) + t.Run("Good", getTestFuncForVM(prog, 5, NewPointerItem(4, prog))) +} + func TestNOT(t *testing.T) { prog := makeProgram(opcode.NOT) t.Run("Bool", getTestFuncForVM(prog, true, false)) From 75fb3af48f2ef661be209001a95ba7d2b8826df1 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 6 May 2020 18:10:20 +0300 Subject: [PATCH 3/3] compiler: add basic support for function literals Implement basic support for function literals. Nested function literals and variables from closure are not supported for now. --- pkg/compiler/codegen.go | 52 +++++++++++++++++++++++++++++++++----- pkg/compiler/func_scope.go | 6 ++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index c40e6c766..709747e57 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -37,6 +37,9 @@ type codegen struct { // A mapping of func identifiers with their scope. funcs map[string]*funcScope + // A mapping of lambda functions into their scope. + lambda map[string]*funcScope + // Current funcScope being converted. scope *funcScope @@ -204,8 +207,8 @@ func (c *codegen) convertGlobals(f ast.Node) { func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { var ( - f *funcScope - ok bool + f *funcScope + ok, isLambda bool ) f, ok = c.funcs[decl.Name.Name] @@ -215,6 +218,9 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { return } c.setLabel(f.label) + } else if f, ok = c.lambda[decl.Name.Name]; ok { + isLambda = ok + c.setLabel(f.label) } else { f = c.newFunc(decl) } @@ -275,6 +281,13 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { } f.rng.End = uint16(c.prog.Len() - 1) + + if !isLambda { + for _, f := range c.lambda { + c.convertFuncDecl(file, f.decl) + } + c.lambda = make(map[string]*funcScope) + } } func (c *codegen) Visit(node ast.Node) ast.Visitor { @@ -521,6 +534,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil + case *ast.FuncLit: + l := c.newLabel() + c.newLambda(l, n) + buf := make([]byte, 4) + binary.LittleEndian.PutUint16(buf, l) + emit.Instruction(c.prog.BinWriter, opcode.PUSHA, buf) + return nil + case *ast.BasicLit: c.emitLoadConst(c.typeInfo.Types[n]) return nil @@ -646,6 +667,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { var ( f *funcScope ok bool + name string numArgs = len(n.Args) isBuiltin = isBuiltin(n.Fun) ) @@ -654,8 +676,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.Ident: f, ok = c.funcs[fun.Name] if !ok && !isBuiltin { - c.prog.Err = fmt.Errorf("could not resolve function %s", fun.Name) - return nil + name = fun.Name } case *ast.SelectorExpr: // If this is a method call we need to walk the AST to load the struct locally. @@ -700,6 +721,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // Use the ident to check, builtins are not in func scopes. // We can be sure builtins are of type *ast.Ident. c.convertBuiltin(n) + case name != "": + // Function was not found thus is can be only an invocation of func-typed variable. + c.emitLoadLocal(name) + emit.Opcode(c.prog.BinWriter, opcode.CALLA) case isSyscall(f): c.convertSyscall(n, f.selector.Name, f.name) default: @@ -1264,6 +1289,15 @@ func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope { return f } +func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) { + name := fmt.Sprintf("lambda@%d", u) + c.lambda[name] = newFuncScope(&ast.FuncDecl{ + Name: ast.NewIdent(name), + Type: lit.Type, + Body: lit.Body, + }, u) +} + func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { // Resolve the entrypoint of the program. main, mainFile := resolveEntryPoint(mainIdent, pkg) @@ -1319,6 +1353,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen { prog: io.NewBufBinWriter(), l: []int{}, funcs: map[string]*funcScope{}, + lambda: map[string]*funcScope{}, labels: map[labelWithType]uint16{}, typeInfo: &pkg.Info, @@ -1364,7 +1399,7 @@ func (c *codegen) writeJumps(b []byte) error { case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL, - opcode.CALLL: + opcode.CALLL, opcode.PUSHA: // we can't use arg returned by ctx.Next() because it is copied nextIP := ctx.NextIP() arg := b[nextIP-4:] @@ -1373,7 +1408,12 @@ func (c *codegen) writeJumps(b []byte) error { if int(index) > len(c.l) { return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l)) } - offset := c.l[index] - nextIP + 5 + var offset int + if op == opcode.PUSHA { + offset = c.l[index] + } else { + offset = c.l[index] - nextIP + 5 + } if offset > math.MaxInt32 || offset < math.MinInt32 { return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)", nextIP-5, offset, math.MaxInt32, math.MinInt32) diff --git a/pkg/compiler/func_scope.go b/pkg/compiler/func_scope.go index 47c9aa238..d3f3810fc 100644 --- a/pkg/compiler/func_scope.go +++ b/pkg/compiler/func_scope.go @@ -42,8 +42,12 @@ type funcScope struct { } func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { + var name string + if decl.Name != nil { + name = decl.Name.Name + } return &funcScope{ - name: decl.Name.Name, + name: name, decl: decl, label: label, locals: map[string]int{},