[dev.simd] internal/cpu: report AVX1 and 2 as supported on macOS 15 Rosetta 2

Apparently, on macOS 15 or newer, Rosetta 2 supports AVX1 and 2.
However, neither CPUID nor the Apple-recommended sysctl says it
has AVX. If AVX is used without checking the CPU feature, it may
run fine without SIGILL, but the runtime doesn't know AVX is
available therefore save and restore its states. This may lead to
value corruption.

Check if we are running under Rosetta 2 on macOS 15 or newer. If so,
report AVX1 and 2 as supported.

Change-Id: Ib981379405b1ae28faa378f051096827d760a4cc
Reviewed-on: https://go-review.googlesource.com/c/go/+/700055
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
Cherry Mui 2025-08-29 20:33:19 -04:00
parent b509516b2e
commit 9125351583
10 changed files with 170 additions and 25 deletions

View file

@ -6,8 +6,6 @@
package cpu package cpu
import _ "unsafe" // for linkname
func osInit() { func osInit() {
// macOS 12 moved these to the hw.optional.arm tree, but as of Go 1.24 we // macOS 12 moved these to the hw.optional.arm tree, but as of Go 1.24 we
// still support macOS 11. See [Determine Encryption Capabilities]. // still support macOS 11. See [Determine Encryption Capabilities].
@ -29,24 +27,3 @@ func osInit() {
ARM64.HasSHA1 = true ARM64.HasSHA1 = true
ARM64.HasSHA2 = true ARM64.HasSHA2 = true
} }
//go:noescape
func getsysctlbyname(name []byte) (int32, int32)
// sysctlEnabled should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/bytedance/gopkg
// - github.com/songzhibin97/gkit
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname sysctlEnabled
func sysctlEnabled(name []byte) bool {
ret, value := getsysctlbyname(name)
if ret < 0 {
return false
}
return value > 0
}

View file

@ -0,0 +1,72 @@
// Copyright 2020 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.
//go:build darwin && !ios
package cpu
import _ "unsafe" // for linkname
// Pushed from runtime.
//
//go:noescape
func sysctlbynameInt32(name []byte) (int32, int32)
// Pushed from runtime.
//
//go:noescape
func sysctlbynameBytes(name, out []byte) int32
// sysctlEnabled should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/bytedance/gopkg
// - github.com/songzhibin97/gkit
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname sysctlEnabled
func sysctlEnabled(name []byte) bool {
ret, value := sysctlbynameInt32(name)
if ret < 0 {
return false
}
return value > 0
}
// darwinKernelVersionCheck reports if Darwin kernel version is at
// least major.minor.patch.
//
// Code borrowed from x/sys/cpu.
func darwinKernelVersionCheck(major, minor, patch int) bool {
var release [256]byte
ret := sysctlbynameBytes([]byte("kern.osrelease\x00"), release[:])
if ret < 0 {
return false
}
var mmp [3]int
c := 0
Loop:
for _, b := range release[:] {
switch {
case b >= '0' && b <= '9':
mmp[c] = 10*mmp[c] + int(b-'0')
case b == '.':
c++
if c > 2 {
return false
}
case b == 0:
break Loop
default:
return false
}
}
if c != 2 {
return false
}
return mmp[0] > major || mmp[0] == major && (mmp[1] > minor || mmp[1] == minor && mmp[2] >= patch)
}

View file

@ -114,6 +114,7 @@ func doinit() {
maxID, _, _, _ := cpuid(0, 0) maxID, _, _, _ := cpuid(0, 0)
if maxID < 1 { if maxID < 1 {
osInit()
return return
} }
@ -158,6 +159,7 @@ func doinit() {
X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX
if maxID < 7 { if maxID < 7 {
osInit()
return return
} }
@ -194,6 +196,7 @@ func doinit() {
maxExtendedInformation, _, _, _ = cpuid(0x80000000, 0) maxExtendedInformation, _, _, _ = cpuid(0x80000000, 0)
if maxExtendedInformation < 0x80000001 { if maxExtendedInformation < 0x80000001 {
osInit()
return return
} }
@ -217,6 +220,8 @@ func doinit() {
X86.HasAVXVNNI = isSet(4, eax71) X86.HasAVXVNNI = isSet(4, eax71)
} }
} }
osInit()
} }
func isSet(hwc uint32, value uint32) bool { func isSet(hwc uint32, value uint32) bool {

View file

@ -0,0 +1,23 @@
// 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.
//go:build (386 || amd64) && darwin && !ios
package cpu
func osInit() {
if isRosetta() && darwinKernelVersionCheck(24, 0, 0) {
// Apparently, on macOS 15 (Darwin kernel version 24) or newer,
// Rosetta 2 supports AVX1 and 2. However, neither CPUID nor
// sysctl says it has AVX. Detect this situation here and report
// AVX1 and 2 as supported.
// TODO: check if any other feature is actually supported.
X86.HasAVX = true
X86.HasAVX2 = true
}
}
func isRosetta() bool {
return sysctlEnabled([]byte("sysctl.proc_translated\x00"))
}

View file

@ -0,0 +1,9 @@
// 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.
//go:build (386 || amd64) && (!darwin || ios)
package cpu
func osInit() {}

View file

@ -0,0 +1,19 @@
// 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 runtime_test
import (
"runtime"
"testing"
)
func TestHasAVX(t *testing.T) {
t.Parallel()
output := runTestProg(t, "testprog", "CheckAVX")
ok := output == "OK\n"
if *runtime.X86HasAVX != ok {
t.Fatalf("x86HasAVX: %v, CheckAVX got:\n%s", *runtime.X86HasAVX, output)
}
}

View file

@ -1940,3 +1940,5 @@ func (t *TraceStackTable) Reset() {
func TraceStack(gp *G, tab *TraceStackTable) { func TraceStack(gp *G, tab *TraceStackTable) {
traceStack(0, gp, (*traceStackTable)(tab)) traceStack(0, gp, (*traceStackTable)(tab))
} }
var X86HasAVX = &x86HasAVX

View file

@ -157,11 +157,22 @@ func sysctlbynameInt32(name []byte) (int32, int32) {
return ret, out return ret, out
} }
//go:linkname internal_cpu_getsysctlbyname internal/cpu.getsysctlbyname func sysctlbynameBytes(name, out []byte) int32 {
func internal_cpu_getsysctlbyname(name []byte) (int32, int32) { nout := uintptr(len(out))
ret := sysctlbyname(&name[0], &out[0], &nout, nil, 0)
return ret
}
//go:linkname internal_cpu_sysctlbynameInt32 internal/cpu.sysctlbynameInt32
func internal_cpu_sysctlbynameInt32(name []byte) (int32, int32) {
return sysctlbynameInt32(name) return sysctlbynameInt32(name)
} }
//go:linkname internal_cpu_sysctlbynameBytes internal/cpu.sysctlbynameBytes
func internal_cpu_sysctlbynameBytes(name, out []byte) int32 {
return sysctlbynameBytes(name, out)
}
const ( const (
_CTL_HW = 6 _CTL_HW = 6
_HW_NCPU = 3 _HW_NCPU = 3

View file

@ -0,0 +1,18 @@
// 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 main
import "fmt"
func init() {
register("CheckAVX", CheckAVX)
}
func CheckAVX() {
checkAVX()
fmt.Println("OK")
}
func checkAVX()

View file

@ -0,0 +1,9 @@
// 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.
#include "textflag.h"
TEXT ·checkAVX(SB), NOSPLIT|NOFRAME, $0-0
VXORPS X1, X2, X3
RET