Repository: mmcloughlin/avo
Pull request #97 (2019-09-15 22:23:57)

ir,build: pragma support
Adds support for arbitrary compiler directives.

Updates #15

codecov-io: # [Codecov](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=h1) Report
> Merging [#97](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=desc) into [master](https://codecov.io/gh/mmcloughlin/avo/commit/0bcbe827310c5a75981497d63dec804cb7ec5448?src=pr&el=desc) will **decrease** coverage by `0.03%`.
> The diff coverage is `0%`.

[![Impacted file tree graph](https://codecov.io/gh/mmcloughlin/avo/pull/97/graphs/tree.svg?width=650&token=DaOLKxzdQS&height=150&src=pr)](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #97      +/-   ##
==========================================
- Coverage   76.13%   76.09%   -0.04%     
==========================================
  Files          52       52              
  Lines       31310    31325      +15     
==========================================
  Hits        23837    23837              
- Misses       7371     7386      +15     
  Partials      102      102
```

| Flag | Coverage Δ | |
|---|---|---|
| #integration | `6.28% <0%> (-0.01%)` | :arrow_down: |
| #unittests | `74.11% <0%> (-0.04%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [printer/stubs.go](https://codecov.io/gh/mmcloughlin/avo/pull/97/diff?src=pr&el=tree#diff-cHJpbnRlci9zdHVicy5nbw==) | `0% <0%> (ø)` | :arrow_up: |
| [ir/ir.go](https://codecov.io/gh/mmcloughlin/avo/pull/97/diff?src=pr&el=tree#diff-aXIvaXIuZ28=) | `86.5% <0%> (-3.58%)` | :arrow_down: |
| [build/context.go](https://codecov.io/gh/mmcloughlin/avo/pull/97/diff?src=pr&el=tree#diff-YnVpbGQvY29udGV4dC5nbw==) | `56% <0%> (-1.15%)` | :arrow_down: |
| [build/global.go](https://codecov.io/gh/mmcloughlin/avo/pull/97/diff?src=pr&el=tree#diff-YnVpbGQvZ2xvYmFsLmdv) | `77.27% <0%> (-1.8%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=footer). Last update [0bcbe82...ca078ee](https://codecov.io/gh/mmcloughlin/avo/pull/97?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
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/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()
+}


Test patch:
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/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))
 	}