rucc_gnu/lib.rs
1//! The GNU compatibility surface: features.toml, attributes, builtins, pragmas.
2//!
3//! Design: `spec/13-gnu-compat.md`. Layer rank 5, 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"), 1);
23//! assert_eq!(rucc_gnu::has_attribute("transparent_union"), 1);
24//! assert_eq!(rucc_gnu::has_attribute("no_such_attribute"), 0);
25//!
26//! // The armoured spelling is the same question.
27//! assert_eq!(rucc_gnu::lookup(Kind::Attribute, "__packed__").map(|f| f.name), Some("packed"));
28//!
29//! // Nested functions are refused rather than pending, and the table says which.
30//! let nested = rucc_gnu::lookup(Kind::Extension, "nested_functions").unwrap();
31//! assert_eq!(nested.status, Status::Rejected);
32//! ```
33//!
34//! Every crate in the workspace is published, and publishing implies a promise. This one is
35//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
36//! Depend on the `rucc` binary's behaviour, not on this.
37
38#![doc(html_root_url = "https://docs.rs/rucc-gnu/0.10.72")]
39
40/// What kind of thing a row of the matrix describes.
41///
42/// The kind is part of the identity of a row, because `deprecated` is both a GNU attribute
43/// and a C23 one and the two are answered by different operators with different values.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub enum Kind {
46 /// `__attribute__((x))` and `[[gnu::x]]`, asked about with `__has_attribute`.
47 Attribute,
48 /// A standard `[[x]]` attribute, asked about with `__has_c_attribute`.
49 CAttribute,
50 /// `__builtin_x`, asked about with `__has_builtin`.
51 Builtin,
52 /// A language or preprocessor feature, asked about with `__has_feature`.
53 Feature,
54 /// A GNU extension to the language, asked about with `__has_extension`.
55 Extension,
56}
57
58/// How far along a row is.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
60pub enum Status {
61 /// Recognised and not done. The `__has_*` operators answer no.
62 Unimplemented,
63 /// Some of it works. The `__has_*` operators still answer no, because a feature that
64 /// works most of the time is exactly the case where the fallback path is the safer one.
65 Partial,
66 /// Done, with a test named against it.
67 Implemented,
68 /// Will not be done, and the row says why. `nested_functions` is the example.
69 Rejected,
70}
71
72impl Status {
73 /// Whether the `__has_*` family answers yes for a row at this status.
74 pub const fn is_available(self) -> bool {
75 matches!(self, Status::Implemented)
76 }
77}
78
79/// What happens when the compiler meets something this row describes and cannot do it.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81pub enum Answer {
82 /// Warn and carry on, which is what GCC does for an attribute it does not know. Ignoring
83 /// `hot` produces slower code and nothing worse.
84 Warn,
85 /// Refuse. Ignoring `packed`, `aligned`, `section`, `no_sanitize` or `naked` produces
86 /// wrong code rather than slow code, and wrong code that compiles is the worst outcome
87 /// available. This is section 13.4's rule.
88 Error,
89}
90
91/// One row of the matrix.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct Feature {
94 /// The spelling asked about, with no `__` armour on it.
95 pub name: &'static str,
96 /// Which operator answers for it.
97 pub kind: Kind,
98 /// The GCC release that introduced it.
99 pub gcc_version: &'static str,
100 /// How far along it is.
101 pub status: Status,
102 /// The type a builtin has, written as a C prototype without the name, or empty.
103 ///
104 /// Empty for everything that is not a builtin, and for a builtin whose type depends on
105 /// what it is handed: `__builtin_constant_p` takes anything, `__builtin_add_overflow`
106 /// takes three types that have to agree, and the atomics are a family rather than a
107 /// function. Those are decided where the arguments are, and a fixed type here would be a
108 /// worse answer than none.
109 ///
110 /// It is a string rather than a structure because `size_t` is a different type on two
111 /// targets and this table has no target. The compiler reads it once per builtin it is
112 /// asked for. The set of words it may use is fixed and `build.rs` checks it, so a typo
113 /// fails this crate's build rather than the compile of whoever first calls the builtin.
114 pub signature: &'static str,
115 /// The library function this builtin is, for the family where that is the whole answer.
116 ///
117 /// Empty for everything else. GCC's `__builtin_abort` is a call to `abort`, its
118 /// `__builtin_strlen` a call to `strlen`, and the prefix is there so that a program can
119 /// reach the function the C library promises even where its own name has been taken by a
120 /// macro or by a definition of its own. GCC folds some of these when the arguments allow
121 /// it, and folding is an optimization on top: the call is the meaning, and a compiler that
122 /// only ever emits the call is right and slow rather than wrong.
123 ///
124 /// The name is written out rather than worked out by stripping the prefix, because the two
125 /// are the same for every row here and need not be for the next one, and a table that says
126 /// what it means is worth more than one that saves thirty words.
127 pub library: &'static str,
128 /// What to do when it is met and is not implemented.
129 pub answer: Answer,
130 /// What `__has_c_attribute` answers with, which the standard fixes per attribute. One for
131 /// every other kind, where the operators answer one or nothing.
132 pub value: u32,
133 /// Projects known to need it, from the corpus in `spec/15-testing.md`.
134 pub used_by: &'static [&'static str],
135 /// The tests that prove the status, named as `crate::test` or as a file path.
136 pub tests: &'static [&'static str],
137 /// Anything a reader needs that the fields above do not say.
138 pub notes: &'static str,
139}
140
141include!(concat!(env!("OUT_DIR"), "/features.rs"));
142
143/// The whole matrix, sorted by kind and then by name.
144pub fn features() -> &'static [Feature] {
145 FEATURES
146}
147
148/// The row for a name, if the matrix has one.
149///
150/// The `__x__` spelling is the same question as `x`, because that is how a header writes an
151/// attribute name that a macro might otherwise have taken.
152pub fn lookup(kind: Kind, name: &str) -> Option<&'static Feature> {
153 let bare = unarmour(name);
154 let at = FEATURES.binary_search_by(|f| f.kind.cmp(&kind).then_with(|| f.name.cmp(bare)));
155 at.ok().map(|at| &FEATURES[at])
156}
157
158/// What `__has_attribute(name)` answers.
159pub fn has_attribute(name: &str) -> u32 {
160 answer(Kind::Attribute, name)
161}
162
163/// What `__has_c_attribute(name)` answers, which is the number the standard gives the
164/// attribute rather than one.
165pub fn has_c_attribute(name: &str) -> u32 {
166 answer(Kind::CAttribute, name)
167}
168
169/// What `__has_builtin(name)` answers.
170pub fn has_builtin(name: &str) -> u32 {
171 answer(Kind::Builtin, name)
172}
173
174/// What `__has_feature(name)` answers.
175pub fn has_feature(name: &str) -> u32 {
176 answer(Kind::Feature, name)
177}
178
179/// What `__has_extension(name)` answers.
180///
181/// GCC treats the two as the same question and so do we: a feature that is available is
182/// available whether or not the mode it is asked in makes it standard.
183pub fn has_extension(name: &str) -> u32 {
184 let extension = answer(Kind::Extension, name);
185 if extension == 0 { answer(Kind::Feature, name) } else { extension }
186}
187
188fn answer(kind: Kind, name: &str) -> u32 {
189 match lookup(kind, name) {
190 Some(feature) if feature.status.is_available() => feature.value,
191 _ => 0,
192 }
193}
194
195/// `__packed__` and `packed` are the same attribute.
196///
197/// This is public because [`lookup`] is not the only thing that has to know it. Anything that
198/// reads a name out of an attribute list and compares it against a spelling has the same
199/// question, and a header writes the armoured form precisely so that a program's own macro
200/// called `packed` cannot take the plain one, so a compiler that only knows the plain one reads
201/// the wrong layout out of a header that was careful.
202#[must_use]
203pub fn unarmour(name: &str) -> &str {
204 let bare = name.strip_prefix("__").and_then(|n| n.strip_suffix("__"));
205 match bare {
206 // `__builtin_x` and the atomics keep their prefix, because it is part of the name
207 // rather than armour around it.
208 Some(bare) if !bare.is_empty() && !name.starts_with("__builtin") => bare,
209 _ => name,
210 }
211}
212
213/// The milestone in `spec/17-milestones.md` that fills this crate in.
214pub const MILESTONE: &str = "M1";
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn the_table_is_sorted_so_the_lookup_can_be_a_search() {
222 let keys: Vec<(Kind, &str)> = FEATURES.iter().map(|f| (f.kind, f.name)).collect();
223 let mut sorted = keys.clone();
224 sorted.sort_unstable();
225 assert_eq!(keys, sorted);
226 }
227
228 #[test]
229 fn every_row_is_findable_by_its_own_name() {
230 for feature in FEATURES {
231 assert_eq!(lookup(feature.kind, feature.name), Some(feature));
232 }
233 }
234
235 #[test]
236 fn a_name_that_is_not_in_the_matrix_answers_no() {
237 assert_eq!(has_attribute("nonesuch"), 0);
238 assert_eq!(has_builtin("__builtin_nonesuch"), 0);
239 assert_eq!(has_feature("nonesuch"), 0);
240 assert_eq!(lookup(Kind::Attribute, "nonesuch"), None);
241 }
242
243 #[test]
244 fn the_armoured_spelling_is_the_same_question() {
245 assert_eq!(lookup(Kind::Attribute, "__packed__").map(|f| f.name), Some("packed"));
246 assert_eq!(lookup(Kind::Attribute, "packed").map(|f| f.name), Some("packed"));
247 assert_eq!(lookup(Kind::Attribute, "__packed"), None, "half the armour is not a name");
248 }
249
250 #[test]
251 fn a_builtin_keeps_the_prefix_that_is_part_of_its_name() {
252 assert!(lookup(Kind::Builtin, "__builtin_expect").is_some());
253 assert_eq!(lookup(Kind::Builtin, "expect"), None);
254 }
255
256 #[test]
257 fn only_an_implemented_row_answers_yes() {
258 for feature in FEATURES {
259 let answered = answer(feature.kind, feature.name);
260 assert_eq!(
261 answered != 0,
262 feature.status == Status::Implemented,
263 "{} answered {answered} at status {:?}",
264 feature.name,
265 feature.status
266 );
267 }
268 }
269
270 #[test]
271 fn an_implemented_row_names_a_test() {
272 // build.rs enforces this too. It is here as well because the build script failing is
273 // a harder message to read than a failing test.
274 for feature in FEATURES {
275 if feature.status == Status::Implemented {
276 assert!(!feature.tests.is_empty(), "{} claims to be implemented", feature.name);
277 }
278 }
279 }
280
281 #[test]
282 fn a_library_builtin_names_the_function_it_is_and_the_type_to_call_it_with() {
283 let abort = lookup(Kind::Builtin, "__builtin_abort").expect("in the table");
284 assert_eq!(abort.library, "abort");
285 assert_eq!(abort.signature, "void(void)");
286 for feature in FEATURES {
287 if feature.library.is_empty() {
288 continue;
289 }
290 assert_eq!(feature.kind, Kind::Builtin, "{} is not a builtin", feature.name);
291 assert!(!feature.signature.is_empty(), "{} has no type to call with", feature.name);
292 }
293 }
294
295 /// Every one of them so far is the name with the prefix taken off, which is the rule GCC
296 /// documents. The field is written out anyway, so this is what checks the two agree.
297 #[test]
298 fn the_library_function_is_the_name_without_the_prefix() {
299 for feature in FEATURES {
300 if feature.library.is_empty() {
301 continue;
302 }
303 let bare = feature.name.strip_prefix("__builtin_");
304 assert_eq!(bare, Some(feature.library), "{} names something else", feature.name);
305 }
306 }
307
308 #[test]
309 fn a_c_attribute_answers_with_the_number_the_standard_gives_it() {
310 let deprecated = lookup(Kind::CAttribute, "deprecated").expect("C23 has it");
311 assert_eq!(deprecated.value, 201904);
312 // And it is a different row from the GNU attribute of the same name.
313 let gnu = lookup(Kind::Attribute, "deprecated").expect("GCC has it too");
314 assert_eq!(gnu.value, 1);
315 }
316
317 #[test]
318 fn ignoring_an_attribute_silently_is_a_decision_the_table_records() {
319 let packed = lookup(Kind::Attribute, "packed").expect("in the table");
320 assert_eq!(packed.answer, Answer::Error, "ignoring it would produce wrong code");
321 let cold = lookup(Kind::Attribute, "cold").expect("in the table");
322 assert_eq!(cold.answer, Answer::Warn, "ignoring it would only produce slow code");
323 }
324
325 #[test]
326 fn nested_functions_are_rejected_rather_than_pending() {
327 let nested = lookup(Kind::Extension, "nested_functions").expect("in the table");
328 assert_eq!(nested.status, Status::Rejected);
329 assert!(!nested.notes.is_empty(), "a rejection has to say why");
330 }
331
332 #[test]
333 fn milestone_is_recorded() {
334 assert!(MILESTONE.starts_with('M'));
335 }
336}