mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-27 03:58:06 +00:00
Merge pull request #1352 from nspcc-dev/compiler/make
Fix MEMCPY, support `copy` and `make` in compiler
This commit is contained in:
commit
a1fbe51bca
8 changed files with 187 additions and 7 deletions
|
@ -7,8 +7,9 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
|
||||||
The compiler is mostly compatible with regular Go language specification, but
|
The compiler is mostly compatible with regular Go language specification, but
|
||||||
there are some important deviations that you need to be aware of that make it
|
there are some important deviations that you need to be aware of that make it
|
||||||
a dialect of Go rather than a complete port of the language:
|
a dialect of Go rather than a complete port of the language:
|
||||||
* `make()` ane `new()` are not supported, most of the time you can substitute
|
* `new()` is not supported, most of the time you can substitute structs with composite literals
|
||||||
them with composite literals
|
* `make()` is supported for maps and slices with elements of basic types
|
||||||
|
* `copy()` is supported only for byte slices, because of underlying `MEMCPY` opcode
|
||||||
* pointers are supported only for struct literals, one can't take an address
|
* pointers are supported only for struct literals, one can't take an address
|
||||||
of an arbitrary variable
|
of an arbitrary variable
|
||||||
* there is no real distinction between different integer types, all of them
|
* there is no real distinction between different integer types, all of them
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Go language builtin functions.
|
// Go language builtin functions.
|
||||||
goBuiltins = []string{"len", "append", "panic"}
|
goBuiltins = []string{"len", "append", "panic", "make", "copy"}
|
||||||
// Custom builtin utility functions.
|
// Custom builtin utility functions.
|
||||||
customBuiltins = []string{
|
customBuiltins = []string{
|
||||||
"FromAddress", "Equals",
|
"FromAddress", "Equals",
|
||||||
|
|
|
@ -1313,6 +1313,44 @@ func (c *codegen) convertSyscall(expr *ast.CallExpr, api, name string) {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.NOP)
|
emit.Opcode(c.prog.BinWriter, opcode.NOP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emitSliceHelper emits 3 items on stack: slice, its first index, and its size.
|
||||||
|
func (c *codegen) emitSliceHelper(e ast.Expr) {
|
||||||
|
if !isByteSlice(c.typeOf(e)) {
|
||||||
|
c.prog.Err = fmt.Errorf("copy is supported only for byte-slices")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var hasLowIndex bool
|
||||||
|
switch src := e.(type) {
|
||||||
|
case *ast.SliceExpr:
|
||||||
|
ast.Walk(c, src.X)
|
||||||
|
if src.High != nil {
|
||||||
|
ast.Walk(c, src.High)
|
||||||
|
} else {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
|
||||||
|
}
|
||||||
|
if src.Low != nil {
|
||||||
|
ast.Walk(c, src.Low)
|
||||||
|
hasLowIndex = true
|
||||||
|
} else {
|
||||||
|
emit.Int(c.prog.BinWriter, 0)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ast.Walk(c, src)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SIZE)
|
||||||
|
emit.Int(c.prog.BinWriter, 0)
|
||||||
|
}
|
||||||
|
if !hasLowIndex {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
|
||||||
|
} else {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.ROT)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SWAP)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.SUB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
||||||
var name string
|
var name string
|
||||||
switch t := expr.Fun.(type) {
|
switch t := expr.Fun.(type) {
|
||||||
|
@ -1323,6 +1361,32 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch name {
|
switch name {
|
||||||
|
case "copy":
|
||||||
|
// stack for MEMCPY is: dst, dst_index, src, src_index, count
|
||||||
|
c.emitSliceHelper(expr.Args[0])
|
||||||
|
c.emitSliceHelper(expr.Args[1])
|
||||||
|
emit.Int(c.prog.BinWriter, 3)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.ROLL)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.MIN)
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.MEMCPY)
|
||||||
|
case "make":
|
||||||
|
typ := c.typeOf(expr.Args[0])
|
||||||
|
switch {
|
||||||
|
case isMap(typ):
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.NEWMAP)
|
||||||
|
default:
|
||||||
|
if len(expr.Args) == 3 {
|
||||||
|
c.prog.Err = fmt.Errorf("`make()` with a capacity argument is not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ast.Walk(c, expr.Args[1])
|
||||||
|
if isByteSlice(typ) {
|
||||||
|
emit.Opcode(c.prog.BinWriter, opcode.NEWBUFFER)
|
||||||
|
} else {
|
||||||
|
neoT := toNeoType(typ.(*types.Slice).Elem())
|
||||||
|
emit.Instruction(c.prog.BinWriter, opcode.NEWARRAYT, []byte{byte(neoT)})
|
||||||
|
}
|
||||||
|
}
|
||||||
case "len":
|
case "len":
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
|
emit.Opcode(c.prog.BinWriter, opcode.ISNULL)
|
||||||
|
@ -1412,8 +1476,11 @@ func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr {
|
||||||
return args[1:]
|
return args[1:]
|
||||||
}
|
}
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
if f.Name == "panic" {
|
switch f.Name {
|
||||||
|
case "panic":
|
||||||
return args[1:]
|
return args[1:]
|
||||||
|
case "make", "copy":
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,8 +70,6 @@ func TestImportOrder(t *testing.T) {
|
||||||
import _ "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/pkg2"
|
import _ "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/pkg2"
|
||||||
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/pkg3"
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/pkg3"
|
||||||
func Main() int { return pkg3.A }`
|
func Main() int { return pkg3.A }`
|
||||||
v := vmAndCompile(t, src)
|
|
||||||
v.PrintOps()
|
|
||||||
eval(t, src, big.NewInt(2))
|
eval(t, src, big.NewInt(2))
|
||||||
})
|
})
|
||||||
t.Run("2,1", func(t *testing.T) {
|
t.Run("2,1", func(t *testing.T) {
|
||||||
|
|
|
@ -2,9 +2,12 @@ package compiler_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sliceTestCases = []testCase{
|
var sliceTestCases = []testCase{
|
||||||
|
@ -323,3 +326,105 @@ func TestJumps(t *testing.T) {
|
||||||
`
|
`
|
||||||
eval(t, src, []byte{0x62, 0x01, 0x00})
|
eval(t, src, []byte{0x62, 0x01, 0x00})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMake(t *testing.T) {
|
||||||
|
t.Run("Map", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() int {
|
||||||
|
a := make(map[int]int)
|
||||||
|
a[1] = 10
|
||||||
|
a[2] = 20
|
||||||
|
return a[1]
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
})
|
||||||
|
t.Run("IntSlice", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() int {
|
||||||
|
a := make([]int, 10)
|
||||||
|
return len(a) + a[0]
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
})
|
||||||
|
t.Run("ByteSlice", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() int {
|
||||||
|
a := make([]byte, 10)
|
||||||
|
return len(a) + int(a[0])
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
})
|
||||||
|
t.Run("CapacityError", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() int {
|
||||||
|
a := make([]int, 1, 2)
|
||||||
|
return a[0]
|
||||||
|
}`
|
||||||
|
_, err := compiler.Compile("foo.go", strings.NewReader(src))
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() []int {
|
||||||
|
src := []int{3, 2, 1}
|
||||||
|
dst := make([]int, 2)
|
||||||
|
copy(dst, src)
|
||||||
|
return dst
|
||||||
|
}`
|
||||||
|
_, err := compiler.Compile("foo.go", strings.NewReader(src))
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() []byte {
|
||||||
|
src := []byte{3, 2, 1}
|
||||||
|
dst := make([]byte, 2)
|
||||||
|
copy(dst, src)
|
||||||
|
return dst
|
||||||
|
}`
|
||||||
|
eval(t, src, []byte{3, 2})
|
||||||
|
})
|
||||||
|
t.Run("LowSrcIndex", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() []byte {
|
||||||
|
src := []byte{3, 2, 1}
|
||||||
|
dst := make([]byte, 2)
|
||||||
|
copy(dst, src[1:])
|
||||||
|
return dst
|
||||||
|
}`
|
||||||
|
eval(t, src, []byte{2, 1})
|
||||||
|
})
|
||||||
|
t.Run("LowDstIndex", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() []byte {
|
||||||
|
src := []byte{3, 2, 1}
|
||||||
|
dst := make([]byte, 2)
|
||||||
|
copy(dst[1:], src[1:])
|
||||||
|
return dst
|
||||||
|
}`
|
||||||
|
eval(t, src, []byte{0, 2})
|
||||||
|
})
|
||||||
|
t.Run("BothIndices", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() []byte {
|
||||||
|
src := []byte{4, 3, 2, 1}
|
||||||
|
dst := make([]byte, 4)
|
||||||
|
copy(dst[1:], src[1:3])
|
||||||
|
return dst
|
||||||
|
}`
|
||||||
|
eval(t, src, []byte{0, 3, 2, 0})
|
||||||
|
})
|
||||||
|
t.Run("EmptySliceExpr", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func Main() []byte {
|
||||||
|
src := []byte{3, 2, 1}
|
||||||
|
dst := make([]byte, 2)
|
||||||
|
copy(dst[1:], src[:])
|
||||||
|
return dst
|
||||||
|
}`
|
||||||
|
eval(t, src, []byte{0, 3})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,11 @@ func isBasicTypeOfKind(typ types.Type, ks ...types.BasicKind) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMap(typ types.Type) bool {
|
||||||
|
_, ok := typ.Underlying().(*types.Map)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func isByte(typ types.Type) bool {
|
func isByte(typ types.Type) bool {
|
||||||
return isBasicTypeOfKind(typ, types.Uint8, types.Int8)
|
return isBasicTypeOfKind(typ, types.Uint8, types.Int8)
|
||||||
}
|
}
|
||||||
|
|
|
@ -659,7 +659,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
panic("invalid destination index")
|
panic("invalid destination index")
|
||||||
}
|
}
|
||||||
dst := v.estack.Pop().value.(*stackitem.Buffer).Value().([]byte)
|
dst := v.estack.Pop().value.(*stackitem.Buffer).Value().([]byte)
|
||||||
if sum := si + n; sum < 0 || sum > len(dst) {
|
if sum := di + n; sum < 0 || sum > len(dst) {
|
||||||
panic("size is too big")
|
panic("size is too big")
|
||||||
}
|
}
|
||||||
copy(dst[di:], src[si:si+n])
|
copy(dst[di:], src[si:si+n])
|
||||||
|
|
|
@ -1344,6 +1344,10 @@ func TestMEMCPY(t *testing.T) {
|
||||||
buf := stackitem.NewBuffer([]byte{0, 1, 2, 3})
|
buf := stackitem.NewBuffer([]byte{0, 1, 2, 3})
|
||||||
runWithArgs(t, prog, stackitem.NewBuffer([]byte{0, 6, 7, 3}), buf, buf, 1, []byte{4, 5, 6, 7}, 2, 2)
|
runWithArgs(t, prog, stackitem.NewBuffer([]byte{0, 6, 7, 3}), buf, buf, 1, []byte{4, 5, 6, 7}, 2, 2)
|
||||||
})
|
})
|
||||||
|
t.Run("NonZeroDstIndex", func(t *testing.T) {
|
||||||
|
buf := stackitem.NewBuffer([]byte{0, 1, 2})
|
||||||
|
runWithArgs(t, prog, stackitem.NewBuffer([]byte{0, 6, 7}), buf, buf, 1, []byte{4, 5, 6, 7}, 2, 2)
|
||||||
|
})
|
||||||
t.Run("NegativeSize", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, 0, -1))
|
t.Run("NegativeSize", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, 0, -1))
|
||||||
t.Run("NegativeSrcIndex", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, -1, 1))
|
t.Run("NegativeSrcIndex", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), 0, []byte{2}, -1, 1))
|
||||||
t.Run("NegativeDstIndex", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), -1, []byte{2}, 0, 1))
|
t.Run("NegativeDstIndex", getTestFuncForVM(prog, nil, stackitem.NewBuffer([]byte{0, 1}), -1, []byte{2}, 0, 1))
|
||||||
|
|
Loading…
Reference in a new issue