Skip to content

Generate and add options for bitfield-like enums. #226

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

Merged
merged 4 commits into from
Nov 8, 2016
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
10 changes: 10 additions & 0 deletions src/bin/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Usage:
[--whitelist-type=<type>...] \
[--whitelist-function=<name>...] \
[--whitelist-var=<name>...] \
[--bitfield-enum=<name>...] \
<input-header> \
[-- <clang-args>...]

Expand Down Expand Up @@ -91,6 +92,10 @@ Options:
matching <regex>. Same behavior on emptyness
than the type whitelisting.

--bitfield-enum=<regex> Mark any enum whose name matches <regex> as a
set of bitfield flags instead of an
enumeration.

--dummy-uses=<path> For testing purposes, generate a C/C++ file
containing dummy uses of all types defined in
the input header.
Expand Down Expand Up @@ -163,6 +168,11 @@ fn parse_args_or_exit(args: Vec<String>) -> (BindgenOptions, Box<io::Write>) {
.expect("--whitelist-var expects a pattern");
options.whitelisted_vars.insert(&var_pat);
}
"--bitfield-enum" => {
let enum_pat = iter.next()
.expect("--bitfield-enum expects a pattern");
options.bitfield_enums.insert(&enum_pat);
}
"--" => {
while let Some(clang_arg) = iter.next() {
options.clang_args.push(clang_arg);
Expand Down
17 changes: 8 additions & 9 deletions src/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ impl Cursor {
/// lexical parents.
pub fn fallible_semantic_parent(&self) -> Option<Cursor> {
let sp = unsafe {
Cursor { x: clang_getCursorSemanticParent(self.x) }
Cursor {
x: clang_getCursorSemanticParent(self.x),
}
};
if sp == *self || !sp.is_valid() {
return None;
Expand Down Expand Up @@ -161,7 +163,8 @@ impl Cursor {
(semantic_parent.unwrap().kind() == CXCursor_Namespace ||
semantic_parent.unwrap().kind() == CXCursor_NamespaceAlias ||
semantic_parent.unwrap().kind() == CXCursor_NamespaceRef) {
semantic_parent = semantic_parent.unwrap().fallible_semantic_parent();
semantic_parent = semantic_parent.unwrap()
.fallible_semantic_parent();
}

let tu = self.translation_unit();
Expand Down Expand Up @@ -363,9 +366,9 @@ impl Cursor {
///
/// Returns None if the cursor's referent is not an enum variant.
pub fn enum_val_unsigned(&self) -> Option<u64> {
unsafe {
unsafe {
if self.kind() == CXCursor_EnumConstantDecl {
Some(clang_getEnumConstantDeclUnsignedValue(self.x) as u64)
Some(clang_getEnumConstantDeclUnsignedValue(self.x) as u64)
} else {
None
}
Expand Down Expand Up @@ -691,11 +694,7 @@ impl Type {
let rt = Type {
x: unsafe { clang_getResultType(self.x) },
};
if rt.is_valid() {
Some(rt)
} else {
None
}
if rt.is_valid() { Some(rt) } else { None }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these changes are simply rustfmt right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, just for the record, you can review individual commits in the github UI, if that's easier :)

}

/// Given that this type is a function type, get its calling convention. If
Expand Down
212 changes: 173 additions & 39 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use aster;
use ir::annotations::FieldAccessorKind;
use ir::comp::{CompInfo, CompKind, Field, Method};
use ir::context::BindgenContext;
use ir::enum_ty::Enum;
use ir::enum_ty::{Enum, EnumVariant, EnumVariantValue};
use ir::function::{Function, FunctionSig};
use ir::int::IntKind;
use ir::item::{Item, ItemCanonicalName, ItemCanonicalPath, ItemId};
Expand Down Expand Up @@ -1224,17 +1224,127 @@ impl MethodCodegen for Method {
}
}

/// A helper type to construct enums, either bitfield ones or rust-style ones.
enum EnumBuilder<'a> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: it would be nice to have brief documentation comments here and throughout the new code (I know I never got around to documenting the codegen module, but we should aim to hold ourselves to documenting new code)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, thanks for pointing it out :)

Rust(aster::item::ItemEnumBuilder<aster::invoke::Identity>),
Bitfield {
canonical_name: &'a str,
aster: P<ast::Item>,
},
}

impl<'a> EnumBuilder<'a> {
/// Create a new enum given an item builder, a canonical name, a name for
/// the representation, and whether it should be represented as a rust enum.
fn new(aster: aster::item::ItemBuilder<aster::invoke::Identity>,
name: &'a str,
repr_name: &str,
is_rust: bool)
-> Self {
if is_rust {
EnumBuilder::Rust(aster.enum_(name))
} else {
EnumBuilder::Bitfield {
canonical_name: name,
aster: aster.tuple_struct(name)
.field()
.pub_()
.ty()
.id(repr_name)
.build(),
}
}
}

/// Add a variant to this enum.
fn with_variant(self,
ctx: &BindgenContext,
variant: &EnumVariant,
mangling_prefix: Option<&String>,
rust_ty: P<ast::Ty>,
result: &mut CodegenResult)
-> Self {
let variant_name = ctx.rust_mangle(variant.name());
let expr = aster::AstBuilder::new().expr();
let expr = match variant.val() {
EnumVariantValue::Signed(v) => expr.int(v),
EnumVariantValue::Unsigned(v) => expr.uint(v),
};

match self {
EnumBuilder::Rust(b) => {
EnumBuilder::Rust(b.with_variant_(ast::Variant_ {
name: ctx.rust_ident(&*variant_name),
attrs: vec![],
data: ast::VariantData::Unit(ast::DUMMY_NODE_ID),
disr_expr: Some(expr),
}))
}
EnumBuilder::Bitfield { canonical_name, .. } => {
let constant_name = match mangling_prefix {
Some(prefix) => {
Cow::Owned(format!("{}_{}", prefix, variant_name))
}
None => variant_name,
};

let constant = aster::AstBuilder::new()
.item()
.pub_()
.const_(&*constant_name)
.expr()
.call()
.id(canonical_name)
.arg()
.build(expr)
.build()
.build(rust_ty);
result.push(constant);
self
}
}
}

fn build(self,
ctx: &BindgenContext,
rust_ty: P<ast::Ty>,
result: &mut CodegenResult)
-> P<ast::Item> {
match self {
EnumBuilder::Rust(b) => b.build(),
EnumBuilder::Bitfield { canonical_name, aster } => {
let rust_ty_name = ctx.rust_ident_raw(canonical_name);
let prefix = ctx.trait_prefix();

let impl_ = quote_item!(ctx.ext_cx(),
impl ::$prefix::ops::BitOr<$rust_ty> for $rust_ty {
type Output = Self;

#[inline]
fn bitor(self, other: Self) -> Self {
$rust_ty_name(self.0 | other.0)
}
}
)
.unwrap();

result.push(impl_);
aster
}
}
}
}

impl CodeGenerator for Enum {
type Extra = Item;

fn codegen(&self,
ctx: &BindgenContext,
result: &mut CodegenResult,
item: &Item) {
use ir::enum_ty::EnumVariantValue;

let name = item.canonical_name(ctx);
let layout = item.expect_type().layout(ctx);
let enum_ty = item.expect_type();
let layout = enum_ty.layout(ctx);

let repr = self.repr().map(|repr| ctx.resolve_type(repr));
let repr = match repr {
Expand Down Expand Up @@ -1270,10 +1380,24 @@ impl CodeGenerator for Enum {

let mut builder = aster::AstBuilder::new().item().pub_();

let is_bitfield = {
ctx.options().bitfield_enums.matches(&name) ||
(enum_ty.name().is_none() &&
self.variants()
.iter()
.any(|v| ctx.options().bitfield_enums.matches(&v.name())))
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should factor this out to a method on the context or on the Enum which takes a context as a parameter. No need for the logic of determining whether a type should be a bitfield to live inside code generation.


let is_rust_enum = !is_bitfield;

// FIXME: Rust forbids repr with empty enums. Remove this condition when
// this is allowed.
if !self.variants().is_empty() {
builder = builder.with_attr(attributes::repr(repr_name));
if is_rust_enum {
if !self.variants().is_empty() {
builder = builder.with_attr(attributes::repr(repr_name));
}
} else {
builder = builder.with_attr(attributes::repr("C"));
}

if let Some(comment) = item.comment() {
Expand All @@ -1289,8 +1413,6 @@ impl CodeGenerator for Enum {

builder = builder.with_attr(derives);

let mut builder = builder.enum_(&name);

fn add_constant(enum_: &Type,
// Only to avoid recomputing every time.
enum_canonical_name: &str,
Expand Down Expand Up @@ -1318,52 +1440,64 @@ impl CodeGenerator for Enum {
result.push(constant);
}

// Used to mangle the constants we generate in the unnamed-enum case.
let mut parent_canonical_name = None;
let mut builder =
EnumBuilder::new(builder, &name, repr_name, is_rust_enum);

// A map where we keep a value -> variant relation.
let mut seen_values = HashMap::<_, String>::new();
let enum_ty = item.expect_type();
let enum_rust_ty = item.to_rust_ty(ctx);
let is_toplevel = item.is_toplevel(ctx);

// Used to mangle the constants we generate in the unnamed-enum case.
let parent_canonical_name = if is_toplevel {
None
} else {
Some(item.parent_id().canonical_name(ctx))
};

let constant_mangling_prefix = if enum_ty.name().is_none() {
parent_canonical_name.as_ref().map(|n| &*n)
} else {
Some(&name)
};

for variant in self.variants().iter() {
match seen_values.entry(variant.val()) {
Entry::Occupied(ref entry) => {
let existing_variant_name = entry.get();
let variant_name = ctx.rust_mangle(variant.name());
add_constant(enum_ty,
&name,
&*variant_name,
existing_variant_name,
enum_rust_ty.clone(),
result);
if is_rust_enum {
let existing_variant_name = entry.get();
let variant_name = ctx.rust_mangle(variant.name());
add_constant(enum_ty,
&name,
&*variant_name,
existing_variant_name,
enum_rust_ty.clone(),
result);
} else {
builder = builder.with_variant(ctx,
variant,
constant_mangling_prefix,
enum_rust_ty.clone(),
result);
}
}
Entry::Vacant(entry) => {
let expr = aster::AstBuilder::new().expr();
let expr = match variant.val() {
EnumVariantValue::Signed(val) => expr.int(val),
EnumVariantValue::Unsigned(val) => expr.uint(val),
};
builder = builder.with_variant(ctx,
variant,
constant_mangling_prefix,
enum_rust_ty.clone(),
result);

let variant_name = ctx.rust_mangle(variant.name());
builder = builder.with_variant_(ast::Variant_ {
name: ctx.rust_ident(&*variant_name),
attrs: vec![],
data: ast::VariantData::Unit(ast::DUMMY_NODE_ID),
disr_expr: Some(expr),
});

// If it's an unnamed enum, we also generate a constant so
// it can be properly accessed.
if enum_ty.name().is_none() {
if is_rust_enum && enum_ty.name().is_none() {
// NB: if we want to do this for other kind of nested
// enums we can probably mangle the name.
let mangled_name = if item.is_toplevel(ctx) {
let mangled_name = if is_toplevel {
variant_name.clone()
} else {
if parent_canonical_name.is_none() {
parent_canonical_name = Some(item.parent_id()
.canonical_name(ctx));
}

let parent_name = parent_canonical_name.as_ref()
.unwrap();

Expand All @@ -1384,8 +1518,8 @@ impl CodeGenerator for Enum {
}
}


result.push(builder.build());
let enum_ = builder.build(ctx, enum_rust_ty, result);
result.push(enum_);
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ impl Builder {
self
}

/// Mark the given enum (or set of enums, if using a pattern) as being
/// bitfield-like.
///
/// This makes bindgen generate a type that isn't a rust `enum`.
pub fn bitfield_enum<T: Borrow<str>>(mut self, arg: T) -> Builder {
self.options.bitfield_enums.insert(&arg);
self
}

/// Add a string to prepend to the generated bindings. The string is passed
/// through without any modification.
pub fn raw_line<T: Into<String>>(mut self, arg: T) -> Builder {
Expand Down Expand Up @@ -262,6 +271,9 @@ pub struct BindgenOptions {
/// Whitelisted variables. See docs for `whitelisted_types` for more.
pub whitelisted_vars: RegexSet,

/// The enum patterns to mark an enum as bitfield.
pub bitfield_enums: RegexSet,

/// Whether we should generate builtins or not.
pub builtins: bool,

Expand Down Expand Up @@ -329,6 +341,7 @@ impl Default for BindgenOptions {
whitelisted_types: Default::default(),
whitelisted_functions: Default::default(),
whitelisted_vars: Default::default(),
bitfield_enums: Default::default(),
builtins: false,
links: vec![],
emit_ast: false,
Expand Down
Loading