From bcfa00cbd259a8653547b227f8207ab43bf7d5c8 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Fri, 25 Aug 2023 11:22:02 +0200 Subject: [PATCH] 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 Reviewed-by: Meng Zhuo LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek Reviewed-by: David Chase --- src/internal/cpu/cpu.go | 12 ++++ src/internal/cpu/cpu_riscv64.go | 11 ++++ src/internal/cpu/cpu_riscv64_linux.go | 91 +++++++++++++++++++++++++++ src/internal/cpu/cpu_riscv64_other.go | 11 ++++ src/runtime/os_linux_riscv64.go | 30 +++++++++ 5 files changed, 155 insertions(+) create mode 100644 src/internal/cpu/cpu_riscv64_linux.go create mode 100644 src/internal/cpu/cpu_riscv64_other.go diff --git a/src/internal/cpu/cpu.go b/src/internal/cpu/cpu.go index cd3db10523..81b8f7022e 100644 --- a/src/internal/cpu/cpu.go +++ b/src/internal/cpu/cpu.go @@ -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, diff --git a/src/internal/cpu/cpu_riscv64.go b/src/internal/cpu/cpu_riscv64.go index 2173fe8886..e6e532c7e7 100644 --- a/src/internal/cpu/cpu_riscv64.go +++ b/src/internal/cpu/cpu_riscv64.go @@ -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 index 0000000000..a076d3e33c --- /dev/null +++ b/src/internal/cpu/cpu_riscv64_linux.go @@ -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 index 0000000000..1307d822b3 --- /dev/null +++ b/src/internal/cpu/cpu_riscv64_other.go @@ -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. +} diff --git a/src/runtime/os_linux_riscv64.go b/src/runtime/os_linux_riscv64.go index 9be88a5ad2..c4a4d4e50d 100644 --- a/src/runtime/os_linux_riscv64.go +++ b/src/runtime/os_linux_riscv64.go @@ -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 +} -- 2.51.0