From eb047b12a76cb36b486d9cfa5888f12f215b74ec Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sun, 2 Aug 2020 14:42:29 +0300 Subject: [PATCH 1/4] compiler: support creating pointers to struct Signed-off-by: Evgenii Stratonikov --- pkg/compiler/codegen.go | 39 +++++++++++++++++++++++++++++++----- pkg/compiler/pointer_test.go | 18 +++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 pkg/compiler/pointer_test.go diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index fc34b7b04..2691ff35e 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -438,7 +438,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emitStoreVar(t.X.(*ast.Ident).Name, t.Sel.Name) return nil } - strct, ok := typ.Underlying().(*types.Struct) + strct, ok := c.getStruct(typ) if !ok { c.prog.Err = fmt.Errorf("nested selector assigns not supported yet") return nil @@ -623,7 +623,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.CompositeLit: switch typ := c.typeOf(n).Underlying().(type) { case *types.Struct: - c.convertStruct(n) + c.convertStruct(n, false) case *types.Map: c.convertMap(n) default: @@ -835,7 +835,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } return nil } - strct, ok := typ.Underlying().(*types.Struct) + strct, ok := c.getStruct(typ) if !ok { c.prog.Err = fmt.Errorf("selectors are supported only on structs") return nil @@ -846,6 +846,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil case *ast.UnaryExpr: + if n.Op == token.AND { + // We support only taking address from struct literals. + // For identifiers we can't support "taking address" in a general way + // because both struct and array are reference types. + lit, ok := n.X.(*ast.CompositeLit) + if ok { + c.convertStruct(lit, true) + return nil + } + c.prog.Err = fmt.Errorf("'&' can be used only with struct literals") + return nil + } + ast.Walk(c, n.X) // From https://golang.org/ref/spec#Operators // there can be only following unary operators @@ -1325,7 +1338,19 @@ func (c *codegen) convertMap(lit *ast.CompositeLit) { } } -func (c *codegen) convertStruct(lit *ast.CompositeLit) { +func (c *codegen) getStruct(typ types.Type) (*types.Struct, bool) { + switch t := typ.Underlying().(type) { + case *types.Struct: + return t, true + case *types.Pointer: + strct, ok := t.Elem().Underlying().(*types.Struct) + return strct, ok + default: + return nil, false + } +} + +func (c *codegen) convertStruct(lit *ast.CompositeLit, ptr bool) { // Create a new structScope to initialize and store // the positions of its variables. strct, ok := c.typeOf(lit).Underlying().(*types.Struct) @@ -1336,7 +1361,11 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { emit.Opcode(c.prog.BinWriter, opcode.NOP) emit.Int(c.prog.BinWriter, int64(strct.NumFields())) - emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT) + if ptr { + emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) + } else { + emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT) + } keyedLit := len(lit.Elts) > 0 if keyedLit { diff --git a/pkg/compiler/pointer_test.go b/pkg/compiler/pointer_test.go new file mode 100644 index 000000000..090101056 --- /dev/null +++ b/pkg/compiler/pointer_test.go @@ -0,0 +1,18 @@ +package compiler_test + +import ( + "math/big" + "testing" +) + +func TestAddressOfLiteral(t *testing.T) { + src := `package foo + type Foo struct { A int } + func Main() int { + f := &Foo{} + setA(f, 3) + return f.A + } + func setA(s *Foo, a int) { s.A = a }` + eval(t, src, big.NewInt(3)) +} From d54c60ded3ac2e805ea23137d37f02daf873535c Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sun, 2 Aug 2020 14:56:47 +0300 Subject: [PATCH 2/4] compiler: support pointer dereferencing for structs Signed-off-by: Evgenii Stratonikov --- pkg/compiler/codegen.go | 10 ++++++++++ pkg/compiler/pointer_test.go | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 2691ff35e..ad4a07029 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -610,6 +610,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emitLoadConst(c.typeAndValueOf(n)) return nil + case *ast.StarExpr: + _, ok := c.getStruct(c.typeOf(n.X)) + if !ok { + c.prog.Err = errors.New("dereferencing is only supported on structs") + return nil + } + ast.Walk(c, n.X) + c.emitConvert(stackitem.StructT) + return nil + case *ast.Ident: if tv := c.typeAndValueOf(n); tv.Value != nil { c.emitLoadConst(tv) diff --git a/pkg/compiler/pointer_test.go b/pkg/compiler/pointer_test.go index 090101056..7b9e8c2dd 100644 --- a/pkg/compiler/pointer_test.go +++ b/pkg/compiler/pointer_test.go @@ -16,3 +16,15 @@ func TestAddressOfLiteral(t *testing.T) { func setA(s *Foo, a int) { s.A = a }` eval(t, src, big.NewInt(3)) } + +func TestPointerDereference(t *testing.T) { + src := `package foo + type Foo struct { A int } + func Main() int { + f := &Foo{A: 4} + setA(*f, 3) + return f.A + } + func setA(s Foo, a int) { s.A = a }` + eval(t, src, big.NewInt(4)) +} From f2107bfbc45c3fece16e604b507d7108be434d2c Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Aug 2020 09:33:18 +0300 Subject: [PATCH 3/4] compiler: copy structs when passing as arguments In Go structs must be copied when used as arguments. To do so we must clone struct on VM level. This is done by appending this struct to an intermediate array. --- pkg/compiler/codegen.go | 12 ++++++++++++ pkg/compiler/pointer_test.go | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index ad4a07029..4b24d39f3 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -793,6 +793,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // Handle the arguments for _, arg := range args { ast.Walk(c, arg) + typ := c.typeOf(arg) + _, ok := typ.Underlying().(*types.Struct) + if ok && !isInteropPath(typ.String()) { + // To clone struct fields we create a new array and append struct to it. + // This way even non-pointer struct fields will be copied. + emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY0) + emit.Opcode(c.prog.BinWriter, opcode.DUP) + emit.Opcode(c.prog.BinWriter, opcode.ROT) + emit.Opcode(c.prog.BinWriter, opcode.APPEND) + emit.Opcode(c.prog.BinWriter, opcode.PUSH0) + emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) + } } // Do not swap for builtin functions. if !isBuiltin { diff --git a/pkg/compiler/pointer_test.go b/pkg/compiler/pointer_test.go index 7b9e8c2dd..eb86e2276 100644 --- a/pkg/compiler/pointer_test.go +++ b/pkg/compiler/pointer_test.go @@ -28,3 +28,41 @@ func TestPointerDereference(t *testing.T) { func setA(s Foo, a int) { s.A = a }` eval(t, src, big.NewInt(4)) } + +func TestStructArgCopy(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + src := `package foo + type Foo struct { A int } + func Main() int { + f := Foo{A: 4} + setA(f, 3) + return f.A + } + func setA(s Foo, a int) { s.A = a }` + eval(t, src, big.NewInt(4)) + }) + t.Run("StructField", func(t *testing.T) { + src := `package foo + type Bar struct { A int } + type Foo struct { B Bar } + func Main() int { + f := Foo{B: Bar{A: 4}} + setA(f, 3) + return f.B.A + } + func setA(s Foo, a int) { s.B.A = a }` + eval(t, src, big.NewInt(4)) + }) + t.Run("StructPointerField", func(t *testing.T) { + src := `package foo + type Bar struct { A int } + type Foo struct { B *Bar } + func Main() int { + f := Foo{B: &Bar{A: 4}} + setA(f, 3) + return f.B.A + } + func setA(s Foo, a int) { s.B.A = a }` + eval(t, src, big.NewInt(3)) + }) +} From 5b78bb2e553654d5d7fa7ee27a158ac5faa63749 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 6 Aug 2020 13:43:40 +0300 Subject: [PATCH 4/4] docs: describe pointer support in compiler.md --- docs/compiler.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/compiler.md b/docs/compiler.md index 64552285b..76002c2c1 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -9,6 +9,8 @@ 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: * `make()` ane `new()` are not supported, most of the time you can substitute them with composite literals + * 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 work as big.Int in Go with a limit of 256 bit in width, so you can use `int` for just about anything. This is the way integers work in Neo VM and