Repository: go
Issue: crypto/sha256: amd64 block assembly smashes frame pointer register
Labels: Performance,OS-Linux,NeedsInvestigation

<!--
Please answer these questions before submitting your issue. Thanks!
-->

### What version of Go are you using (`go version`)?

<pre>
$ go version
go version devel go1.22-f09750e8ef Wed Oct 11 13:53:10 2023 -0400 linux/amd64
</pre>

### Does this issue reproduce with the latest release?

Yes

### What operating system and processor architecture are you using (`go env`)?

linux-amd64

<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/usr/local/google/home/mpratt/.cache/go-build'
GOENV='/usr/local/google/home/mpratt/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/usr/local/google/home/mpratt/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/usr/local/google/home/mpratt/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/google/home/mpratt/src/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/google/home/mpratt/src/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='devel go1.22-f09750e8ef Wed Oct 11 13:53:10 2023 -0400'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/usr/local/google/home/mpratt/src/go/src/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1220716334=/tmp/go-build -gno-record-gcc-switches'
</pre></details>

### What did you do?

<!--
If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on go.dev/play is best.
-->

Profile the `crypto/sha256` benchmarks, using both the built-in pprof profiling and using Linux `perf`:

```
$ go test -c crypto/sha256
$ ./sha256.test -test.run=^$ -test.bench=. -test.cpuprofile=/tmp/cpu.pprof
$ perf record -g ./sha256.test -test.run=^$ -test.bench=.
```

Then open the profiles with `pprof` and compare the flame graphs:

```
$ pprof -http=localhost:8080 /tmp/cpu.pprof
$ pprof -http=localhost:8080 perf.data
```

### What did you expect to see?

Approximately the same profile.

### What did you see instead?

The pprof profile looks normal, as expected:

![image](https://github.com/golang/go/assets/1293187/eb2c691d-5721-4785-ab6a-c33522da013d)

The perf profile has broken call stacks, displaying `sha256.block` by itself, rather than as a callee of `Write`.

![image](https://github.com/golang/go/assets/1293187/e66823b6-4ecc-4ef4-98ec-b6abba9443c1)

-------

pprof profiling uses the runtime's internal knowledge of frame sizes to find the caller, so it works fine. The Linux kernel doesn't know about Go's metadata and instead typically uses frame pointers (`BP`) to find callees.

For some reason, the amd64 implementation of `block` [overwrites `BP`](https://cs.opensource.google/go/go/+/master:src/crypto/sha256/sha256block_amd64.s;l=638-649), which I presume is what is confusing Linux.

I admittedly don't understand everything going on in that function (wow, it is complicated!), but writing to `BP` doesn't seem to be necessary? The function doesn't seem to actually adjust the stack or create new frames? It looks like `BP` might just be getting used as a scratch register?

It looks like this has existed since the original https://go.dev/cl/28460043.

cc @FiloSottile @rolandshoemaker @felixge @4a6f656c