Skip to content

Commit bcfa00c

Browse files
markdryan4a6f656c
authored andcommitted
cpu/internal: provide runtime detection of RISC-V extensions on Linux
Add a RISCV64 variable to cpu/internal that indicates both the presence of RISC-V extensions and performance information about the underlying RISC-V cores. The variable is only populated with non false values on Linux. The detection code relies on the riscv_hwprobe syscall introduced in Linux 6.4. The patch can detect RVV 1.0 and whether the CPU supports fast misaligned accesses. It can only detect RVV 1.0 on a 6.5 kernel or later (without backports). Updates #61416 Change-Id: I2d8289345c885b699afff441d417cae38f6bdc54 Reviewed-on: https://go-review.googlesource.com/c/go/+/522995 Reviewed-by: Joel Sing <[email protected]> Reviewed-by: Meng Zhuo <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent 664aeba commit bcfa00c

File tree

5 files changed

+155
-0
lines changed

5 files changed

+155
-0
lines changed

src/internal/cpu/cpu.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ var S390X struct {
136136
_ CacheLinePad
137137
}
138138

139+
// RISCV64 contains the supported CPU features and performance characteristics for riscv64
140+
// platforms. The booleans in RISCV64, with the exception of HasFastMisaligned, indicate
141+
// the presence of RISC-V extensions.
142+
// The struct is padded to avoid false sharing.
143+
var RISCV64 struct {
144+
_ CacheLinePad
145+
HasFastMisaligned bool // Fast misaligned accesses
146+
HasV bool // Vector extension compatible with RVV 1.0
147+
_ CacheLinePad
148+
}
149+
139150
// CPU feature variables are accessed by assembly code in various packages.
140151
//go:linkname X86
141152
//go:linkname ARM
@@ -144,6 +155,7 @@ var S390X struct {
144155
//go:linkname MIPS64X
145156
//go:linkname PPC64
146157
//go:linkname S390X
158+
//go:linkname RISCV64
147159

148160
// Initialize examines the processor and sets the relevant variables above.
149161
// This is called by the runtime package early in program initialization,

src/internal/cpu/cpu_riscv64.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,16 @@ package cpu
66

77
const CacheLinePadSize = 64
88

9+
// RISC-V doesn't have a 'cpuid' equivalent. On Linux we rely on the riscv_hwprobe syscall.
10+
911
func doinit() {
12+
options = []option{
13+
{Name: "fastmisaligned", Feature: &RISCV64.HasFastMisaligned},
14+
{Name: "v", Feature: &RISCV64.HasV},
15+
}
16+
osInit()
17+
}
18+
19+
func isSet(hwc uint, value uint) bool {
20+
return hwc&value != 0
1021
}

src/internal/cpu/cpu_riscv64_linux.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 riscv64 && linux
6+
7+
package cpu
8+
9+
import _ "unsafe"
10+
11+
// RISC-V extension discovery code for Linux.
12+
//
13+
// A note on detection of the Vector extension using HWCAP.
14+
//
15+
// Support for the Vector extension version 1.0 was added to the Linux kernel in release 6.5.
16+
// Support for the riscv_hwprobe syscall was added in 6.4. It follows that if the riscv_hwprobe
17+
// syscall is not available then neither is the Vector extension (which needs kernel support).
18+
// The riscv_hwprobe syscall should then be all we need to detect the Vector extension.
19+
// However, some RISC-V board manufacturers ship boards with an older kernel on top of which
20+
// they have back-ported various versions of the Vector extension patches but not the riscv_hwprobe
21+
// patches. These kernels advertise support for the Vector extension using HWCAP. Falling
22+
// back to HWCAP to detect the Vector extension, if riscv_hwprobe is not available, or simply not
23+
// bothering with riscv_hwprobe at all and just using HWCAP may then seem like an attractive option.
24+
//
25+
// Unfortunately, simply checking the 'V' bit in AT_HWCAP will not work as this bit is used by
26+
// RISC-V board and cloud instance providers to mean different things. The Lichee Pi 4A board
27+
// and the Scaleway RV1 cloud instances use the 'V' bit to advertise their support for the unratified
28+
// 0.7.1 version of the Vector Specification. The Banana Pi BPI-F3 and the CanMV-K230 board use
29+
// it to advertise support for 1.0 of the Vector extension. Versions 0.7.1 and 1.0 of the Vector
30+
// extension are binary incompatible. HWCAP can then not be used in isolation to populate the
31+
// HasV field as this field indicates that the underlying CPU is compatible with RVV 1.0.
32+
// Go will only support the ratified versions >= 1.0 and so any vector code it might generate
33+
// would crash on a Scaleway RV1 instance or a Lichee Pi 4a, if allowed to run.
34+
//
35+
// There is a way at runtime to distinguish between versions 0.7.1 and 1.0 of the Vector
36+
// specification by issuing a RVV 1.0 vsetvli instruction and checking the vill bit of the vtype
37+
// register. This check would allow us to safely detect version 1.0 of the Vector extension
38+
// with HWCAP, if riscv_hwprobe were not available. However, the check cannot
39+
// be added until the assembler supports the Vector instructions.
40+
//
41+
// Note the riscv_hwprobe syscall does not suffer from these ambiguities by design as all of the
42+
// extensions it advertises support for are explicitly versioned. It's also worth noting that
43+
// the riscv_hwprobe syscall is the only way to detect multi-letter RISC-V extensions, e.g., Zvbb.
44+
// These cannot be detected using HWCAP and so riscv_hwprobe must be used to detect the majority
45+
// of RISC-V extensions.
46+
//
47+
// Please see https://docs.kernel.org/arch/riscv/hwprobe.html for more information.
48+
49+
const (
50+
// Copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go.
51+
riscv_HWPROBE_KEY_IMA_EXT_0 = 0x4
52+
riscv_HWPROBE_IMA_V = 0x4
53+
riscv_HWPROBE_KEY_CPUPERF_0 = 0x5
54+
riscv_HWPROBE_MISALIGNED_FAST = 0x3
55+
riscv_HWPROBE_MISALIGNED_MASK = 0x7
56+
)
57+
58+
// riscvHWProbePairs is copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go.
59+
type riscvHWProbePairs struct {
60+
key int64
61+
value uint64
62+
}
63+
64+
//go:linkname riscvHWProbe
65+
func riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool
66+
67+
func osInit() {
68+
// A slice of key/value pair structures is passed to the RISCVHWProbe syscall. The key
69+
// field should be initialised with one of the key constants defined above, e.g.,
70+
// RISCV_HWPROBE_KEY_IMA_EXT_0. The syscall will set the value field to the appropriate value.
71+
// If the kernel does not recognise a key it will set the key field to -1 and the value field to 0.
72+
73+
pairs := []riscvHWProbePairs{
74+
{riscv_HWPROBE_KEY_IMA_EXT_0, 0},
75+
{riscv_HWPROBE_KEY_CPUPERF_0, 0},
76+
}
77+
78+
// This call only indicates that extensions are supported if they are implemented on all cores.
79+
if !riscvHWProbe(pairs, 0) {
80+
return
81+
}
82+
83+
if pairs[0].key != -1 {
84+
v := uint(pairs[0].value)
85+
RISCV64.HasV = isSet(v, riscv_HWPROBE_IMA_V)
86+
}
87+
if pairs[1].key != -1 {
88+
v := pairs[1].value & riscv_HWPROBE_MISALIGNED_MASK
89+
RISCV64.HasFastMisaligned = v == riscv_HWPROBE_MISALIGNED_FAST
90+
}
91+
}

src/internal/cpu/cpu_riscv64_other.go

Lines changed: 11 additions & 0 deletions
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 riscv64 && !linux
6+
7+
package cpu
8+
9+
func osInit() {
10+
// Other operating systems do not support the riscv_hwprobe syscall.
11+
}

src/runtime/os_linux_riscv64.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,34 @@
44

55
package runtime
66

7+
import (
8+
"internal/runtime/syscall"
9+
"unsafe"
10+
)
11+
712
func osArchInit() {}
13+
14+
type riscvHWProbePairs = struct {
15+
key int64
16+
value uint64
17+
}
18+
19+
// TODO: Consider whether to use the VDSO entry for riscv_hwprobe.
20+
// There is a VDSO entry for riscv_hwprobe that should allow us to avoid the syscall
21+
// entirely as it can handle the case where the caller only requests extensions that are
22+
// supported on all cores, which is what we're doing here. However, as we're only calling
23+
// this syscall once, it may not be worth the added effort to implement the VDSO call.
24+
25+
//go:linkname internal_cpu_riscvHWProbe internal/cpu.riscvHWProbe
26+
func internal_cpu_riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool {
27+
// sys_RISCV_HWPROBE is copied from golang.org/x/sys/unix/zsysnum_linux_riscv64.go.
28+
const sys_RISCV_HWPROBE uintptr = 258
29+
30+
if len(pairs) == 0 {
31+
return false
32+
}
33+
// Passing in a cpuCount of 0 and a cpu of nil ensures that only extensions supported by all the
34+
// cores are returned, which is the behaviour we want in internal/cpu.
35+
_, _, e1 := syscall.Syscall6(sys_RISCV_HWPROBE, uintptr(unsafe.Pointer(&pairs[0])), uintptr(len(pairs)), uintptr(0), uintptr(unsafe.Pointer(nil)), uintptr(flags), 0)
36+
return e1 == 0
37+
}

0 commit comments

Comments
 (0)