]> Cypherpunks repositories - gostls13.git/commitdiff
cpu/internal: provide runtime detection of RISC-V extensions on Linux
authorMark Ryan <markdryan@rivosinc.com>
Fri, 25 Aug 2023 09:22:02 +0000 (11:22 +0200)
committerJoel Sing <joel@sing.id.au>
Wed, 5 Feb 2025 11:47:25 +0000 (03:47 -0800)
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 <joel@sing.id.au>
Reviewed-by: Meng Zhuo <mengzhuo1203@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: David Chase <drchase@google.com>
src/internal/cpu/cpu.go
src/internal/cpu/cpu_riscv64.go
src/internal/cpu/cpu_riscv64_linux.go [new file with mode: 0644]
src/internal/cpu/cpu_riscv64_other.go [new file with mode: 0644]
src/runtime/os_linux_riscv64.go

index cd3db105238eec5c1937023edf2b914a4f23cf18..81b8f7022e8e5907ae04fc4bdc5d0f44a126109b 100644 (file)
@@ -136,6 +136,17 @@ var S390X struct {
        _         CacheLinePad
 }
 
+// RISCV64 contains the supported CPU features and performance characteristics for riscv64
+// platforms. The booleans in RISCV64, with the exception of HasFastMisaligned, indicate
+// the presence of RISC-V extensions.
+// The struct is padded to avoid false sharing.
+var RISCV64 struct {
+       _                 CacheLinePad
+       HasFastMisaligned bool // Fast misaligned accesses
+       HasV              bool // Vector extension compatible with RVV 1.0
+       _                 CacheLinePad
+}
+
 // CPU feature variables are accessed by assembly code in various packages.
 //go:linkname X86
 //go:linkname ARM
@@ -144,6 +155,7 @@ var S390X struct {
 //go:linkname MIPS64X
 //go:linkname PPC64
 //go:linkname S390X
+//go:linkname RISCV64
 
 // Initialize examines the processor and sets the relevant variables above.
 // This is called by the runtime package early in program initialization,
index 2173fe8886090411d8b8c0da5eb8f91efea9736b..e6e532c7e7e18f5c5279117fccee1bc521b7639c 100644 (file)
@@ -6,5 +6,16 @@ package cpu
 
 const CacheLinePadSize = 64
 
+// RISC-V doesn't have a 'cpuid' equivalent. On Linux we rely on the riscv_hwprobe syscall.
+
 func doinit() {
+       options = []option{
+               {Name: "fastmisaligned", Feature: &RISCV64.HasFastMisaligned},
+               {Name: "v", Feature: &RISCV64.HasV},
+       }
+       osInit()
+}
+
+func isSet(hwc uint, value uint) bool {
+       return hwc&value != 0
 }
diff --git a/src/internal/cpu/cpu_riscv64_linux.go b/src/internal/cpu/cpu_riscv64_linux.go
new file mode 100644 (file)
index 0000000..a076d3e
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build riscv64 && linux
+
+package cpu
+
+import _ "unsafe"
+
+// RISC-V extension discovery code for Linux.
+//
+// A note on detection of the Vector extension using HWCAP.
+//
+// Support for the Vector extension version 1.0 was added to the Linux kernel in release 6.5.
+// Support for the riscv_hwprobe syscall was added in 6.4. It follows that if the riscv_hwprobe
+// syscall is not available then neither is the Vector extension (which needs kernel support).
+// The riscv_hwprobe syscall should then be all we need to detect the Vector extension.
+// However, some RISC-V board manufacturers ship boards with an older kernel on top of which
+// they have back-ported various versions of the Vector extension patches but not the riscv_hwprobe
+// patches. These kernels advertise support for the Vector extension using HWCAP. Falling
+// back to HWCAP to detect the Vector extension, if riscv_hwprobe is not available, or simply not
+// bothering with riscv_hwprobe at all and just using HWCAP may then seem like an attractive option.
+//
+// Unfortunately, simply checking the 'V' bit in AT_HWCAP will not work as this bit is used by
+// RISC-V board and cloud instance providers to mean different things. The Lichee Pi 4A board
+// and the Scaleway RV1 cloud instances use the 'V' bit to advertise their support for the unratified
+// 0.7.1 version of the Vector Specification. The Banana Pi BPI-F3 and the CanMV-K230 board use
+// it to advertise support for 1.0 of the Vector extension. Versions 0.7.1 and 1.0 of the Vector
+// extension are binary incompatible. HWCAP can then not be used in isolation to populate the
+// HasV field as this field indicates that the underlying CPU is compatible with RVV 1.0.
+// Go will only support the ratified versions >= 1.0 and so any vector code it might generate
+// would crash on a Scaleway RV1 instance or a Lichee Pi 4a, if allowed to run.
+//
+// There is a way at runtime to distinguish between versions 0.7.1 and 1.0 of the Vector
+// specification by issuing a RVV 1.0 vsetvli instruction and checking the vill bit of the vtype
+// register. This check would allow us to safely detect version 1.0 of the Vector extension
+// with HWCAP, if riscv_hwprobe were not available. However, the check cannot
+// be added until the assembler supports the Vector instructions.
+//
+// Note the riscv_hwprobe syscall does not suffer from these ambiguities by design as all of the
+// extensions it advertises support for are explicitly versioned. It's also worth noting that
+// the riscv_hwprobe syscall is the only way to detect multi-letter RISC-V extensions, e.g., Zvbb.
+// These cannot be detected using HWCAP and so riscv_hwprobe must be used to detect the majority
+// of RISC-V extensions.
+//
+// Please see https://docs.kernel.org/arch/riscv/hwprobe.html for more information.
+
+const (
+       // Copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go.
+       riscv_HWPROBE_KEY_IMA_EXT_0   = 0x4
+       riscv_HWPROBE_IMA_V           = 0x4
+       riscv_HWPROBE_KEY_CPUPERF_0   = 0x5
+       riscv_HWPROBE_MISALIGNED_FAST = 0x3
+       riscv_HWPROBE_MISALIGNED_MASK = 0x7
+)
+
+// riscvHWProbePairs is copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go.
+type riscvHWProbePairs struct {
+       key   int64
+       value uint64
+}
+
+//go:linkname riscvHWProbe
+func riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool
+
+func osInit() {
+       // A slice of key/value pair structures is passed to the RISCVHWProbe syscall. The key
+       // field should be initialised with one of the key constants defined above, e.g.,
+       // RISCV_HWPROBE_KEY_IMA_EXT_0. The syscall will set the value field to the appropriate value.
+       // If the kernel does not recognise a key it will set the key field to -1 and the value field to 0.
+
+       pairs := []riscvHWProbePairs{
+               {riscv_HWPROBE_KEY_IMA_EXT_0, 0},
+               {riscv_HWPROBE_KEY_CPUPERF_0, 0},
+       }
+
+       // This call only indicates that extensions are supported if they are implemented on all cores.
+       if !riscvHWProbe(pairs, 0) {
+               return
+       }
+
+       if pairs[0].key != -1 {
+               v := uint(pairs[0].value)
+               RISCV64.HasV = isSet(v, riscv_HWPROBE_IMA_V)
+       }
+       if pairs[1].key != -1 {
+               v := pairs[1].value & riscv_HWPROBE_MISALIGNED_MASK
+               RISCV64.HasFastMisaligned = v == riscv_HWPROBE_MISALIGNED_FAST
+       }
+}
diff --git a/src/internal/cpu/cpu_riscv64_other.go b/src/internal/cpu/cpu_riscv64_other.go
new file mode 100644 (file)
index 0000000..1307d82
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build riscv64 && !linux
+
+package cpu
+
+func osInit() {
+       // Other operating systems do not support the riscv_hwprobe syscall.
+}
index 9be88a5ad24468098de47bc72cfc607cc6465991..c4a4d4e50d45061c227a38656e0a94cedbb2382d 100644 (file)
@@ -4,4 +4,34 @@
 
 package runtime
 
+import (
+       "internal/runtime/syscall"
+       "unsafe"
+)
+
 func osArchInit() {}
+
+type riscvHWProbePairs = struct {
+       key   int64
+       value uint64
+}
+
+// TODO: Consider whether to use the VDSO entry for riscv_hwprobe.
+// There is a VDSO entry for riscv_hwprobe that should allow us to avoid the syscall
+// entirely as it can handle the case where the caller only requests extensions that are
+// supported on all cores, which is what we're doing here. However, as we're only calling
+// this syscall once, it may not be worth the added effort to implement the VDSO call.
+
+//go:linkname internal_cpu_riscvHWProbe internal/cpu.riscvHWProbe
+func internal_cpu_riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool {
+       // sys_RISCV_HWPROBE is copied from golang.org/x/sys/unix/zsysnum_linux_riscv64.go.
+       const sys_RISCV_HWPROBE uintptr = 258
+
+       if len(pairs) == 0 {
+               return false
+       }
+       // Passing in a cpuCount of 0 and a cpu of nil ensures that only extensions supported by all the
+       // cores are returned, which is the behaviour we want in internal/cpu.
+       _, _, e1 := syscall.Syscall6(sys_RISCV_HWPROBE, uintptr(unsafe.Pointer(&pairs[0])), uintptr(len(pairs)), uintptr(0), uintptr(unsafe.Pointer(nil)), uintptr(flags), 0)
+       return e1 == 0
+}