Skip to content

Commit c155931

Browse files
committed
internal/cpu: add darwin/arm64 CPU feature detection support
Fixes #42747 Change-Id: I6b1679348c77161f075f0678818bb003fc0e8c86 Reviewed-on: https://go-review.googlesource.com/c/go/+/271989 Trust: Martin Möhrmann <[email protected]> Run-TryBot: Martin Möhrmann <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent e10c94a commit c155931

14 files changed

+223
-128
lines changed

src/internal/cpu/cpu_android.go

-7
This file was deleted.

src/internal/cpu/cpu_arm64.go

+2-95
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,6 @@ package cpu
66

77
const CacheLinePadSize = 64
88

9-
// HWCap may be initialized by archauxv and
10-
// should not be changed after it was initialized.
11-
var HWCap uint
12-
13-
// HWCAP bits. These are exposed by Linux.
14-
const (
15-
hwcap_AES = 1 << 3
16-
hwcap_PMULL = 1 << 4
17-
hwcap_SHA1 = 1 << 5
18-
hwcap_SHA2 = 1 << 6
19-
hwcap_CRC32 = 1 << 7
20-
hwcap_ATOMICS = 1 << 8
21-
hwcap_CPUID = 1 << 11
22-
)
23-
249
func doinit() {
2510
options = []option{
2611
{Name: "aes", Feature: &ARM64.HasAES},
@@ -34,86 +19,8 @@ func doinit() {
3419
{Name: "isZeus", Feature: &ARM64.IsZeus},
3520
}
3621

37-
switch GOOS {
38-
case "linux", "android":
39-
// HWCap was populated by the runtime from the auxiliary vector.
40-
// Use HWCap information since reading aarch64 system registers
41-
// is not supported in user space on older linux kernels.
42-
ARM64.HasAES = isSet(HWCap, hwcap_AES)
43-
ARM64.HasPMULL = isSet(HWCap, hwcap_PMULL)
44-
ARM64.HasSHA1 = isSet(HWCap, hwcap_SHA1)
45-
ARM64.HasSHA2 = isSet(HWCap, hwcap_SHA2)
46-
ARM64.HasCRC32 = isSet(HWCap, hwcap_CRC32)
47-
ARM64.HasCPUID = isSet(HWCap, hwcap_CPUID)
48-
49-
// The Samsung S9+ kernel reports support for atomics, but not all cores
50-
// actually support them, resulting in SIGILL. See issue #28431.
51-
// TODO(elias.naur): Only disable the optimization on bad chipsets on android.
52-
ARM64.HasATOMICS = isSet(HWCap, hwcap_ATOMICS) && GOOS != "android"
53-
54-
// Check to see if executing on a NeoverseN1 and in order to do that,
55-
// check the AUXV for the CPUID bit. The getMIDR function executes an
56-
// instruction which would normally be an illegal instruction, but it's
57-
// trapped by the kernel, the value sanitized and then returned. Without
58-
// the CPUID bit the kernel will not trap the instruction and the process
59-
// will be terminated with SIGILL.
60-
if ARM64.HasCPUID {
61-
midr := getMIDR()
62-
part_num := uint16((midr >> 4) & 0xfff)
63-
implementor := byte((midr >> 24) & 0xff)
64-
65-
if implementor == 'A' && part_num == 0xd0c {
66-
ARM64.IsNeoverseN1 = true
67-
}
68-
if implementor == 'A' && part_num == 0xd40 {
69-
ARM64.IsZeus = true
70-
}
71-
}
72-
73-
case "freebsd":
74-
// Retrieve info from system register ID_AA64ISAR0_EL1.
75-
isar0 := getisar0()
76-
77-
// ID_AA64ISAR0_EL1
78-
switch extractBits(isar0, 4, 7) {
79-
case 1:
80-
ARM64.HasAES = true
81-
case 2:
82-
ARM64.HasAES = true
83-
ARM64.HasPMULL = true
84-
}
85-
86-
switch extractBits(isar0, 8, 11) {
87-
case 1:
88-
ARM64.HasSHA1 = true
89-
}
90-
91-
switch extractBits(isar0, 12, 15) {
92-
case 1, 2:
93-
ARM64.HasSHA2 = true
94-
}
95-
96-
switch extractBits(isar0, 16, 19) {
97-
case 1:
98-
ARM64.HasCRC32 = true
99-
}
100-
101-
switch extractBits(isar0, 20, 23) {
102-
case 2:
103-
ARM64.HasATOMICS = true
104-
}
105-
default:
106-
// Other operating systems do not support reading HWCap from auxiliary vector
107-
// or reading privileged aarch64 system registers in user space.
108-
}
109-
}
110-
111-
func extractBits(data uint64, start, end uint) uint {
112-
return (uint)(data>>start) & ((1 << (end - start + 1)) - 1)
113-
}
114-
115-
func isSet(hwc uint, value uint) bool {
116-
return hwc&value != 0
22+
// arm64 uses different ways to detect CPU features at runtime depending on the operating system.
23+
osInit()
11724
}
11825

11926
func getisar0() uint64

src/internal/cpu/cpu_linux.go renamed to src/internal/cpu/cpu_arm64_android.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// +build !android
5+
// +build arm64
66

77
package cpu
88

9-
const GOOS = "linux"
9+
func osInit() {
10+
hwcapInit("android")
11+
}

src/internal/cpu/cpu_arm64_darwin.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2020 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+
// +build arm64
6+
// +build darwin
7+
// +build !ios
8+
9+
package cpu
10+
11+
func osInit() {
12+
ARM64.HasATOMICS = sysctlEnabled([]byte("hw.optional.armv8_1_atomics\x00"))
13+
ARM64.HasCRC32 = sysctlEnabled([]byte("hw.optional.armv8_crc32\x00"))
14+
15+
// There are no hw.optional sysctl values for the below features on Mac OS 11.0
16+
// to detect their supported state dynamically. Assume the CPU features that
17+
// Apple Silicon M1 supports to be available as a minimal set of features
18+
// to all Go programs running on darwin/arm64.
19+
ARM64.HasAES = true
20+
ARM64.HasPMULL = true
21+
ARM64.HasSHA1 = true
22+
ARM64.HasSHA2 = true
23+
}
24+
25+
//go:noescape
26+
func getsysctlbyname(name []byte) (int32, int32)
27+
28+
func sysctlEnabled(name []byte) bool {
29+
ret, value := getsysctlbyname(name)
30+
if ret < 0 {
31+
return false
32+
}
33+
return value > 0
34+
}

src/internal/cpu/cpu_arm64_freebsd.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2020 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+
// +build arm64
6+
7+
package cpu
8+
9+
func osInit() {
10+
// Retrieve info from system register ID_AA64ISAR0_EL1.
11+
isar0 := getisar0()
12+
13+
// ID_AA64ISAR0_EL1
14+
switch extractBits(isar0, 4, 7) {
15+
case 1:
16+
ARM64.HasAES = true
17+
case 2:
18+
ARM64.HasAES = true
19+
ARM64.HasPMULL = true
20+
}
21+
22+
switch extractBits(isar0, 8, 11) {
23+
case 1:
24+
ARM64.HasSHA1 = true
25+
}
26+
27+
switch extractBits(isar0, 12, 15) {
28+
case 1, 2:
29+
ARM64.HasSHA2 = true
30+
}
31+
32+
switch extractBits(isar0, 16, 19) {
33+
case 1:
34+
ARM64.HasCRC32 = true
35+
}
36+
37+
switch extractBits(isar0, 20, 23) {
38+
case 2:
39+
ARM64.HasATOMICS = true
40+
}
41+
}
42+
43+
func extractBits(data uint64, start, end uint) uint {
44+
return (uint)(data>>start) & ((1 << (end - start + 1)) - 1)
45+
}

src/internal/cpu/cpu_arm64_hwcap.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2020 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+
// +build arm64
6+
// +build linux
7+
8+
package cpu
9+
10+
// HWCap may be initialized by archauxv and
11+
// should not be changed after it was initialized.
12+
var HWCap uint
13+
14+
// HWCAP bits. These are exposed by Linux.
15+
const (
16+
hwcap_AES = 1 << 3
17+
hwcap_PMULL = 1 << 4
18+
hwcap_SHA1 = 1 << 5
19+
hwcap_SHA2 = 1 << 6
20+
hwcap_CRC32 = 1 << 7
21+
hwcap_ATOMICS = 1 << 8
22+
hwcap_CPUID = 1 << 11
23+
)
24+
25+
func hwcapInit(os string) {
26+
// HWCap was populated by the runtime from the auxiliary vector.
27+
// Use HWCap information since reading aarch64 system registers
28+
// is not supported in user space on older linux kernels.
29+
ARM64.HasAES = isSet(HWCap, hwcap_AES)
30+
ARM64.HasPMULL = isSet(HWCap, hwcap_PMULL)
31+
ARM64.HasSHA1 = isSet(HWCap, hwcap_SHA1)
32+
ARM64.HasSHA2 = isSet(HWCap, hwcap_SHA2)
33+
ARM64.HasCRC32 = isSet(HWCap, hwcap_CRC32)
34+
ARM64.HasCPUID = isSet(HWCap, hwcap_CPUID)
35+
36+
// The Samsung S9+ kernel reports support for atomics, but not all cores
37+
// actually support them, resulting in SIGILL. See issue #28431.
38+
// TODO(elias.naur): Only disable the optimization on bad chipsets on android.
39+
ARM64.HasATOMICS = isSet(HWCap, hwcap_ATOMICS) && os != "android"
40+
41+
// Check to see if executing on a NeoverseN1 and in order to do that,
42+
// check the AUXV for the CPUID bit. The getMIDR function executes an
43+
// instruction which would normally be an illegal instruction, but it's
44+
// trapped by the kernel, the value sanitized and then returned. Without
45+
// the CPUID bit the kernel will not trap the instruction and the process
46+
// will be terminated with SIGILL.
47+
if ARM64.HasCPUID {
48+
midr := getMIDR()
49+
part_num := uint16((midr >> 4) & 0xfff)
50+
implementor := byte((midr >> 24) & 0xff)
51+
52+
if implementor == 'A' && part_num == 0xd0c {
53+
ARM64.IsNeoverseN1 = true
54+
}
55+
if implementor == 'A' && part_num == 0xd40 {
56+
ARM64.IsZeus = true
57+
}
58+
}
59+
}
60+
61+
func isSet(hwc uint, value uint) bool {
62+
return hwc&value != 0
63+
}

src/internal/cpu/cpu_other.go renamed to src/internal/cpu/cpu_arm64_linux.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// +build !linux
6-
// +build !freebsd
5+
// +build arm64
6+
// +build linux
77
// +build !android
88

99
package cpu
1010

11-
const GOOS = "other"
11+
func osInit() {
12+
hwcapInit("linux")
13+
}

src/internal/cpu/cpu_arm64_other.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2020 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+
// +build arm64
6+
// +build !linux
7+
// +build !freebsd
8+
// +build !android
9+
// +build !darwin ios
10+
11+
package cpu
12+
13+
func osInit() {
14+
// Other operating systems do not support reading HWCap from auxiliary vector,
15+
// reading privileged aarch64 system registers or sysctl in user space to detect
16+
// CPU features at runtime.
17+
}

src/internal/cpu/cpu_freebsd.go

-7
This file was deleted.

src/internal/cpu/cpu_test.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestMinimalFeatures(t *testing.T) {
1818
// TODO: maybe do MustSupportFeatureDectection(t) ?
1919
if runtime.GOARCH == "arm64" {
2020
switch runtime.GOOS {
21-
case "linux", "android":
21+
case "linux", "android", "darwin":
2222
default:
2323
t.Skipf("%s/%s is not supported", runtime.GOOS, runtime.GOARCH)
2424
}
@@ -38,10 +38,7 @@ func MustHaveDebugOptionsSupport(t *testing.T) {
3838
}
3939

4040
func MustSupportFeatureDectection(t *testing.T) {
41-
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
42-
t.Skipf("CPU feature detection is not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
43-
}
44-
// TODO: maybe there are other platforms?
41+
// TODO: add platforms that do not have CPU feature detection support.
4542
}
4643

4744
func runDebugOptionsTest(t *testing.T, test string, options string) {

src/runtime/os_darwin.go

+12
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@ func osinit() {
130130
physPageSize = getPageSize()
131131
}
132132

133+
func sysctlbynameInt32(name []byte) (int32, int32) {
134+
out := int32(0)
135+
nout := unsafe.Sizeof(out)
136+
ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0)
137+
return ret, out
138+
}
139+
140+
//go:linkname internal_cpu_getsysctlbyname internal/cpu.getsysctlbyname
141+
func internal_cpu_getsysctlbyname(name []byte) (int32, int32) {
142+
return sysctlbynameInt32(name)
143+
}
144+
133145
const (
134146
_CTL_HW = 6
135147
_HW_NCPU = 3

src/runtime/sys_darwin.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,18 @@ func setitimer_trampoline()
360360

361361
//go:nosplit
362362
//go:cgo_unsafe_args
363-
func sysctl(mib *uint32, miblen uint32, out *byte, size *uintptr, dst *byte, ndst uintptr) int32 {
363+
func sysctl(mib *uint32, miblen uint32, oldp *byte, oldlenp *uintptr, newp *byte, newlen uintptr) int32 {
364364
return libcCall(unsafe.Pointer(funcPC(sysctl_trampoline)), unsafe.Pointer(&mib))
365365
}
366366
func sysctl_trampoline()
367367

368+
//go:nosplit
369+
//go:cgo_unsafe_args
370+
func sysctlbyname(name *byte, oldp *byte, oldlenp *uintptr, newp *byte, newlen uintptr) int32 {
371+
return libcCall(unsafe.Pointer(funcPC(sysctlbyname_trampoline)), unsafe.Pointer(&name))
372+
}
373+
func sysctlbyname_trampoline()
374+
368375
//go:nosplit
369376
//go:cgo_unsafe_args
370377
func fcntl(fd, cmd, arg int32) int32 {
@@ -486,6 +493,7 @@ func setNonblock(fd int32) {
486493
//go:cgo_import_dynamic libc_kill kill "/usr/lib/libSystem.B.dylib"
487494
//go:cgo_import_dynamic libc_setitimer setitimer "/usr/lib/libSystem.B.dylib"
488495
//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib"
496+
//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib"
489497
//go:cgo_import_dynamic libc_fcntl fcntl "/usr/lib/libSystem.B.dylib"
490498
//go:cgo_import_dynamic libc_kqueue kqueue "/usr/lib/libSystem.B.dylib"
491499
//go:cgo_import_dynamic libc_kevent kevent "/usr/lib/libSystem.B.dylib"

0 commit comments

Comments
 (0)