From 9eb409b935dcee298b49327fddb3a4e3caa6ecea Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 27 Jan 2019 19:22:21 -0800 Subject: [PATCH] gotypes,build: pointer dereference (#61) Provides a method on `gotypes.Component` to allow pointer dereference. This will enable `gotypes` helpers to be used with struct pointer arguments, for example. Updates #53 Fixes #54 --- build/global.go | 3 ++ build/pseudo.go | 7 ++++ examples/args/args.s | 7 ++++ examples/args/args_test.go | 10 ++++++ examples/args/asm.go | 6 ++++ examples/args/stub.go | 2 ++ gotypes/components.go | 66 ++++++++++++++++++++++---------------- gotypes/components_test.go | 60 +++++++++++++++++++++++++++++++++- gotypes/signature.go | 5 ++- 9 files changed, 137 insertions(+), 29 deletions(-) diff --git a/build/global.go b/build/global.go index d2d49c3..148b90c 100644 --- a/build/global.go +++ b/build/global.go @@ -112,6 +112,9 @@ func Load(src gotypes.Component, dst reg.Register) reg.Register { return ctx.Loa // attempt to select the right MOV instruction based on the types involved. func Store(src reg.Register, dst gotypes.Component) { ctx.Store(src, dst) } +// Dereference loads a pointer and returns its element type. +func Dereference(ptr gotypes.Component) gotypes.Component { return ctx.Dereference(ptr) } + // Doc sets documentation comment lines for the currently active function. func Doc(lines ...string) { ctx.Doc(lines...) } diff --git a/build/pseudo.go b/build/pseudo.go index 69aeed3..a728bb8 100644 --- a/build/pseudo.go +++ b/build/pseudo.go @@ -54,6 +54,13 @@ func (c *Context) Store(src reg.Register, dst gotypes.Component) { c.mov(src, b.Addr, int(src.Bytes()), int(gotypes.Sizes.Sizeof(b.Type)), b.Type) } +// Dereference loads a pointer and returns its element type. +func (c *Context) Dereference(ptr gotypes.Component) gotypes.Component { + r := c.GP64() + c.Load(ptr, r) + return ptr.Dereference(r) +} + // ConstData builds a static data section containing just the given constant. func (c *Context) ConstData(name string, v operand.Constant) operand.Mem { g := c.StaticGlobal(name) diff --git a/examples/args/args.s b/examples/args/args.s index 6d65041..f6186e3 100644 --- a/examples/args/args.s +++ b/examples/args/args.s @@ -109,3 +109,10 @@ TEXT ·FieldComplex128Real(SB), NOSPLIT, $0-184 MOVSD s_Complex128_real+160(FP), X0 MOVSD X0, ret+176(FP) RET + +// func DereferenceFloat32(s *Struct) float32 +TEXT ·DereferenceFloat32(SB), NOSPLIT, $0-12 + MOVQ s+0(FP), AX + MOVSS 16(AX), X0 + MOVSS X0, ret+8(FP) + RET diff --git a/examples/args/args_test.go b/examples/args/args_test.go index e8f1d95..8d1b406 100644 --- a/examples/args/args_test.go +++ b/examples/args/args_test.go @@ -1,6 +1,7 @@ package args import ( + "math" "testing" "testing/quick" ) @@ -36,3 +37,12 @@ func TestFunctionsEqual(t *testing.T) { } } } + +func TestDereferenceFloat32(t *testing.T) { + expect := float32(math.Pi) + s := Struct{Float32: expect} + got := DereferenceFloat32(&s) + if got != expect { + t.Errorf("DereferenceFloat32(%v) = %v; expect %v", s, got, expect) + } +} diff --git a/examples/args/asm.go b/examples/args/asm.go index 5123f1f..ee04e7a 100644 --- a/examples/args/asm.go +++ b/examples/args/asm.go @@ -99,5 +99,11 @@ func main() { Store(c128r, ReturnIndex(0)) RET() + TEXT("DereferenceFloat32", NOSPLIT, "func(s *Struct) float32") + s := Dereference(Param("s")) + f := Load(s.Field("Float32"), XMM()) + Store(f, ReturnIndex(0)) + + RET() Generate() } diff --git a/examples/args/stub.go b/examples/args/stub.go index b962a19..f30c076 100644 --- a/examples/args/stub.go +++ b/examples/args/stub.go @@ -37,3 +37,5 @@ func FieldArrayOneC(s Struct) uint16 func FieldComplex64Imag(s Struct) float32 func FieldComplex128Real(s Struct) float64 + +func DereferenceFloat32(s *Struct) float32 diff --git a/gotypes/components.go b/gotypes/components.go index 56e0136..dd224b3 100644 --- a/gotypes/components.go +++ b/gotypes/components.go @@ -7,6 +7,8 @@ import ( "go/types" "strconv" + "github.com/mmcloughlin/avo/reg" + "github.com/mmcloughlin/avo/operand" ) @@ -27,13 +29,14 @@ type Component interface { // resolution time. Resolve() (*Basic, error) - Base() Component // base pointer of a string or slice - Len() Component // length of a string or slice - Cap() Component // capacity of a slice - Real() Component // real part of a complex value - Imag() Component // imaginary part of a complex value - Index(int) Component // index into an array - Field(string) Component // access a struct field + Dereference(r reg.Register) Component // dereference a pointer + Base() Component // base pointer of a string or slice + Len() Component // length of a string or slice + Cap() Component // capacity of a slice + Real() Component // real part of a complex value + Imag() Component // imaginary part of a complex value + Index(int) Component // index into an array + Field(string) Component // access a struct field } // componenterr is an error that also provides a null implementation of the @@ -45,28 +48,27 @@ func errorf(format string, args ...interface{}) Component { return componenterr(fmt.Sprintf(format, args...)) } -func (c componenterr) Error() string { return string(c) } -func (c componenterr) Resolve() (*Basic, error) { return nil, c } -func (c componenterr) Base() Component { return c } -func (c componenterr) Len() Component { return c } -func (c componenterr) Cap() Component { return c } -func (c componenterr) Real() Component { return c } -func (c componenterr) Imag() Component { return c } -func (c componenterr) Index(int) Component { return c } -func (c componenterr) Field(string) Component { return c } +func (c componenterr) Error() string { return string(c) } +func (c componenterr) Resolve() (*Basic, error) { return nil, c } +func (c componenterr) Dereference(r reg.Register) Component { return c } +func (c componenterr) Base() Component { return c } +func (c componenterr) Len() Component { return c } +func (c componenterr) Cap() Component { return c } +func (c componenterr) Real() Component { return c } +func (c componenterr) Imag() Component { return c } +func (c componenterr) Index(int) Component { return c } +func (c componenterr) Field(string) Component { return c } type component struct { - name string - typ types.Type - offset int + typ types.Type + addr operand.Mem } -// NewComponent builds a component for the named type at the given memory offset. -func NewComponent(name string, t types.Type, offset int) Component { +// NewComponent builds a component for the named type at the given address. +func NewComponent(t types.Type, addr operand.Mem) Component { return &component{ - name: name, - typ: t, - offset: offset, + typ: t, + addr: addr, } } @@ -76,11 +78,19 @@ func (c *component) Resolve() (*Basic, error) { return nil, errors.New("component is not primitive") } return &Basic{ - Addr: operand.NewParamAddr(c.name, c.offset), + Addr: c.addr, Type: b, }, nil } +func (c *component) Dereference(r reg.Register) Component { + p, ok := c.typ.Underlying().(*types.Pointer) + if !ok { + return errorf("not pointer type") + } + return NewComponent(p.Elem(), operand.Mem{Base: r}) +} + // Reference: https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/reflect/value.go#L1800-L1804 // // type SliceHeader struct { @@ -194,8 +204,10 @@ func (c *component) Field(n string) Component { func (c *component) sub(suffix string, offset int, t types.Type) *component { s := *c - s.name += suffix - s.offset += offset + if s.addr.Symbol.Name != "" { + s.addr.Symbol.Name += suffix + } + s.addr = s.addr.Offset(offset) s.typ = t return &s } diff --git a/gotypes/components_test.go b/gotypes/components_test.go index 4d021b9..b0b2366 100644 --- a/gotypes/components_test.go +++ b/gotypes/components_test.go @@ -2,7 +2,12 @@ package gotypes import ( "go/types" + "strings" "testing" + + "github.com/mmcloughlin/avo/reg" + + "github.com/mmcloughlin/avo/operand" ) func TestBasicKindsArePrimitive(t *testing.T) { @@ -33,8 +38,61 @@ func TestPointersArePrimitive(t *testing.T) { } func AssertPrimitive(t *testing.T, typ types.Type) { - c := NewComponent("primitive", typ, 0) + c := NewComponent(typ, operand.NewParamAddr("primitive", 0)) if _, err := c.Resolve(); err != nil { t.Errorf("expected type %s to be primitive: got error '%s'", typ, err) } } + +func TestComponentErrors(t *testing.T) { + comp := NewComponent(types.Typ[types.Uint32], operand.Mem{}) + cases := []struct { + Component Component + ErrorSubstring string + }{ + {comp.Base(), "only slices and strings"}, + {comp.Len(), "only slices and strings"}, + {comp.Cap(), "only slices have"}, + {comp.Real(), "only complex"}, + {comp.Imag(), "only complex"}, + {comp.Index(42), "not array type"}, + {comp.Field("a"), "not struct type"}, + {comp.Dereference(reg.R12), "not pointer type"}, + } + for _, c := range cases { + _, err := c.Component.Resolve() + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), c.ErrorSubstring) { + t.Fatalf("error message %q; expected substring %q", err.Error(), c.ErrorSubstring) + } + } +} + +func TestComponentErrorChaining(t *testing.T) { + // Build a component with an error. + comp := NewComponent(types.Typ[types.Uint32], operand.Mem{}).Index(3) + _, expect := comp.Resolve() + if expect == nil { + t.Fatal("expected error") + } + + // Confirm that the error is preserved through chaining operations. + cases := []Component{ + comp.Dereference(reg.R13), + comp.Base(), + comp.Len(), + comp.Cap(), + comp.Real(), + comp.Imag(), + comp.Index(42), + comp.Field("field"), + } + for _, c := range cases { + _, err := c.Resolve() + if err != expect { + t.Fatal("chaining should preserve error") + } + } +} diff --git a/gotypes/signature.go b/gotypes/signature.go index 5fed65e..e000020 100644 --- a/gotypes/signature.go +++ b/gotypes/signature.go @@ -7,6 +7,8 @@ import ( "go/token" "go/types" "strconv" + + "github.com/mmcloughlin/avo/operand" ) // Signature represents a Go function signature. @@ -136,7 +138,8 @@ func newTuple(t *types.Tuple, offsets []int64, size int64, defaultprefix string) name += strconv.Itoa(i) } } - c := NewComponent(name, v.Type(), int(offsets[i])) + addr := operand.NewParamAddr(name, int(offsets[i])) + c := NewComponent(v.Type(), addr) tuple.components = append(tuple.components, c) if v.Name() != "" { tuple.byname[v.Name()] = c