• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #![recursion_limit = "128"]
6 
7 extern crate proc_macro;
8 extern crate proc_macro2;
9 extern crate quote;
10 extern crate syn;
11 
12 use proc_macro2::{Ident, TokenStream};
13 use quote::quote;
14 use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Index, Member, Variant};
15 
16 #[cfg(test)]
17 mod tests;
18 
19 // The method for packing an enum into a u64 is as follows:
20 // 1) Reserve the lowest "ceil(log_2(x))" bits where x is the number of enum variants.
21 // 2) Store the enum variant's index (0-based index based on order in the enum definition) in
22 //    reserved bits.
23 // 3) If there is data in the enum variant, store the data in remaining bits.
24 // The method for unpacking is as follows
25 // 1) Mask the raw token to just the reserved bits
26 // 2) Match the reserved bits to the enum variant token.
27 // 3) If the indicated enum variant had data, extract it from the unreserved bits.
28 
29 // Calculates the number of bits needed to store the variant index. Essentially the log base 2
30 // of the number of variants, rounded up.
variant_bits(variants: &[Variant]) -> u3231 fn variant_bits(variants: &[Variant]) -> u32 {
32     if variants.is_empty() {
33         // The degenerate case of no variants.
34         0
35     } else {
36         variants.len().next_power_of_two().trailing_zeros()
37     }
38 }
39 
40 // Name of the field if it has one, otherwise 0 assuming this is the zeroth
41 // field of a tuple variant.
field_member(field: &Field) -> Member42 fn field_member(field: &Field) -> Member {
43     match &field.ident {
44         Some(name) => Member::Named(name.clone()),
45         None => Member::Unnamed(Index::from(0)),
46     }
47 }
48 
49 // Generates the function body for `as_raw_token`.
generate_as_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream50 fn generate_as_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream {
51     let variant_bits = variant_bits(variants);
52 
53     // Each iteration corresponds to one variant's match arm.
54     let cases = variants.iter().enumerate().map(|(index, variant)| {
55         let variant_name = &variant.ident;
56         let index = index as u64;
57 
58         // The capture string is for everything between the variant identifier and the `=>` in
59         // the match arm: the variant's data capture.
60         let capture = variant.fields.iter().next().map(|field| {
61             let member = field_member(&field);
62             quote!({ #member: data })
63         });
64 
65         // The modifier string ORs the variant index with extra bits from the variant data
66         // field.
67         let modifier = match variant.fields {
68             Fields::Named(_) | Fields::Unnamed(_) => Some(quote! {
69                 | ((data as u64) << #variant_bits)
70             }),
71             Fields::Unit => None,
72         };
73 
74         // Assembly of the match arm.
75         quote! {
76             #enum_name::#variant_name #capture => #index #modifier
77         }
78     });
79 
80     quote! {
81         match *self {
82             #(
83                 #cases,
84             )*
85         }
86     }
87 }
88 
89 // Generates the function body for `from_raw_token`.
generate_from_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream90 fn generate_from_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream {
91     let variant_bits = variant_bits(variants);
92     let variant_mask = ((1 << variant_bits) - 1) as u64;
93 
94     // Each iteration corresponds to one variant's match arm.
95     let cases = variants.iter().enumerate().map(|(index, variant)| {
96         let variant_name = &variant.ident;
97         let index = index as u64;
98 
99         // The data string is for extracting the enum variant's data bits out of the raw token
100         // data, which includes both variant index and data bits.
101         let data = variant.fields.iter().next().map(|field| {
102             let member = field_member(&field);
103             let ty = &field.ty;
104             quote!({ #member: (data >> #variant_bits) as #ty })
105         });
106 
107         // Assembly of the match arm.
108         quote! {
109             #index => #enum_name::#variant_name #data
110         }
111     });
112 
113     quote! {
114         // The match expression only matches the bits for the variant index.
115         match data & #variant_mask {
116             #(
117                 #cases,
118             )*
119             _ => unreachable!(),
120         }
121     }
122 }
123 
124 // The proc_macro::TokenStream type can only be constructed from within a
125 // procedural macro, meaning that unit tests are not able to invoke `fn
126 // poll_token` below as an ordinary Rust function. We factor out the logic into
127 // a signature that deals with Syn and proc-macro2 types only which are not
128 // restricted to a procedural macro invocation.
poll_token_inner(input: DeriveInput) -> TokenStream129 fn poll_token_inner(input: DeriveInput) -> TokenStream {
130     let variants: Vec<Variant> = match input.data {
131         Data::Enum(data) => data.variants.into_iter().collect(),
132         Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
133     };
134 
135     for variant in &variants {
136         assert!(variant.fields.iter().count() <= 1);
137     }
138 
139     // Given our basic model of a user given enum that is suitable as a token, we generate the
140     // implementation. The implementation is NOT always well formed, such as when a variant's data
141     // type is not bit shiftable or castable to u64, but we let Rust generate such errors as it
142     // would be difficult to detect every kind of error. Importantly, every implementation that we
143     // generate here and goes on to compile succesfully is sound.
144 
145     let enum_name = input.ident;
146     let as_raw_token = generate_as_raw_token(&enum_name, &variants);
147     let from_raw_token = generate_from_raw_token(&enum_name, &variants);
148 
149     quote! {
150         impl PollToken for #enum_name {
151             fn as_raw_token(&self) -> u64 {
152                 #as_raw_token
153             }
154 
155             fn from_raw_token(data: u64) -> Self {
156                 #from_raw_token
157             }
158         }
159     }
160 }
161 
162 /// Implements the PollToken trait for a given `enum`.
163 ///
164 /// There are limitations on what `enum`s this custom derive will work on:
165 ///
166 /// * Each variant must be a unit variant (no data), or have a single (un)named data field.
167 /// * If a variant has data, it must be a primitive type castable to and from a `u64`.
168 /// * If a variant data has size greater than or equal to a `u64`, its most significant bits must be
169 ///   zero. The number of bits truncated is equal to the number of bits used to store the variant
170 ///   index plus the number of bits above 64.
171 #[proc_macro_derive(PollToken)]
poll_token(input: proc_macro::TokenStream) -> proc_macro::TokenStream172 pub fn poll_token(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
173     let input = parse_macro_input!(input as DeriveInput);
174     poll_token_inner(input).into()
175 }
176