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

{struct,union}: support #[repr(align(...))] and #[repr(packed)] #431

Merged
merged 7 commits into from
Dec 15, 2019
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
28 changes: 22 additions & 6 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ You can learn about all of the different repr attributes [by reading Rust's refe
* `#[repr(u8, u16, ... etc)]`: give this enum the same layout and ABI as the given integer type
* `#[repr(transparent)]`: give this single-field struct the same ABI as its field (useful for newtyping integers but keeping the integer ABI)

cbindgen does not currently support the align or packed reprs.
cbindgen supports the `#[repr(align(N))]` and `#[repr(packed)]` attributes, but currently does not support `#[repr(packed(N))]`.

However it *does* support using `repr(C)`/`repr(u8)` on non-C-like enums (enums with fields). This gives a C-compatible tagged union layout, as [defined by this RFC 2195][really-tagged-unions]. `repr(C)` will give a simpler layout that is perhaps more intuitive, while `repr(u8)` will produce a more compact layout.
cbindgen also supports using `repr(C)`/`repr(u8)` on non-C-like enums (enums with fields). This gives a C-compatible tagged union layout, as [defined by this RFC 2195][really-tagged-unions]. `repr(C)` will give a simpler layout that is perhaps more intuitive, while `repr(u8)` will produce a more compact layout.

If you ensure everything has a guaranteed repr, then cbindgen will generate definitions for:

Expand Down Expand Up @@ -494,8 +494,27 @@ renaming_overrides_prefixing = true
void cppMethod() const;
"""

[layout]
# A string that should come before the name of any type which has been marked
# as `#[repr(packed)]`. For instance, "__attribute__((packed))" would be a
# reasonable value if targeting gcc/clang. A more portable solution would
# involve emitting the name of a macro which you define in a platform-specific
# way. e.g. "PACKED"
#
# default: `#[repr(packed)]` types will be treated as opaque, since it would
# be unsafe for C callers to use a incorrectly laid-out union.
packed = "PACKED"


# A string that should come before the name of any type which has been marked
# as `#[repr(align(n))]`. This string must be a function-like macro which takes
# a single argument (the requested alignment, `n`). For instance, a macro
# `#define`d as `ALIGNED(n)` in `header` which translates to
# `__attribute__((aligned(n)))` would be a reasonable value if targeting
# gcc/clang.
#
# default: `#[repr(align(n))]` types will be treated as opaque, since it
# could be unsafe for C callers to use a incorrectly-aligned union.
aligned_n = "ALIGNED"


[fn]
Expand Down Expand Up @@ -546,9 +565,6 @@ must_use = "MUST_USE_FUNC"
rename_args = "PascalCase"





[struct]
# A rule to use to rename struct field names. The renaming assumes the input is
# the Rust standard snake_case, however it acccepts all the different rename_args
Expand Down
14 changes: 3 additions & 11 deletions src/bindgen/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ impl Builder {
}

for x in &self.srcs {
result.extend_with(&parser::parse_src(x, &self.config.macro_expansion)?);
result.extend_with(&parser::parse_src(x, &self.config)?);
}

if let Some((lib_dir, binding_lib_name)) = self.lib.clone() {
Expand All @@ -319,17 +319,9 @@ impl Builder {
)?
};

result.extend_with(&parser::parse_lib(
cargo,
&self.config.macro_expansion,
&self.config.parse,
)?);
result.extend_with(&parser::parse_lib(cargo, &self.config)?);
} else if let Some(cargo) = self.lib_cargo.clone() {
result.extend_with(&parser::parse_lib(
cargo,
&self.config.macro_expansion,
&self.config.parse,
)?);
result.extend_with(&parser::parse_lib(cargo, &self.config)?);
}

Library::new(
Expand Down
29 changes: 28 additions & 1 deletion src/bindgen/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use toml;

use bindgen::ir::annotation::AnnotationSet;
use bindgen::ir::path::Path;
use bindgen::ir::repr::ReprAlign;
pub use bindgen::rename::RenameRule;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");
Expand Down Expand Up @@ -245,6 +246,29 @@ impl ExportConfig {
}
}

/// Settings to apply to generated types with layout modifiers.
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct LayoutConfig {
/// The way to annotate C types as #[repr(packed)].
pub packed: Option<String>,
/// The way to annotate C types as #[repr(align(...))]. This is assumed to be a functional
/// macro which takes a single argument (the alignment).
pub aligned_n: Option<String>,
}

impl LayoutConfig {
pub(crate) fn ensure_safe_to_represent(&self, align: &ReprAlign) -> Result<(), String> {
match (align, &self.packed, &self.aligned_n) {
(ReprAlign::Packed, None, _) => Err("Cannot safely represent #[repr(packed)] type without configured 'packed' annotation.".to_string()),
(ReprAlign::Align(_), _, None) => Err("Cannot safely represent #[repr(aligned(...))] type without configured 'aligned_n' annotation.".to_string()),
_ => Ok(()),
}
}
}

/// Settings to apply to generated functions.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -317,7 +341,7 @@ pub struct StructConfig {
/// Whether associated constants should be in the body. Only applicable to
/// non-transparent structs, and in C++-only.
pub associated_constants_in_body: bool,
/// The way to annotation this struct as #[must_use].
/// The way to annotate this struct as #[must_use].
pub must_use: Option<String>,
}

Expand Down Expand Up @@ -637,6 +661,8 @@ pub struct Config {
pub export: ExportConfig,
/// The configuration options for macros.
pub macro_expansion: MacroExpansionConfig,
/// The configuration options for type layouts.
pub layout: LayoutConfig,
/// The configuration options for functions
#[serde(rename = "fn")]
pub function: FunctionConfig,
Expand Down Expand Up @@ -680,6 +706,7 @@ impl Default for Config {
macro_expansion: Default::default(),
parse: ParseConfig::default(),
export: ExportConfig::default(),
layout: LayoutConfig::default(),
function: FunctionConfig::default(),
structure: StructConfig::default(),
enumeration: EnumConfig::default(),
Expand Down
10 changes: 8 additions & 2 deletions src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ impl EnumVariant {
parse_fields(is_tagged, &fields.named)?,
is_tagged,
true,
None,
false,
false,
Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
Expand All @@ -117,6 +118,7 @@ impl EnumVariant {
parse_fields(is_tagged, &fields.unnamed)?,
is_tagged,
true,
None,
false,
true,
Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
Expand Down Expand Up @@ -250,8 +252,12 @@ impl Enum {

pub fn load(item: &syn::ItemEnum, mod_cfg: Option<&Cfg>) -> Result<Enum, String> {
let repr = Repr::load(&item.attrs)?;
if repr == Repr::RUST {
return Err("Enum not marked with a valid repr(prim) or repr(C).".to_owned());
if repr.style == ReprStyle::Rust && repr.ty.is_none() {
cyphar marked this conversation as resolved.
Show resolved Hide resolved
return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)].".to_owned());
}
// TODO: Implement translation of aligned enums.
cyphar marked this conversation as resolved.
Show resolved Hide resolved
if repr.align.is_some() {
return Err("Enum is marked with #[repr(align(...))] or #[repr(packed)].".to_owned());
}

let generic_params = GenericParams::new(&item.generics);
Expand Down
124 changes: 95 additions & 29 deletions src/bindgen/ir/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,20 @@ pub enum ReprType {
ISize,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ReprAlign {
Packed,
Align(u64),
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct Repr {
pub style: ReprStyle,
pub ty: Option<ReprType>,
pub align: Option<ReprAlign>,
}

impl Repr {
pub const C: Self = Repr {
style: ReprStyle::C,
ty: None,
};

pub const TRANSPARENT: Self = Repr {
style: ReprStyle::Transparent,
ty: None,
};

pub const RUST: Self = Repr {
style: ReprStyle::Rust,
ty: None,
};

pub fn load(attrs: &[syn::Attribute]) -> Result<Repr, String> {
let ids = attrs
.iter()
Expand All @@ -67,33 +59,107 @@ impl Repr {
.flat_map(|nested| nested)
.filter_map(|meta| match meta {
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
Some(path.segments.first().unwrap().ident.to_string())
Some((path.segments.first().unwrap().ident.to_string(), None))
}
syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { path, nested, .. })) => {
Some((
path.segments.first().unwrap().ident.to_string(),
Some(
nested
.iter()
.filter_map(|meta| match meta {
// Only used for #[repr(align(...))].
syn::NestedMeta::Lit(syn::Lit::Int(literal)) => {
Some(literal.base10_digits().to_string())
}
// Only single levels of nesting supported at the moment.
_ => None,
})
.collect::<Vec<_>>(),
),
))
}
_ => None,
});

let mut repr = Repr::default();
for id in ids {
let new_ty = match id.as_ref() {
"u8" => ReprType::U8,
"u16" => ReprType::U16,
"u32" => ReprType::U32,
"usize" => ReprType::USize,
"i8" => ReprType::I8,
"i16" => ReprType::I16,
"i32" => ReprType::I32,
"isize" => ReprType::ISize,
"C" => {
let new_ty = match (id.0.as_ref(), id.1) {
("u8", None) => ReprType::U8,
("u16", None) => ReprType::U16,
("u32", None) => ReprType::U32,
("usize", None) => ReprType::USize,
("i8", None) => ReprType::I8,
("i16", None) => ReprType::I16,
("i32", None) => ReprType::I32,
("isize", None) => ReprType::ISize,
("C", None) => {
repr.style = ReprStyle::C;
continue;
}
"transparent" => {
("transparent", None) => {
repr.style = ReprStyle::Transparent;
continue;
}
_ => {
return Err(format!("Unsupported #[repr({})].", id));
("packed", args) => {
// #[repr(packed(n))] not supported because of some open questions about how
// to calculate the native alignment of types. See eqrion/cbindgen#433.
if args.is_some() {
return Err(format!(
"Not-yet-implemented #[repr(packed(...))] encountered."
));
}
let align = ReprAlign::Packed;
// Only permit a single alignment-setting repr.
if let Some(old_align) = repr.align {
return Err(format!(
"Conflicting #[repr(align(...))] type hints {:?} and {:?}.",
old_align, align
));
}
repr.align = Some(align);
continue;
}
("align", Some(args)) => {
// #[repr(align(...))] only allows a single argument.
if args.len() != 1 {
return Err(format!(
"Unsupported #[repr(align({}))], align must have exactly one argument.",
args.join(", ")
));
}
// Must be a positive integer.
let align = match args.first().unwrap().parse::<u64>() {
Ok(align) => align,
Err(_) => {
return Err(format!("Non-numeric #[repr(align({}))].", args.join(", ")))
}
};
// Must be a power of 2.
if !align.is_power_of_two() || align == 0 {
return Err(format!("Invalid alignment to #[repr(align({}))].", align));
}
// Only permit a single alignment-setting repr.
if let Some(old_align) = repr.align {
return Err(format!(
"Conflicting #[repr(align(...))] type hints {:?} and {:?}.",
old_align,
ReprAlign::Align(align)
));
}
repr.align = Some(ReprAlign::Align(align));
continue;
}
(path, args) => match args {
None => return Err(format!("Unsupported #[repr({})].", path)),
Some(args) => {
return Err(format!(
"Unsupported #[repr({}({}))].",
path,
args.join(", ")
));
}
},
};
if let Some(old_ty) = repr.ty {
return Err(format!(
Expand Down
Loading