diff --git a/docs/compiler.md b/docs/compiler.md index 7249a085b..56b95a90d 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -9,6 +9,7 @@ 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: * `new()` is not supported, most of the time you can substitute structs 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 of an arbitrary variable * there is no real distinction between different integer types, all of them diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index e83873d31..62ca08ce6 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -14,7 +14,7 @@ import ( var ( // Go language builtin functions. - goBuiltins = []string{"len", "append", "panic", "make"} + goBuiltins = []string{"len", "append", "panic", "make", "copy"} // Custom builtin utility functions. customBuiltins = []string{ "FromAddress", "Equals", diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 42a9881c8..693b3f109 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1295,6 +1295,44 @@ func (c *codegen) convertSyscall(expr *ast.CallExpr, api, name string) { 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) { var name string switch t := expr.Fun.(type) { @@ -1305,6 +1343,14 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { } 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 { @@ -1412,10 +1458,10 @@ func transformArgs(fun ast.Expr, args []ast.Expr) []ast.Expr { return args[1:] } case *ast.Ident: - if f.Name == "panic" { + switch f.Name { + case "panic": return args[1:] - } - if f.Name == "make" { + case "make", "copy": return nil } } diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index ca1d62fe7..6ceb967dc 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -364,3 +364,67 @@ func TestMake(t *testing.T) { 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}) + }) +}