Skip to content

Commit

Permalink
Emit C volatile type qualifier for transparent 1-field structs and st…
Browse files Browse the repository at this point in the history
…ruct/union fields that have the `volatile` annotation.
  • Loading branch information
flaviojs committed May 31, 2024
1 parent 91db3ea commit f34ca63
Show file tree
Hide file tree
Showing 20 changed files with 1,088 additions and 46 deletions.
8 changes: 8 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ pub struct Foo { .. }; // This won't be emitted by cbindgen in the header
fn bar() -> Foo { .. } // Will be emitted as `struct foo bar();`
```

### Volatile annotation

cbindgen will emit the C volatile type qualifier for transparent 1-field structs and struct/union fields that have the `volatile` annotation.

There is no equivalent in rust. You should use `read_volatile` and `write_volatile` to get C-like behavior.

Example usage can be found in `tests/rust/volatile.rs`.

### Struct Annotations

* field-names=\[field1, field2, ...\] -- sets the names of all the fields in the output struct. These names will be output verbatim, and are not eligible for renaming.
Expand Down
5 changes: 4 additions & 1 deletion src/bindgen/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ impl Bindings {
loop {
let mut found = None;
self.typedef_map.for_items(&resolved_path, |item| {
if let Type::Path { ref generic_path } = item.aliased {
if let Type::Path {
ref generic_path, ..
} = item.aliased
{
found = Some(generic_path.path().clone());
}
});
Expand Down
57 changes: 39 additions & 18 deletions src/bindgen/cdecl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::bindgen::{Config, Language};
enum CDeclarator {
Ptr {
is_const: bool,
is_volatile: bool,
is_nullable: bool,
is_ref: bool,
},
Expand All @@ -36,7 +37,7 @@ impl CDeclarator {
}

struct CDecl {
type_qualifers: String,
type_qualifiers: Vec<String>,
type_name: String,
type_generic_args: Vec<GenericArgument>,
declarators: Vec<CDeclarator>,
Expand All @@ -47,7 +48,7 @@ struct CDecl {
impl CDecl {
fn new() -> CDecl {
CDecl {
type_qualifers: String::new(),
type_qualifiers: Vec::new(),
type_name: String::new(),
type_generic_args: Vec::new(),
declarators: Vec::new(),
Expand Down Expand Up @@ -111,14 +112,20 @@ impl CDecl {

fn build_type(&mut self, t: &Type, is_const: bool, config: &Config) {
match t {
Type::Path { ref generic_path } => {
Type::Path {
ref generic_path,
is_volatile,
} => {
assert!(
self.type_qualifiers.is_empty(),
"error generating cdecl for {:?}",
t
);
if is_const {
assert!(
self.type_qualifers.is_empty(),
"error generating cdecl for {:?}",
t
);
"const".clone_into(&mut self.type_qualifers);
self.type_qualifiers.push("const".into());
}
if *is_volatile && config.language != Language::Cython {
self.type_qualifiers.push("volatile".into());
}

assert!(
Expand All @@ -137,14 +144,20 @@ impl CDecl {
.clone_into(&mut self.type_generic_args);
self.type_ctype = generic_path.ctype().cloned();
}
Type::Primitive { ref primitive } => {
Type::Primitive {
ref primitive,
is_volatile,
} => {
assert!(
self.type_qualifiers.is_empty(),
"error generating cdecl for {:?}",
t
);
if is_const {
assert!(
self.type_qualifers.is_empty(),
"error generating cdecl for {:?}",
t
);
"const".clone_into(&mut self.type_qualifers);
self.type_qualifiers.push("const".into());
}
if *is_volatile && config.language != Language::Cython {
self.type_qualifiers.push("volatile".into());
}

assert!(
Expand All @@ -156,12 +169,14 @@ impl CDecl {
}
Type::Ptr {
ref ty,
is_volatile,
is_nullable,
is_const: ptr_is_const,
is_ref,
} => {
self.declarators.push(CDeclarator::Ptr {
is_const,
is_volatile: *is_volatile,
is_nullable: *is_nullable,
is_ref: *is_ref,
});
Expand All @@ -175,6 +190,7 @@ impl CDecl {
Type::FuncPtr {
ref ret,
ref args,
is_volatile,
is_nullable: _,
never_return,
} => {
Expand All @@ -184,6 +200,7 @@ impl CDecl {
.collect();
self.declarators.push(CDeclarator::Ptr {
is_const: false,
is_volatile: *is_volatile,
is_nullable: true,
is_ref: false,
});
Expand All @@ -205,8 +222,8 @@ impl CDecl {
config: &Config,
) {
// Write the type-specifier and type-qualifier first
if !self.type_qualifers.is_empty() {
write!(out, "{} ", self.type_qualifers);
for type_qualifier in self.type_qualifiers.iter() {
write!(out, "{} ", type_qualifier);
}

if config.language != Language::Cython {
Expand Down Expand Up @@ -246,13 +263,17 @@ impl CDecl {
match *declarator {
CDeclarator::Ptr {
is_const,
is_volatile,
is_nullable,
is_ref,
} => {
out.write(if is_ref { "&" } else { "*" });
if is_const {
out.write("const ");
}
if is_volatile && config.language != Language::Cython {
out.write("volatile ");
}
if !is_nullable && !is_ref && config.language != Language::Cython {
if let Some(attr) = &config.pointer.non_null_attribute {
write!(out, "{} ", attr);
Expand Down
7 changes: 6 additions & 1 deletion src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl EnumVariant {
inline_name.map_or_else(|| "tag".to_string(), |name| format!("{}_tag", name)),
Type::Path {
generic_path: GenericPath::new(Path::new("Tag"), vec![]),
is_volatile: false,
},
));
}
Expand Down Expand Up @@ -513,7 +514,10 @@ impl Item for Enum {
if let VariantBody::Body { ref mut body, .. } = variant.body {
let path = Path::new(new_tag.clone());
let generic_path = GenericPath::new(path, vec![]);
body.fields[0].ty = Type::Path { generic_path };
body.fields[0].ty = Type::Path {
generic_path,
is_volatile: false,
};
}
}
}
Expand Down Expand Up @@ -1252,6 +1256,7 @@ impl Enum {
let return_type = Type::Ptr {
ty: Box::new(return_type),
is_const: const_casts,
is_volatile: false,
is_ref: true,
is_nullable: false,
};
Expand Down
23 changes: 16 additions & 7 deletions src/bindgen/ir/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,26 @@ impl Field {

pub fn load(field: &syn::Field, self_path: &Path) -> Result<Option<Field>, String> {
Ok(if let Some(mut ty) = Type::load(&field.ty)? {
let name = field
.ident
.as_ref()
.ok_or_else(|| "field is missing identifier".to_string())?
.unraw()
.to_string();
ty.replace_self_with(self_path);
let annotations = AnnotationSet::load(&field.attrs)?;
if annotations.bool("volatile").unwrap_or(false) {
if let Some(volatile_ty) = ty.make_volatile(true) {
ty = volatile_ty;
} else {
return Err(format!("Field {:?} cannot be made volatile", name));
}
}
Some(Field {
name: field
.ident
.as_ref()
.ok_or_else(|| "field is missing identifier".to_string())?
.unraw()
.to_string(),
name,
ty,
cfg: Cfg::load(&field.attrs),
annotations: AnnotationSet::load(&field.attrs)?,
annotations: annotations,
documentation: Documentation::load(&field.attrs),
})
} else {
Expand Down
6 changes: 5 additions & 1 deletion src/bindgen/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ impl Function {
name: None,
ty: Type::Primitive {
primitive: super::PrimitiveType::VaList,
is_volatile: false,
},
array_length: None,
})
Expand Down Expand Up @@ -230,6 +231,7 @@ trait SynFnArgHelpers {
fn gen_self_type(receiver: &syn::Receiver) -> Result<Type, String> {
let mut self_ty = Type::Path {
generic_path: GenericPath::self_path(),
is_volatile: false,
};

// Custom self type
Expand All @@ -245,6 +247,7 @@ fn gen_self_type(receiver: &syn::Receiver) -> Result<Type, String> {
Ok(Type::Ptr {
ty: Box::new(self_ty),
is_const,
is_volatile: false,
is_nullable: false,
is_ref: false,
})
Expand All @@ -266,7 +269,8 @@ impl SynFnArgHelpers for syn::FnArg {
if matches!(
ty,
Type::Primitive {
primitive: super::PrimitiveType::VaList
primitive: super::PrimitiveType::VaList,
is_volatile: false
}
) {
None
Expand Down
5 changes: 4 additions & 1 deletion src/bindgen/ir/generic_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ impl GenericArgument {
pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> GenericArgument {
match *self {
GenericArgument::Type(ref ty) => {
if let Type::Path { ref generic_path } = *ty {
if let Type::Path {
ref generic_path, ..
} = *ty
{
if generic_path.is_single_identifier() {
// See note on `GenericArgument` above: `ty` may
// actually be the name of a const. Check for that now.
Expand Down
16 changes: 15 additions & 1 deletion src/bindgen/ir/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl Struct {
pub fn new(
path: Path,
generic_params: GenericParams,
fields: Vec<Field>,
mut fields: Vec<Field>,
has_tag_field: bool,
is_enum_variant_body: bool,
alignment: Option<ReprAlign>,
Expand All @@ -128,6 +128,20 @@ impl Struct {
documentation: Documentation,
) -> Self {
let export_name = path.name().to_owned();
if annotations.bool("volatile").unwrap_or(false) {
if is_transparent && fields.len() == 1 {
if let Some(volatile_ty) = fields[0].ty.make_volatile(true) {
fields[0].ty = volatile_ty;
} else {
error!(
"Field of structure {:?} cannot be made volatile",
export_name
);
}
} else {
error!("Structure {:?} cannot be volatile, it must be transparent and have exactly 1 field", export_name);
}
}
Self {
path,
export_name,
Expand Down
Loading

0 comments on commit f34ca63

Please sign in to comment.