Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for out-of-line bitfields declarations #884

Merged
merged 1 commit into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 106 additions & 36 deletions src/bindgen/bitflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use syn::parse::{Parse, ParseStream, Parser, Result as ParseResult};
// )+
// }
#[derive(Debug)]
pub struct Bitflags {
pub struct BitflagsStruct {
attrs: Vec<syn::Attribute>,
vis: syn::Visibility,
#[allow(dead_code)]
Expand All @@ -27,46 +27,91 @@ pub struct Bitflags {
flags: Flags,
}

// impl $BitFlags:ident: $T:ty {
// $(
// $(#[$inner:ident $($args:tt)*])*
// const $Flag:ident = $value:expr;
// )+
// }
#[derive(Debug)]
pub struct BitflagsImpl {
#[allow(dead_code)]
impl_token: Token![impl],
name: syn::Ident,
#[allow(dead_code)]
colon_token: Token![:],
repr: syn::Type,
flags: Flags,
}

#[derive(Debug)]
pub enum Bitflags {
Struct(BitflagsStruct),
Impl(BitflagsImpl),
}

impl Bitflags {
pub fn expand(&self) -> (syn::ItemStruct, syn::ItemImpl) {
let Bitflags {
ref attrs,
ref vis,
ref name,
ref repr,
ref flags,
..
} = *self;
pub fn expand(&self) -> (Option<syn::ItemStruct>, syn::ItemImpl) {
match self {
Bitflags::Struct(BitflagsStruct {
attrs,
vis,
name,
repr,
flags,
..
}) => {
let struct_ = parse_quote! {
#(#attrs)*
#vis struct #name {
bits: #repr,
}
};

let struct_ = parse_quote! {
/// cbindgen:internal-derive-bitflags=true
#(#attrs)*
#vis struct #name {
bits: #repr,
}
};
let consts = flags.expand(name, repr, false);
let impl_ = parse_quote! {
impl #name {
#consts
}
};

let consts = flags.expand(name, repr);
let impl_ = parse_quote! {
impl #name {
#consts
(Some(struct_), impl_)
}
};

(struct_, impl_)
Bitflags::Impl(BitflagsImpl {
name, repr, flags, ..
}) => {
let consts = flags.expand(name, repr, true);
let impl_: syn::ItemImpl = parse_quote! {
impl #name {
#consts
}
};
(None, impl_)
}
}
}
}

impl Parse for Bitflags {
fn parse(input: ParseStream) -> ParseResult<Self> {
Ok(Self {
attrs: input.call(syn::Attribute::parse_outer)?,
vis: input.parse()?,
struct_token: input.parse()?,
name: input.parse()?,
colon_token: input.parse()?,
repr: input.parse()?,
flags: input.parse()?,
Ok(if input.peek(Token![impl]) {
Self::Impl(BitflagsImpl {
impl_token: input.parse()?,
name: input.parse()?,
colon_token: input.parse()?,
repr: input.parse()?,
flags: input.parse()?,
})
} else {
Self::Struct(BitflagsStruct {
attrs: input.call(syn::Attribute::parse_outer)?,
vis: input.parse()?,
struct_token: input.parse()?,
name: input.parse()?,
colon_token: input.parse()?,
repr: input.parse()?,
flags: input.parse()?,
})
})
}
}
Expand All @@ -89,6 +134,7 @@ struct Flag {
struct FlagValueFold<'a> {
struct_name: &'a syn::Ident,
flag_names: &'a HashSet<String>,
out_of_line: bool,
}

impl<'a> FlagValueFold<'a> {
Expand All @@ -114,6 +160,19 @@ impl<'a> Fold for FlagValueFold<'a> {
// as far as our bindings generation is concerned, `bits` is available as a field,
// so by replacing `StructName::FLAG.bits()` with `StructName::FLAG.bits`, we make
// e.g. `Flags::AB` available in the generated bindings.
// For out-of-line definitions of the struct(*), where the struct is defined as a
// newtype, we replace it with `StructName::FLAGS.0`.
// * definitions like:
// ```
// struct Flags(u8);
// bitflags! {
// impl Flags: u8 {
// const A = 1;
// const B = 1 << 1;
// const AB = Flags::A.bits() | Flags::B.bits();
// }
// }
// ```
match node {
syn::Expr::MethodCall(syn::ExprMethodCall {
attrs,
Expand All @@ -136,7 +195,11 @@ impl<'a> Fold for FlagValueFold<'a> {
attrs,
base: receiver,
dot_token,
member: syn::Member::Named(method),
member: if self.out_of_line {
syn::Member::Unnamed(parse_quote! {0})
} else {
syn::Member::Named(method)
},
});
}
_ => {}
Expand All @@ -151,6 +214,7 @@ impl Flag {
struct_name: &syn::Ident,
repr: &syn::Type,
flag_names: &HashSet<String>,
out_of_line: bool,
) -> TokenStream {
let Flag {
ref attrs,
Expand All @@ -161,11 +225,17 @@ impl Flag {
let folded_value = FlagValueFold {
struct_name,
flag_names,
out_of_line,
}
.fold_expr(value.clone());
let value = if out_of_line {
quote! { ((#folded_value) as #repr) }
} else {
quote! { { bits: (#folded_value) as #repr } }
};
quote! {
#(#attrs)*
pub const #name : #struct_name = #struct_name { bits: (#folded_value) as #repr };
pub const #name : #struct_name = #struct_name #value;
}
}
}
Expand Down Expand Up @@ -199,15 +269,15 @@ impl Parse for Flags {
}

impl Flags {
fn expand(&self, struct_name: &syn::Ident, repr: &syn::Type) -> TokenStream {
fn expand(&self, struct_name: &syn::Ident, repr: &syn::Type, out_of_line: bool) -> TokenStream {
let mut ts = quote! {};
let flag_names = self
.0
.iter()
.map(|flag| flag.name.to_string())
.collect::<HashSet<_>>();
for flag in &self.0 {
ts.extend(flag.expand(struct_name, repr, &flag_names));
ts.extend(flag.expand(struct_name, repr, &flag_names, out_of_line));
}
ts
}
Expand Down
13 changes: 7 additions & 6 deletions src/bindgen/ir/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ impl Struct {
other: &str,
out: &mut SourceWriter<F>,
) {
let bits = &self.fields[0].name;
out.new_line();
write!(
out,
Expand All @@ -223,10 +224,8 @@ impl Struct {
out.open_brace();
write!(
out,
"return {} {{ static_cast<decltype(bits)>(this->bits {} {}.bits) }};",
self.export_name(),
operator,
other
"return {} {{ static_cast<decltype({bits})>(this->{bits} {operator} {other}.{bits}) }};",
self.export_name()
);
out.close_brace(false);

Expand Down Expand Up @@ -534,6 +533,8 @@ impl Source for Struct {
.bool("internal-derive-bitflags")
.unwrap_or(false)
{
assert_eq!(self.fields.len(), 1);
let bits = &self.fields[0].name;
if !wrote_start_newline {
wrote_start_newline = true;
out.new_line();
Expand All @@ -547,7 +548,7 @@ impl Source for Struct {
out.new_line();
write!(out, "{}explicit operator bool() const", constexpr_prefix);
out.open_brace();
write!(out, "return !!bits;");
write!(out, "return !!{bits};");
out.close_brace(false);

out.new_line();
Expand All @@ -560,7 +561,7 @@ impl Source for Struct {
out.open_brace();
write!(
out,
"return {} {{ static_cast<decltype(bits)>(~bits) }};",
"return {} {{ static_cast<decltype({bits})>(~{bits}) }};",
self.export_name()
);
out.close_brace(false);
Expand Down
26 changes: 17 additions & 9 deletions src/bindgen/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::bindgen::cargo::{Cargo, PackageRef};
use crate::bindgen::config::{Config, ParseConfig};
use crate::bindgen::error::Error;
use crate::bindgen::ir::{
AnnotationSet, Cfg, Constant, Documentation, Enum, Function, GenericParam, GenericParams,
ItemMap, OpaqueItem, Path, Static, Struct, Type, Typedef, Union,
AnnotationSet, AnnotationValue, Cfg, Constant, Documentation, Enum, Function, GenericParam,
GenericParams, ItemMap, OpaqueItem, Path, Static, Struct, Type, Typedef, Union,
};
use crate::bindgen::utilities::{SynAbiHelpers, SynAttributeHelpers, SynItemHelpers};

Expand Down Expand Up @@ -547,7 +547,7 @@ impl Parse {
}
}
syn::Item::Macro(ref item) => {
self.load_builtin_macro(config, crate_name, mod_cfg, item)
self.load_builtin_macro(config, crate_name, mod_cfg, item);
}
syn::Item::Mod(ref item) => {
nested_modules.push(item);
Expand Down Expand Up @@ -986,18 +986,26 @@ impl Parse {
}

let bitflags = match bitflags::parse(item.mac.tokens.clone()) {
Ok(b) => b,
Ok(bf) => bf,
Err(e) => {
warn!("Failed to parse bitflags invocation: {:?}", e);
return;
}
};

let (struct_, impl_) = bitflags.expand();
self.load_syn_struct(config, crate_name, mod_cfg, &struct_);
// We know that the expansion will only reference `struct_`, so it's
// fine to just do it here instead of deferring it like we do with the
// other calls to this function.
self.load_syn_assoc_consts_from_impl(crate_name, mod_cfg, &impl_);
if let Some(struct_) = struct_ {
self.load_syn_struct(config, crate_name, mod_cfg, &struct_);
}
if let syn::Type::Path(ref path) = *impl_.self_ty {
if let Some(type_name) = path.path.get_ident() {
self.structs
.for_items_mut(&Path::new(type_name.unraw().to_string()), |item| {
item.annotations
.add_default("internal-derive-bitflags", AnnotationValue::Bool(true));
});
}
}
self.load_syn_assoc_consts_from_impl(crate_name, mod_cfg, &impl_)
}
}
12 changes: 11 additions & 1 deletion tests/expectations/bitflags.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,14 @@ typedef struct {
#define LargeFlags_LARGE_SHIFT (LargeFlags){ .bits = (uint64_t)(1ull << 44) }
#define LargeFlags_INVERTED (LargeFlags){ .bits = (uint64_t)~(LargeFlags_LARGE_SHIFT).bits }

void root(AlignFlags flags, DebugFlags bigger_flags, LargeFlags largest_flags);
typedef struct {
uint32_t _0;
} OutOfLine;
#define OutOfLine_A (OutOfLine){ ._0 = (uint32_t)1 }
#define OutOfLine_B (OutOfLine){ ._0 = (uint32_t)2 }
#define OutOfLine_AB (OutOfLine){ ._0 = (uint32_t)((OutOfLine_A)._0 | (OutOfLine_B)._0) }

void root(AlignFlags flags,
DebugFlags bigger_flags,
LargeFlags largest_flags,
OutOfLine out_of_line);
12 changes: 11 additions & 1 deletion tests/expectations/bitflags.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,21 @@ typedef struct {
#define LargeFlags_LARGE_SHIFT (LargeFlags){ .bits = (uint64_t)(1ull << 44) }
#define LargeFlags_INVERTED (LargeFlags){ .bits = (uint64_t)~(LargeFlags_LARGE_SHIFT).bits }

typedef struct {
uint32_t _0;
} OutOfLine;
#define OutOfLine_A (OutOfLine){ ._0 = (uint32_t)1 }
#define OutOfLine_B (OutOfLine){ ._0 = (uint32_t)2 }
#define OutOfLine_AB (OutOfLine){ ._0 = (uint32_t)((OutOfLine_A)._0 | (OutOfLine_B)._0) }

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void root(AlignFlags flags, DebugFlags bigger_flags, LargeFlags largest_flags);
void root(AlignFlags flags,
DebugFlags bigger_flags,
LargeFlags largest_flags,
OutOfLine out_of_line);

#ifdef __cplusplus
} // extern "C"
Expand Down
Loading