use darling::FromMeta as _;
use proc_macro::TokenStream;
use quote::{
__private::{Span as Span2, TokenStream as TokenStream2},
format_ident,
quote,
ToTokens,
};
macro_rules! unwrap {
($expr:expr) => {
match $expr {
Ok(it) => it,
Err(e) => {
return e;
}
}
};
}
macro_rules! fail {
($span:expr, $msg:expr $(,)?) => {{
return make_error($span, $msg);
}};
}
fn make_error(span: Span2, msg: &'static str) -> TokenStream {
syn::Error::new(span, msg).into_compile_error().into()
}
macro_rules! assert_no_generics {
($item_span:expr, $item_generics:expr $(,)?) => {
if !$item_generics.params.is_empty() {
fail!($item_span, "generic parameters are not supported in this context");
}
};
}
macro_rules! assert_expected_width_is_correct {
($expected_width:expr, $item_span:expr, $item_width:expr, $prelude:expr $(,)?) => {
if let Some(expected_width) = $expected_width {
match &$item_width {
Width::Lit(actual_width) => {
if expected_width != *actual_width {
fail!($item_span, "expected width of item does not match actual width");
}
}
Width::Expr(actual_width) => {
$prelude.extend(quote! {
const _: () = if #expected_width != (#actual_width) {
::core::panicking::panic("expected width of item does not match actual width");
};
});
}
}
}
};
}
#[derive(Clone)]
enum Width {
Lit(usize),
Expr(TokenStream2),
}
impl Width {
fn zero() -> Self {
Self::Lit(0)
}
}
macro_rules! impl_binop_for_width {
($trait:ident, $fn:ident, $op:tt $(,)?) => {
impl std::ops::$trait for Width {
type Output = Self;
fn $fn(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Lit(lhs), Self::Lit(rhs)) => Self::Lit(lhs $op rhs),
(lhs, rhs) => Self::Expr(quote!(#lhs $op #rhs)),
}
}
}
};
}
impl_binop_for_width!(Mul, mul, *);
impl_binop_for_width!(Add, add, +);
impl std::ops::AddAssign<&Self> for Width {
fn add_assign(&mut self, rhs: &Self) {
match (&mut *self, rhs) {
(Self::Lit(inner), Self::Lit(rhs)) => {
*inner += rhs;
}
(Self::Lit(inner), Self::Expr(rhs)) => {
*self = Self::Expr(quote!(#inner + #rhs));
}
(Self::Expr(inner), rhs) => {
inner.extend(quote!(+ #rhs));
}
}
}
}
impl std::iter::Sum for Width {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let mut acc = Self::zero();
for elem in iter {
acc += &elem;
}
acc
}
}
impl ToTokens for Width {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Lit(width) => {
quote!(#width).to_tokens(tokens);
}
Self::Expr(width) => {
width.to_tokens(tokens);
}
}
}
}
fn determine_item_repr(
expected_width: Option<usize>,
item_span: Span2,
item_attrs: &mut Vec<syn::Attribute>,
item_calc_width: &Width,
) -> Result<UIntType, TokenStream> {
let repr = if let Some((i, attr)) =
item_attrs.iter_mut().enumerate().find(|(_, attr)| attr.path.is_ident("repr"))
{
let arg = attr.parse_args::<syn::Ident>().unwrap().to_string();
let Some(result) = UIntType::parse(item_span, &arg) else {
return Err(make_error(item_span, "'bitwise' items can only be represented by unsigned integer primitives"));
};
item_attrs.remove(i);
result?
} else if let Some(width) = expected_width {
UIntType { width }.round_up()
} else {
match item_calc_width {
Width::Lit(width) => UIntType { width: *width }.round_up(),
Width::Expr(_) => {
return Err(make_error(
item_span,
"not enough information to compute item width at macro evaluation time",
));
}
}
};
if repr.exists() {
Ok(repr)
} else {
Err(make_error(
item_span,
"item cannot be represented by any existing unsigned integer primitive",
))
}
}
struct Item {
attrs: TokenStream2,
vis: syn::Visibility,
token: TokenStream2,
ident: syn::Ident,
body: TokenStream2,
methods: Vec<Method>,
bitwise_impl: BitwiseImpl,
}
impl ToTokens for Item {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let attrs = &self.attrs;
let vis = &self.vis;
let token = &self.token;
let ident = &self.ident;
let body = &self.body;
let method_prelude = quote! {
#[allow(unused)]
const ITEM_WIDTH: usize = <#ident as ::regent::Bitwise>::WIDTH;
#[allow(unused)]
const ITEM_REPR_WIDTH: usize = <#ident as ::regent::BitwiseExt>::REPR_WIDTH;
#[allow(unsued)]
type ItemRepr = <#ident as ::regent::Bitwise>::Repr;
};
let methods: TokenStream2 = self
.methods
.iter()
.map(|it| {
let sig = &it.sig;
let body = &it.body;
quote! {
#sig {
#method_prelude
#body
}
}
})
.collect();
let bitwise_width = &self.bitwise_impl.width;
let bitwise_repr = &self.bitwise_impl.repr;
let bitwise_from_repr = &self.bitwise_impl.from_repr;
let bitwise_from_repr_checked = &self.bitwise_impl.from_repr_checked;
let bitwise_to_repr = &self.bitwise_impl.to_repr;
let impl_trait_token =
if cfg!(feature = "nightly") { quote!(impl const) } else { quote!(impl) };
tokens.extend(quote! {
#attrs
#vis #token #ident #body
impl #ident {
#methods
}
#impl_trait_token ::regent::Bitwise for #ident {
const WIDTH: usize = #bitwise_width;
type Repr = #bitwise_repr;
fn from_repr(repr: Self::Repr) -> Self {
#method_prelude
#bitwise_from_repr
}
fn from_repr_checked(repr: Self::Repr) -> Option<Self> {
#method_prelude
#bitwise_from_repr_checked
}
fn to_repr(&self) -> Self::Repr {
#method_prelude
#bitwise_to_repr
}
}
})
}
}
struct Method {
sig: TokenStream2,
body: TokenStream2,
}
struct BitwiseImpl {
width: Width,
repr: UIntType,
from_repr: TokenStream2,
from_repr_checked: TokenStream2,
to_repr: TokenStream2,
}
#[proc_macro_attribute]
pub fn bitwise(attr: TokenStream, item: TokenStream) -> TokenStream {
#[derive(darling::FromMeta)]
struct ItemArgs {
size: Option<usize>,
width: Option<usize>,
}
let item_args = syn::parse_macro_input!(attr as syn::AttributeArgs);
let item_args = ItemArgs::from_list(&item_args).unwrap();
if item_args.size.is_some() && item_args.width.is_some() {
fail!(
Span2::call_site(),
"'size' and 'width' are mutually exclusive arguments to the 'bitwise' macro"
);
}
let expected_width = item_args.width.or_else(|| item_args.size.map(|it| it * 8));
match syn::parse_macro_input!(item as syn::Item) {
syn::Item::Enum(item) => bitwise_on_enum(expected_width, item),
syn::Item::Struct(item) => bitwise_on_struct(expected_width, item),
_ => make_error(Span2::call_site(), "'bitwise' can only be applied to structs and enums"),
}
}
fn bitwise_on_enum(_expected_width: Option<usize>, item: syn::ItemEnum) -> TokenStream {
let syn::ItemEnum {
attrs: item_attrs,
generics: item_generics,
ident: item_ident,
enum_token: item_enum,
vis: item_vis,
..
} = item;
let item_span = item_enum.span;
assert_no_generics!(item_span, item_generics);
quote! {
#(#item_attrs)*
#item_vis enum #item_ident {}
}
.into()
}
fn bitwise_on_struct(expected_width: Option<usize>, item: syn::ItemStruct) -> TokenStream {
let syn::ItemStruct {
attrs: mut item_attrs,
fields: item_fields,
generics: item_generics,
ident: item_ident,
struct_token: item_struct,
vis: item_vis,
..
} = item;
let item_span = item_struct.span;
assert_no_generics!(item_span, item_generics);
let mut item_width = Width::zero();
let mut item_methods = Vec::new();
let mut item_new_args = Vec::new();
let mut new_stmts = Vec::new();
let mut from_repr_checked_body = TokenStream2::new();
for (i, field) in item_fields.into_iter().enumerate() {
impl Type {
fn decode(
&self,
field_as_repr: TokenStream2,
field_width: &Width,
) -> TokenStream2 {
match self {
Self::Prime(ty) => ty.decode(field_as_repr, field_width),
Self::Tuple(tys) => {
let mut tmp_elems = Vec::new();
for ty in tys.iter().rev() {
let elem_width = ty.width();
let elem_decoded =
ty.decode(field_as_repr.clone(), &elem_width);
tmp_elems.push(quote!({
let elem = #elem_decoded;
#field_as_repr >>= #elem_width;
elem
}));
}
let mut elems = Vec::new();
for i in (0..tys.len()).rev() {
let i = syn::Index::from(i);
elems.push(quote!(tmp.#i));
}
quote!({
let tmp = (#(#tmp_elems),*);
(#(#elems),*)
})
}
Self::Array { ty, len } => {
let elem_width = ty.width();
let elem_decoded =
ty.decode(field_as_repr.clone(), &elem_width);
let mut elems = Vec::new();
for _ in 0..(*len) {
elems.push(quote! { get_elem() });
}
quote!({
let mut get_elem = || {
let elem = #elem_decoded;
#field_as_repr >>= #elem_width;
elem
};
[#(#elems),*]
})
}
}
}
fn encode(
&self,
field: TokenStream2,
field_width: &Width,
) -> TokenStream2 {
match self {
Self::Prime(ty) => ty.encode(field, field_width),
Self::Tuple(tys) => {
let mut block_body = quote! { let mut result = 0; };
for (i, ty) in tys.iter().enumerate() {
let i = syn::Index::from(i);
let elem_width = ty.width();
let elem_encoded =
ty.encode(quote!(#field.#i), &elem_width);
block_body.extend(quote! {
result <<= #elem_width;
result |= #elem_encoded;
});
}
block_body.extend(quote!(result));
quote!({ #block_body })
}
Self::Array { ty, len } => {
let elem_width = ty.width();
let elem_encoded =
ty.encode(quote!(#field[i]), &elem_width);
quote!({
let mut result = 0;
for i in 0..#len {
result <<= #elem_width;
result |= #elem_encoded;
}
result
})
}
}
}
}
impl PrimeType {
fn decode(
&self,
field_as_repr: TokenStream2,
field_width: &Width,
) -> TokenStream2 {
let inner = quote!(#field_as_repr & (!0 >> (ITEM_REPR_WIDTH - (#field_width))));
match self {
Self::Bool => quote! { (#inner) == 1 },
Self::UInt(_) => quote! { (#inner) as _ },
Self::Other(_) => quote! { ::regent::Bitwise::from_repr(#inner) },
}
}
fn encode(
&self,
field: TokenStream2,
field_width: &Width,
) -> TokenStream2 {
let expr = match self {
Self::Other(_) => quote! { ::regent::Bitwise::to_repr(#field) },
_ => quote! { (#field as ItemRepr) },
};
quote! { #expr & (!0 >> (ITEM_REPR_WIDTH - (#field_width))) }
}
}
let syn::Field {
attrs: field_attrs, ident: field_ident, ty: field_ty, vis: field_vis, ..
} = field;
let (field_getter_ident, field_setter_ident) = match field_ident {
Some(it) => (it.clone(), format_ident!("set_{it}")),
None => (
format_ident!("_{i}", span = item_span),
format_ident!("set_{i}", span = item_span),
),
};
let field_ident = &field_getter_ident;
let field_span = field_getter_ident.span();
let field_ty = unwrap!(Type::parse(field_span, field_ty));
unwrap!(field_ty.validate(field_span));
let field_offset = item_width.clone();
let field_width = field_ty.width();
item_width += &field_width;
let field_ty = field_ty.as_rust_primitives();
if !field_ty.exists() {
fail!(
field_span,
"'bitwise' field cannot be represented in terms of primitive Rust types"
);
}
let mut field_value = None;
for attr in field_attrs {
let metas = darling::util::parse_attribute_to_meta_list(&attr).unwrap();
if metas.path.is_ident("constant") {
#[derive(darling::FromMeta)]
struct ConstantArgs {
value: Option<syn::Expr>,
}
let nested_metas: Vec<_> = metas.nested.into_iter().collect();
let args = ConstantArgs::from_list(&nested_metas).unwrap();
field_value = Some(match args.value {
Some(it) => it.into_token_stream(),
None => quote!(<#field_ty as ::core::default::Default>::default()),
});
} else {
fail!(field_span, "invalid attribute")
}
}
let new_glue: TokenStream2;
if let Some(field_value) = field_value {
let field_decoded = field_ty.decode(quote!(repr), &field_width);
from_repr_checked_body.extend(quote!({
let repr: ItemRepr = repr >> (#field_offset);
let actual_value: #field_ty = #field_decoded;
let expected_value: #field_ty = #field_value;
if actual_value != expected_value {
return None;
}
}));
new_glue = field_ty.encode(field_value, &field_width);
} else {
let field_decoded =
field_ty.decode(quote!(field_as_repr), &field_width);
let field_encoded =
field_ty.encode(quote!(field), &field_width);
new_glue = field_ty.encode(
field_ident.to_token_stream(),
&field_width,
);
item_methods.push(Method {
sig: quote!(#field_vis fn #field_getter_ident(&self) -> #field_ty),
body: quote! {
let mut field_as_repr: ItemRepr = self.0 >> (#field_offset);
#field_decoded
},
});
item_methods.push(Method {
sig: quote!(#field_vis fn #field_setter_ident(&mut self, field: #field_ty)),
body: quote! {
let mut field_as_repr: ItemRepr = #field_encoded;
self.0 &= !((!0 >> (ITEM_REPR_WIDTH - (#field_width))) << (#field_offset));
self.0 |= field_as_repr << (#field_offset);
},
});
item_new_args.push(quote!(#field_ident: #field_ty));
}
new_stmts.push(quote! {
bits <<= #field_width;
bits |= #new_glue;
});
}
let mut prelude = TokenStream2::new();
assert_expected_width_is_correct!(expected_width, item_span, item_width, prelude);
let item_repr =
unwrap!(determine_item_repr(expected_width, item_span, &mut item_attrs, &item_width));
new_stmts.reverse();
item_methods.push(Method {
sig: quote!(#item_vis fn new(#(#item_new_args),*) -> Self),
body: quote! {
let mut bits: ItemRepr = 0;
#(#new_stmts)*
Self(bits)
},
});
let mut output = Item {
attrs: quote! {
#(#item_attrs)*
#[repr(transparent)]
},
vis: item_vis,
token: quote!(struct),
ident: item_ident,
body: quote! { (#item_repr); },
methods: item_methods,
bitwise_impl: BitwiseImpl {
width: item_width,
repr: item_repr,
from_repr: quote!(Self(repr)),
from_repr_checked: quote! {
#from_repr_checked_body
Some(Self(repr))
},
to_repr: quote!(self.0),
},
}
.into_token_stream();
output.extend(prelude);
output.into()
}
#[derive(Clone)]
enum Type {
Prime(PrimeType),
Tuple(Vec<PrimeType>),
Array {
ty: PrimeType,
len: usize,
},
}
impl Type {
fn parse(span: Span2, ty: syn::Type) -> Result<Self, TokenStream> {
match ty {
syn::Type::Path(ty) => PrimeType::parse(span, ty).map(Self::Prime),
syn::Type::Tuple(syn::TypeTuple { elems: tys, .. }) => {
let tys = tys
.into_iter()
.map(|ty| {
if let syn::Type::Path(ty) = ty {
PrimeType::parse(span, ty)
} else {
Err(make_error(span, "tuple element type must be a path"))
}
})
.collect::<Result<Vec<PrimeType>, _>>()?;
Ok(Self::Tuple(tys))
}
syn::Type::Array(syn::TypeArray { elem: ty, len, .. }) => {
let syn::Type::Path(ty) = *ty else {
return Err(make_error(span, "array element type must be a path"));
};
let ty = PrimeType::parse(span, ty)?;
let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(len), .. }) = len else {
return Err(make_error(span, "array length must be an integer literal"));
};
let len =
len.base10_parse().map_err(|e| TokenStream::from(e.into_compile_error()))?;
Ok(Self::Array { ty, len })
}
_ => Err(make_error(span, "unsupported type")),
}
}
fn as_rust_primitives(self) -> Self {
match self {
Self::Prime(ty) => Self::Prime(ty.as_rust_primitive()),
Self::Tuple(tys) => {
Self::Tuple(tys.into_iter().map(|it| it.as_rust_primitive()).collect())
}
Self::Array { ty, len } => Self::Array { ty: ty.as_rust_primitive(), len },
}
}
fn exists(&self) -> bool {
match self {
Self::Prime(ty) => ty.exists(),
Self::Tuple(tys) => tys.iter().all(PrimeType::exists),
Self::Array { ty, .. } => ty.exists(),
}
}
fn width(&self) -> Width {
match self {
Self::Prime(ty) => ty.width(),
Self::Tuple(tys) => tys.iter().map(PrimeType::width).sum(),
Self::Array { ty, len } => ty.width() * Width::Lit(*len),
}
}
fn validate(&self, span: Span2) -> Result<(), TokenStream> {
match self {
Type::Tuple(tys) => {
if tys.is_empty() {
return Err(make_error(span, "'bitwise' fields cannot be the unit type"));
}
}
Type::Array { len, .. } => {
if *len == 0 {
return Err(make_error(span, "'bitwise' fields cannot be zero-sized arrays"));
}
}
_ => {}
}
Ok(())
}
}
impl ToTokens for Type {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Prime(ty) => {
ty.to_tokens(tokens);
}
Self::Tuple(tys) => {
tokens.extend(quote! { ( #(#tys),* ) });
}
Self::Array { ty, len } => {
tokens.extend(quote! { [#ty; #len] });
}
}
}
}
#[derive(Clone)]
enum PrimeType {
Bool,
UInt(UIntType),
Other(syn::TypePath),
}
impl PrimeType {
fn parse(span: Span2, ty: syn::TypePath) -> Result<Self, TokenStream> {
if let Some(ty) = ty.path.get_ident().map(ToString::to_string) {
if ty == "bool" {
return Ok(Self::Bool);
} else if let Some(result) = UIntType::parse(span, &ty) {
return result.map(Self::UInt);
}
}
Ok(Self::Other(ty))
}
fn as_rust_primitive(self) -> Self {
if let Self::UInt(ty) = self {
Self::UInt(ty.round_up())
} else {
self
}
}
fn exists(&self) -> bool {
match self {
Self::UInt(ty) => ty.exists(),
_ => true,
}
}
fn width(&self) -> Width {
match self {
Self::Bool => Width::Lit(1),
Self::UInt(ty) => Width::Lit(ty.width),
Self::Other(ty) => Width::Expr(quote!(<#ty as ::regent::Bitwise>::WIDTH)),
}
}
}
impl ToTokens for PrimeType {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Bool => {
tokens.extend(quote!(bool));
}
Self::UInt(ty) => {
ty.to_tokens(tokens);
}
Self::Other(ty) => {
ty.to_tokens(tokens);
}
}
}
}
#[derive(Clone, Copy)]
struct UIntType {
width: usize,
}
impl UIntType {
fn parse(span: Span2, ty: &str) -> Option<Result<Self, TokenStream>> {
let Some(("", width)) = ty.split_once('u') else {
return None;
};
let Ok(width) = width.parse() else {
return Some(Err(make_error(span, "failed to parse integer width suffix")));
};
if width == 0 {
return Some(Err(make_error(span, "'bitwise' does not support zero-sized types")));
}
Some(Ok(Self { width }))
}
fn round_up(self) -> Self {
let width = if self.width <= 8 {
8
} else {
let mag = self.width.ilog2() as usize;
let frac = self.width & ((1 << mag) - 1);
if frac == 0 {
self.width
} else {
1 << (mag + 1)
}
};
Self { width }
}
fn exists(self) -> bool {
match self.width {
8 | 16 | 32 | 64 | 128 => true,
_ => false,
}
}
}
impl ToTokens for UIntType {
fn to_tokens(&self, tokens: &mut TokenStream2) {
tokens.extend(format_ident!("u{}", self.width).into_token_stream());
}
}