From c8004ba627cacd7bab12cf9243c2f14d89c71cbe Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Mon, 16 Sep 2019 11:01:48 -0700 Subject: [PATCH] ir,build: pragma support (#97) Adds support for arbitrary compiler directives. Fixes #15 --- README.md | 1 + build/context.go | 5 +++++ build/global.go | 3 +++ examples/README.md | 2 +- examples/pragma/README.md | 33 +++++++++++++++++++++++++++++++++ examples/pragma/asm.go | 24 ++++++++++++++++++++++++ examples/pragma/doc.go | 2 ++ examples/pragma/pragma.s | 14 ++++++++++++++ examples/pragma/pragma_test.go | 16 ++++++++++++++++ examples/pragma/stub.go | 7 +++++++ ir/ir.go | 15 +++++++++++++++ printer/stubs.go | 11 +++++++++++ printer/stubs_test.go | 28 ++++++++++++++++++++++++++++ printer/util_test.go | 1 + 14 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 examples/pragma/README.md create mode 100644 examples/pragma/asm.go create mode 100644 examples/pragma/doc.go create mode 100644 examples/pragma/pragma.s create mode 100644 examples/pragma/pragma_test.go create mode 100644 examples/pragma/stub.go create mode 100644 printer/stubs_test.go diff --git a/README.md b/README.md index dff4db9..ae97d28 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ For demonstrations of `avo` features: * **[complex](examples/complex):** Working with `complex{64,128}` types. * **[data](examples/data):** Defining `DATA` sections. * **[ext](examples/ext):** Interacting with types from external packages. +* **[pragma](examples/pragma):** Apply compiler directives to generated functions. ### Real Examples diff --git a/build/context.go b/build/context.go index 52921d8..beb4f60 100644 --- a/build/context.go +++ b/build/context.go @@ -92,6 +92,11 @@ func (c *Context) Doc(lines ...string) { c.activefunc().Doc = lines } +// Pragma adds a compiler directive to the currently active function. +func (c *Context) Pragma(directive string, args ...string) { + c.activefunc().AddPragma(directive, args...) +} + // Attributes sets function attributes for the currently active function. func (c *Context) Attributes(a attr.Attribute) { c.activefunc().Attributes = a diff --git a/build/global.go b/build/global.go index 148b90c..044d168 100644 --- a/build/global.go +++ b/build/global.go @@ -118,6 +118,9 @@ func Dereference(ptr gotypes.Component) gotypes.Component { return ctx.Dereferen // Doc sets documentation comment lines for the currently active function. func Doc(lines ...string) { ctx.Doc(lines...) } +// Pragma adds a compiler directive to the currently active function. +func Pragma(directive string, args ...string) { ctx.Pragma(directive, args...) } + // Attributes sets function attributes for the currently active function. func Attributes(a attr.Attribute) { ctx.Attributes(a) } diff --git a/examples/README.md b/examples/README.md index d0e2c17..167abd2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,7 +12,7 @@ Features: * **[complex](complex):** Working with `complex{64,128}` types. * **[data](data):** Defining `DATA` sections. * **[ext](ext):** Interacting with types from external packages. - +* **[pragma](pragma):** Apply compiler directives to generated functions. "Real" examples: diff --git a/examples/pragma/README.md b/examples/pragma/README.md new file mode 100644 index 0000000..cca9767 --- /dev/null +++ b/examples/pragma/README.md @@ -0,0 +1,33 @@ +# pragma + +Apply [compiler directives](https://golang.org/pkg/cmd/compile/#hdr-Compiler_Directives) to `avo` functions. + +The [code generator](asm.go) uses the `Pragma` function to apply the `//go:noescape` directive to the `Add` function: + +[embedmd]:# (asm.go go /func main/ /^}/) +```go +func main() { + TEXT("Add", NOSPLIT, "func(z, x, y *uint64)") + Pragma("noescape") + Doc("Add adds the values at x and y and writes the result to z.") + zptr := Mem{Base: Load(Param("z"), GP64())} + xptr := Mem{Base: Load(Param("x"), GP64())} + yptr := Mem{Base: Load(Param("y"), GP64())} + x, y := GP64(), GP64() + MOVQ(xptr, x) + MOVQ(yptr, y) + ADDQ(x, y) + MOVQ(y, zptr) + RET() + Generate() +} +``` + +Note the directive is applied in the generated stub file: + +[embedmd]:# (stub.go go /\/\/ Add/ /func/) +```go +// Add adds the values at x and y and writes the result to z. +//go:noescape +func +``` diff --git a/examples/pragma/asm.go b/examples/pragma/asm.go new file mode 100644 index 0000000..edf1c9b --- /dev/null +++ b/examples/pragma/asm.go @@ -0,0 +1,24 @@ +// +build ignore + +package main + +import ( + . "github.com/mmcloughlin/avo/build" + . "github.com/mmcloughlin/avo/operand" +) + +func main() { + TEXT("Add", NOSPLIT, "func(z, x, y *uint64)") + Pragma("noescape") + Doc("Add adds the values at x and y and writes the result to z.") + zptr := Mem{Base: Load(Param("z"), GP64())} + xptr := Mem{Base: Load(Param("x"), GP64())} + yptr := Mem{Base: Load(Param("y"), GP64())} + x, y := GP64(), GP64() + MOVQ(xptr, x) + MOVQ(yptr, y) + ADDQ(x, y) + MOVQ(y, zptr) + RET() + Generate() +} diff --git a/examples/pragma/doc.go b/examples/pragma/doc.go new file mode 100644 index 0000000..e2092fe --- /dev/null +++ b/examples/pragma/doc.go @@ -0,0 +1,2 @@ +// Package pragma demonstrates the use of compiler directives. +package pragma diff --git a/examples/pragma/pragma.s b/examples/pragma/pragma.s new file mode 100644 index 0000000..66f1070 --- /dev/null +++ b/examples/pragma/pragma.s @@ -0,0 +1,14 @@ +// Code generated by command: go run asm.go -out pragma.s -stubs stub.go. DO NOT EDIT. + +#include "textflag.h" + +// func Add(z *uint64, x *uint64, y *uint64) +TEXT ·Add(SB), NOSPLIT, $0-24 + MOVQ z+0(FP), AX + MOVQ x+8(FP), CX + MOVQ y+16(FP), DX + MOVQ (CX), CX + MOVQ (DX), DX + ADDQ CX, DX + MOVQ DX, (AX) + RET diff --git a/examples/pragma/pragma_test.go b/examples/pragma/pragma_test.go new file mode 100644 index 0000000..50ee338 --- /dev/null +++ b/examples/pragma/pragma_test.go @@ -0,0 +1,16 @@ +package pragma + +import ( + "testing" + "testing/quick" +) + +//go:generate go run asm.go -out pragma.s -stubs stub.go + +func TestAdd(t *testing.T) { + got := func(x, y uint64) (z uint64) { Add(&z, &x, &y); return } + expect := func(x, y uint64) uint64 { return x + y } + if err := quick.CheckEqual(got, expect, nil); err != nil { + t.Fatal(err) + } +} diff --git a/examples/pragma/stub.go b/examples/pragma/stub.go new file mode 100644 index 0000000..f987576 --- /dev/null +++ b/examples/pragma/stub.go @@ -0,0 +1,7 @@ +// Code generated by command: go run asm.go -out pragma.s -stubs stub.go. DO NOT EDIT. + +package pragma + +// Add adds the values at x and y and writes the result to z. +//go:noescape +func Add(z *uint64, x *uint64, y *uint64) diff --git a/ir/ir.go b/ir/ir.go index 2b9b7c7..3788b3e 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -144,10 +144,17 @@ func (f *File) Functions() []*Function { return fns } +// Pragma represents a function compiler directive. +type Pragma struct { + Directive string + Arguments []string +} + // Function represents an assembly function. type Function struct { Name string Attributes attr.Attribute + Pragmas []Pragma Doc []string Signature *gotypes.Signature LocalSize int @@ -171,6 +178,14 @@ func NewFunction(name string) *Function { } } +// AddPragma adds a pragma to this function. +func (f *Function) AddPragma(directive string, args ...string) { + f.Pragmas = append(f.Pragmas, Pragma{ + Directive: directive, + Arguments: args, + }) +} + // SetSignature sets the function signature. func (f *Function) SetSignature(s *gotypes.Signature) { f.Signature = s diff --git a/printer/stubs.go b/printer/stubs.go index 1aedcf0..171bc62 100644 --- a/printer/stubs.go +++ b/printer/stubs.go @@ -28,7 +28,18 @@ func (s *stubs) Print(f *ir.File) ([]byte, error) { for _, fn := range f.Functions() { s.NL() s.Comment(fn.Doc...) + for _, pragma := range fn.Pragmas { + s.pragma(pragma) + } s.Printf("%s\n", fn.Stub()) } return s.Result() } + +func (s *stubs) pragma(p ir.Pragma) { + s.Printf("//go:%s", p.Directive) + for _, arg := range p.Arguments { + s.Printf(" %s", arg) + } + s.NL() +} diff --git a/printer/stubs_test.go b/printer/stubs_test.go new file mode 100644 index 0000000..9666168 --- /dev/null +++ b/printer/stubs_test.go @@ -0,0 +1,28 @@ +package printer_test + +import ( + "testing" + + "github.com/mmcloughlin/avo/build" + "github.com/mmcloughlin/avo/printer" +) + +func TestStubsPragmas(t *testing.T) { + ctx := build.NewContext() + ctx.Function("f") + ctx.Pragma("noescape") + ctx.Pragma("linkname f remote.f") + ctx.SignatureExpr("func(x *uint64)") + ctx.RET() + + AssertPrintsLines(t, ctx, printer.NewStubs, []string{ + "// Code generated by avo. DO NOT EDIT.", + "", + "package printer", + "", + "//go:noescape", + "//go:linkname f remote.f", + "func f(x *uint64)", + "", + }) +} diff --git a/printer/util_test.go b/printer/util_test.go index 16c6efe..d4835d7 100644 --- a/printer/util_test.go +++ b/printer/util_test.go @@ -15,6 +15,7 @@ func AssertPrintsLines(t *testing.T, ctx *build.Context, pb printer.Builder, exp lines := strings.Split(output, "\n") if len(expect) != len(lines) { + t.Logf("output:\n%s", output) t.Fatalf("have %d lines of output; expected %d", len(lines), len(expect)) }