Skip to content

Commit 1ba4687

Browse files
committed
cpu: support darwin/arm64 CPU feature detection
Support ARM64 features detection. The CPU features which are supported by Apple Silicon M1 are assumed as the minimal set of features for Go programs running on darwin/arm64. The ARM64 supporting features are referred to https://en.wikichip.org/wiki/arm/armv8#ARMv8_Extensions_and_Processor_Features
1 parent 59db8d7 commit 1ba4687

8 files changed

+271
-26
lines changed

cpu/cpu_android_arm64.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2021 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 android
7+
8+
package cpu
9+
10+
// HWCAP/HWCAP2 bits. These are exposed by Linux.
11+
const (
12+
hwcap_FP = 1 << 0
13+
hwcap_ASIMD = 1 << 1
14+
hwcap_EVTSTRM = 1 << 2
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_FPHP = 1 << 9
22+
hwcap_ASIMDHP = 1 << 10
23+
hwcap_CPUID = 1 << 11
24+
hwcap_ASIMDRDM = 1 << 12
25+
hwcap_JSCVT = 1 << 13
26+
hwcap_FCMA = 1 << 14
27+
hwcap_LRCPC = 1 << 15
28+
hwcap_DCPOP = 1 << 16
29+
hwcap_SHA3 = 1 << 17
30+
hwcap_SM3 = 1 << 18
31+
hwcap_SM4 = 1 << 19
32+
hwcap_ASIMDDP = 1 << 20
33+
hwcap_SHA512 = 1 << 21
34+
hwcap_SVE = 1 << 22
35+
hwcap_ASIMDFHM = 1 << 23
36+
)
37+
38+
func osInit() {
39+
if err := readHWCAP(); err != nil {
40+
// failed to read /proc/self/auxv, try reading registers directly
41+
readARM64Registers()
42+
return
43+
}
44+
45+
// HWCap was populated by the runtime from the auxiliary vector.
46+
// Use HWCap information since reading aarch64 system registers
47+
// is not supported in user space on older linux kernels.
48+
ARM64.HasFP = isSet(hwCap, hwcap_FP)
49+
ARM64.HasASIMD = isSet(hwCap, hwcap_ASIMD)
50+
ARM64.HasEVTSTRM = isSet(hwCap, hwcap_EVTSTRM)
51+
ARM64.HasAES = isSet(hwCap, hwcap_AES)
52+
ARM64.HasPMULL = isSet(hwCap, hwcap_PMULL)
53+
ARM64.HasSHA1 = isSet(hwCap, hwcap_SHA1)
54+
ARM64.HasSHA2 = isSet(hwCap, hwcap_SHA2)
55+
ARM64.HasCRC32 = isSet(hwCap, hwcap_CRC32)
56+
ARM64.HasFPHP = isSet(hwCap, hwcap_FPHP)
57+
ARM64.HasASIMDHP = isSet(hwCap, hwcap_ASIMDHP)
58+
ARM64.HasASIMDRDM = isSet(hwCap, hwcap_ASIMDRDM)
59+
ARM64.HasJSCVT = isSet(hwCap, hwcap_JSCVT)
60+
ARM64.HasFCMA = isSet(hwCap, hwcap_FCMA)
61+
ARM64.HasLRCPC = isSet(hwCap, hwcap_LRCPC)
62+
ARM64.HasDCPOP = isSet(hwCap, hwcap_DCPOP)
63+
ARM64.HasSHA3 = isSet(hwCap, hwcap_SHA3)
64+
ARM64.HasSM3 = isSet(hwCap, hwcap_SM3)
65+
ARM64.HasSM4 = isSet(hwCap, hwcap_SM4)
66+
ARM64.HasASIMDDP = isSet(hwCap, hwcap_ASIMDDP)
67+
ARM64.HasSHA512 = isSet(hwCap, hwcap_SHA512)
68+
ARM64.HasSVE = isSet(hwCap, hwcap_SVE)
69+
ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM)
70+
71+
// The Samsung S9+ kernel reports support for atomics, but not all cores
72+
// actually support them, resulting in SIGILL. See issue #28431.
73+
// TODO(elias.naur): Only disable the optimization on bad chipsets on android.
74+
ARM64.HasATOMICS = false
75+
76+
// Check to see if executing on a NeoverseN1 and in order to do that,
77+
// check the AUXV for the CPUID bit. The getMIDR function executes an
78+
// instruction which would normally be an illegal instruction, but it's
79+
// trapped by the kernel, the value sanitized and then returned. Without
80+
// the CPUID bit the kernel will not trap the instruction and the process
81+
// will be terminated with SIGILL.
82+
if ARM64.HasCPUID {
83+
midr := getMIDR()
84+
part_num := uint16((midr >> 4) & 0xfff)
85+
implementor := byte((midr >> 24) & 0xff)
86+
87+
if implementor == 'A' && part_num == 0xd0c {
88+
ARM64.IsNeoverseN1 = true
89+
}
90+
if implementor == 'A' && part_num == 0xd40 {
91+
ARM64.IsZeus = true
92+
}
93+
}
94+
}
95+
96+
func isSet(hwc uint, value uint) bool {
97+
return hwc&value != 0
98+
}

cpu/cpu_arm64.go

+11-21
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
package cpu
66

7-
import "runtime"
8-
97
const cacheLineSize = 64
108

119
func initOptions() {
@@ -38,25 +36,8 @@ func initOptions() {
3836
}
3937

4038
func archInit() {
41-
switch runtime.GOOS {
42-
case "freebsd":
43-
readARM64Registers()
44-
case "linux", "netbsd":
45-
doinit()
46-
default:
47-
// Most platforms don't seem to allow reading these registers.
48-
//
49-
// OpenBSD:
50-
// See https://golang.org/issue/31746
51-
setMinimalFeatures()
52-
}
53-
}
54-
55-
// setMinimalFeatures fakes the minimal ARM64 features expected by
56-
// TestARM64minimalFeatures.
57-
func setMinimalFeatures() {
58-
ARM64.HasASIMD = true
59-
ARM64.HasFP = true
39+
setMinimalFeatures()
40+
osInit()
6041
}
6142

6243
func readARM64Registers() {
@@ -170,3 +151,12 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) {
170151
func extractBits(data uint64, start, end uint) uint {
171152
return (uint)(data>>start) & ((1 << (end - start + 1)) - 1)
172153
}
154+
155+
// setMinimalFeatures fakes the minimal ARM64 features expected by
156+
// TestARM64minimalFeatures.
157+
func setMinimalFeatures() {
158+
ARM64.HasASIMD = true
159+
ARM64.HasFP = true
160+
}
161+
162+
func getMIDR() uint64

cpu/cpu_arm64.s

+6
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ TEXT ·getpfr0(SB),NOSPLIT,$0-8
3030
WORD $0xd5380400
3131
MOVD R0, ret+0(FP)
3232
RET
33+
34+
// func getMIDR() uint64
35+
TEXT ·getMIDR(SB), NOSPLIT, $0-8
36+
MRS MIDR_EL1, R0
37+
MOVD R0, ret+0(FP)
38+
RET

cpu/cpu_darwin_arm64.go

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2021 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+
import (
12+
"fmt"
13+
"strings"
14+
"syscall"
15+
"unsafe"
16+
)
17+
18+
func osInit() {
19+
ARM64.HasFP = sysctlEnabled("hw.optional.floatingpoint")
20+
ARM64.HasASIMD = sysctlEnabled("hw.optional.neon")
21+
ARM64.HasCRC32 = sysctlEnabled("hw.optional.armv8_crc32")
22+
ARM64.HasATOMICS = sysctlEnabled("hw.optional.armv8_1_atomics")
23+
ARM64.HasFPHP = sysctlEnabled("hw.optional.neon_hpfp")
24+
ARM64.HasASIMDHP = sysctlEnabled("hw.optional.floatingpoint")
25+
ARM64.HasSHA3 = sysctlEnabled("hw.optional.armv8_2_sha3")
26+
ARM64.HasSHA512 = sysctlEnabled("hw.optional.armv8_2_sha512")
27+
ARM64.HasASIMDFHM = sysctlEnabled("hw.optional.armv8_2_fhm")
28+
29+
// There are no hw.optional sysctl values for the below features on Mac OS 11.0
30+
// to detect their supported state dynamically. Assume the CPU features that
31+
// Apple Silicon M1 supports to be available as a minimal set of features
32+
// to all Go programs running on darwin/arm64.
33+
ARM64.HasEVTSTRM = true
34+
ARM64.HasAES = true
35+
ARM64.HasPMULL = true
36+
ARM64.HasSHA1 = true
37+
ARM64.HasSHA2 = true
38+
ARM64.HasCPUID = true
39+
ARM64.HasASIMDRDM = true
40+
ARM64.HasJSCVT = true
41+
ARM64.HasFCMA = true
42+
ARM64.HasLRCPC = true
43+
ARM64.HasDCPOP = true
44+
ARM64.HasSM3 = true
45+
ARM64.HasSM4 = true
46+
ARM64.HasASIMDDP = true
47+
ARM64.HasSVE = true
48+
}
49+
50+
// The following is minimal copy of functionality from x/sys/unix so the cpu package can call
51+
// sysctl without depending on x/sys/unix.
52+
53+
func sysctlEnabled(name string, args ...int) bool {
54+
mib, err := nametomib(name)
55+
if err != nil {
56+
return false
57+
}
58+
59+
for _, a := range args {
60+
mib = append(mib, _C_int(a))
61+
}
62+
63+
// Find size.
64+
n := uintptr(0)
65+
if err := sysctl(mib, nil, &n, nil, 0); err != nil {
66+
return false
67+
}
68+
69+
return true
70+
}
71+
72+
type _C_int int32
73+
74+
func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) {
75+
var _zero uintptr
76+
var _p0 unsafe.Pointer
77+
if len(mib) > 0 {
78+
_p0 = unsafe.Pointer(&mib[0])
79+
} else {
80+
_p0 = unsafe.Pointer(&_zero)
81+
}
82+
_, _, errno := syscall.Syscall6(
83+
syscall.SYS___SYSCTL,
84+
uintptr(_p0),
85+
uintptr(len(mib)),
86+
uintptr(unsafe.Pointer(old)),
87+
uintptr(unsafe.Pointer(oldlen)),
88+
uintptr(unsafe.Pointer(new)),
89+
uintptr(newlen))
90+
if errno != 0 {
91+
return errno
92+
}
93+
return nil
94+
}
95+
96+
// nametomib is a copy from "unix.nametomib()" in "unix/syscall_darwin.go".
97+
func nametomib(name string) (mib []_C_int, err error) {
98+
const CTL_MAXNAME = 0xc
99+
const siz = unsafe.Sizeof(mib[0])
100+
101+
// NOTE(rsc): It seems strange to set the buffer to have
102+
// size CTL_MAXNAME+2 but use only CTL_MAXNAME
103+
// as the size. I don't know why the +2 is here, but the
104+
// kernel uses +2 for its own implementation of this function.
105+
// I am scared that if we don't include the +2 here, the kernel
106+
// will silently write 2 words farther than we specify
107+
// and we'll get memory corruption.
108+
var buf [CTL_MAXNAME + 2]_C_int
109+
n := uintptr(CTL_MAXNAME) * siz
110+
111+
p := (*byte)(unsafe.Pointer(&buf[0]))
112+
bytes, err := byteSliceFromString(name)
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
// Magic sysctl: "setting" 0.3 to a string name
118+
// lets you read back the array of integers form.
119+
if err = sysctl([]_C_int{0, 3}, p, &n, &bytes[0], uintptr(len(name))); err != nil {
120+
return nil, err
121+
}
122+
return buf[0 : n/siz], nil
123+
}
124+
125+
// byteSliceFromString is a simple copy of "unix.ByteSliceFromString()"
126+
func byteSliceFromString(s string) ([]byte, error) {
127+
if strings.IndexByte(s, 0) != -1 {
128+
return nil, fmt.Errorf("invalid argument in cpu.byteSliceFromString()")
129+
}
130+
a := make([]byte, len(s)+1)
131+
copy(a, s)
132+
return a, nil
133+
}

cpu/cpu_freebsd_arm64.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2021 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 freebsd
7+
8+
package cpu
9+
10+
func osInit() {
11+
readARM64Registers()
12+
}

cpu/cpu_linux_arm64.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +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 arm64
6+
// +build linux
7+
// +build !android
8+
59
package cpu
610

711
// HWCAP/HWCAP2 bits. These are exposed by Linux.
@@ -32,7 +36,7 @@ const (
3236
hwcap_ASIMDFHM = 1 << 23
3337
)
3438

35-
func doinit() {
39+
func osInit() {
3640
if err := readHWCAP(); err != nil {
3741
// failed to read /proc/self/auxv, try reading registers directly
3842
readARM64Registers()

cpu/cpu_other_arm64.go

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

5-
//go:build !linux && !netbsd && arm64
6-
// +build !linux,!netbsd,arm64
5+
//go:build !linux && !netbsd && !darwin && arm64
6+
// +build !linux,!netbsd,!darwin,arm64
77

88
package cpu
99

10-
func doinit() {}
10+
func osInit() {
11+
setMinimalFeatures()
12+
}

cpu/cpu_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestAVX512HasAVX2AndAVX(t *testing.T) {
4242
}
4343

4444
func TestARM64minimalFeatures(t *testing.T) {
45-
if runtime.GOARCH != "arm64" || (runtime.GOOS == "darwin" || runtime.GOOS == "ios") {
45+
if runtime.GOARCH != "arm64" || runtime.GOOS == "ios" {
4646
return
4747
}
4848
if !cpu.ARM64.HasASIMD {

0 commit comments

Comments
 (0)