From eb225e9d2ca0419d680f4394d3d76494f6746d1e Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Tue, 22 Jan 2019 22:35:01 -0800 Subject: [PATCH] gotypes,build: add Implement (#58) By using Implement you can provide a definition of a function, taking the signature from a stub in the package. One major benefit of this approach is it makes it easy to handle external types in the function signature. Updates #55 --- README.md | 3 ++- build/context.go | 17 ++++++++++++ build/global.go | 4 +++ examples/README.md | 2 ++ examples/ext/README.md | 33 +++++++++++++++++++++++ examples/ext/asm.go | 16 +++++++++++ examples/ext/ext.s | 7 +++++ examples/ext/ext/ext.go | 9 +++++++ examples/ext/ext_test.go | 17 ++++++++++++ examples/ext/stub.go | 7 +++++ gotypes/signature.go | 22 ++++++++++++++- gotypes/signature_test.go | 56 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 examples/ext/README.md create mode 100644 examples/ext/asm.go create mode 100644 examples/ext/ext.s create mode 100644 examples/ext/ext/ext.go create mode 100644 examples/ext/ext_test.go create mode 100644 examples/ext/stub.go diff --git a/README.md b/README.md index bdc962a2..073eb273 100644 --- a/README.md +++ b/README.md @@ -171,8 +171,9 @@ For demonstrations of `avo` features: * **[args](examples/args):** Loading function arguments. * **[returns](examples/returns):** Building return values. -* **[data](examples/data):** Defining `DATA` sections. * **[complex](examples/complex):** Working with `complex{64,128}` types. +* **[data](examples/data):** Defining `DATA` sections. +* **[ext](examples/ext):** Interacting with types from external packages. ### Real Examples diff --git a/build/context.go b/build/context.go index 7e14627d..21fd6d0e 100644 --- a/build/context.go +++ b/build/context.go @@ -111,6 +111,23 @@ func (c *Context) SignatureExpr(expr string) { c.Signature(s) } +// Implement starts building a function of the given name, whose type is +// specified by a stub in the containing package. +func (c *Context) Implement(name string) { + pkg := c.types() + if pkg == nil { + c.adderrormessage("no package specified") + return + } + s, err := gotypes.LookupSignature(pkg, name) + if err != nil { + c.adderror(err) + return + } + c.Function(name) + c.Signature(s) +} + func (c *Context) types() *types.Package { if c.pkg == nil { return nil diff --git a/build/global.go b/build/global.go index b50235df..d2d49c3a 100644 --- a/build/global.go +++ b/build/global.go @@ -118,6 +118,10 @@ func Doc(lines ...string) { ctx.Doc(lines...) } // Attributes sets function attributes for the currently active function. func Attributes(a attr.Attribute) { ctx.Attributes(a) } +// Implement starts building a function of the given name, whose type is +// specified by a stub in the containing package. +func Implement(name string) { ctx.Implement(name) } + // AllocLocal allocates size bytes in the stack of the currently active function. // Returns a reference to the base pointer for the newly allocated region. func AllocLocal(size int) operand.Mem { return ctx.AllocLocal(size) } diff --git a/examples/README.md b/examples/README.md index 5b9e842b..d0e2c17d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,6 +11,8 @@ Features: * **[returns](returns):** Building return values. * **[complex](complex):** Working with `complex{64,128}` types. * **[data](data):** Defining `DATA` sections. +* **[ext](ext):** Interacting with types from external packages. + "Real" examples: diff --git a/examples/ext/README.md b/examples/ext/README.md new file mode 100644 index 00000000..a56c1842 --- /dev/null +++ b/examples/ext/README.md @@ -0,0 +1,33 @@ +# ext + +Demonstrates how to use external types in an `avo` function signature. + +In this case, you will need to write the function stub yourself. + +[embedmd]:# (stub.go /package/ $) +```go +package ext + +import "github.com/mmcloughlin/avo/examples/ext/ext" + +// StructFieldB returns field B. +func StructFieldB(e ext.Struct) byte +``` + +Then in place of the usual `TEXT` declaration we use `Implement` to state that we are beginning the definition of a function already declared in the package stub file. + +[embedmd]:# (asm.go go /.*Package.*/ /RET.*/) +```go + Package("github.com/mmcloughlin/avo/examples/ext") + Implement("StructFieldB") + b := Load(Param("e").Field("B"), GP8()) + Store(b, ReturnIndex(0)) + RET() +``` + +Finally, in this case the `go:generate` line is different since we do not need to generate the stub file. + +[embedmd]:# (ext_test.go go /.*go:generate.*/) +```go +//go:generate go run asm.go -out ext.s +``` diff --git a/examples/ext/asm.go b/examples/ext/asm.go new file mode 100644 index 00000000..bfa8079e --- /dev/null +++ b/examples/ext/asm.go @@ -0,0 +1,16 @@ +// +build ignore + +package main + +import ( + . "github.com/mmcloughlin/avo/build" +) + +func main() { + Package("github.com/mmcloughlin/avo/examples/ext") + Implement("StructFieldB") + b := Load(Param("e").Field("B"), GP8()) + Store(b, ReturnIndex(0)) + RET() + Generate() +} diff --git a/examples/ext/ext.s b/examples/ext/ext.s new file mode 100644 index 00000000..b43e77ff --- /dev/null +++ b/examples/ext/ext.s @@ -0,0 +1,7 @@ +// Code generated by command: go run asm.go -out ext.s. DO NOT EDIT. + +// func StructFieldB(e ext.Struct) byte +TEXT ·StructFieldB(SB), $0-25 + MOVB e_B+6(FP), AL + MOVB AL, ret+24(FP) + RET diff --git a/examples/ext/ext/ext.go b/examples/ext/ext/ext.go new file mode 100644 index 00000000..88732a5c --- /dev/null +++ b/examples/ext/ext/ext.go @@ -0,0 +1,9 @@ +// Package ext is used as a target package in the external types example. +package ext + +// Struct is the target type in the external types example. +type Struct struct { + A [3]uint16 + B byte + C string +} diff --git a/examples/ext/ext_test.go b/examples/ext/ext_test.go new file mode 100644 index 00000000..1d382875 --- /dev/null +++ b/examples/ext/ext_test.go @@ -0,0 +1,17 @@ +package ext + +import ( + "testing" + "testing/quick" + + "github.com/mmcloughlin/avo/examples/ext/ext" +) + +//go:generate go run asm.go -out ext.s + +func TestFunc(t *testing.T) { + expect := func(e ext.Struct) byte { return e.B } + if err := quick.CheckEqual(StructFieldB, expect, nil); err != nil { + t.Fatal(err) + } +} diff --git a/examples/ext/stub.go b/examples/ext/stub.go new file mode 100644 index 00000000..37630d32 --- /dev/null +++ b/examples/ext/stub.go @@ -0,0 +1,7 @@ +// Package ext demonstrates how to interact with external types. +package ext + +import "github.com/mmcloughlin/avo/examples/ext/ext" + +// StructFieldB returns field B. +func StructFieldB(e ext.Struct) byte diff --git a/gotypes/signature.go b/gotypes/signature.go index 44a4606a..5fed65e2 100644 --- a/gotypes/signature.go +++ b/gotypes/signature.go @@ -3,6 +3,7 @@ package gotypes import ( "bytes" "errors" + "fmt" "go/token" "go/types" "strconv" @@ -31,6 +32,20 @@ func NewSignatureVoid() *Signature { return NewSignature(nil, types.NewSignature(nil, nil, nil, false)) } +// LookupSignature returns the signature of the named function in the provided package. +func LookupSignature(pkg *types.Package, name string) (*Signature, error) { + scope := pkg.Scope() + obj := scope.Lookup(name) + if obj == nil { + return nil, fmt.Errorf("could not find function \"%s\"", name) + } + s, ok := obj.Type().(*types.Signature) + if !ok { + return nil, fmt.Errorf("object \"%s\" does not have signature type", name) + } + return NewSignature(pkg, s), nil +} + // ParseSignature builds a Signature by parsing a Go function type expression. // The function type must reference builtin types only; see // ParseSignatureInPackage if custom types are required. @@ -67,7 +82,12 @@ func (s *Signature) Bytes() int { return s.Params().Bytes() + s.Results().Bytes( // String writes Signature as a string. This does not include the "func" keyword. func (s *Signature) String() string { var buf bytes.Buffer - types.WriteSignature(&buf, s.sig, types.RelativeTo(s.pkg)) + types.WriteSignature(&buf, s.sig, func(pkg *types.Package) string { + if pkg == s.pkg { + return "" + } + return pkg.Name() + }) return buf.String() } diff --git a/gotypes/signature_test.go b/gotypes/signature_test.go index aa400510..5c8e6559 100644 --- a/gotypes/signature_test.go +++ b/gotypes/signature_test.go @@ -5,8 +5,64 @@ import ( "go/types" "strings" "testing" + + "golang.org/x/tools/go/packages" ) +func TestLookupSignature(t *testing.T) { + pkg := LoadPackageTypes(t, "math") + s, err := LookupSignature(pkg, "Frexp") + if err != nil { + t.Fatal(err) + } + + expect, err := ParseSignature("func(f float64) (frac float64, exp int)") + if err != nil { + t.Fatal(err) + } + + if s.String() != expect.String() { + t.Errorf("\n got: %s\nexpect: %s\n", s, expect) + } +} + +func TestLookupSignatureErrors(t *testing.T) { + cases := []struct { + PackagePath string + FunctionName string + ExpectedError string + }{ + {"runtime", "HmmIdk", "could not find function \"HmmIdk\""}, + {"crypto", "Decrypter", "object \"Decrypter\" does not have signature type"}, + {"encoding/base64", "StdEncoding", "object \"StdEncoding\" does not have signature type"}, + } + for _, c := range cases { + pkg := LoadPackageTypes(t, c.PackagePath) + _, err := LookupSignature(pkg, c.FunctionName) + if err == nil { + t.Fatalf("expected error looking up '%s' in package '%s'", c.FunctionName, c.PackagePath) + } + if err.Error() != c.ExpectedError { + t.Fatalf("wrong error message\n got: %q\nexpect: %q", err.Error(), c.ExpectedError) + } + } +} + +func LoadPackageTypes(t *testing.T, path string) *types.Package { + t.Helper() + cfg := &packages.Config{ + Mode: packages.LoadTypes, + } + pkgs, err := packages.Load(cfg, path) + if err != nil { + t.Fatal(err) + } + if len(pkgs) != 1 { + t.Fatal("expected to load exactly one package") + } + return pkgs[0].Types +} + func TestParseSignature(t *testing.T) { cases := []struct { Expr string