Skip to content

compress/flate: EstimatedBits output depends on CPU architecture ["fused multiply and add" (FMA)] #81589

Open

Description

@malt3

Go version

go version go1.27.1 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='/Users/malte/go/bin'
GOCACHE='/Users/malte/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/malte/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/hz/zbtrlcls4257v1kdjg0qtnt00000gn/T/go-build540391637=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/malte/github.com/malt3/pgzip-detect/go.mod'
GOMODCACHE='/Users/malte/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPACKAGESDRIVER=''
GOPATH='/Users/malte/go'
GOPRIVATE=''
GOPROXY='REDACTED|direct'
GOROOT='/opt/homebrew/Cellar/go/1.27.1/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/malte/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.27.1/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.27.1'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I'm using compress/gzip to compress data.

I created an in-depth reproducer using an example payload that has a different compressed stream on arm64 vs amd64: https://github.com/malt3/pgzip-cpu-dependent-output-repro

The root cause is with the EstimatedBits and mFastLog2 functions in compress/flate/token.go. For example:

log2 += ((-0.34484843)*uval+2.02466578)*uval - 0.67487759

https://godbolt.org/z/dTzxMb8a6

The Go spec allows this to be replaced with a "fused multiply and add" (FMA), which changes how the result is rounded. FMA is used with GOARCH=aarch64, but not with GOARCH=amd64 GOAMD64=v1.

What did you see happen?

I used the test data from https://github.com/malt3/pgzip-cpu-dependent-output-repro and compressed it with compress/flate (as well as klauspost/pgzip and klauspost/compress). The exact code can be found in the reproducer.

What did you expect to see?

I understand that the behaviour (and expected output) of the compression changed with Go 1.27 (https://go-review.googlesource.com/c/go/+/707355) and users shouldn't depend on the exact bytes of the output.
However, I would expect that the same version of Go has a deterministic implementation of gzip (even across CPU architectures).
This is not true in my tests.

Activity

  1. added a commit that references this issue on Sep 18, 2026
    f8ee3c8
  2. gopherbot commented on Sep 18, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/834885 mentions this issue: compress/flate: avoid FMA in mFastLog2 for portable rounding

  3. changed the title [-]compress/flate: mFastLog2 output depends on CPU architecture ["fused multiply and add" (FMA)][/-] [+]compress/flate: EstimatedBits output depends on CPU architecture ["fused multiply and add" (FMA)][/+] on Sep 18, 2026
  4. added a commit that references this issue on Sep 18, 2026
    5401fe6
  5. jakebailey commented on Sep 18, 2026

    @jakebailey
    Contributor

    I think this is actually pretty critical to fix, because the linker does DWARF compression and therefore binaries may not be reproducible depending on the FMA behavior of the system.

    (Generally to me this means that implicit FMA should be outright banned in cmd/compile and cmd/link, maybe more, but that's a more drastic opinion.)

  6. thepudds commented on Sep 18, 2026

    @thepudds
    Member

    This might also be affected by GOAMD64=v1 versus GOAMD64=v3?

  7. malt3 commented on Sep 18, 2026

    @malt3
    Author

    @thepudds yes. See the godbolt link above. It shows that the issue also exists on GOAMD64=v3: https://godbolt.org/z/dTzxMb8a6

  8. added
    FixPendingIssues that have a fix which has not yet been reviewed or submitted.
    on Sep 18, 2026
  9. added this to the Go1.28 milestone on Sep 18, 2026
  10. cherrymui commented on Sep 18, 2026

    @cherrymui
    Member
  11. added 2 commits that reference this issue on Sep 19, 2026
    75b484a
    657dd6f
  12. malt3 commented on Sep 30, 2026

    @malt3
    Author

    Gentle ping on this. The same fix was already merged in klauspost/compress.
    Given the statement from @jakebailey above, I think we may even want to backport this to Go 1.27:

    I think this is actually pretty critical to fix, because the linker does DWARF compression and therefore binaries may not be reproducible depending on the FMA behavior of the system.

  13. jakebailey commented on Sep 30, 2026

    @jakebailey
    Contributor

    I might be wrong about it; when I looked and tried to reproduce a bug, the FMA code wasn't actually hit because the DWARF compression level used by the linker is not actually high enough. Still, bad bug!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixPendingIssues that have a fix which has not yet been reviewed or submitted.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions