Skip to content

Commit

Permalink
Add support for transparent enum
Browse files Browse the repository at this point in the history
  • Loading branch information
scovich committed Aug 14, 2024
1 parent 42ae2fe commit afa9286
Show file tree
Hide file tree
Showing 20 changed files with 1,223 additions and 263 deletions.
16 changes: 13 additions & 3 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 @@ -407,9 +409,17 @@ The rest are just local overrides for the same options found in the cbindgen.tom

### 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
61 changes: 56 additions & 5 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, TransparentTypeEraser, Type,
ReprStyle, Struct, ToCondition, TransparentTypeEraser, Type, Typedef,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::library::Library;
Expand Down Expand Up @@ -308,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 @@ -345,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 @@ -418,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 @@ -438,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 @@ -470,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 Down
12 changes: 7 additions & 5 deletions src/bindgen/ir/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,12 @@ impl Struct {
// [2] https://github.com/rust-lang/rust/issues/77841#issuecomment-716796313
// [3] https://doc.rust-lang.org/nomicon/other-reprs.html
if fields.is_empty() {
warn!(
"Passing zero-sized struct {} across the FFI boundary is undefined behavior",
&path
);
if !is_enum_variant_body {
warn!(
"Passing zero-sized struct {} across the FFI boundary is undefined behavior",
&path
);
}
is_transparent = false;
}

Expand All @@ -163,7 +165,7 @@ impl Struct {
/// Attempts to convert this struct to a typedef (only works for transparent structs).
pub fn as_typedef(&self) -> Option<Typedef> {
match self.fields.first() {
Some(field) if self.is_transparent => Some(Typedef::new_from_struct_field(self, field)),
Some(field) if self.is_transparent => Some(Typedef::new_from_item_field(self, field)),
_ => None,
}
}
Expand Down
1 change: 1 addition & 0 deletions src/bindgen/ir/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ impl Type {
ItemContainer::OpaqueItem(o) => o.as_transparent_alias(&generics),
ItemContainer::Typedef(t) => t.as_transparent_alias(&generics),
ItemContainer::Struct(s) => s.as_transparent_alias(&generics),
ItemContainer::Enum(e) => e.as_transparent_alias(&generics),
_ => None,
};
if let Some(mut ty) = aliased_ty {
Expand Down
8 changes: 4 additions & 4 deletions src/bindgen/ir/typedef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
AnnotationSet, Cfg, Documentation, Field, GenericArgument, GenericParams, Item, ItemContainer,
Path, Struct, TransparentTypeEraser, Type,
Path, TransparentTypeEraser, Type,
};
use crate::bindgen::library::Library;
use crate::bindgen::mangle;
Expand Down Expand Up @@ -69,12 +69,12 @@ impl Typedef {
}
}

// Used to convert a transparent Struct to a Typedef.
pub fn new_from_struct_field(item: &Struct, field: &Field) -> Self {
// Used to convert a transparent Struct or Enum to a Typedef.
pub fn new_from_item_field(item: &impl Item, field: &Field) -> Self {
Self {
path: item.path().clone(),
export_name: item.export_name().to_string(),
generic_params: item.generic_params.clone(),
generic_params: item.generic_params().clone(),
aliased: field.ty.clone(),
cfg: item.cfg().cloned(),
annotations: item.annotations().clone(),
Expand Down
23 changes: 22 additions & 1 deletion src/bindgen/language_backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,27 @@ pub trait LanguageBackend: Sized {
}
}

/// If the enum is transparent, emit a typedef of its NZST field type instead.
fn write_enum_or_typedef<W: Write>(
&mut self,
out: &mut SourceWriter<W>,
e: &Enum,
_b: &Bindings,
) {
if let Some(typedef) = e.as_typedef() {
self.write_type_def(out, &typedef);
// TODO: Associated constants are not supported for enums. Should they be? Rust
// enum exports as a union, and C/C++ at least supports static union members?
//
//for constant in &e.associated_constants {
// out.new_line();
// constant.write(b.config, self, out, Some(e));
//}
} else {
self.write_enum(out, e);
}
}

fn write_items<W: Write>(&mut self, out: &mut SourceWriter<W>, b: &Bindings) {
for item in &b.items {
if item
Expand All @@ -172,7 +193,7 @@ pub trait LanguageBackend: Sized {
match *item {
ItemContainer::Constant(..) => unreachable!(),
ItemContainer::Static(..) => unreachable!(),
ItemContainer::Enum(ref x) => self.write_enum(out, x),
ItemContainer::Enum(ref x) => self.write_enum_or_typedef(out, x, b),
ItemContainer::Struct(ref x) => self.write_struct_or_typedef(out, x, b),
ItemContainer::Union(ref x) => self.write_union(out, x),
ItemContainer::OpaqueItem(ref x) => self.write_opaque_item(out, x),
Expand Down
12 changes: 12 additions & 0 deletions tests/expectations/const_transparent.compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@
#include <stdint.h>
#include <stdlib.h>

#define TransparentEnum_ASSOC_ENUM_FOO 8

typedef uint8_t TransparentStruct;
#define TransparentStruct_ASSOC_STRUCT_FOO 1
#define TransparentStruct_ASSOC_STRUCT_BAR 2


typedef uint8_t TransparentTupleStruct;

typedef uint8_t TransparentEnum;

#define STRUCT_FOO 4

#define STRUCT_BAR 5












15 changes: 15 additions & 0 deletions tests/expectations/const_transparent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <ostream>
#include <new>

constexpr static const int64_t TransparentEnum_ASSOC_ENUM_FOO = 8;

template<typename T>
using Wrapper = T;

Expand All @@ -17,10 +19,23 @@ using TransparentTupleStruct = uint8_t;
template<typename T>
using TransparentStructWithErasedField = T;

using TransparentEnum = uint8_t;

template<typename T>
using TransparentWrapperEnum = T;

constexpr static const TransparentStruct STRUCT_FOO = 4;

constexpr static const TransparentTupleStruct STRUCT_BAR = 5;

constexpr static const TransparentStruct STRUCT_BAZ = 6;

constexpr static const TransparentStructWithErasedField<TransparentStruct> COMPLEX = 7;








12 changes: 12 additions & 0 deletions tests/expectations/const_transparent.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,29 @@ cdef extern from *:

cdef extern from *:

const int64_t TransparentEnum_ASSOC_ENUM_FOO # = 8

ctypedef uint8_t TransparentStruct;
const int64_t TransparentStruct_ASSOC_STRUCT_FOO # = 1
const TransparentStruct TransparentStruct_ASSOC_STRUCT_BAR # = 2


ctypedef uint8_t TransparentTupleStruct;

ctypedef uint8_t TransparentEnum;

const TransparentStruct STRUCT_FOO # = 4

const TransparentTupleStruct STRUCT_BAR # = 5












Loading

0 comments on commit afa9286

Please sign in to comment.