llvm_plugin_inkwell_internals/
lib.rs

1//! These macros are only intended to be used by inkwell internally
2//! and should not be expected to have public support nor stability.
3//! Here be dragons 🐉
4
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use quote::quote;
8use syn::fold::Fold;
9use syn::parse::{Error, Parse, ParseStream, Result};
10use syn::spanned::Spanned;
11use syn::{parse_macro_input, parse_quote};
12use syn::{Attribute, Field, Ident, Item, LitFloat, Token, Variant};
13
14// This array should match the LLVM features in the top level Cargo manifest
15const FEATURE_VERSIONS: [&str; 13] = [
16    "llvm4-0", "llvm5-0", "llvm6-0", "llvm7-0", "llvm8-0", "llvm9-0", "llvm10-0", "llvm11-0", "llvm12-0", "llvm13-0",
17    "llvm14-0", "llvm15-0", "llvm16-0",
18];
19
20/// Gets the index of the feature version that represents `latest`
21fn get_latest_feature_index(features: &[&str]) -> usize {
22    features.len() - 1
23}
24
25/// Gets the index of the feature version that matches the input string
26fn get_feature_index(features: &[&str], feature: String, span: Span) -> Result<usize> {
27    let feat = feature.as_str();
28    match features.iter().position(|&s| s == feat) {
29        None => Err(Error::new(
30            span,
31            format!("Invalid feature version: {}, not defined", feature),
32        )),
33        Some(index) => Ok(index),
34    }
35}
36
37/// Gets a vector of feature versions represented by the given VersionType
38fn get_features(vt: VersionType) -> Result<Vec<&'static str>> {
39    let features = FEATURE_VERSIONS;
40    let latest = get_latest_feature_index(&features);
41    match vt {
42        VersionType::Specific(version, span) => {
43            let feature = f64_to_feature_string(version);
44            let index = get_feature_index(&features, feature, span)?;
45            Ok(features[index..=index].to_vec())
46        },
47        VersionType::InclusiveRangeToLatest(version, span) => {
48            let feature = f64_to_feature_string(version);
49            let index = get_feature_index(&features, feature, span)?;
50            Ok(features[index..=latest].to_vec())
51        },
52        VersionType::InclusiveRange((start, start_span), (end, end_span)) => {
53            let start_feature = f64_to_feature_string(start);
54            let end_feature = f64_to_feature_string(end);
55            let start_index = get_feature_index(&features, start_feature, start_span)?;
56            let end_index = get_feature_index(&features, end_feature, end_span)?;
57            if end_index < start_index {
58                let message = format!(
59                    "Invalid version range: {} must be greater than or equal to {}",
60                    start, end
61                );
62                Err(Error::new(end_span, message))
63            } else {
64                Ok(features[start_index..=end_index].to_vec())
65            }
66        },
67        VersionType::ExclusiveRangeToLatest(version, span) => {
68            let feature = f64_to_feature_string(version);
69            let index = get_feature_index(&features, feature, span)?;
70            if latest == index {
71                let message = format!(
72                    "Invalid version range: {}..latest produces an empty feature set",
73                    version
74                );
75                Err(Error::new(span, message))
76            } else {
77                Ok(features[index..latest].to_vec())
78            }
79        },
80        VersionType::ExclusiveRange((start, start_span), (end, end_span)) => {
81            let start_feature = f64_to_feature_string(start);
82            let end_feature = f64_to_feature_string(end);
83            let start_index = get_feature_index(&features, start_feature, start_span)?;
84            let end_index = get_feature_index(&features, end_feature, end_span)?;
85
86            match end_index.cmp(&start_index) {
87                std::cmp::Ordering::Equal => {
88                    let message = format!(
89                        "Invalid version range: {}..{} produces an empty feature set",
90                        start, end
91                    );
92                    Err(Error::new(start_span, message))
93                },
94                std::cmp::Ordering::Less => {
95                    let message = format!("Invalid version range: {} must be greater than {}", start, end);
96                    Err(Error::new(end_span, message))
97                },
98
99                std::cmp::Ordering::Greater => Ok(features[start_index..end_index].to_vec()),
100            }
101        },
102    }
103}
104
105/// Converts a version number as a float to its feature version
106/// string form (e.g. 8.0 ..= llvm8-0)
107fn f64_to_feature_string(float: f64) -> String {
108    let int = float as u64;
109
110    format!("llvm{}-{}", int, (float * 10.) % 10.)
111}
112
113/// This struct defines the type of version expressions parsable by `llvm_versions`
114#[derive(Debug)]
115enum VersionType {
116    Specific(f64, Span),
117    InclusiveRange((f64, Span), (f64, Span)),
118    InclusiveRangeToLatest(f64, Span),
119    ExclusiveRange((f64, Span), (f64, Span)),
120    ExclusiveRangeToLatest(f64, Span),
121}
122impl Parse for VersionType {
123    fn parse(input: ParseStream) -> Result<Self> {
124        // We use lookahead to produce better syntax errors
125        let lookahead = input.lookahead1();
126        // All version specifiers begin with a float
127        if lookahead.peek(LitFloat) {
128            let from = input.parse::<LitFloat>().unwrap();
129            let from_val = from.base10_parse().unwrap();
130            // If that's the end of the input, this was a specific version string
131            if input.is_empty() {
132                return Ok(VersionType::Specific(from_val, from.span()));
133            }
134            // If the next token is ..= it is an inclusive range, .. is exclusive
135            // In both cases the right-hand operand must be either a float or an ident, `latest`
136            let lookahead = input.lookahead1();
137            if lookahead.peek(Token![..=]) {
138                let _: Token![..=] = input.parse().unwrap();
139                let lookahead = input.lookahead1();
140                if lookahead.peek(Ident) {
141                    let to = input.parse::<Ident>().unwrap();
142                    if to == "latest" {
143                        Ok(VersionType::InclusiveRangeToLatest(from_val, from.span()))
144                    } else {
145                        Err(Error::new(to.span(), "expected `latest` or `X.Y`"))
146                    }
147                } else if lookahead.peek(LitFloat) {
148                    let to = input.parse::<LitFloat>().unwrap();
149                    let to_val = to.base10_parse().unwrap();
150                    Ok(VersionType::InclusiveRange(
151                        (from_val, from.span()),
152                        (to_val, to.span()),
153                    ))
154                } else {
155                    Err(lookahead.error())
156                }
157            } else if lookahead.peek(Token![..]) {
158                let _: Token![..] = input.parse().unwrap();
159                let lookahead = input.lookahead1();
160                if lookahead.peek(Ident) {
161                    let to = input.parse::<Ident>().unwrap();
162                    if to == "latest" {
163                        Ok(VersionType::ExclusiveRangeToLatest(from_val, from.span()))
164                    } else {
165                        Err(Error::new(to.span(), "expected `latest` or `X.Y`"))
166                    }
167                } else if lookahead.peek(LitFloat) {
168                    let to = input.parse::<LitFloat>().unwrap();
169                    let to_val = to.base10_parse().unwrap();
170                    Ok(VersionType::ExclusiveRange(
171                        (from_val, from.span()),
172                        (to_val, to.span()),
173                    ))
174                } else {
175                    Err(lookahead.error())
176                }
177            } else {
178                Err(lookahead.error())
179            }
180        } else {
181            Err(lookahead.error())
182        }
183    }
184}
185
186/// Handler for parsing of TokenStreams contained in item attributes
187#[derive(Debug)]
188struct ParenthesizedFeatureSet(FeatureSet);
189impl Parse for ParenthesizedFeatureSet {
190    fn parse(input: ParseStream) -> Result<Self> {
191        input.parse::<FeatureSet>().map(Self)
192    }
193}
194
195/// Handler for parsing of TokenStreams from macro input
196#[derive(Clone, Debug)]
197struct FeatureSet(std::vec::IntoIter<&'static str>, Option<Error>);
198impl Default for FeatureSet {
199    fn default() -> Self {
200        // Default to all versions
201        #[allow(clippy::unnecessary_to_owned)] // Falsely fires since array::IntoIter != vec::IntoIter
202        Self(FEATURE_VERSIONS.to_vec().into_iter(), None)
203    }
204}
205impl Parse for FeatureSet {
206    fn parse(input: ParseStream) -> Result<Self> {
207        let version_type = input.parse::<VersionType>()?;
208        let features = get_features(version_type)?;
209        Ok(Self(features.into_iter(), None))
210    }
211}
212impl Iterator for FeatureSet {
213    type Item = &'static str;
214
215    fn next(&mut self) -> Option<Self::Item> {
216        self.0.next()
217    }
218}
219impl FeatureSet {
220    #[inline]
221    fn has_error(&self) -> bool {
222        self.1.is_some()
223    }
224
225    #[inline]
226    fn set_error(&mut self, err: Error) {
227        self.1 = Some(err);
228    }
229
230    fn into_error(self) -> Error {
231        self.1.unwrap()
232    }
233
234    fn into_compile_error(self) -> TokenStream {
235        TokenStream::from(self.1.unwrap().to_compile_error())
236    }
237
238    fn expand_llvm_versions_attr(&mut self, attr: &Attribute) -> Attribute {
239        // Make no modifications if we've generated an error
240        if self.has_error() {
241            return attr.clone();
242        }
243
244        // If this isn't an llvm_versions attribute, skip it
245        if !attr.path().is_ident("llvm_versions") {
246            return attr.clone();
247        }
248
249        // Expand from llvm_versions to raw cfg attribute
250        match attr.parse_args() {
251            Ok(ParenthesizedFeatureSet(features)) => {
252                parse_quote! {
253                    #[cfg(any(#(feature = #features),*))]
254                }
255            },
256            Err(err) => {
257                // We've hit an error, but we can't break out yet,
258                // so we set the error in the FeatureSet state and
259                // avoid any further modifications until we can produce
260                // the error
261                self.set_error(err);
262                attr.clone()
263            },
264        }
265    }
266}
267impl Fold for FeatureSet {
268    fn fold_variant(&mut self, mut variant: Variant) -> Variant {
269        if self.has_error() {
270            return variant;
271        }
272
273        let attrs = variant
274            .attrs
275            .iter()
276            .map(|attr| self.expand_llvm_versions_attr(attr))
277            .collect::<Vec<_>>();
278        variant.attrs = attrs;
279        variant
280    }
281
282    fn fold_field(&mut self, mut field: Field) -> Field {
283        if self.has_error() {
284            return field;
285        }
286
287        let attrs = field
288            .attrs
289            .iter()
290            .map(|attr| self.expand_llvm_versions_attr(attr))
291            .collect::<Vec<_>>();
292        field.attrs = attrs;
293        field
294    }
295}
296
297/// This macro can be used to specify version constraints for an enum/struct/union or
298/// other item which can be decorated with an attribute.
299///
300/// To use with enum variants or struct fields, you need to decorate the parent item with
301/// the `#[llvm_versioned_item]` attribute, as this is the hook we need to modify the AST
302/// of those items
303///
304/// # Examples
305///
306/// ```ignore
307/// // Inclusive range from 3.6 up to and including 3.9
308/// #[llvm_versions(3.6..=3.9)]
309///
310/// // Exclusive range from 3.6 up to but not including 4.0
311/// #[llvm_versions(3.6..4.0)]
312///
313/// // Inclusive range from 3.6 up to and including the latest release
314/// #[llvm_versions(3.6..=latest)]
315/// ```
316#[proc_macro_attribute]
317pub fn llvm_versions(attribute_args: TokenStream, attributee: TokenStream) -> TokenStream {
318    let mut features = parse_macro_input!(attribute_args as FeatureSet);
319
320    let attributee = parse_macro_input!(attributee as Item);
321    let folded = features.fold_item(attributee);
322
323    if features.has_error() {
324        return features.into_compile_error();
325    }
326
327    // Add nightly only doc cfgs to improve documentation on nightly builds
328    // such as our own hosted docs.
329    let doc = if cfg!(feature = "nightly") {
330        let features2 = features.clone();
331        quote! {
332            #[doc(cfg(any(#(feature = #features2),*)))]
333        }
334    } else {
335        quote! {}
336    };
337
338    let q = quote! {
339        #[cfg(any(#(feature = #features),*))]
340        #doc
341        #folded
342    };
343
344    q.into()
345}
346
347/// This attribute is used to decorate enums, structs, or unions which may contain
348/// variants/fields which make use of `#[llvm_versions(..)]`
349///
350/// # Examples
351///
352/// ```ignore
353/// #[llvm_versioned_item]
354/// enum InstructionOpcode {
355///     Call,
356///     #[llvm_versions(3.8..=latest)]
357///     CatchPad,
358///     ...
359/// }
360/// ```
361#[proc_macro_attribute]
362pub fn llvm_versioned_item(_attribute_args: TokenStream, attributee: TokenStream) -> TokenStream {
363    let attributee = parse_macro_input!(attributee as Item);
364
365    let mut features = FeatureSet::default();
366    let folded = features.fold_item(attributee);
367
368    if features.has_error() {
369        return features.into_compile_error();
370    }
371
372    quote!(#folded).into()
373}
374
375/// Used to track an enum variant and its corresponding mappings (LLVM <-> Rust),
376/// as well as attributes
377struct EnumVariant {
378    llvm_variant: Ident,
379    rust_variant: Ident,
380    attrs: Vec<Attribute>,
381}
382impl EnumVariant {
383    fn new(variant: &Variant) -> Self {
384        let rust_variant = variant.ident.clone();
385        let llvm_variant = Ident::new(&format!("LLVM{}", rust_variant), variant.span());
386        let mut attrs = variant.attrs.clone();
387        attrs.retain(|attr| !attr.path().is_ident("llvm_variant"));
388        Self {
389            llvm_variant,
390            rust_variant,
391            attrs,
392        }
393    }
394
395    fn with_name(variant: &Variant, mut llvm_variant: Ident) -> Self {
396        let rust_variant = variant.ident.clone();
397        llvm_variant.set_span(rust_variant.span());
398        let mut attrs = variant.attrs.clone();
399        attrs.retain(|attr| !attr.path().is_ident("llvm_variant"));
400        Self {
401            llvm_variant,
402            rust_variant,
403            attrs,
404        }
405    }
406}
407
408/// Used when constructing the variants of an enum declaration.
409#[derive(Default)]
410struct EnumVariants {
411    variants: Vec<EnumVariant>,
412    error: Option<Error>,
413}
414impl EnumVariants {
415    #[inline]
416    fn len(&self) -> usize {
417        self.variants.len()
418    }
419
420    #[inline]
421    fn iter(&self) -> core::slice::Iter<'_, EnumVariant> {
422        self.variants.iter()
423    }
424
425    #[inline]
426    fn has_error(&self) -> bool {
427        self.error.is_some()
428    }
429
430    #[inline]
431    fn set_error(&mut self, err: &str, span: Span) {
432        self.error = Some(Error::new(span, err));
433    }
434
435    fn into_error(self) -> Error {
436        self.error.unwrap()
437    }
438}
439impl Fold for EnumVariants {
440    fn fold_variant(&mut self, mut variant: Variant) -> Variant {
441        use syn::Meta;
442
443        if self.has_error() {
444            return variant;
445        }
446
447        // Check for llvm_variant
448        if let Some(attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("llvm_variant")) {
449            // Extract attribute meta
450            if let Meta::List(meta) = &attr.meta {
451                // We should only have one element
452
453                if let Ok(Meta::Path(name)) = meta.parse_args() {
454                    self.variants
455                        .push(EnumVariant::with_name(&variant, name.get_ident().unwrap().clone()));
456                    // Strip the llvm_variant attribute from the final AST
457                    variant.attrs.retain(|attr| !attr.path().is_ident("llvm_variant"));
458                    return variant;
459                }
460            }
461
462            // If at any point we fall through to here, it is the same basic issue, invalid format
463            self.set_error("expected #[llvm_variant(VARIANT_NAME)]", attr.span());
464            return variant;
465        }
466
467        self.variants.push(EnumVariant::new(&variant));
468        variant
469    }
470}
471
472/// Used to parse an enum declaration decorated with `#[llvm_enum(..)]`
473struct LLVMEnumType {
474    name: Ident,
475    decl: syn::ItemEnum,
476    variants: EnumVariants,
477}
478impl Parse for LLVMEnumType {
479    fn parse(input: ParseStream) -> Result<Self> {
480        // Parse enum declaration
481        let decl = input.parse::<syn::ItemEnum>()?;
482        let name = decl.ident.clone();
483        // Fold over variants and expand llvm_versions
484        let mut features = FeatureSet::default();
485        let decl = features.fold_item_enum(decl);
486        if features.has_error() {
487            return Err(features.into_error());
488        }
489
490        let mut variants = EnumVariants::default();
491        let decl = variants.fold_item_enum(decl);
492        if variants.has_error() {
493            return Err(variants.into_error());
494        }
495
496        Ok(Self { name, decl, variants })
497    }
498}
499
500/// This attribute macro allows you to decorate an enum declaration which represents
501/// an LLVM enum with versioning constraints and/or custom variant names. There are
502/// a few expectations around the LLVM and Rust enums:
503///
504/// - Both enums have the same number of variants
505/// - The name of the LLVM variant can be derived by appending 'LLVM' to the Rust variant
506///
507/// The latter can be worked around manually with `#[llvm_variant]` if desired.
508///
509/// # Examples
510///
511/// ```ignore
512/// #[llvm_enum(LLVMOpcode)]
513/// enum InstructionOpcode {
514///     Call,
515///     #[llvm_versions(3.8..=latest)]
516///     CatchPad,
517///     ...,
518///     #[llvm_variant(LLVMRet)]
519///     Return,
520///     ...
521/// }
522/// ```
523///
524/// The use of `#[llvm_variant(NAME)]` allows you to override the default
525/// naming scheme by providing the variant name which the source enum maps
526/// to. In the above example, `Ret` was deemed unnecessarily concise, so the
527/// source variant is named `Return` and mapped manually to `LLVMRet`.
528#[proc_macro_attribute]
529pub fn llvm_enum(attribute_args: TokenStream, attributee: TokenStream) -> TokenStream {
530    use syn::{Arm, PatPath, Path};
531
532    // Expect something like #[llvm_enum(LLVMOpcode)]
533    let llvm_ty = parse_macro_input!(attribute_args as Path);
534    let llvm_enum_type = parse_macro_input!(attributee as LLVMEnumType);
535
536    // Construct match arms for LLVM -> Rust enum conversion
537    let mut from_arms = Vec::with_capacity(llvm_enum_type.variants.len());
538    for variant in llvm_enum_type.variants.iter() {
539        let src_variant = variant.llvm_variant.clone();
540        // Filter out doc comments or else rustc will warn about docs on match arms in newer versions.
541        let src_attrs: Vec<_> = variant
542            .attrs
543            .iter()
544            .filter(|&attr| !attr.meta.path().is_ident("doc"))
545            .collect();
546        let src_ty = llvm_ty.clone();
547        let dst_variant = variant.rust_variant.clone();
548        let dst_ty = llvm_enum_type.name.clone();
549
550        let pat = PatPath {
551            attrs: Vec::new(),
552            qself: None,
553            path: parse_quote!(#src_ty::#src_variant),
554        };
555
556        let arm: Arm = parse_quote! {
557            #(#src_attrs)*
558            #pat => { #dst_ty::#dst_variant }
559        };
560        from_arms.push(arm);
561    }
562
563    // Construct match arms for Rust -> LLVM enum conversion
564    let mut to_arms = Vec::with_capacity(llvm_enum_type.variants.len());
565    for variant in llvm_enum_type.variants.iter() {
566        let src_variant = variant.rust_variant.clone();
567        // Filter out doc comments or else rustc will warn about docs on match arms in newer versions.
568        let src_attrs: Vec<_> = variant
569            .attrs
570            .iter()
571            .filter(|&attr| !attr.meta.path().is_ident("doc"))
572            .collect();
573        let src_ty = llvm_enum_type.name.clone();
574        let dst_variant = variant.llvm_variant.clone();
575        let dst_ty = llvm_ty.clone();
576
577        let pat = PatPath {
578            attrs: Vec::new(),
579            qself: None,
580            path: parse_quote!(#src_ty::#src_variant),
581        };
582
583        let arm: Arm = parse_quote! {
584            #(#src_attrs)*
585            #pat => { #dst_ty::#dst_variant }
586        };
587        to_arms.push(arm);
588    }
589
590    let enum_ty = llvm_enum_type.name.clone();
591    let enum_decl = llvm_enum_type.decl;
592
593    let q = quote! {
594        #enum_decl
595
596        impl #enum_ty {
597            fn new(src: #llvm_ty) -> Self {
598                match src {
599                    #(#from_arms)*
600                }
601            }
602        }
603        impl From<#llvm_ty> for #enum_ty {
604            fn from(src: #llvm_ty) -> Self {
605                Self::new(src)
606            }
607        }
608        impl Into<#llvm_ty> for #enum_ty {
609            fn into(self) -> #llvm_ty {
610                match self {
611                    #(#to_arms),*
612                }
613            }
614        }
615    };
616    q.into()
617}