From fc3b840335bb14afbfa5ca576829722fd206ae1a Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Tue, 16 Feb 2021 16:20:19 +0300 Subject: [PATCH 1/2] compiler: append bytes >0x80 properly NeoVM encoding for int(0x80) is []byte{0x80, 0x00} and byte constant can be used both as integer and as an element of byte-slice. Thus special case needs to be taken in `append`. --- pkg/compiler/codegen.go | 40 ++++++++++++---------- pkg/compiler/slice_test.go | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 41b6aa963..53b0cd1d9 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -783,7 +783,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ln := len(n.Elts) // ByteArrays needs a different approach than normal arrays. if isByteSlice(typ) { - c.convertByteArray(n) + c.convertByteArray(n.Elts) return nil } for i := ln - 1; i >= 0; i-- { @@ -1621,20 +1621,24 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { case "append": arg := expr.Args[0] typ := c.typeInfo.Types[arg].Type - c.emitReverse(len(expr.Args)) + ast.Walk(c, arg) emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ISNULL) - emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 3}) if isByteSlice(typ) { - emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.PUSH0, opcode.NEWBUFFER) - } else { - emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.NEWARRAY0, opcode.NOP) - } - // Jump target. - for range expr.Args[1:] { - if isByteSlice(typ) { - emit.Opcodes(c.prog.BinWriter, opcode.SWAP, opcode.CAT) + emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 3}) + emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.PUSHDATA1, 0) + if expr.Ellipsis.IsValid() { + ast.Walk(c, expr.Args[1]) } else { - emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.ROT, opcode.APPEND) + c.convertByteArray(expr.Args[1:]) + } + emit.Opcodes(c.prog.BinWriter, opcode.CAT) + } else { + emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 2}) + emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.NEWARRAY0) + for _, e := range expr.Args[1:] { + emit.Opcodes(c.prog.BinWriter, opcode.DUP) + ast.Walk(c, e) + emit.Opcodes(c.prog.BinWriter, opcode.APPEND) } } case "panic": @@ -1696,7 +1700,7 @@ func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr { } case *ast.Ident: switch f.Name { - case "make", "copy": + case "make", "copy", "append": return nil } } @@ -1709,11 +1713,11 @@ func (c *codegen) emitConvert(typ stackitem.Type) { emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) } -func (c *codegen) convertByteArray(lit *ast.CompositeLit) { - buf := make([]byte, len(lit.Elts)) +func (c *codegen) convertByteArray(elems []ast.Expr) { + buf := make([]byte, len(elems)) varIndices := []int{} - for i := 0; i < len(lit.Elts); i++ { - t := c.typeAndValueOf(lit.Elts[i]) + for i := 0; i < len(elems); i++ { + t := c.typeAndValueOf(elems[i]) if t.Value != nil { val, _ := constant.Int64Val(t.Value) buf[i] = byte(val) @@ -1726,7 +1730,7 @@ func (c *codegen) convertByteArray(lit *ast.CompositeLit) { for _, i := range varIndices { emit.Opcodes(c.prog.BinWriter, opcode.DUP) emit.Int(c.prog.BinWriter, int64(i)) - ast.Walk(c, lit.Elts[i]) + ast.Walk(c, elems[i]) emit.Opcodes(c.prog.BinWriter, opcode.SETITEM) } } diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index 95e667235..4e3e9b82c 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -336,6 +336,74 @@ func TestSliceOperations(t *testing.T) { runTestCases(t, sliceTestCases) } +func TestByteSlices(t *testing.T) { + testCases := []testCase{ + { + "append bytes bigger than 0x79", + `package foo + func Main() []byte { + var z []byte + z = append(z, 0x78, 0x79, 0x80, 0x81, 0xFF) + return z + }`, + []byte{0x78, 0x79, 0x80, 0x81, 0xFF}, + }, + { + "append bytes bigger than 0x79, not nil", + `package foo + func Main() []byte { + z := []byte{0x78} + z = append(z, 0x79, 0x80, 0x81, 0xFF) + return z + }`, + []byte{0x78, 0x79, 0x80, 0x81, 0xFF}, + }, + { + "append bytes bigger than 0x79, function return", + `package foo + func getByte() byte { return 0x80 } + func Main() []byte { + var z []byte + z = append(z, 0x78, 0x79, getByte(), 0x81, 0xFF) + return z + }`, + []byte{0x78, 0x79, 0x80, 0x81, 0xFF}, + }, + { + "append ints bigger than 0x79, function return byte", + `package foo + func getByte() byte { return 0x80 } + func Main() []int { + var z []int + z = append(z, 0x78, int(getByte())) + return z + }`, + []stackitem.Item{stackitem.Make(0x78), stackitem.Make(0x80)}, + }, + { + "slice literal, bytes bigger than 0x79, function return", + `package foo + func getByte() byte { return 0x80 } + func Main() []byte { + z := []byte{0x79, getByte(), 0x81} + return z + }`, + []byte{0x79, 0x80, 0x81}, + }, + { + "compare bytes as integers", + `package foo + func getByte1() byte { return 0x79 } + func getByte2() byte { return 0x80 } + func Main() bool { + return getByte1() < getByte2() + }`, + true, + }, + } + runTestCases(t, testCases) +} + func TestSubsliceCompound(t *testing.T) { src := `package foo func Main() []int { From d16ef536530c8881d3662acffc1943a3bb5217d5 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Tue, 16 Feb 2021 16:45:06 +0300 Subject: [PATCH 2/2] compiler: support ellipsis for append of non-byte slices NeoVM lacks opcode for array append, thus some kind of loop is needed for this. --- pkg/compiler/codegen.go | 30 ++++++++++++++++++++++++++---- pkg/compiler/slice_test.go | 17 +++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 53b0cd1d9..0d4f59a17 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1635,10 +1635,32 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { } else { emit.Instruction(c.prog.BinWriter, opcode.JMPIFNOT, []byte{2 + 2}) emit.Opcodes(c.prog.BinWriter, opcode.DROP, opcode.NEWARRAY0) - for _, e := range expr.Args[1:] { - emit.Opcodes(c.prog.BinWriter, opcode.DUP) - ast.Walk(c, e) - emit.Opcodes(c.prog.BinWriter, opcode.APPEND) + if expr.Ellipsis.IsValid() { + ast.Walk(c, expr.Args[1]) // x y + emit.Opcodes(c.prog.BinWriter, opcode.PUSH0) // x y cnt=0 + start := c.newLabel() + c.setLabel(start) + emit.Opcodes(c.prog.BinWriter, opcode.PUSH2, opcode.PICK) // x y cnt x + emit.Opcodes(c.prog.BinWriter, opcode.PUSH2, opcode.PICK) // x y cnt x y + emit.Opcodes(c.prog.BinWriter, opcode.DUP, opcode.SIZE) // x y cnt x y len(y) + emit.Opcodes(c.prog.BinWriter, opcode.PUSH3, opcode.PICK) // x y cnt x y len(y) cnt + after := c.newLabel() + emit.Jmp(c.prog.BinWriter, opcode.JMPEQL, after) // x y cnt x y + emit.Opcodes(c.prog.BinWriter, opcode.PUSH2, opcode.PICK, // x y cnt x y cnt + opcode.PICKITEM, // x y cnt x y[cnt] + opcode.APPEND, // x=append(x, y[cnt]) y cnt + opcode.INC) // x y cnt+1 + emit.Jmp(c.prog.BinWriter, opcode.JMPL, start) + c.setLabel(after) + for i := 0; i < 4; i++ { // leave x on stack + emit.Opcodes(c.prog.BinWriter, opcode.DROP) + } + } else { + for _, e := range expr.Args[1:] { + emit.Opcodes(c.prog.BinWriter, opcode.DUP) + ast.Walk(c, e) + emit.Opcodes(c.prog.BinWriter, opcode.APPEND) + } } } case "panic": diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index 4e3e9b82c..37f163b38 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -181,6 +181,23 @@ var sliceTestCases = []testCase{ stackitem.NewBigInteger(big.NewInt(5)), }, }, + { + "int slice, append slice", + `package foo + func getByte() byte { return 0x80 } + func Main() []int { + x := []int{1} + y := []int{2, 3} + x = append(x, y...) + x = append(x, y...) + return x + }`, + []stackitem.Item{ + stackitem.Make(1), + stackitem.Make(2), stackitem.Make(3), + stackitem.Make(2), stackitem.Make(3), + }, + }, { "declare compound slice", `package foo