| --- | |
| title: Target-Specific Code | |
| --- | |
| Sometimes, for performance or compatibility reasons, custom code needs to be written for specific GOARCH and GOOS targets. This page presents some best practices to adopt in that case. It is a required policy for crypto packages as of April 2019. | |
| 1. **Minimize code in tagged files.** As much code as possible should build for every target. In particular, <u>the generic Go implementation must build also for targets that have an optimized implementation</u>. This is critical for testing the optimized code against the generic Go, and makes it quicker to notice some build failures. The linker will drop unused code from final binaries. | |
| 2. **Name files after their tags**, like `poly1305_amd64.go`. Remember that if a file ends in `_$GOARCH.go`, that counts as a build tag. `_noasm.go` is also a good suffix. | |
| 3. **No exported functions in tagged files.** Exported functions define the public API and its documentation, which must be the same across all targets. Having exported functions repeated in each target-specific file makes it likely for them to get out of sync. The mid-stack inliner will probably take care of some of the performance cost. | |
| 4. **Test all available implementations.** Running `go test` on a target that has an optimized implementation should test both the generic and the optimized code. You can use sub-tests for this. Ideally, benchmarks too. | |
| 5. **Write comparative test.** There should be a test which runs the two implementations for random or edge inputs, and compares the results. As [#19109](https://github.com/golang/go/issues/19109) progresses, these should be fuzz tests. | |
| Tip: you can test that your code and tests compile for a target easily by running e.g. `GOARCH=arm64 go test -c`. | |
| ## Example | |
| poly1305.go | |
| ```go | |
| package poly1305 | |
| // Sum generates an authenticator for m using a one-time key and puts the | |
| // 16-byte result into out. Authenticating two different messages with the same | |
| // key allows an attacker to forge messages at will. | |
| func Sum(out *[16]byte, m []byte, key *[32]byte) { | |
| sum(out, m, key) | |
| } | |
| func sumGeneric(out *[16]byte, m []byte, key *[32]byte) { | |
| // ... | |
| } | |
| ``` | |
| poly1305_amd64.go | |
| ```go | |
| //go:build !purego | |
| package poly1305 | |
| //go:noescape | |
| func sum(out *[16]byte, m []byte, key *[32]byte) | |
| ``` | |
| poly1305_amd64.s | |
| ``` | |
| //go:build !purego | |
| // func sum(out *[16]byte, m []byte, key *[32]byte) | |
| TEXT ·sum(SB), $0-128 | |
| // ... | |
| ``` | |
| poly1305_noasm.go | |
| ```go | |
| //go:build !amd64 || purego | |
| package poly1305 | |
| func sum(out *[16]byte, m []byte, key *[32]byte) { | |
| sumGeneric(out, m, key) | |
| } | |
| ``` | |
| poly1305_test.go | |
| ```go | |
| package poly1305 | |
| import "testing" | |
| func testSum(t *testing.T, sum func(tag *[16]byte, msg []byte, key *[32]byte)) { | |
| // ... | |
| } | |
| func TestSum(t *testing.T) { | |
| t.Run("Generic", func(t *testing.T) { testSum(t, sumGeneric) }) | |
| t.Run("Native", func(t *testing.T) { testSum(t, sum) }) | |
| } | |
| // TestSumCompare checks the output of sum against sumGeneric. | |
| func TestSumCompare(t *testing.T) { | |
| // ... | |
| } | |
| ``` | |
| For more complete examples see the [x/crypto/poly1305](https://github.com/golang/crypto/tree/master/poly1305) and [x/crypto/salsa20/salsa](https://github.com/golang/crypto/tree/master/salsa20/salsa) packages. |