Skip to content

Commit 8f2aa9f

Browse files
vsivsirandall77
authored andcommitted
cpu: conditionally re-enable AVX512 support on darwin/amd64
Darwin opmask clobbering bug was fixed in kernel version 21.3.0 as released in MacOS 12.2.0. This commit resolves issue by checking for Darwin AVX512 support via a sysctl call with the addition of a kernel minimum version check. The kernel version check is completed without adding new dependencies to x/sys/cpu. A sysctl call is accomplished by copying a minimal amount of code from x/sys/unix, to retrieve only the needed KERN_OSRELEASE value. This code is structured in the same manner as an existing analogous AIX/PPC64 syscall. The resulting dotted version string value is then parsed for numeric comparison with a dependency free function. All code in this contribution is structured to ease removal of the special darwin/amd64 codepaths when that OS/arch combination is eventually no longer supported by golang. Resolves issue: golang/go#49233, reinstates fix for issue: golang/go#43089 Change-Id: I4755fc8b3865eb6562b0959ecc910e2c46ac6cb4 Reviewed-on: https://go-review.googlesource.com/c/sys/+/620256 Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Keith Randall <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Carlos Amedee <[email protected]> Reviewed-by: [email protected] <[email protected]>
1 parent 054f1fc commit 8f2aa9f

8 files changed

+192
-13
lines changed

cpu/asm_darwin_x86_gc.s

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin && amd64 && gc
6+
7+
#include "textflag.h"
8+
9+
TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0
10+
JMP libc_sysctl(SB)
11+
GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8
12+
DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB)
13+
14+
TEXT libc_sysctlbyname_trampoline<>(SB),NOSPLIT,$0-0
15+
JMP libc_sysctlbyname(SB)
16+
GLOBL ·libc_sysctlbyname_trampoline_addr(SB), RODATA, $8
17+
DATA ·libc_sysctlbyname_trampoline_addr(SB)/8, $libc_sysctlbyname_trampoline<>(SB)

cpu/cpu_darwin_x86.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin && amd64 && gc
6+
7+
package cpu
8+
9+
// darwinSupportsAVX512 checks Darwin kernel for AVX512 support via sysctl
10+
// call (see issue 43089). It also restricts AVX512 support for Darwin to
11+
// kernel version 21.3.0 (MacOS 12.2.0) or later (see issue 49233).
12+
//
13+
// Background:
14+
// Darwin implements a special mechanism to economize on thread state when
15+
// AVX512 specific registers are not in use. This scheme minimizes state when
16+
// preempting threads that haven't yet used any AVX512 instructions, but adds
17+
// special requirements to check for AVX512 hardware support at runtime (e.g.
18+
// via sysctl call or commpage inspection). See issue 43089 and link below for
19+
// full background:
20+
// https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.1.10/osfmk/i386/fpu.c#L214-L240
21+
//
22+
// Additionally, all versions of the Darwin kernel from 19.6.0 through 21.2.0
23+
// (corresponding to MacOS 10.15.6 - 12.1) have a bug that can cause corruption
24+
// of the AVX512 mask registers (K0-K7) upon signal return. For this reason
25+
// AVX512 is considered unsafe to use on Darwin for kernel versions prior to
26+
// 21.3.0, where a fix has been confirmed. See issue 49233 for full background.
27+
func darwinSupportsAVX512() bool {
28+
return darwinSysctlEnabled([]byte("hw.optional.avx512f\x00")) && darwinKernelVersionCheck(21, 3, 0)
29+
}
30+
31+
// Ensure Darwin kernel version is at least major.minor.patch, avoiding dependencies
32+
func darwinKernelVersionCheck(major, minor, patch int) bool {
33+
var release [256]byte
34+
err := darwinOSRelease(&release)
35+
if err != nil {
36+
return false
37+
}
38+
39+
var mmp [3]int
40+
c := 0
41+
Loop:
42+
for _, b := range release[:] {
43+
switch {
44+
case b >= '0' && b <= '9':
45+
mmp[c] = 10*mmp[c] + int(b-'0')
46+
case b == '.':
47+
c++
48+
if c > 2 {
49+
return false
50+
}
51+
case b == 0:
52+
break Loop
53+
default:
54+
return false
55+
}
56+
}
57+
if c != 2 {
58+
return false
59+
}
60+
return mmp[0] > major || mmp[0] == major && (mmp[1] > minor || mmp[1] == minor && mmp[2] >= patch)
61+
}

cpu/cpu_gc_x86.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
package cpu
88

9-
// cpuid is implemented in cpu_x86.s for gc compiler
9+
// cpuid is implemented in cpu_gc_x86.s for gc compiler
1010
// and in cpu_gccgo.c for gccgo.
1111
func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
1212

13-
// xgetbv with ecx = 0 is implemented in cpu_x86.s for gc compiler
13+
// xgetbv with ecx = 0 is implemented in cpu_gc_x86.s for gc compiler
1414
// and in cpu_gccgo.c for gccgo.
1515
func xgetbv() (eax, edx uint32)

cpu/cpu_x86.s cpu/cpu_gc_x86.s

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ TEXT ·cpuid(SB), NOSPLIT, $0-24
1818
RET
1919

2020
// func xgetbv() (eax, edx uint32)
21-
TEXT ·xgetbv(SB),NOSPLIT,$0-8
21+
TEXT ·xgetbv(SB), NOSPLIT, $0-8
2222
MOVL $0, CX
2323
XGETBV
2424
MOVL AX, eax+0(FP)

cpu/cpu_gccgo_x86.go

-6
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,3 @@ func xgetbv() (eax, edx uint32) {
2323
gccgoXgetbv(&a, &d)
2424
return a, d
2525
}
26-
27-
// gccgo doesn't build on Darwin, per:
28-
// https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/gcc.rb#L76
29-
func darwinSupportsAVX512() bool {
30-
return false
31-
}

cpu/cpu_other_x86.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build 386 || amd64p32 || (amd64 && (!darwin || !gc))
6+
7+
package cpu
8+
9+
func darwinSupportsAVX512() bool {
10+
panic("only implemented for gc && amd64 && darwin")
11+
}

cpu/cpu_x86.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,8 @@ func archInit() {
9292
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
9393

9494
if runtime.GOOS == "darwin" {
95-
// Darwin doesn't save/restore AVX-512 mask registers correctly across signal handlers.
96-
// Since users can't rely on mask register contents, let's not advertise AVX-512 support.
97-
// See issue 49233.
98-
osSupportsAVX512 = false
95+
// Darwin requires special AVX512 checks, see cpu_darwin_x86.go
96+
osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512()
9997
} else {
10098
// Check if OPMASK and ZMM registers have OS support.
10199
osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax)

cpu/syscall_darwin_x86_gc.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Minimal copy of x/sys/unix so the cpu package can make a
6+
// system call on Darwin without depending on x/sys/unix.
7+
8+
//go:build darwin && amd64 && gc
9+
10+
package cpu
11+
12+
import (
13+
"syscall"
14+
"unsafe"
15+
)
16+
17+
type _C_int int32
18+
19+
// adapted from unix.Uname() at x/sys/unix/syscall_darwin.go L419
20+
func darwinOSRelease(release *[256]byte) error {
21+
// from x/sys/unix/zerrors_openbsd_amd64.go
22+
const (
23+
CTL_KERN = 0x1
24+
KERN_OSRELEASE = 0x2
25+
)
26+
27+
mib := []_C_int{CTL_KERN, KERN_OSRELEASE}
28+
n := unsafe.Sizeof(*release)
29+
30+
return sysctl(mib, &release[0], &n, nil, 0)
31+
}
32+
33+
type Errno = syscall.Errno
34+
35+
var _zero uintptr // Single-word zero for use when we need a valid pointer to 0 bytes.
36+
37+
// from x/sys/unix/zsyscall_darwin_amd64.go L791-807
38+
func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
39+
var _p0 unsafe.Pointer
40+
if len(mib) > 0 {
41+
_p0 = unsafe.Pointer(&mib[0])
42+
} else {
43+
_p0 = unsafe.Pointer(&_zero)
44+
}
45+
if _, _, err := syscall_syscall6(
46+
libc_sysctl_trampoline_addr,
47+
uintptr(_p0),
48+
uintptr(len(mib)),
49+
uintptr(unsafe.Pointer(old)),
50+
uintptr(unsafe.Pointer(oldlen)),
51+
uintptr(unsafe.Pointer(new)),
52+
uintptr(newlen),
53+
); err != 0 {
54+
return err
55+
}
56+
57+
return nil
58+
}
59+
60+
var libc_sysctl_trampoline_addr uintptr
61+
62+
// adapted from internal/cpu/cpu_arm64_darwin.go
63+
func darwinSysctlEnabled(name []byte) bool {
64+
out := int32(0)
65+
nout := unsafe.Sizeof(out)
66+
if ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); ret != nil {
67+
return false
68+
}
69+
return out > 0
70+
}
71+
72+
//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib"
73+
74+
var libc_sysctlbyname_trampoline_addr uintptr
75+
76+
// adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix
77+
func sysctlbyname(name *byte, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
78+
if _, _, err := syscall_syscall6(
79+
libc_sysctlbyname_trampoline_addr,
80+
uintptr(unsafe.Pointer(name)),
81+
uintptr(unsafe.Pointer(old)),
82+
uintptr(unsafe.Pointer(oldlen)),
83+
uintptr(unsafe.Pointer(new)),
84+
uintptr(newlen),
85+
0,
86+
); err != 0 {
87+
return err
88+
}
89+
90+
return nil
91+
}
92+
93+
//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib"
94+
95+
// Implemented in the runtime package (runtime/sys_darwin.go)
96+
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
97+
98+
//go:linkname syscall_syscall6 syscall.syscall6

0 commit comments

Comments
 (0)