Skip to content

Commit 76cf32c

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 76cf32c

7 files changed

+173
-14
lines changed

Diff for: cpu/cpu.go

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ var ARM64 struct {
100100
HasSHA512 bool // SHA512 hardware implementation
101101
HasSVE bool // Scalable Vector Extensions
102102
HasASIMDFHM bool // Advanced SIMD multiplication FP16 to FP32
103+
HasFMA bool // Fused Multiply Add
103104
_ CacheLinePad
104105
}
105106

Diff for: cpu/cpu_android_arm64.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
// Use HWCap information since reading aarch64 system registers
46+
// is not supported in user space on older linux kernels.
47+
ARM64.HasFP = isSet(hwCap, hwcap_FP)
48+
ARM64.HasASIMD = isSet(hwCap, hwcap_ASIMD)
49+
ARM64.HasEVTSTRM = isSet(hwCap, hwcap_EVTSTRM)
50+
ARM64.HasAES = isSet(hwCap, hwcap_AES)
51+
ARM64.HasPMULL = isSet(hwCap, hwcap_PMULL)
52+
ARM64.HasSHA1 = isSet(hwCap, hwcap_SHA1)
53+
ARM64.HasSHA2 = isSet(hwCap, hwcap_SHA2)
54+
ARM64.HasCRC32 = isSet(hwCap, hwcap_CRC32)
55+
ARM64.HasFPHP = isSet(hwCap, hwcap_FPHP)
56+
ARM64.HasASIMDHP = isSet(hwCap, hwcap_ASIMDHP)
57+
ARM64.HasASIMDRDM = isSet(hwCap, hwcap_ASIMDRDM)
58+
ARM64.HasJSCVT = isSet(hwCap, hwcap_JSCVT)
59+
ARM64.HasFCMA = isSet(hwCap, hwcap_FCMA)
60+
ARM64.HasLRCPC = isSet(hwCap, hwcap_LRCPC)
61+
ARM64.HasDCPOP = isSet(hwCap, hwcap_DCPOP)
62+
ARM64.HasSHA3 = isSet(hwCap, hwcap_SHA3)
63+
ARM64.HasSM3 = isSet(hwCap, hwcap_SM3)
64+
ARM64.HasSM4 = isSet(hwCap, hwcap_SM4)
65+
ARM64.HasASIMDDP = isSet(hwCap, hwcap_ASIMDDP)
66+
ARM64.HasSHA512 = isSet(hwCap, hwcap_SHA512)
67+
ARM64.HasSVE = isSet(hwCap, hwcap_SVE)
68+
ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM)
69+
70+
// The Samsung S9+ kernel reports support for atomics, but not all cores
71+
// actually support them, resulting in SIGILL. See issue #28431.
72+
// TODO(elias.naur): Only disable the optimization on bad chipsets on android.
73+
ARM64.HasATOMICS = false
74+
}
75+
76+
func isSet(hwc uint, value uint) bool {
77+
return hwc&value != 0
78+
}

Diff for: cpu/cpu_arm64.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ func archInit() {
4141
switch runtime.GOOS {
4242
case "freebsd":
4343
readARM64Registers()
44-
case "linux", "netbsd":
45-
doinit()
44+
case "linux", "netbsd", "android", "darwin":
45+
osInit()
4646
default:
4747
// Most platforms don't seem to allow reading these registers.
4848
//
@@ -52,13 +52,6 @@ func archInit() {
5252
}
5353
}
5454

55-
// setMinimalFeatures fakes the minimal ARM64 features expected by
56-
// TestARM64minimalFeatures.
57-
func setMinimalFeatures() {
58-
ARM64.HasASIMD = true
59-
ARM64.HasFP = true
60-
}
61-
6255
func readARM64Registers() {
6356
Initialized = true
6457

@@ -170,3 +163,10 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) {
170163
func extractBits(data uint64, start, end uint) uint {
171164
return (uint)(data>>start) & ((1 << (end - start + 1)) - 1)
172165
}
166+
167+
// setMinimalFeatures fakes the minimal ARM64 features expected by
168+
// TestARM64minimalFeatures.
169+
func setMinimalFeatures() {
170+
ARM64.HasASIMD = true
171+
ARM64.HasFP = true
172+
}

Diff for: cpu/cpu_darwin_arm64.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
"unsafe"
13+
)
14+
15+
const (
16+
commpageHasNeonFP16 uint64 = 0x00000008 // ARM v8.2 NEON FP16 supported
17+
commpageHasNeon uint64 = 0x00000100 // Advanced SIMD is supported
18+
commpageHasNeonHPFP uint64 = 0x00000200 // Advanced SIMD half-precision
19+
commpageHasVfp uint64 = 0x00000400 // VFP is supported
20+
commpageHasEvent uint64 = 0x00001000 // WFE/SVE and period event wakeup
21+
commpageHasFMA uint64 = 0x00002000 // Fused multiply add is supported
22+
commpageHasARMv82FHM uint64 = 0x00004000 // Optional ARMv8.2 FMLAL/FMLSL instructions (required in ARMv8.4)
23+
commpageHasARMv8Crypto uint64 = 0x01000000 // Optional ARMv8 Crypto extensions
24+
commpageHasARMv81Atomics uint64 = 0x02000000 // ARMv8.1 Atomic instructions supported
25+
commpageHasARMv8Crc32 uint64 = 0x04000000 // Optional ARMv8 crc32 instructions (required in ARMv8.1)
26+
commpageHasARMv82SHA512 uint64 = 0x80000000 // Optional ARMv8.2 SHA512 instructions
27+
commpageHasARMv82SHA3 uint64 = 0x0000000100000000 // Optional ARMv8.2 SHA3 instructions
28+
)
29+
30+
func osInit() {
31+
ARM64.HasFP = darwinCheckFeatureEnabled(commpageHasVfp)
32+
ARM64.HasASIMD = darwinCheckFeatureEnabled(commpageHasNeon)
33+
ARM64.HasCRC32 = darwinCheckFeatureEnabled(commpageHasARMv8Crc32)
34+
ARM64.HasATOMICS = darwinCheckFeatureEnabled(commpageHasARMv81Atomics)
35+
ARM64.HasFPHP = darwinCheckFeatureEnabled(commpageHasNeonFP16)
36+
ARM64.HasASIMDHP = darwinCheckFeatureEnabled(commpageHasNeonHPFP)
37+
ARM64.HasSHA3 = darwinCheckFeatureEnabled(commpageHasARMv82SHA3)
38+
ARM64.HasSHA512 = darwinCheckFeatureEnabled(commpageHasARMv82SHA512)
39+
ARM64.HasASIMDFHM = darwinCheckFeatureEnabled(commpageHasARMv82FHM)
40+
ARM64.HasSVE = darwinCheckFeatureEnabled(commpageHasEvent)
41+
ARM64.HasFMA = darwinCheckFeatureEnabled(commpageHasFMA)
42+
43+
// There are no hw.optional sysctl values for the below features on Mac OS 11.0
44+
// to detect their supported state dynamically. Assume the CPU features that
45+
// Apple Silicon M1 supports to be available as a minimal set of features
46+
// to all Go programs running on darwin/arm64.
47+
ARM64.HasEVTSTRM = true
48+
ARM64.HasAES = true
49+
ARM64.HasPMULL = true
50+
ARM64.HasSHA1 = true
51+
ARM64.HasSHA2 = true
52+
ARM64.HasCPUID = true
53+
ARM64.HasASIMDRDM = true
54+
ARM64.HasJSCVT = true
55+
ARM64.HasFCMA = true
56+
ARM64.HasLRCPC = true
57+
ARM64.HasDCPOP = true
58+
ARM64.HasSM3 = true
59+
ARM64.HasSM4 = true
60+
ARM64.HasASIMDDP = true
61+
}
62+
63+
const (
64+
commpage64_base_address = 0x0000000fffffc000
65+
commpage64_cpu_capabilities64 = (commpage64_base_address + 0x010)
66+
)
67+
68+
func darwinCheckFeatureEnabled(feature_vec uint64) bool {
69+
if (*(*int64)(unsafe.Pointer(uintptr(commpage64_cpu_capabilities64))))&int64(feature_vec) > 0 {
70+
return true
71+
}
72+
73+
return false
74+
}

Diff for: 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()

Diff for: 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+
}

Diff for: 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)