Skip to main content

rucc_gnu/
lib.rs

1//! The GNU compatibility surface: features.toml, attributes, builtins, pragmas.
2//!
3//! Design: `spec/13-gnu-compat.md`. Layer rank 4, see `spec/18-package-layout.md`.
4//!
5//! # Status
6//!
7//! The matrix is real. `features.toml` next to this file is the source of truth for what the
8//! compiler claims to support, `build.rs` turns it into the table below, and the `__has_*`
9//! family in the preprocessor answers out of it. The attributes and builtins themselves land
10//! with the parser, and every row that says `unimplemented` says so because it is.
11//!
12//! The rule that makes the table worth having is in section 13.2: answering `__has_builtin`
13//! untruthfully is worse than answering no, because a header that gets a yes and then fails
14//! to compile is much harder to diagnose than one that takes its fallback path. So only a row
15//! marked `implemented` answers yes, and a row marked `implemented` with no test named
16//! against it fails the build.
17//!
18//! ```
19//! use rucc_gnu::{Kind, Status};
20//!
21//! assert_eq!(rucc_gnu::has_feature("__has_include"), 1);
22//! assert_eq!(rucc_gnu::has_attribute("cleanup"), 0, "not until the parser lands");
23//! assert_eq!(rucc_gnu::has_attribute("no_such_attribute"), 0);
24//!
25//! // The armoured spelling is the same question.
26//! assert_eq!(rucc_gnu::lookup(Kind::Attribute, "__packed__").map(|f| f.name), Some("packed"));
27//!
28//! // Nested functions are refused rather than pending, and the table says which.
29//! let nested = rucc_gnu::lookup(Kind::Extension, "nested_functions").unwrap();
30//! assert_eq!(nested.status, Status::Rejected);
31//! ```
32//!
33//! Every crate in the workspace is published, and publishing implies a promise. This one is
34//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
35//! Depend on the `rucc` binary's behaviour, not on this.
36
37#![doc(html_root_url = "https://docs.rs/rucc-gnu/0.2.8")]
38
39/// What kind of thing a row of the matrix describes.
40///
41/// The kind is part of the identity of a row, because `deprecated` is both a GNU attribute
42/// and a C23 one and the two are answered by different operators with different values.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub enum Kind {
45    /// `__attribute__((x))` and `[[gnu::x]]`, asked about with `__has_attribute`.
46    Attribute,
47    /// A standard `[[x]]` attribute, asked about with `__has_c_attribute`.
48    CAttribute,
49    /// `__builtin_x`, asked about with `__has_builtin`.
50    Builtin,
51    /// A language or preprocessor feature, asked about with `__has_feature`.
52    Feature,
53    /// A GNU extension to the language, asked about with `__has_extension`.
54    Extension,
55}
56
57/// How far along a row is.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub enum Status {
60    /// Recognised and not done. The `__has_*` operators answer no.
61    Unimplemented,
62    /// Some of it works. The `__has_*` operators still answer no, because a feature that
63    /// works most of the time is exactly the case where the fallback path is the safer one.
64    Partial,
65    /// Done, with a test named against it.
66    Implemented,
67    /// Will not be done, and the row says why. `nested_functions` is the example.
68    Rejected,
69}
70
71impl Status {
72    /// Whether the `__has_*` family answers yes for a row at this status.
73    pub const fn is_available(self) -> bool {
74        matches!(self, Status::Implemented)
75    }
76}
77
78/// What happens when the compiler meets something this row describes and cannot do it.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub enum Answer {
81    /// Warn and carry on, which is what GCC does for an attribute it does not know. Ignoring
82    /// `hot` produces slower code and nothing worse.
83    Warn,
84    /// Refuse. Ignoring `packed`, `aligned`, `section`, `no_sanitize` or `naked` produces
85    /// wrong code rather than slow code, and wrong code that compiles is the worst outcome
86    /// available. This is section 13.4's rule.
87    Error,
88}
89
90/// One row of the matrix.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct Feature {
93    /// The spelling asked about, with no `__` armour on it.
94    pub name: &'static str,
95    /// Which operator answers for it.
96    pub kind: Kind,
97    /// The GCC release that introduced it.
98    pub gcc_version: &'static str,
99    /// How far along it is.
100    pub status: Status,
101    /// What to do when it is met and is not implemented.
102    pub answer: Answer,
103    /// What `__has_c_attribute` answers with, which the standard fixes per attribute. One for
104    /// every other kind, where the operators answer one or nothing.
105    pub value: u32,
106    /// Projects known to need it, from the corpus in `spec/15-testing.md`.
107    pub used_by: &'static [&'static str],
108    /// The tests that prove the status, named as `crate::test` or as a file path.
109    pub tests: &'static [&'static str],
110    /// Anything a reader needs that the fields above do not say.
111    pub notes: &'static str,
112}
113
114include!(concat!(env!("OUT_DIR"), "/features.rs"));
115
116/// The whole matrix, sorted by kind and then by name.
117pub fn features() -> &'static [Feature] {
118    FEATURES
119}
120
121/// The row for a name, if the matrix has one.
122///
123/// The `__x__` spelling is the same question as `x`, because that is how a header writes an
124/// attribute name that a macro might otherwise have taken.
125pub fn lookup(kind: Kind, name: &str) -> Option<&'static Feature> {
126    let bare = unarmour(name);
127    let at = FEATURES.binary_search_by(|f| f.kind.cmp(&kind).then_with(|| f.name.cmp(bare)));
128    at.ok().map(|at| &FEATURES[at])
129}
130
131/// What `__has_attribute(name)` answers.
132pub fn has_attribute(name: &str) -> u32 {
133    answer(Kind::Attribute, name)
134}
135
136/// What `__has_c_attribute(name)` answers, which is the number the standard gives the
137/// attribute rather than one.
138pub fn has_c_attribute(name: &str) -> u32 {
139    answer(Kind::CAttribute, name)
140}
141
142/// What `__has_builtin(name)` answers.
143pub fn has_builtin(name: &str) -> u32 {
144    answer(Kind::Builtin, name)
145}
146
147/// What `__has_feature(name)` answers.
148pub fn has_feature(name: &str) -> u32 {
149    answer(Kind::Feature, name)
150}
151
152/// What `__has_extension(name)` answers.
153///
154/// GCC treats the two as the same question and so do we: a feature that is available is
155/// available whether or not the mode it is asked in makes it standard.
156pub fn has_extension(name: &str) -> u32 {
157    let extension = answer(Kind::Extension, name);
158    if extension == 0 { answer(Kind::Feature, name) } else { extension }
159}
160
161fn answer(kind: Kind, name: &str) -> u32 {
162    match lookup(kind, name) {
163        Some(feature) if feature.status.is_available() => feature.value,
164        _ => 0,
165    }
166}
167
168/// `__packed__` and `packed` are the same attribute.
169fn unarmour(name: &str) -> &str {
170    let bare = name.strip_prefix("__").and_then(|n| n.strip_suffix("__"));
171    match bare {
172        // `__builtin_x` and the atomics keep their prefix, because it is part of the name
173        // rather than armour around it.
174        Some(bare) if !bare.is_empty() && !name.starts_with("__builtin") => bare,
175        _ => name,
176    }
177}
178
179/// The milestone in `spec/17-milestones.md` that fills this crate in.
180pub const MILESTONE: &str = "M1";
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn the_table_is_sorted_so_the_lookup_can_be_a_search() {
188        let keys: Vec<(Kind, &str)> = FEATURES.iter().map(|f| (f.kind, f.name)).collect();
189        let mut sorted = keys.clone();
190        sorted.sort_unstable();
191        assert_eq!(keys, sorted);
192    }
193
194    #[test]
195    fn every_row_is_findable_by_its_own_name() {
196        for feature in FEATURES {
197            assert_eq!(lookup(feature.kind, feature.name), Some(feature));
198        }
199    }
200
201    #[test]
202    fn a_name_that_is_not_in_the_matrix_answers_no() {
203        assert_eq!(has_attribute("nonesuch"), 0);
204        assert_eq!(has_builtin("__builtin_nonesuch"), 0);
205        assert_eq!(has_feature("nonesuch"), 0);
206        assert_eq!(lookup(Kind::Attribute, "nonesuch"), None);
207    }
208
209    #[test]
210    fn the_armoured_spelling_is_the_same_question() {
211        assert_eq!(lookup(Kind::Attribute, "__packed__").map(|f| f.name), Some("packed"));
212        assert_eq!(lookup(Kind::Attribute, "packed").map(|f| f.name), Some("packed"));
213        assert_eq!(lookup(Kind::Attribute, "__packed"), None, "half the armour is not a name");
214    }
215
216    #[test]
217    fn a_builtin_keeps_the_prefix_that_is_part_of_its_name() {
218        assert!(lookup(Kind::Builtin, "__builtin_expect").is_some());
219        assert_eq!(lookup(Kind::Builtin, "expect"), None);
220    }
221
222    #[test]
223    fn only_an_implemented_row_answers_yes() {
224        for feature in FEATURES {
225            let answered = answer(feature.kind, feature.name);
226            assert_eq!(
227                answered != 0,
228                feature.status == Status::Implemented,
229                "{} answered {answered} at status {:?}",
230                feature.name,
231                feature.status
232            );
233        }
234    }
235
236    #[test]
237    fn an_implemented_row_names_a_test() {
238        // build.rs enforces this too. It is here as well because the build script failing is
239        // a harder message to read than a failing test.
240        for feature in FEATURES {
241            if feature.status == Status::Implemented {
242                assert!(!feature.tests.is_empty(), "{} claims to be implemented", feature.name);
243            }
244        }
245    }
246
247    #[test]
248    fn a_c_attribute_answers_with_the_number_the_standard_gives_it() {
249        let deprecated = lookup(Kind::CAttribute, "deprecated").expect("C23 has it");
250        assert_eq!(deprecated.value, 201904);
251        // And it is a different row from the GNU attribute of the same name.
252        let gnu = lookup(Kind::Attribute, "deprecated").expect("GCC has it too");
253        assert_eq!(gnu.value, 1);
254    }
255
256    #[test]
257    fn ignoring_an_attribute_silently_is_a_decision_the_table_records() {
258        let packed = lookup(Kind::Attribute, "packed").expect("in the table");
259        assert_eq!(packed.answer, Answer::Error, "ignoring it would produce wrong code");
260        let cold = lookup(Kind::Attribute, "cold").expect("in the table");
261        assert_eq!(cold.answer, Answer::Warn, "ignoring it would only produce slow code");
262    }
263
264    #[test]
265    fn nested_functions_are_rejected_rather_than_pending() {
266        let nested = lookup(Kind::Extension, "nested_functions").expect("in the table");
267        assert_eq!(nested.status, Status::Rejected);
268        assert!(!nested.notes.is_empty(), "a rejection has to say why");
269    }
270
271    #[test]
272    fn milestone_is_recorded() {
273        assert!(MILESTONE.starts_with('M'));
274    }
275}