diff --git a/README.md b/README.md index fd9c1d5..c0b000b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ $ go get -u github.com/mmcloughlin/avo `avo` assembly generators are pure Go programs. Here's a function that adds two `uint64` values: -[embedmd]:# (examples/add/asm.go) ```go //go:build ignore // +build ignore @@ -57,14 +56,12 @@ func main() { `go run` this code to see the assembly output. To integrate this into the rest of your Go package we recommend a [`go:generate`](https://blog.golang.org/generate) line to produce the assembly and the corresponding Go stub file. -[embedmd]:# (examples/add/add_test.go go /.*go:generate.*/) ```go //go:generate go run asm.go -out add.s -stubs stub.go ``` After running `go generate` the [`add.s`](examples/add/add.s) file will contain the Go assembly. -[embedmd]:# (examples/add/add.s) ```s // Code generated by command: go run asm.go -out add.s -stubs stub.go. DO NOT EDIT. @@ -81,7 +78,6 @@ TEXT ·Add(SB), NOSPLIT, $0-24 The same call will produce the stub file [`stub.go`](examples/add/stub.go) which will enable the function to be called from your Go code. -[embedmd]:# (examples/add/stub.go) ```go // Code generated by command: go run asm.go -out add.s -stubs stub.go. DO NOT EDIT. @@ -101,7 +97,6 @@ See [`examples`](examples) for the full suite of examples. Sum a slice of `uint64`s: -[embedmd]:# (examples/sum/asm.go /func main/ /^}/) ```go func main() { TEXT("Sum", NOSPLIT, "func(xs []uint64) uint64") @@ -136,7 +131,6 @@ func main() { The result from this code generator is: -[embedmd]:# (examples/sum/sum.s) ```s // Code generated by command: go run asm.go -out sum.s -stubs stub.go. DO NOT EDIT. diff --git a/internal/cmd/docgen/main.go b/internal/cmd/docgen/main.go new file mode 100644 index 0000000..a0eca2f --- /dev/null +++ b/internal/cmd/docgen/main.go @@ -0,0 +1,148 @@ +// Command docgen generates documentation from templates. +package main + +import ( + "bufio" + "bytes" + "embed" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + "text/template" +) + +func main() { + log.SetPrefix("docgen: ") + log.SetFlags(0) + if err := mainerr(); err != nil { + log.Fatal(err) + } +} + +var ( + typ = flag.String("type", "", "documentation type") + tmpl = flag.String("tmpl", "", "explicit template file (overrides -type)") + output = flag.String("output", "", "path to output file (default stdout)") +) + +func mainerr() (err error) { + flag.Parse() + + // Initialize template. + t := template.New("doc") + + t.Funcs(template.FuncMap{ + "include": include, + "snippet": snippet, + }) + + // Load template. + s, err := load() + if err != nil { + return err + } + + if _, err := t.Parse(s); err != nil { + return err + } + + // Execute. + var buf bytes.Buffer + if err := t.Execute(&buf, nil); err != nil { + return err + } + body := buf.Bytes() + + // Output. + if *output != "" { + err = ioutil.WriteFile(*output, body, 0o640) + } else { + _, err = os.Stdout.Write(body) + } + + if err != nil { + return err + } + + return nil +} + +//go:embed templates +var templates embed.FS + +// load template. +func load() (string, error) { + // Prefer explicit filename, if provided. + if *tmpl != "" { + b, err := ioutil.ReadFile(*tmpl) + if err != nil { + return "", err + } + return string(b), nil + } + + // Otherwise expect a named type. + if *typ == "" { + return "", errors.New("missing documentation type") + } + path := fmt.Sprintf("templates/%s.tmpl", *typ) + b, err := templates.ReadFile(path) + if err != nil { + return "", fmt.Errorf("unknown documentation type %q", *typ) + } + + return string(b), nil +} + +// include template function. +func include(filename string) (string, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return "", err + } + return string(b), nil +} + +// snippet of a file between start and end regular expressions. +func snippet(filename, start, end string) (string, error) { + // Parse regular expressions. + startx, err := regexp.Compile(start) + if err != nil { + return "", err + } + + endx, err := regexp.Compile(end) + if err != nil { + return "", err + } + + // Read the full file. + data, err := include(filename) + if err != nil { + return "", err + } + + // Collect matched lines. + var buf bytes.Buffer + output := false + s := bufio.NewScanner(strings.NewReader(data)) + for s.Scan() { + line := s.Text() + if startx.MatchString(line) { + output = true + } + if output { + fmt.Fprintln(&buf, line) + } + if endx.MatchString(line) { + output = false + } + } + + return buf.String(), nil +} diff --git a/internal/cmd/docgen/templates/readme.tmpl b/internal/cmd/docgen/templates/readme.tmpl new file mode 100644 index 0000000..6b7c1fc --- /dev/null +++ b/internal/cmd/docgen/templates/readme.tmpl @@ -0,0 +1,118 @@ +
+ +Generate x86 Assembly with Go
+ +`avo` makes high-performance Go assembly easier to write, review and maintain. The `avo` package presents a familiar assembly-like interface that simplifies development without sacrificing performance: + +* **Use Go control structures** for assembly generation; `avo` programs _are_ Go programs +* **Register allocation**: write functions with virtual registers and `avo` assigns physical registers for you +* **Automatically load arguments and store return values**: ensure memory offsets are correct for complex structures +* **Generation of stub files** to interface with your Go package + +For more about `avo`: + +* Introductory talk ["Better `x86` Assembly Generation with Go"](https://www.youtube.com/watch?v=6Y5CZ7_tyA4) at [dotGo 2019](https://2019.dotgo.eu/) ([slides](https://speakerdeck.com/mmcloughlin/better-x86-assembly-generation-with-go)) +* [Longer tutorial at Gophercon 2019](https://www.youtube.com/watch?v=WaD8sNqroAw) showing a highly-optimized dot product ([slides](https://speakerdeck.com/mmcloughlin/better-x86-assembly-generation-with-go-gophercon-2019)) +* Watch [Filippo Valsorda](https://filippo.io/) live code the [rewrite of `filippo.io/edwards25519` assembly with `avo`](https://vimeo.com/679848853) +* Discuss `avo` and general Go assembly topics in the [#assembly](https://gophers.slack.com/archives/C6WDZJ70S) channel of [Gophers Slack](https://invite.slack.golangbridge.org/) + +_Note: APIs subject to change while `avo` is still in an experimental phase. You can use it to build [real things](examples) but we suggest you pin a version with your package manager of choice._ + +## Quick Start + +Install `avo` with `go get`: + +``` +$ go get -u github.com/mmcloughlin/avo +``` + +`avo` assembly generators are pure Go programs. Here's a function that adds two `uint64` values: + +```go +{{ include "examples/add/asm.go" -}} +``` + +`go run` this code to see the assembly output. To integrate this into the rest of your Go package we recommend a [`go:generate`](https://blog.golang.org/generate) line to produce the assembly and the corresponding Go stub file. + +```go +{{ snippet "examples/add/add_test.go" "go:generate" "" -}} +``` + +After running `go generate` the [`add.s`](examples/add/add.s) file will contain the Go assembly. + +```s +{{ include "examples/add/add.s" -}} +``` + +The same call will produce the stub file [`stub.go`](examples/add/stub.go) which will enable the function to be called from your Go code. + +```go +{{ include "examples/add/stub.go" -}} +``` + +See the [`examples/add`](examples/add) directory for the complete working example. + +## Examples + +See [`examples`](examples) for the full suite of examples. + +### Slice Sum + +Sum a slice of `uint64`s: + +```go +{{ snippet "examples/sum/asm.go" "func main" "^}" -}} +``` + +The result from this code generator is: + +```s +{{ include "examples/sum/sum.s" -}} +``` + +Full example at [`examples/sum`](examples/sum). + +### Features + +For demonstrations of `avo` features: + +* **[args](examples/args):** Loading function arguments. +* **[returns](examples/returns):** Building return values. +* **[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 + +Implementations of full algorithms: + +* **[sha1](examples/sha1):** [SHA-1](https://en.wikipedia.org/wiki/SHA-1) cryptographic hash. +* **[fnv1a](examples/fnv1a):** [FNV-1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash) hash function. +* **[dot](examples/dot):** Vector dot product. +* **[md5x16](examples/md5x16):** AVX-512 accelerated [MD5](https://en.wikipedia.org/wiki/MD5). +* **[geohash](examples/geohash):** Integer [geohash](https://en.wikipedia.org/wiki/Geohash) encoding. +* **[stadtx](examples/stadtx):** [`StadtX` hash](https://github.com/demerphq/BeagleHash) port from [dgryski/go-stadtx](https://github.com/dgryski/go-stadtx). + +## Contributing + +Contributions to `avo` are welcome: + +* Feedback from using `avo` in a real project is incredibly valuable. Consider [porting an existing project to `avo`](https://github.com/mmcloughlin/avo/issues/40). +* [Submit bug reports](https://github.com/mmcloughlin/avo/issues/new) to the issues page. +* Pull requests accepted. Take a look at outstanding [issues](https://github.com/mmcloughlin/avo/issues) for ideas (especially the ["good first issue"](https://github.com/mmcloughlin/avo/labels/good%20first%20issue) label). +* Join us in the [#assembly](https://gophers.slack.com/archives/C6WDZJ70S) channel of [Gophers Slack](https://invite.slack.golangbridge.org/). + +## Credits + +Inspired by the [PeachPy](https://github.com/Maratyszcza/PeachPy) and [asmjit](https://github.com/asmjit/asmjit) projects. Thanks to [Damian Gryski](https://github.com/dgryski) for advice, and his [extensive library of PeachPy Go projects](https://github.com/mmcloughlin/avo/issues/40). + +## License + +`avo` is available under the [BSD 3-Clause License](LICENSE). diff --git a/script/doc b/script/doc index 6211ae6..76d3a80 100755 --- a/script/doc +++ b/script/doc @@ -1,3 +1,9 @@ #!/bin/bash -ex +# Generate some specific files with docgen. +go install ./internal/cmd/docgen + +docgen -type readme -output README.md + +# Process simple file embeddings with embedmd. find . -name '*.md' | xargs embedmd -w