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