Skip to content

cmd/loopvec-toolexec: require the toolchain's exact Go version - #13

Merged
thesyncim merged 4 commits into
masterfrom
toolexec-exact-version
Nov 1, 2026
Merged

thesyncim merged 4 commits into
masterfrom
toolexec-exact-version

Conversation

@thesyncim

@thesyncim thesyncim commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #4.

I tried the wrapper on goav1, which was still on go1.27.0, with a wrapper built by 1.27.1, and the build failed instead of falling back:

internal/av1/transform/fdct.go:80:48: cannot use uint64(31) (constant 31 of type uint64) as uint8 value in argument to _v1.ShiftAllRight

go1.27.0's simd stubs declare ShiftAllRight(uint64), but the compiler implements it with a uint8 (1.27.1 fixed that). go/types only sees the stubs, so the rewritten package type-checks in the wrapper and then fails in the compiler. The release check compared go1.27 to go1.27, so nothing stopped it.

This compares the whole version word instead: go1.27.3, go1.28rc1, tip's go1.28-<hash>, or a vendor build such as go1.27.3-acme. Experiments are left out, and the two sides spell them differently: the compiler says go1.27.3 X:simd, while a wrapper built with GOEXPERIMENT=simd reports go1.27.3-X:simd. A version it can't find never matches. toolexec.md already says to build the wrapper with the toolchain that runs the build; now a mismatch leaves packages unchanged with the existing debug message.

toolexec_toolchain_mismatch.txt gets a same-release, different-patch case, and a unit test covers the version strings (releases, prereleases, tip, vendor builds, with and without experiments). The scripts build the wrapper without GOEXPERIMENT, so I also checked by hand that a wrapper built with GOEXPERIMENT=simd rewrites under go1.27.3 and skips under go1.27.1.

ubuntu-latest is now ubuntu-26.04, which has no qemu-user-static
package, so every run fails at the install step and skips the arm64
tsvc run, the golden check, fmt and lint. qemu-user ships the static
emulators there; link qemu-aarch64-static to it when the name is
missing, as the Makefile runs that name.
Go tip writes export data in iexport version 4, which encodes float
constants exactly. x/tools v0.51.0, and the v0.52.0 release, read only
up to version 3, so on tip go/packages fails for every package:

  cannot import "math" (unstable iexport format version 4, just rebuild
  compiler and std library), export data is newer version - update tool

and every script that runs loopvec fails in the tip job. x/tools master
reads version 4 (golang/tools@966056e2637c). Update to it until a release
includes it. The pinned toolchain is unaffected.
go1.27.3 is the latest 1.27 release. Its simd package is the same as
go1.27.1's, so the capability table only changes in its header, and
the -methods crash (golang/go#80657) is still there, as
flag_methods_ice.txt checks. go.mod keeps go 1.27.1: that is the first
release whose simd package loopvec's output compiles against
(go1.27.0's ShiftAllRight takes a uint8).
@thesyncim
thesyncim force-pushed the toolexec-exact-version branch from a17e43f to 3b7b5a3 Compare October 31, 2026 22:10
The wrapper rewrites a package only when it was built with the same Go
release as the toolchain running the build, and compared releases as
go1.27. But the simd package can change in a patch release without
go/types seeing it: go1.27.0 declares ShiftAllRight(uint64) in its stubs
and compiles it as taking a uint8, go1.27.1 takes a uint64. A wrapper
built with go1.27.1 or later rewrote a go1.27.0 build into code that
type-checked here and then failed to compile, so the build failed:

  cannot use uint64(31) (constant 31 of type uint64) as uint8 value in
  argument to _v1.ShiftAllRight

The wrapper now compares the whole version word (go1.27.3, go1.28rc1, a
development build's go1.28-6ae0b1f8a9, or a vendor's go1.27.3-acme), as
toolexec.md already asks: build loopvec-toolexec with the same Go
toolchain that runs the build. Experiments are left out of it: the
compiler reports go1.27.3 X:simd, while a wrapper built with
GOEXPERIMENT=simd reports go1.27.3-X:simd. A version it cannot find
never matches.
@thesyncim
thesyncim force-pushed the toolexec-exact-version branch from 3b7b5a3 to fb09951 Compare October 31, 2026 23:20
@thesyncim
thesyncim merged commit b7e4206 into master Nov 1, 2026
2 checks passed
mauri870 added a commit that referenced this pull request Nov 3, 2026
)

Follow-up to #2.

#2 started replacing every constant start or limit with its value, so
that the checks parsing `Trip.Start`/`Trip.Limit` as text would also see
named constants. The emitted file is compiled for every build, though,
and a named constant can have a different value in another one. With
`const width = 32` for amd64 and `64` for arm64, `loopvec -split` on
amd64 hard-coded `32` into the vector loop of `for i := 0; i < width;
i++`, while the scalar fallback kept `width`. The arm64 build would then
vectorize only half the elements. `plusOne` and `i > low` loops had
always done the same for `<=` limits and loops that count down.

What changes:

- **Constant names are kept.** `Trip` carries the values of a constant
start and limit. The checks read those values instead of parsing text:
the negative-index check, the short-loop rule and the copy check. A
constant is written as its value only when it's built from literals
alone (`16`, `1 - 1`). A constant that uses a name keeps the name. `i <=
width-1` comes out as `width`.
- **Dead code removed.** `negativeSource` could no longer return true
after #2.
- **The negative-index check can't overflow.** It negates the offset
instead of adding it to the bound. It also runs in the same pass over
the loop body as the stored-offset checks.
- **Bounds pushed past the largest int are rejected.** A positive offset
that pushes a constant start or limit past the largest int is rejected
as that bound. `for i := 0; i < math.MaxInt64; i++ { dst[i] = a[i+1] }`
used to produce an overlap window of `math.MaxInt64 + 1`, which doesn't
compile.
- **Tests:**
- `offset_negative_constant.txt` now checks the rewritten source and
runs the loops under both builds.
- The rejected loops moved to `offset_negative_constant_no_rewrite.txt`,
which adds a loop counting down to 0, a range over a slice, and the two
loops pushed past the largest int.
- `bound_arch_constant.txt` covers the GOARCH case for `<`, `<=`,
counting down and `>`.
- **AGENTS.md** documents the new rejections and the rule for keeping
names.

Tested locally:

- `bound_arch_constant.txt` fails against #2's `internal/loopir`, which
writes `_ = dst[32-1]` where the test expects `_ = dst[width-1]`.
- `make test` and `make test-qemu-arm64` pass.
- `make test-update` and `make fix` leave no diff, and `make lint`
reports 0 issues.
- `loopvec -json` gives identical output before and after over std (1663
loops) and gonum (3252 loops).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant