Skip to content

Commit c9f7d90

Browse files
committed
[DO NOT MERGE] RISC-V: OS-independent implication logic
RFC: where to put `imply_features`? 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 enable that loop, `cache::Initializer` is modified to derive `Eq`.
1 parent 5e29430 commit c9f7d90

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-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/os/linux/riscv.rs

+74-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,75 @@
33
use super::auxvec;
44
use crate::detect::{Feature, bit, cache};
55

6+
/// Imply features by the given set of enabled features.
7+
///
8+
/// Note that it does not perform any consistency checks including existence of
9+
/// conflicting extensions and/or complicated requirements. Eliminating such
10+
/// inconsistencies is the responsibility of the feature detection logic and
11+
/// its provider(s).
12+
pub(crate) fn imply_features(mut value: cache::Initializer) -> cache::Initializer {
13+
loop {
14+
// Check convergence of feature flags later.
15+
let prev = value;
16+
17+
// Expect that the optimizer turns repeated operations into
18+
// a fewer number of bit-manipulation operations.
19+
macro_rules! imply {
20+
// Regular implication:
21+
// A1 => (B1[, B2...]), A2 => (B1[, B2...]) and so on.
22+
($($from: ident)|+ => $($to: ident)&+) => {
23+
if [$(Feature::$from as u32),+].iter().any(|&x| value.test(x)) {
24+
$(
25+
value.set(Feature::$to as u32);
26+
)+
27+
}
28+
};
29+
// Implication with multiple requirements:
30+
// A1 && A2 ... => (B1[, B2...]).
31+
($($from: ident)&+ => $($to: ident)&+) => {
32+
if [$(Feature::$from as u32),+].iter().all(|&x| value.test(x)) {
33+
$(
34+
value.set(Feature::$to as u32);
35+
)+
36+
}
37+
};
38+
}
39+
macro_rules! group {
40+
($group: ident == $($member: ident)&+) => {
41+
// Forward implication as defined in the specifications.
42+
imply!($group => $($member)&+);
43+
// Reverse implication to "group extension" from its members.
44+
// This is not a part of specifications but convenient for
45+
// feature detection and implemented in e.g. LLVM.
46+
imply!($($member)&+ => $group);
47+
};
48+
}
49+
50+
group!(zkn == zbkb & zbkc & zbkx & zkne & zknd & zknh);
51+
group!(zks == zbkb & zbkc & zbkx & zksed & zksh);
52+
group!(zk == zkn & zkr & zkt);
53+
54+
group!(a == zalrsc & zaamo);
55+
56+
group!(b == zba & zbb & zbs);
57+
58+
imply!(zhinx => zhinxmin);
59+
imply!(zdinx | zhinxmin => zfinx);
60+
61+
imply!(zfh => zfhmin);
62+
imply!(q => d);
63+
imply!(d | zfhmin => f);
64+
65+
imply!(zicntr | zihpm | zkr | f | zfinx => zicsr);
66+
imply!(s | h => zicsr);
67+
68+
// Loop until the feature flags converge.
69+
if prev == value {
70+
return value;
71+
}
72+
}
73+
}
74+
675
/// Read list of supported features from the auxiliary vector.
776
pub(crate) fn detect_features() -> cache::Initializer {
877
let mut value = cache::Initializer::default();
@@ -12,22 +81,16 @@ pub(crate) fn detect_features() -> cache::Initializer {
1281
}
1382
};
1483

15-
// Use auxiliary vector to enable single-letter ISA extensions and Zicsr.
84+
// Use auxiliary vector to enable single-letter ISA extensions.
1685
// The values are part of the platform-specific [asm/hwcap.h][hwcap]
1786
//
1887
// [hwcap]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/include/uapi/asm/hwcap.h?h=v6.14
1988
let auxv = auxvec::auxv().expect("read auxvec"); // should not fail on RISC-V platform
2089
#[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);
90+
enable_feature(Feature::a, bit::test(auxv.hwcap, (b'a' - b'a').into()));
2591
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);
92+
enable_feature(Feature::d, bit::test(auxv.hwcap, (b'd' - b'a').into()));
93+
enable_feature(Feature::f, bit::test(auxv.hwcap, (b'f' - b'a').into()));
3194
enable_feature(Feature::h, bit::test(auxv.hwcap, (b'h' - b'a').into()));
3295
enable_feature(Feature::m, bit::test(auxv.hwcap, (b'm' - b'a').into()));
3396

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

51-
value
114+
imply_features(value)
52115
}

0 commit comments

Comments
 (0)