// Copyright 2018 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34#![recursion_limit = "128"]56extern crate proc_macro;78use proc_macro2::Ident;9use proc_macro2::TokenStream;10use quote::quote;11use syn::parse_macro_input;12use syn::Data;13use syn::DeriveInput;14use syn::Field;15use syn::Fields;16use syn::Index;17use syn::Member;18use syn::Variant;1920#[cfg(test)]21mod tests;2223// The method for packing an enum into a u64 is as follows:24// 1) Reserve the lowest "ceil(log_2(x))" bits where x is the number of enum variants.25// 2) Store the enum variant's index (0-based index based on order in the enum definition) in26// reserved bits.27// 3) If there is data in the enum variant, store the data in remaining bits.28// The method for unpacking is as follows29// 1) Mask the raw token to just the reserved bits30// 2) Match the reserved bits to the enum variant token.31// 3) If the indicated enum variant had data, extract it from the unreserved bits.3233// Calculates the number of bits needed to store the variant index. Essentially the log base 234// of the number of variants, rounded up.35fn variant_bits(variants: &[Variant]) -> u32 {36if variants.is_empty() {37// The degenerate case of no variants.38039} else {40variants.len().next_power_of_two().trailing_zeros()41}42}4344// Name of the field if it has one, otherwise 0 assuming this is the zeroth45// field of a tuple variant.46fn field_member(field: &Field) -> Member {47match &field.ident {48Some(name) => Member::Named(name.clone()),49None => Member::Unnamed(Index::from(0)),50}51}5253// Generates the function body for `as_raw_token`.54fn generate_as_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream {55let variant_bits = variant_bits(variants);5657// Each iteration corresponds to one variant's match arm.58let cases = variants.iter().enumerate().map(|(index, variant)| {59let variant_name = &variant.ident;60let index = index as u64;6162// The capture string is for everything between the variant identifier and the `=>` in63// the match arm: the variant's data capture.64let capture = variant.fields.iter().next().map(|field| {65let member = field_member(field);66quote!({ #member: data })67});6869// The modifier string ORs the variant index with extra bits from the variant data70// field.71let modifier = match variant.fields {72Fields::Named(_) | Fields::Unnamed(_) => Some(quote! {73| ((data as u64) << #variant_bits)74}),75Fields::Unit => None,76};7778// Assembly of the match arm.79quote! {80#enum_name::#variant_name #capture => #index #modifier81}82});8384quote! {85match *self {86#(87#cases,88)*89}90}91}9293// Generates the function body for `from_raw_token`.94fn generate_from_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream {95let variant_bits = variant_bits(variants);96let variant_mask = ((1 << variant_bits) - 1) as u64;9798// Each iteration corresponds to one variant's match arm.99let cases = variants.iter().enumerate().map(|(index, variant)| {100let variant_name = &variant.ident;101let index = index as u64;102103// The data string is for extracting the enum variant's data bits out of the raw token104// data, which includes both variant index and data bits.105let data = variant.fields.iter().next().map(|field| {106let member = field_member(field);107let ty = &field.ty;108quote!({ #member: (data >> #variant_bits) as #ty })109});110111// Assembly of the match arm.112quote! {113#index => #enum_name::#variant_name #data114}115});116117quote! {118// The match expression only matches the bits for the variant index.119match data & #variant_mask {120#(121#cases,122)*123_ => unreachable!(),124}125}126}127128// The proc_macro::TokenStream type can only be constructed from within a129// procedural macro, meaning that unit tests are not able to invoke `fn130// event_token` below as an ordinary Rust function. We factor out the logic into131// a signature that deals with Syn and proc-macro2 types only which are not132// restricted to a procedural macro invocation.133fn event_token_inner(input: DeriveInput) -> TokenStream {134let variants: Vec<Variant> = match input.data {135Data::Enum(data) => data.variants.into_iter().collect(),136Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),137};138139for variant in &variants {140assert!(variant.fields.iter().count() <= 1);141}142143// Given our basic model of a user given enum that is suitable as a token, we generate the144// implementation. The implementation is NOT always well formed, such as when a variant's data145// type is not bit shiftable or castable to u64, but we let Rust generate such errors as it146// would be difficult to detect every kind of error. Importantly, every implementation that we147// generate here and goes on to compile succesfully is sound.148149let enum_name = input.ident;150let as_raw_token = generate_as_raw_token(&enum_name, &variants);151let from_raw_token = generate_from_raw_token(&enum_name, &variants);152153quote! {154impl EventToken for #enum_name {155fn as_raw_token(&self) -> u64 {156#as_raw_token157}158159fn from_raw_token(data: u64) -> Self {160#from_raw_token161}162}163}164}165166/// Implements the EventToken trait for a given `enum`.167///168/// There are limitations on what `enum`s this custom derive will work on:169///170/// * Each variant must be a unit variant (no data), or have a single (un)named data field.171/// * If a variant has data, it must be a primitive type castable to and from a `u64`.172/// * If a variant data has size greater than or equal to a `u64`, its most significant bits must be173/// zero. The number of bits truncated is equal to the number of bits used to store the variant174/// index plus the number of bits above 64.175#[proc_macro_derive(EventToken)]176pub fn event_token(input: proc_macro::TokenStream) -> proc_macro::TokenStream {177let input = parse_macro_input!(input as DeriveInput);178event_token_inner(input).into()179}180181182