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 transparent enums #992

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
119 changes: 98 additions & 21 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ You can learn about all of the different repr attributes [by reading Rust's refe

* `#[repr(C)]`: give this struct/union/enum the same layout and ABI C would
* `#[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)
* `#[repr(transparent)]`: give this single-field struct or enum the same ABI as its field (useful for newtyping integers but keeping the integer ABI)

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

cbindgen supports using `repr(transparent)` on single-field structs and single-variant enums with fields. Transparent structs and enums are exported as typedefs that alias the underlying single field's type.

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 @@ -299,7 +301,76 @@ fn bar() -> Foo { .. } // Will be emitted as `struct foo bar();`

### 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.
* 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.

* transparent-typedef -- when emitting the typedef for a transparent struct, mark it as
transparent. All references to the struct will be replaced with the type of its underlying NZST
field, effectively making the struct invisible on the FFI side. For example, consider the
following Rust code:

```rust
#[repr(transparent)]
pub struct Handle<T> {
ptr: NonNull<T>,
}

pub struct Foo { }

#[no_mangle]
pub extern "C" fn foo_operation(foo: Option<Handle<Foo>>) { }
```

By default, the exported C++ code would fail to compile, because the function takes `Option<...>`
(which is an opaque type) by value:

```cpp
template<typename T>
struct Option<T>;

template<typename T>
using Handle = T;

struct Foo;

void foo_operation(Option<Handle<Foo>> foo);
```

If we annotate `Handle` with `transparent-typedef` (leaving the rest of the code unchanged):
```rust
/// cbindgen:transparent-typedef
#[repr(transparent)]
pub struct Handle<T> {
ptr: NonNull<T>,
}
```

Then cbindgen is able to simplify the exported C++ code to just:
```cpp
struct Foo;

void foo_operation(Foo* foo);
```

NOTE: This annotation does _NOT_ affect user-defined type aliases for transparent structs. If we
we adjust the previous example to use a type alias:

```rust
type NullableFooHandle = Option<Handle<Foo>>;

#[no_mangle]
pub extern "C" fn foo_operation(foo: NullableFooHandle) { }
```

Then the exported code will use it as expected:

```cpp
struct Foo;

using NullableFooHandle = Foo*;

void foo_operation(NullableFooHandle foo);
```

The rest are just local overrides for the same options found in the cbindgen.toml:

Expand All @@ -316,33 +387,39 @@ The rest are just local overrides for the same options found in the cbindgen.tom
/ etc(if any). The idea is for this to be used to annotate the operator with
attributes, for example:

```rust
/// cbindgen:eq-attributes=MY_ATTRIBUTES
#[repr(C)]
pub struct Foo { .. }
```

Will generate something like:
```rust
/// cbindgen:eq-attributes=MY_ATTRIBUTES
#[repr(C)]
pub struct Foo { .. }
```

```
MY_ATTRIBUTES bool operator==(const Foo& other) const {
...
}
```
Will generate something like:

Combined with something like:
```
MY_ATTRIBUTES bool operator==(const Foo& other) const {
...
}
```

```
#define MY_ATTRIBUTES [[nodiscard]]
```
Combined with something like:

for example.
```
#define MY_ATTRIBUTES [[nodiscard]]
```

### Enum Annotations

* enum-trailing-values=\[variant1, variant2, ...\] -- add the following fieldless enum variants to the end of the enum's definition. These variant names *will* have the enum's renaming rules applied.
* enum-trailing-values=\[variant1, variant2, ...\] -- add the following fieldless enum variants to
the end of the enum's definition. These variant names *will* have the enum's renaming rules
applied.

WARNING: if any of these values are ever passed into Rust, behaviour will be Undefined. Rust does
not know about them, and will assume they cannot happen.

WARNING: if any of these values are ever passed into Rust, behaviour will be Undefined. Rust does not know about them, and will assume they cannot happen.
* transparent-typedef -- when emitting the typedef for a transparent enum, mark it as
transparent. All references to the enum will be replaced with the type of its underlying NZST
variant field, effectively making the enum invisible on the FFI side. For exmaples of how this
works, see [Struct Annotations](#struct-annotations).

The rest are just local overrides for the same options found in the cbindgen.toml:

Expand Down
2 changes: 1 addition & 1 deletion src/bindgen/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ impl Builder {
let mut result = Parse::new();

if self.std_types {
result.add_std_types();
result.add_std_types(self.config.language);
}

for x in &self.srcs {
Expand Down
16 changes: 14 additions & 2 deletions src/bindgen/ir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use crate::bindgen::config::{Config, Language};
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, Item, ItemContainer, Path,
Struct, ToCondition, Type,
AnnotationSet, Cfg, ConditionWrite, Documentation, GenericArgument, GenericParams, Item,
ItemContainer, Path, Struct, ToCondition, TransparentTypeEraser, Type,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::library::Library;
Expand Down Expand Up @@ -603,6 +603,18 @@ impl Item for Constant {
fn generic_params(&self) -> &GenericParams {
GenericParams::empty()
}

fn erase_transparent_types_inplace(
&mut self,
library: &Library,
eraser: &mut TransparentTypeEraser,
_generics: &[GenericArgument],
) {
// NOTE: We also need to simplify the literal initializer value to match the underlying
// type, but that is true for all transparent structs (not just transparent-typedef
// structs), and is handled by the `write` method below.
eraser.erase_transparent_types_inplace(library, &mut self.ty, &[]);
}
}

impl Constant {
Expand Down
97 changes: 80 additions & 17 deletions src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
AnnotationSet, AnnotationValue, Cfg, ConditionWrite, DeprecatedNoteKind, Documentation, Field,
GenericArgument, GenericParams, GenericPath, Item, ItemContainer, Literal, Path, Repr,
ReprStyle, Struct, ToCondition, Type,
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type, Typedef,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::library::Library;
Expand Down Expand Up @@ -247,12 +247,6 @@ impl EnumVariant {
}
}

fn simplify_standard_types(&mut self, config: &Config) {
if let VariantBody::Body { ref mut body, .. } = self.body {
body.simplify_standard_types(config);
}
}

fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
if let VariantBody::Body { ref body, .. } = self.body {
body.add_dependencies(library, out);
Expand Down Expand Up @@ -314,7 +308,12 @@ impl Enum {

/// Enum with data turns into a union of structs with each struct having its own tag field.
pub(crate) fn inline_tag_field(repr: &Repr) -> bool {
repr.style != ReprStyle::C
// NOTE: repr(C) requires an out of line tag field, and repr(transparent) doesn't use tags.
repr.style == ReprStyle::Rust
}

pub fn is_transparent(&self) -> bool {
self.repr.style == ReprStyle::Transparent
}

pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
Expand Down Expand Up @@ -351,7 +350,7 @@ impl Enum {
) -> Result<Enum, String> {
let repr = Repr::load(&item.attrs)?;
if repr.style == ReprStyle::Rust && repr.ty.is_none() {
return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)].".to_owned());
return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)] or #[repr(transparent)].".to_owned());
}
// TODO: Implement translation of aligned enums.
if repr.align.is_some() {
Expand Down Expand Up @@ -424,13 +423,35 @@ impl Enum {
pub fn new(
path: Path,
generic_params: GenericParams,
repr: Repr,
mut repr: Repr,
variants: Vec<EnumVariant>,
tag: Option<String>,
cfg: Option<Cfg>,
annotations: AnnotationSet,
documentation: Documentation,
) -> Self {
// WARNING: A transparent enum with no fields (or whose fields are all 1-ZST) is legal rust
// [1], but it is a zero-sized type and as such is "best avoided entirely" [2] because it
// "will be nonsensical or problematic if passed through the FFI boundary" [1]. Further,
// because no well-defined underlying native type exists for a 1-ZST, we cannot emit a
// typedef and must fall back to repr(C) behavior that defines a tagged enum.
//
// [1] https://doc.rust-lang.org/nomicon/other-reprs.html
// [2] https://github.com/rust-lang/rust/issues/77841#issuecomment-716796313
if repr.style == ReprStyle::Transparent {
let zero_sized = match variants.first() {
Some(EnumVariant {
body: VariantBody::Body { ref body, .. },
..
}) => body.fields.is_empty(),
_ => true,
};
if zero_sized {
warn!("Passing zero-sized transparent enum {} across the FFI boundary is undefined behavior", &path);
repr.style = ReprStyle::C;
}
}

let export_name = path.name().to_owned();
Self {
path,
Expand All @@ -444,6 +465,28 @@ impl Enum {
documentation,
}
}

/// Attempts to convert this enum to a typedef (only works for transparent enums).
pub fn as_typedef(&self) -> Option<Typedef> {
if self.is_transparent() {
if let Some(EnumVariant {
body: VariantBody::Body { ref body, .. },
..
}) = self.variants.first()
{
if let Some(field) = body.fields.first() {
return Some(Typedef::new_from_item_field(self, field));
}
}
}
None
}

// Transparent enums become typedefs, so try converting to typedef and recurse on that.
pub fn as_transparent_alias(&self, generics: &[GenericArgument]) -> Option<Type> {
self.as_typedef()
.and_then(|t| t.as_transparent_alias(generics))
}
}

impl Item for Enum {
Expand Down Expand Up @@ -476,7 +519,9 @@ impl Item for Enum {
}

fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) {
if self.tag.is_some() {
if self.repr.style == ReprStyle::Transparent {
resolver.add_none(&self.path);
} else if self.tag.is_some() {
if self.repr.style == ReprStyle::C {
resolver.add_struct(&self.path);
} else {
Expand All @@ -500,6 +545,30 @@ impl Item for Enum {
&self.generic_params
}

fn erase_transparent_types_inplace(
&mut self,
library: &Library,
eraser: &mut TransparentTypeEraser,
generics: &[GenericArgument],
) {
let mut skip_inline_tag_field = Self::inline_tag_field(&self.repr);
let generics = self.generic_params.defaulted_generics(generics);
let mappings = self.generic_params.call(self.name(), &generics);
for variant in self.variants.iter_mut() {
if let VariantBody::Body { ref mut body, .. } = variant.body {
for field in body.fields.iter_mut() {
// Ignore the inline Tag field, if any (it's always first)
if skip_inline_tag_field {
debug!("Skipping inline Tag field {:?}", field);
skip_inline_tag_field = false;
} else {
eraser.erase_transparent_types_inplace(library, &mut field.ty, &mappings);
}
}
}
}
}

fn rename_for_config(&mut self, config: &Config) {
config.export.rename(&mut self.export_name);

Expand Down Expand Up @@ -1493,10 +1562,4 @@ impl Enum {
}
}
}

pub fn simplify_standard_types(&mut self, config: &Config) {
for variant in &mut self.variants {
variant.simplify_standard_types(config);
}
}
}
23 changes: 15 additions & 8 deletions src/bindgen/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use syn::ext::IdentExt;
use crate::bindgen::config::{Config, Language};
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{AnnotationSet, Cfg, Documentation, GenericPath, Path, Type};
use crate::bindgen::ir::{
AnnotationSet, Cfg, Documentation, GenericPath, Path, TransparentTypeEraser, Type,
};
use crate::bindgen::library::Library;
use crate::bindgen::monomorph::Monomorphs;
use crate::bindgen::rename::{IdentifierType, RenameRule};
Expand Down Expand Up @@ -115,13 +117,6 @@ impl Function {
&self.path
}

pub fn simplify_standard_types(&mut self, config: &Config) {
self.ret.simplify_standard_types(config);
for arg in &mut self.args {
arg.ty.simplify_standard_types(config);
}
}

pub fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
self.ret.add_dependencies(library, out);
for arg in &self.args {
Expand Down Expand Up @@ -150,6 +145,18 @@ impl Function {
}
}

// NOTE: No `generics` arg because Functions do not support generics and do not `impl Item`.
pub fn erase_transparent_types_inplace(
&mut self,
library: &Library,
eraser: &mut TransparentTypeEraser,
) {
eraser.erase_transparent_types_inplace(library, &mut self.ret, &[]);
for arg in &mut self.args {
eraser.erase_transparent_types_inplace(library, &mut arg.ty, &[]);
}
}

pub fn rename_for_config(&mut self, config: &Config) {
// Rename the types used in arguments
let generic_params = Default::default();
Expand Down
Loading
Loading