From: Cherry Mui Date: Sat, 30 Aug 2025 00:33:19 +0000 (-0400) Subject: [dev.simd] internal/cpu: report AVX1 and 2 as supported on macOS 15 Rosetta 2 X-Git-Tag: go1.26rc1~147^2~89 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=9125351583;p=gostls13.git [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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Junyang Shao --- diff --git a/src/internal/cpu/cpu_arm64_darwin.go b/src/internal/cpu/cpu_arm64_darwin.go index 28b47d60e8..bd89cd4e80 100644 --- a/src/internal/cpu/cpu_arm64_darwin.go +++ b/src/internal/cpu/cpu_arm64_darwin.go @@ -6,8 +6,6 @@ package cpu -import _ "unsafe" // for linkname - func osInit() { // 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]. @@ -29,24 +27,3 @@ func osInit() { ARM64.HasSHA1 = 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 -} diff --git a/src/internal/cpu/cpu_darwin.go b/src/internal/cpu/cpu_darwin.go new file mode 100644 index 0000000000..2d4ac54fc2 --- /dev/null +++ b/src/internal/cpu/cpu_darwin.go @@ -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) +} diff --git a/src/internal/cpu/cpu_x86.go b/src/internal/cpu/cpu_x86.go index f07fc82df1..ef1874ad68 100644 --- a/src/internal/cpu/cpu_x86.go +++ b/src/internal/cpu/cpu_x86.go @@ -114,6 +114,7 @@ func doinit() { maxID, _, _, _ := cpuid(0, 0) if maxID < 1 { + osInit() return } @@ -158,6 +159,7 @@ func doinit() { X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX if maxID < 7 { + osInit() return } @@ -194,6 +196,7 @@ func doinit() { maxExtendedInformation, _, _, _ = cpuid(0x80000000, 0) if maxExtendedInformation < 0x80000001 { + osInit() return } @@ -217,6 +220,8 @@ func doinit() { X86.HasAVXVNNI = isSet(4, eax71) } } + + osInit() } func isSet(hwc uint32, value uint32) bool { diff --git a/src/internal/cpu/cpu_x86_darwin.go b/src/internal/cpu/cpu_x86_darwin.go new file mode 100644 index 0000000000..12380a7802 --- /dev/null +++ b/src/internal/cpu/cpu_x86_darwin.go @@ -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")) +} diff --git a/src/internal/cpu/cpu_x86_other.go b/src/internal/cpu/cpu_x86_other.go new file mode 100644 index 0000000000..824131226c --- /dev/null +++ b/src/internal/cpu/cpu_x86_other.go @@ -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() {} diff --git a/src/runtime/cpuflags_amd64_test.go b/src/runtime/cpuflags_amd64_test.go new file mode 100644 index 0000000000..f238e7fdf2 --- /dev/null +++ b/src/runtime/cpuflags_amd64_test.go @@ -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) + } +} diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 1f55717f0a..fc77b535da 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1940,3 +1940,5 @@ func (t *TraceStackTable) Reset() { func TraceStack(gp *G, tab *TraceStackTable) { traceStack(0, gp, (*traceStackTable)(tab)) } + +var X86HasAVX = &x86HasAVX diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index 0c7144e9d0..ab8aa8037b 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -157,11 +157,22 @@ func sysctlbynameInt32(name []byte) (int32, int32) { return ret, out } -//go:linkname internal_cpu_getsysctlbyname internal/cpu.getsysctlbyname -func internal_cpu_getsysctlbyname(name []byte) (int32, int32) { +func sysctlbynameBytes(name, out []byte) 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) } +//go:linkname internal_cpu_sysctlbynameBytes internal/cpu.sysctlbynameBytes +func internal_cpu_sysctlbynameBytes(name, out []byte) int32 { + return sysctlbynameBytes(name, out) +} + const ( _CTL_HW = 6 _HW_NCPU = 3 diff --git a/src/runtime/testdata/testprog/cpuflags_amd64.go b/src/runtime/testdata/testprog/cpuflags_amd64.go new file mode 100644 index 0000000000..d53eacbe99 --- /dev/null +++ b/src/runtime/testdata/testprog/cpuflags_amd64.go @@ -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() diff --git a/src/runtime/testdata/testprog/cpuflags_amd64.s b/src/runtime/testdata/testprog/cpuflags_amd64.s new file mode 100644 index 0000000000..1610c5729a --- /dev/null +++ b/src/runtime/testdata/testprog/cpuflags_amd64.s @@ -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