]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.simd] internal/cpu: report AVX1 and 2 as supported on macOS 15 Rosetta 2
authorCherry Mui <cherryyz@google.com>
Sat, 30 Aug 2025 00:33:19 +0000 (20:33 -0400)
committerCherry Mui <cherryyz@google.com>
Tue, 2 Sep 2025 21:17:26 +0000 (14:17 -0700)
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>
src/internal/cpu/cpu_arm64_darwin.go
src/internal/cpu/cpu_darwin.go [new file with mode: 0644]
src/internal/cpu/cpu_x86.go
src/internal/cpu/cpu_x86_darwin.go [new file with mode: 0644]
src/internal/cpu/cpu_x86_other.go [new file with mode: 0644]
src/runtime/cpuflags_amd64_test.go [new file with mode: 0644]
src/runtime/export_test.go
src/runtime/os_darwin.go
src/runtime/testdata/testprog/cpuflags_amd64.go [new file with mode: 0644]
src/runtime/testdata/testprog/cpuflags_amd64.s [new file with mode: 0644]

index 28b47d60e8aecfe5d568dc404359051dc3d98be2..bd89cd4e8017797c7a8344de6fc2fa652f3ca7da 100644 (file)
@@ -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 (file)
index 0000000..2d4ac54
--- /dev/null
@@ -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)
+}
index f07fc82df1cfcb4f8253d1afecd247a0c18bf979..ef1874ad68c23e201c244625770e57fe01585203 100644 (file)
@@ -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 (file)
index 0000000..12380a7
--- /dev/null
@@ -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 (file)
index 0000000..8241312
--- /dev/null
@@ -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 (file)
index 0000000..f238e7f
--- /dev/null
@@ -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)
+       }
+}
index 1f55717f0a1a6019f91245b56805fa9fc8d9744f..fc77b535da0176db57eeee8df0017a0e35fc8bae 100644 (file)
@@ -1940,3 +1940,5 @@ func (t *TraceStackTable) Reset() {
 func TraceStack(gp *G, tab *TraceStackTable) {
        traceStack(0, gp, (*traceStackTable)(tab))
 }
+
+var X86HasAVX = &x86HasAVX
index 0c7144e9d0fa82a090364141bec7c959b436411f..ab8aa8037b9d4b3f2e7cb8a5bd71b7b6fd35842e 100644 (file)
@@ -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 (file)
index 0000000..d53eacb
--- /dev/null
@@ -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 (file)
index 0000000..1610c57
--- /dev/null
@@ -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