Skip to content

Commit 60b91fe

Browse files
a4lgAmanieu
authored andcommitted
RISC-V: OS-independent implication logic
This commit adds the OS-independent extension implication logic for RISC-V. It implements: 1. Regular implication (A → B) a. "the extension A implies the extension B" b. "the extension A requires the extension B" c. "the extension A depends on the extension B" 2. Extension group or shorthand (A == B1 & B2...) a. "the extension A is shorthand for other extensions: B1, B2..." b. "the extension A comprises instructions provided by B1, B2..." This is implemented as (A → B1 & B2... + B1 & B2... → A) where the former is a regular implication as required by specifications and the latter is a "reverse" implication to improve usability. and prepares for: 3. Implication with multiple requirements (A1 & A2... → B) a. "A1 + A2 implies B" b. (implicitly used to implement reverse implication of case 2) Although it uses macros and iterators, good optimizers turn the series of implications into fast bit-manipulation operations. In the case 2 (extension group or shorthand; where a superset extension is just a collection of other subextensions and provides no features by a superset itself), specifications do specify that an extension group implies its members but not vice versa. However, implying an extension group from its members would improve usability on the feature detection (especially when the feature provider does not provide existence of such extension group but provides existence of its members). Similar "reverse implication" on RISC-V is implemented on LLVM. Case 3 is implicitly used to implement reverse implication of case 2 but there's another use case: implication with multiple requirements like "Zcf" and "Zcd" extensions (not yet implemented in this crate for now). To handle extension groups perfectly, we need to loop implication several times (until they converge; normally 2 times and up to 4 times when we add most of `riscv_hwprobe`-based features). To make implementation of that loop possible, `cache::Initializer` is modified to implement `PartialEq` and `Eq`.
1 parent 7be3ab6 commit 60b91fe

File tree

4 files changed

+158
-12
lines changed

4 files changed

+158
-12
lines changed

crates/std_detect/src/detect/cache.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const CACHE_CAPACITY: u32 = 93;
3131
/// This type is used to initialize the cache
3232
// The derived `Default` implementation will initialize the field to zero,
3333
// which is what we want.
34-
#[derive(Copy, Clone, Default)]
34+
#[derive(Copy, Clone, Default, PartialEq, Eq)]
3535
pub(crate) struct Initializer(u128);
3636

3737
// NOTE: the `debug_assert!` would catch that we do not add more Features than

crates/std_detect/src/detect/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ cfg_if! {
4949
#[path = "os/x86.rs"]
5050
mod os;
5151
} else if #[cfg(all(any(target_os = "linux", target_os = "android"), feature = "libc"))] {
52+
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
53+
#[path = "os/riscv.rs"]
54+
mod riscv;
5255
#[path = "os/linux/mod.rs"]
5356
mod os;
5457
} else if #[cfg(all(target_os = "freebsd", feature = "libc"))] {

crates/std_detect/src/detect/os/linux/riscv.rs

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Run-time feature detection for RISC-V on Linux.
22
3+
use super::super::riscv::imply_features;
34
use super::auxvec;
45
use crate::detect::{Feature, bit, cache};
56

@@ -12,22 +13,16 @@ pub(crate) fn detect_features() -> cache::Initializer {
1213
}
1314
};
1415

15-
// Use auxiliary vector to enable single-letter ISA extensions and Zicsr.
16+
// Use auxiliary vector to enable single-letter ISA extensions.
1617
// The values are part of the platform-specific [asm/hwcap.h][hwcap]
1718
//
1819
// [hwcap]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/include/uapi/asm/hwcap.h?h=v6.14
1920
let auxv = auxvec::auxv().expect("read auxvec"); // should not fail on RISC-V platform
2021
#[allow(clippy::eq_op)]
21-
let has_a = bit::test(auxv.hwcap, (b'a' - b'a').into());
22-
enable_feature(Feature::a, has_a);
23-
enable_feature(Feature::zalrsc, has_a);
24-
enable_feature(Feature::zaamo, has_a);
22+
enable_feature(Feature::a, bit::test(auxv.hwcap, (b'a' - b'a').into()));
2523
enable_feature(Feature::c, bit::test(auxv.hwcap, (b'c' - b'a').into()));
26-
let has_d = bit::test(auxv.hwcap, (b'd' - b'a').into());
27-
let has_f = bit::test(auxv.hwcap, (b'f' - b'a').into());
28-
enable_feature(Feature::d, has_d);
29-
enable_feature(Feature::f, has_d | has_f);
30-
enable_feature(Feature::zicsr, has_d | has_f);
24+
enable_feature(Feature::d, bit::test(auxv.hwcap, (b'd' - b'a').into()));
25+
enable_feature(Feature::f, bit::test(auxv.hwcap, (b'f' - b'a').into()));
3126
enable_feature(Feature::h, bit::test(auxv.hwcap, (b'h' - b'a').into()));
3227
enable_feature(Feature::m, bit::test(auxv.hwcap, (b'm' - b'a').into()));
3328

@@ -48,5 +43,5 @@ pub(crate) fn detect_features() -> cache::Initializer {
4843
// to detect when Rust is used to write Linux kernel modules.
4944
// These should be more than Auxvec way to detect supervisor features.
5045

51-
value
46+
imply_features(value)
5247
}
+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//! Run-time feature detection utility for RISC-V.
2+
//!
3+
//! On RISC-V, full feature detection needs a help of one or more
4+
//! feature detection mechanisms (usually provided by the operating system).
5+
//!
6+
//! RISC-V architecture defines many extensions and some have dependency to others.
7+
//! More importantly, some of them cannot be enabled without resolving such
8+
//! dependencies due to limited set of features that such mechanisms provide.
9+
//!
10+
//! This module provides an OS-independent utility to process such relations
11+
//! between RISC-V extensions.
12+
13+
use crate::detect::{Feature, cache};
14+
15+
/// Imply features by the given set of enabled features.
16+
///
17+
/// Note that it does not perform any consistency checks including existence of
18+
/// conflicting extensions and/or complicated requirements. Eliminating such
19+
/// inconsistencies is the responsibility of the feature detection logic and
20+
/// its provider(s).
21+
pub(crate) fn imply_features(mut value: cache::Initializer) -> cache::Initializer {
22+
loop {
23+
// Check convergence of the feature flags later.
24+
let prev = value;
25+
26+
// Expect that the optimizer turns repeated operations into
27+
// a fewer number of bit-manipulation operations.
28+
macro_rules! imply {
29+
// Regular implication:
30+
// A1 => (B1[, B2...]), A2 => (B1[, B2...]) and so on.
31+
($($from: ident)|+ => $($to: ident)&+) => {
32+
if [$(Feature::$from as u32),+].iter().any(|&x| value.test(x)) {
33+
$(
34+
value.set(Feature::$to as u32);
35+
)+
36+
}
37+
};
38+
// Implication with multiple requirements:
39+
// A1 && A2 ... => (B1[, B2...]).
40+
($($from: ident)&+ => $($to: ident)&+) => {
41+
if [$(Feature::$from as u32),+].iter().all(|&x| value.test(x)) {
42+
$(
43+
value.set(Feature::$to as u32);
44+
)+
45+
}
46+
};
47+
}
48+
macro_rules! group {
49+
($group: ident == $($member: ident)&+) => {
50+
// Forward implication as defined in the specifications.
51+
imply!($group => $($member)&+);
52+
// Reverse implication to "group extension" from its members.
53+
// This is not a part of specifications but convenient for
54+
// feature detection and implemented in e.g. LLVM.
55+
imply!($($member)&+ => $group);
56+
};
57+
}
58+
59+
/*
60+
If a dependency/implication is not explicitly stated in the
61+
specification, it is denoted as a comment as follows:
62+
"defined as subset":
63+
The latter extension is described as a subset of the former
64+
(but the evidence is weak).
65+
*/
66+
67+
imply!(zbc => zbkc); // defined as subset
68+
group!(zkn == zbkb & zbkc & zbkx & zkne & zknd & zknh);
69+
group!(zks == zbkb & zbkc & zbkx & zksed & zksh);
70+
group!(zk == zkn & zkr & zkt);
71+
72+
group!(a == zalrsc & zaamo);
73+
74+
group!(b == zba & zbb & zbs);
75+
76+
imply!(zhinx => zhinxmin);
77+
imply!(zdinx | zhinxmin => zfinx);
78+
79+
imply!(zfh => zfhmin);
80+
imply!(q => d);
81+
imply!(d | zfhmin => f);
82+
83+
imply!(zicntr | zihpm | f | zfinx => zicsr);
84+
imply!(s | h => zicsr);
85+
86+
// Loop until the feature flags converge.
87+
if prev == value {
88+
return value;
89+
}
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use super::*;
96+
97+
#[test]
98+
fn simple_direct() {
99+
let mut value = cache::Initializer::default();
100+
value.set(Feature::f as u32);
101+
// F (and other extensions with CSRs) -> Zicsr
102+
assert!(imply_features(value).test(Feature::zicsr as u32));
103+
}
104+
105+
#[test]
106+
fn simple_indirect() {
107+
let mut value = cache::Initializer::default();
108+
value.set(Feature::q as u32);
109+
// Q -> D, D -> F, F -> Zicsr
110+
assert!(imply_features(value).test(Feature::zicsr as u32));
111+
}
112+
113+
#[test]
114+
fn group_simple_forward() {
115+
let mut value = cache::Initializer::default();
116+
// A -> Zalrsc & Zaamo (forward implication)
117+
value.set(Feature::a as u32);
118+
let value = imply_features(value);
119+
assert!(value.test(Feature::zalrsc as u32));
120+
assert!(value.test(Feature::zaamo as u32));
121+
}
122+
123+
#[test]
124+
fn group_simple_backward() {
125+
let mut value = cache::Initializer::default();
126+
// Zalrsc & Zaamo -> A (reverse implication)
127+
value.set(Feature::zalrsc as u32);
128+
value.set(Feature::zaamo as u32);
129+
assert!(imply_features(value).test(Feature::a as u32));
130+
}
131+
132+
#[test]
133+
fn group_complex_convergence() {
134+
let mut value = cache::Initializer::default();
135+
// Needs 2 iterations to converge
136+
// (and 3rd iteration for convergence checking):
137+
// 1. [Zk] -> Zkn & Zkr & Zkt
138+
// 2. Zkn -> {Zbkb} & {Zbkc} & {Zbkx} & {Zkne} & {Zknd} & {Zknh}
139+
value.set(Feature::zk as u32);
140+
let value = imply_features(value);
141+
assert!(value.test(Feature::zbkb as u32));
142+
assert!(value.test(Feature::zbkc as u32));
143+
assert!(value.test(Feature::zbkx as u32));
144+
assert!(value.test(Feature::zkne as u32));
145+
assert!(value.test(Feature::zknd as u32));
146+
assert!(value.test(Feature::zknh as u32));
147+
}
148+
}

0 commit comments

Comments
 (0)