The cpufeatures pass treated any SIMD-typed value in a block as
implying the presence of the CPU feature its type needs ("a fault is
a fault"). That reasoning is wrong for values that emit no machine
code: in particular, a SIMD-typed zero is materialized as a reference
to the fixed all-zeros register X15 and flows freely through code that
must run on machines without AVX. Since rematerializable values are
placed in the entry block, a single inlined SIMD zero could mark an
entire function as AVX-capable. Under GOEXPERIMENT=simd this happened
to the map fast paths: the inlined AES hash (guarded only by a runtime
flag the pass cannot see) put a SIMD zero in the entry block, so every
block of mapaccess claimed AVX, including the unconditionally executed
legacy-SSE group-match code. This is latent until block features gain
consumers that change instruction encodings.
Skip no-code values (SIMD zeros, phis, copies, args, SelectN) when
computing block feature effects. Functions whose signatures mention
SIMD types still claim features for their entry block; that is a
deliberate contract, not an inference from generated code.
The loop-header expectation removed from test/simd.go relied on the
old behavior: that block holds only rematerializable SIMD zeros and
emits no vector instruction.
For #80835
Change-Id: Iec0ab0cf2f1df6c31951b6c36d1f5ae64653531e
Reviewed-on: https://go-review.googlesource.com/c/go/+/825185
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Junyang Shao <shaojunyang@google.com>
190 lines
4.8 KiB
Go
190 lines
4.8 KiB
Go
// errorcheck -0 -d=ssa/cpufeatures/debug=1,ssa/rewrite_tern/debug=1
|
|
|
|
//go:build goexperiment.simd && amd64
|
|
|
|
// Copyright 2025 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"simd/archsimd"
|
|
)
|
|
|
|
func f1(x archsimd.Int8x16) {
|
|
return // ERROR "has features avx"
|
|
}
|
|
|
|
func g1() archsimd.Int8x16 {
|
|
var x archsimd.Int8x16
|
|
return x // ERROR "has features avx$"
|
|
}
|
|
|
|
type T1 archsimd.Int8x16
|
|
|
|
func (x T1) h() {
|
|
return // ERROR "has features avx$"
|
|
}
|
|
|
|
func f2(x archsimd.Int8x64) {
|
|
return // ERROR "has features avx[+]avx2[+]avx512$"
|
|
}
|
|
|
|
func g2() archsimd.Int8x64 {
|
|
var x archsimd.Int8x64
|
|
return x // ERROR "has features avx[+]avx2[+]avx512$"
|
|
}
|
|
|
|
type T2 archsimd.Int8x64
|
|
|
|
func (x T2) h() {
|
|
return // ERROR "has features avx[+]avx2[+]avx512$"
|
|
}
|
|
|
|
var a int
|
|
|
|
func f() {
|
|
if a == 0 {
|
|
if !archsimd.X86.AVX512() {
|
|
return
|
|
}
|
|
println("has avx512") // ERROR "has features avx[+]avx2[+]avx512$"
|
|
} else {
|
|
if !archsimd.X86.AVX2() {
|
|
return
|
|
}
|
|
println("has avx2") // ERROR "has features avx[+]avx2$"
|
|
}
|
|
println("has something")
|
|
} // ERROR "has features avx[+]avx2$"
|
|
|
|
func g() {
|
|
if archsimd.X86.AVX2() { // ERROR "has features avx[+]avx2$"
|
|
for range 5 { // ERROR "has features avx[+]avx2$"
|
|
if a < 0 { // ERROR "has features avx[+]avx2$"
|
|
a++ // ERROR "has features avx[+]avx2$"
|
|
}
|
|
}
|
|
}
|
|
println("ahoy!") // ERROR "has features avx[+]avx2$" // this is an artifact of flaky block numbering and why isn't it fused?
|
|
if a > 0 {
|
|
a--
|
|
}
|
|
}
|
|
|
|
//go:noinline
|
|
func p() bool {
|
|
return true
|
|
}
|
|
|
|
func hasIrreducibleLoop() {
|
|
if archsimd.X86.AVX2() {
|
|
goto a // ERROR "has features avx[+]avx2$"
|
|
} else {
|
|
goto b
|
|
}
|
|
a:
|
|
println("a")
|
|
if p() {
|
|
goto c
|
|
}
|
|
b:
|
|
println("b")
|
|
if p() {
|
|
goto a
|
|
}
|
|
c:
|
|
println("c")
|
|
}
|
|
|
|
func ternRewrite(m, w, x, y, z archsimd.Int32x16) (t0, t1, t2 archsimd.Int32x16) {
|
|
if !archsimd.X86.AVX512() { // ERROR "has features avx[+]avx2[+]avx512$"
|
|
return // ERROR "has features avx[+]avx2[+]avx512$" // all blocks have it because of the vector size
|
|
}
|
|
t0 = w.Xor(y).Xor(z) // ERROR "Rewriting.*ternInt"
|
|
t1 = m.And(w.Xor(y).Xor(z.Not())) // ERROR "Rewriting.*ternInt"
|
|
t2 = x.Xor(y).Xor(z).And(x.Xor(y).Xor(z.Not())) // ERROR "Rewriting.*ternInt"
|
|
return // ERROR "has features avx[+]avx2[+]avx512$"
|
|
}
|
|
|
|
func ternTricky1(x, y, z archsimd.Int32x8) archsimd.Int32x8 {
|
|
// Int32x8 is a 256-bit vector and does not guarantee AVX-512
|
|
// a is a 3-variable logical expression occurring outside AVX-512 feature check
|
|
a := x.Xor(y).Xor(z)
|
|
var w archsimd.Int32x8
|
|
if !archsimd.X86.AVX512() { // ERROR "has features avx$"
|
|
// do nothing
|
|
} else {
|
|
w = y.AndNot(a) // ERROR "has features avx[+]avx2[+]avx512" "Rewriting.*ternInt"
|
|
}
|
|
// a is a common subexpression
|
|
return a.Or(w) // ERROR "has features avx$"
|
|
}
|
|
|
|
func ternTricky2(x, y, z archsimd.Int32x8) archsimd.Int32x8 {
|
|
// Int32x8 is a 256-bit vector and does not guarantee AVX-512
|
|
var a, w archsimd.Int32x8
|
|
if !archsimd.X86.AVX512() { // ERROR "has features avx$"
|
|
// do nothing
|
|
} else {
|
|
a = x.Xor(y).Xor(z)
|
|
w = y.AndNot(a) // ERROR "has features avx[+]avx2[+]avx512" "Rewriting.*ternInt"
|
|
}
|
|
// a is a common subexpression
|
|
return a.Or(w) // ERROR "has features avx$"
|
|
}
|
|
|
|
func ternTricky3(x, y, z archsimd.Int32x8) archsimd.Int32x8 {
|
|
// Int32x8 is a 256-bit vector and does not guarantee AVX-512
|
|
a := x.Xor(y).Xor(z)
|
|
w := y.AndNot(a)
|
|
if !archsimd.X86.AVX512() { // ERROR "has features avx$"
|
|
return a // ERROR "has features avx$"
|
|
}
|
|
// a is a common subexpression
|
|
return a.Or(w) // ERROR "has features avx[+]avx2[+]avx512" // This does not rewrite, do we want it to?
|
|
}
|
|
|
|
func vpternlogdPanic() {
|
|
resultsMask := archsimd.Mask64x8{}
|
|
|
|
// The loop header holds only rematerializable SIMD zeros, which emit
|
|
// no code, so it claims no features.
|
|
for {
|
|
resultsMask = archsimd.Mask64x8FromBits(0).Or( // ERROR "has features avx[+]avx2[+]avx512"
|
|
archsimd.Float64x8{}.Less(
|
|
archsimd.BroadcastFloat64x8(0))).Or(resultsMask) // ERROR "Rewriting.*ternInt" "Skipping rewrite"
|
|
fmt.Print(resultsMask.And(resultsMask.And(archsimd.Mask64x8{})))
|
|
}
|
|
}
|
|
|
|
type notSIMD struct {
|
|
v archsimd.Int8x16
|
|
n int
|
|
}
|
|
|
|
var cond bool
|
|
|
|
// A SIMD-typed zero emits no code (it is just a reference to the fixed
|
|
// all-zeros register), so it must imply no CPU features: this function
|
|
// must stay compilable for machines without AVX and produce no feature
|
|
// diagnostics at all.
|
|
func zeroOnly(p *notSIMD) {
|
|
p.v = archsimd.Int8x16{}
|
|
p.n++
|
|
}
|
|
|
|
// Only the block executing a real SIMD instruction claims features;
|
|
// they do not leak into the unconditionally executed parts of the
|
|
// function through the no-code zero or the merge.
|
|
func zeroMerge(p *notSIMD, s []int8) {
|
|
x := archsimd.Int8x16{}
|
|
if cond {
|
|
x = archsimd.LoadInt8x16(s) // ERROR "has features avx$"
|
|
}
|
|
p.v = x
|
|
p.n++
|
|
}
|