Skip to main content

cinrs_core/
gnu.rs

1//! The GNU extensions' name tables.
2//!
3//! Two questions are asked of this module, and it matters that both are
4//! answered from the same place: the [parser](crate::parse) and
5//! [sema](crate::sema) ask "what does this attribute or builtin mean?", and the
6//! [preprocessor](crate::pp) answers `__has_attribute`, `__has_builtin`,
7//! `__has_feature` and `__has_extension` from it — so a program that guards a
8//! construct with `#if __has_attribute(packed)` gets an answer that is true of
9//! this implementation rather than of GCC's.
10//!
11//! The one set of names that is *not* here is the atomic builtins', which
12//! live beside the code that implements them in
13//! [`crate::sema::is_atomic_builtin`] — for the same reason: one table, asked
14//! by both.
15//!
16//! `doc/gnu-extensions.md` is the prose version of the same tables.
17
18/// What an `__attribute__` (or a C23 `[[…]]`) asks for.
19#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub enum Attribute {
21    /// `noreturn`, `_Noreturn`: a call to the function does not come back.
22    Noreturn,
23    /// `always_inline`: `#[inline(always)]`.
24    AlwaysInline,
25    /// `noinline`: `#[inline(never)]`.
26    NoInline,
27    /// `cold`, and `hot` as its opposite: `#[cold]`.
28    Cold,
29    /// `hot`, which cancels `cold`.
30    Hot,
31    /// `deprecated`: `#[deprecated]`.
32    Deprecated,
33    /// `fallthrough`, which is a statement attribute and means nothing here —
34    /// a `switch` group falls through in the generated Rust either way.
35    Fallthrough,
36    /// `packed`: no padding, on a record or on one member.
37    Packed,
38    /// `aligned(N)`: raise the alignment.
39    Aligned,
40    /// `section("…")`: `#[unsafe(link_section = "…")]`.
41    Section,
42    /// `constructor`, optionally with a priority: run before `main`.
43    Constructor,
44    /// `destructor`, likewise: run after it.
45    Destructor,
46    /// `cleanup(f)`: call `f(&x)` when `x` goes out of scope.
47    Cleanup,
48    /// `mode(M)`: the declared type is the one the machine mode names.
49    Mode,
50    /// `cinrs_safe`, and `[[cinrs::safe]]`: this crate's own attribute, which
51    /// generates the function without `unsafe` so that `rustc` checks it.
52    Safe,
53    /// `weak`: the symbol may be missing at link time, and its address is then
54    /// null. Refused on a *definition*, where Rust's unstable `#[linkage]`
55    /// would be the only way to say it, and ignored on a declaration; see
56    /// [`crate::sema`].
57    Weak,
58    /// `asm("symbol")` written as an attribute is not a thing, but
59    /// `alias`, `weakref` and the rest are: known, and refused with the reason.
60    Unsupported,
61    /// Known and safe to ignore: a hint, a diagnostic request, or something
62    /// the generated Rust cannot observe.
63    Ignored,
64}
65
66/// The attributes that are refused rather than ignored, with the reason.
67///
68/// Silently ignoring one of these would change what the program *means*, which
69/// is the one thing this crate will not do.
70pub const UNSUPPORTED_ATTRIBUTES: &[(&str, &str)] = &[
71    (
72        "weakref",
73        "is not supported: Rust's `#[linkage]` is unstable, so weak linkage cannot be asked for",
74    ),
75    (
76        "alias",
77        "is not supported: write a function that forwards to the other one instead",
78    ),
79    (
80        "ifunc",
81        "is not supported: choosing an implementation at load time has no stable Rust counterpart",
82    ),
83    (
84        "vector_size",
85        "is not supported: the vector extensions need `core::simd`, which is unstable",
86    ),
87    (
88        "scalar_storage_order",
89        "is not supported: it reverses the byte order of every scalar in the record, and \
90         nothing in the generated Rust could carry that",
91    ),
92];
93
94/// The attribute a name spells, accepting both the `name` and the `__name__`
95/// forms GCC does.
96pub fn attribute(name: &str) -> Option<Attribute> {
97    let bare = name
98        .strip_prefix("__")
99        .and_then(|rest| rest.strip_suffix("__"))
100        .unwrap_or(name);
101    if UNSUPPORTED_ATTRIBUTES.iter().any(|(n, _)| *n == bare) {
102        return Some(Attribute::Unsupported);
103    }
104    Some(match bare {
105        "noreturn" => Attribute::Noreturn,
106        "always_inline" => Attribute::AlwaysInline,
107        "noinline" => Attribute::NoInline,
108        "cold" => Attribute::Cold,
109        "hot" => Attribute::Hot,
110        "deprecated" => Attribute::Deprecated,
111        "fallthrough" => Attribute::Fallthrough,
112        "packed" => Attribute::Packed,
113        "aligned" | "alignas" => Attribute::Aligned,
114        "section" => Attribute::Section,
115        "constructor" => Attribute::Constructor,
116        "destructor" => Attribute::Destructor,
117        "cleanup" => Attribute::Cleanup,
118        "mode" => Attribute::Mode,
119        "weak" => Attribute::Weak,
120        // Not GCC's: this crate's own, spelled the way a GNU attribute of
121        // another vendor's is, so that it works in every entry point.
122        "cinrs_safe" => Attribute::Safe,
123        _ if IGNORED_ATTRIBUTES.contains(&bare) => Attribute::Ignored,
124        _ => return None,
125    })
126}
127
128/// The names `[[cinrs::…]]` knows, for the diagnostic that lists them.
129///
130/// C23 6.7.13.1p3 lets an implementation ignore an attribute in a namespace it
131/// does not know, and [`attribute`] does exactly that for `[[clang::…]]` and
132/// the rest — but `cinrs` is *our* namespace, so a name we do not know there is
133/// a mistake worth reporting, exactly as an unknown `#pragma cinrs` option is.
134pub const CINRS_ATTRIBUTES: &[&str] = &["safe"];
135
136/// What a `[[cinrs::name]]` attribute asks for.
137///
138/// The same attributes are spelled `__attribute__((cinrs_name))` for the entry
139/// points where `[[…]]` is C23 and later only; see [`attribute`].
140pub fn cinrs_attribute(name: &str) -> Option<Attribute> {
141    match name {
142        "safe" => Some(Attribute::Safe),
143        _ => None,
144    }
145}
146
147/// The reason an [`Attribute::Unsupported`] one is refused.
148pub fn unsupported_reason(name: &str) -> Option<&'static str> {
149    let bare = name
150        .strip_prefix("__")
151        .and_then(|rest| rest.strip_suffix("__"))
152        .unwrap_or(name);
153    UNSUPPORTED_ATTRIBUTES
154        .iter()
155        .find(|(n, _)| *n == bare)
156        .map(|(_, reason)| *reason)
157}
158
159/// Attributes that are hints, diagnostic requests or optimiser instructions:
160/// accepted, and ignored exactly as C23 6.7.13.1p3 allows.
161const IGNORED_ATTRIBUTES: &[&str] = &[
162    "access",
163    "alloc_align",
164    "alloc_size",
165    "artificial",
166    "assume_aligned",
167    "cdecl",
168    "const",
169    "designated_init",
170    "error",
171    "externally_visible",
172    "fastcall",
173    "flatten",
174    "format",
175    "format_arg",
176    "gnu_inline",
177    "leaf",
178    "malloc",
179    "may_alias",
180    "maybe_unused",
181    "no_instrument_function",
182    "no_sanitize",
183    "no_split_stack",
184    "noclone",
185    "nodiscard",
186    "noipa",
187    "nonnull",
188    "nonstring",
189    "nothrow",
190    "optimize",
191    "pure",
192    "reproducible",
193    "returns_nonnull",
194    "returns_twice",
195    "sentinel",
196    "stdcall",
197    "target",
198    "target_clones",
199    "transparent_union",
200    "unavailable",
201    "unsequenced",
202    "unused",
203    "used",
204    "visibility",
205    "warn_unused_result",
206    "warning",
207];
208
209/// Whether `__has_attribute(name)` answers yes.
210///
211/// `weak` answers **no** although a declaration carrying it is accepted: what
212/// the question is really asked for is whether a weak *reference* can be
213/// tested for null, and here it cannot — the symbol has to be there at link
214/// time. A program that guards on the answer therefore takes the portable
215/// branch, and one that writes the attribute unguarded on a declaration — as
216/// glibc's `<pthread.h>` does — is not stopped by it.
217pub fn has_attribute(name: &str) -> bool {
218    attribute(name).is_some_and(|a| !matches!(a, Attribute::Unsupported | Attribute::Weak))
219}
220
221/// The value `__has_c_attribute(name)` answers with.
222///
223/// C23 6.10.1p6 wants the revision that added the attribute; every one this
224/// crate honours arrived with C23 itself, so `202311L` is the honest answer,
225/// and everything else is 0.
226pub fn has_c_attribute(name: &str) -> u64 {
227    let standard = matches!(
228        name,
229        "deprecated"
230            | "fallthrough"
231            | "maybe_unused"
232            | "nodiscard"
233            | "noreturn"
234            | "unsequenced"
235            | "reproducible"
236    );
237    if standard { 202_311 } else { 0 }
238}
239
240/// Whether `__has_builtin(name)` answers yes.
241///
242/// The atomic families are builtins too, and are not spelled `__builtin_…`:
243/// `__has_builtin(__atomic_load_n)` and `__has_builtin(__sync_synchronize)`
244/// are what a program guards those with, and GCC and Clang answer both.
245pub fn has_builtin(name: &str) -> bool {
246    if crate::sema::is_atomic_builtin(name) {
247        return true;
248    }
249    let Some(rest) = name.strip_prefix("__builtin_") else {
250        return false;
251    };
252    SPECIAL_BUILTINS.contains(&rest)
253        || LIBRARY_BUILTINS.contains(&rest)
254        || long_double_math(rest).is_some()
255}
256
257/// Whether `__has_feature(name)` / `__has_extension(name)` answers yes.
258///
259/// Clang's vocabulary, answered for what this crate really has:
260/// `c_thread_local`, `blocks` and `nested_functions` are deliberately absent,
261/// because the constructs behind them are diagnosed rather than translated —
262/// a nested function definition is refused where it stands, in
263/// [the parser](crate::parse), so a program that guards one with
264/// `#if __has_extension(nested_functions)` takes the other branch and never
265/// reaches the diagnostic. `c_atomic` is *not* absent any more:
266/// `_Atomic` and `<stdatomic.h>` are here.
267pub fn has_feature(name: &str) -> bool {
268    SUPPORTED_FEATURES.contains(&name)
269}
270
271/// The features `__has_feature` and `__has_extension` answer yes to.
272const SUPPORTED_FEATURES: &[&str] = &[
273    "c_alignas",
274    "c_alignof",
275    "c_atomic",
276    "c_attributes",
277    "c_generic_selection",
278    "c_generic_selections",
279    "c_static_assert",
280    "cinrs",
281];
282
283/// The builtins the front end implements itself.
284///
285/// Sema recognises exactly these names; `__has_builtin` answers from the same
286/// list, so the two can never drift.
287pub const SPECIAL_BUILTINS: &[&str] = &[
288    "add_overflow",
289    "add_overflow_p",
290    "alloca",
291    "alloca_with_align",
292    "assume",
293    "assume_aligned",
294    "bswap16",
295    "bswap32",
296    "bswap64",
297    "choose_expr",
298    "cimag",
299    "cimagf",
300    "cimagl",
301    "classify_type",
302    "clrsb",
303    "clrsbl",
304    "clrsbll",
305    "clz",
306    "clzl",
307    "clzll",
308    "complex",
309    "conj",
310    "conjf",
311    "conjl",
312    "constant_p",
313    "copysign",
314    "copysignf",
315    "copysignl",
316    "cproj",
317    "cprojf",
318    "cprojl",
319    "creal",
320    "crealf",
321    "creall",
322    "ctz",
323    "ctzl",
324    "ctzll",
325    "dynamic_object_size",
326    "expect",
327    "expect_with_probability",
328    "fabs",
329    "fabsf",
330    "fabsl",
331    "ffs",
332    "ffsl",
333    "ffsll",
334    "fpclassify",
335    "huge_val",
336    "huge_valf",
337    "huge_vall",
338    "inf",
339    "inff",
340    "infl",
341    "isfinite",
342    "isgreater",
343    "isgreaterequal",
344    "isinf",
345    "isinf_sign",
346    "isinff",
347    "isinfl",
348    "isless",
349    "islessequal",
350    "islessgreater",
351    "isnan",
352    "isnanf",
353    "isnanl",
354    "isnormal",
355    "issignaling",
356    "isunordered",
357    "mul_overflow",
358    "mul_overflow_p",
359    "nan",
360    "nanf",
361    "nanl",
362    "nans",
363    "nansf",
364    "nansl",
365    "object_size",
366    "offsetof",
367    "parity",
368    "parityl",
369    "parityll",
370    "popcount",
371    "popcountl",
372    "popcountll",
373    "prefetch",
374    "signbit",
375    "signbitf",
376    "signbitl",
377    "sub_overflow",
378    "sub_overflow_p",
379    "trap",
380    "types_compatible_p",
381    "unreachable",
382    "va_arg",
383    "va_copy",
384    "va_end",
385    "va_start",
386    "FILE",
387    "FUNCTION",
388    "LINE",
389];
390
391/// The maths functions whose `long double` form is the `double` one here.
392///
393/// `long double` **is** `double` in `cinrs` — no portable Rust type has an x87
394/// extended double's layout — so `__builtin_sqrtl` may not become a call to
395/// the platform's `sqrtl`, whose argument and result would be an eighty-bit
396/// value the generated Rust cannot pass. It becomes a call to `sqrt`, which is
397/// the same function at the width this implementation gives the type.
398pub fn long_double_math(rest: &str) -> Option<&'static str> {
399    let base = rest.strip_suffix('l')?;
400    LONG_DOUBLE_MATH.iter().copied().find(|name| *name == base)
401}
402
403/// The base names [`long_double_math`] recognises. Every one of them is in
404/// [`LIBRARY_BUILTINS`] too, so a name that reaches the `l` rule has already
405/// failed to match exactly — which is what keeps `__builtin_atoll` from being
406/// read as the `long double` form of `atol`.
407const LONG_DOUBLE_MATH: &[&str] = &[
408    "acos",
409    "asin",
410    "atan",
411    "atan2",
412    "cbrt",
413    "ceil",
414    "cos",
415    "cosh",
416    "erf",
417    "erfc",
418    "exp",
419    "exp2",
420    "expm1",
421    "fdim",
422    "floor",
423    "fma",
424    "fmax",
425    "fmin",
426    "fmod",
427    "frexp",
428    "hypot",
429    "ldexp",
430    "lgamma",
431    "log",
432    "log10",
433    "log1p",
434    "log2",
435    "modf",
436    "nearbyint",
437    "nextafter",
438    "pow",
439    "remainder",
440    "rint",
441    "round",
442    "scalbn",
443    "sin",
444    "sinh",
445    "sqrt",
446    "tan",
447    "tanh",
448    "tgamma",
449    "trunc",
450];
451
452/// The operation a typed overflow builtin performs, if the name spells one.
453///
454/// `__builtin_sadd_overflow` and its fifteen relatives are the generic
455/// builtins with the result type written into the name; the type the value is
456/// stored in says the same thing, so only the operation has to be read out.
457pub fn typed_overflow(rest: &str) -> Option<&'static str> {
458    let rest = rest.strip_prefix('s').or_else(|| rest.strip_prefix('u'))?;
459    let (op, rest) = if let Some(rest) = rest.strip_prefix("add") {
460        ("add", rest)
461    } else if let Some(rest) = rest.strip_prefix("sub") {
462        ("sub", rest)
463    } else {
464        ("mul", rest.strip_prefix("mul")?)
465    };
466    matches!(rest, "_overflow" | "l_overflow" | "ll_overflow").then_some(op)
467}
468
469/// The library functions `__builtin_X` may name.
470///
471/// GCC has a builtin for every standard library function; `cinrs` calls the
472/// real one, declaring it if the unit did not include the header. The list is
473/// what the bundled headers declare, which is what a call can be typed from.
474pub const LIBRARY_BUILTINS: &[&str] = &[
475    "_Exit",
476    "abort",
477    "abs",
478    "acos",
479    "acosf",
480    "asin",
481    "asinf",
482    "atan",
483    "atan2",
484    "atan2f",
485    "atanf",
486    "atof",
487    "atoi",
488    "atol",
489    "atoll",
490    "bcmp",
491    "bcopy",
492    "bzero",
493    "calloc",
494    "cbrt",
495    "cbrtf",
496    "ceil",
497    "ceilf",
498    "cos",
499    "cosf",
500    "cosh",
501    "coshf",
502    "erf",
503    "erfc",
504    "exit",
505    "exp",
506    "exp2",
507    "exp2f",
508    "expf",
509    "expm1",
510    "expm1f",
511    "fdim",
512    "fdimf",
513    "floor",
514    "floorf",
515    "fma",
516    "fmaf",
517    "fmax",
518    "fmaxf",
519    "fmin",
520    "fminf",
521    "fmod",
522    "fmodf",
523    "fprintf",
524    "fputc",
525    "fputs",
526    "free",
527    "frexp",
528    "frexpf",
529    "hypot",
530    "hypotf",
531    "imaxabs",
532    "index",
533    "isalnum",
534    "isalpha",
535    "isblank",
536    "iscntrl",
537    "isdigit",
538    "isgraph",
539    "islower",
540    "isprint",
541    "ispunct",
542    "isspace",
543    "isupper",
544    "isxdigit",
545    "labs",
546    "ldexp",
547    "ldexpf",
548    "lgamma",
549    "llabs",
550    "log",
551    "log10",
552    "log10f",
553    "log1p",
554    "log1pf",
555    "log2",
556    "log2f",
557    "logf",
558    "malloc",
559    "memchr",
560    "memcmp",
561    "memcpy",
562    "memmove",
563    "mempcpy",
564    "memset",
565    "modf",
566    "modff",
567    "nearbyint",
568    "nearbyintf",
569    "nextafter",
570    "nextafterf",
571    "pow",
572    "powf",
573    "printf",
574    "putchar",
575    "puts",
576    "realloc",
577    "remainder",
578    "remainderf",
579    "rindex",
580    "rint",
581    "rintf",
582    "round",
583    "roundf",
584    "scalbn",
585    "scalbnf",
586    "sin",
587    "sinf",
588    "sinh",
589    "sinhf",
590    "snprintf",
591    "sprintf",
592    "sqrt",
593    "sqrtf",
594    "stpcpy",
595    "stpncpy",
596    "strcasecmp",
597    "strcat",
598    "strchr",
599    "strcmp",
600    "strcoll",
601    "strcpy",
602    "strcspn",
603    "strdup",
604    "strlen",
605    "strncasecmp",
606    "strncat",
607    "strncmp",
608    "strncpy",
609    "strpbrk",
610    "strrchr",
611    "strspn",
612    "strstr",
613    "strtod",
614    "strtof",
615    "strtol",
616    "strtoll",
617    "strtoul",
618    "strtoull",
619    "tan",
620    "tanf",
621    "tanh",
622    "tanhf",
623    "tgamma",
624    "tolower",
625    "toupper",
626    "trunc",
627    "truncf",
628];