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 #[repr(align(x))] on bridge structs #902

Open
wants to merge 2 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions book/src/shared.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,18 @@ C++ data type:
- `PartialOrd` produces `operator<`, `operator<=`, `operator>`, `operator>=`

[hash]: https://en.cppreference.com/w/cpp/utility/hash

## Alignment

Enforcing minimum alignment for structs using `repr(align(x))` is supported within the
CXX bridge module. The alignment value must be a power of two from 1 up to 2<sup>29</sup>.

```rust,noplayground
#[cxx::bridge]
mod ffi {
#[repr(align(4))]
struct ExampleStruct {
b: [u8; 4],
}
}
```
9 changes: 7 additions & 2 deletions gen/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::syntax::set::UnorderedSet;
use crate::syntax::symbol::Symbol;
use crate::syntax::trivial::{self, TrivialReason};
use crate::syntax::{
derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait,
derive, mangle, Alignment, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait,
Type, TypeAlias, Types, Var,
};
use proc_macro2::Ident;
Expand Down Expand Up @@ -238,7 +238,12 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern
for line in strct.doc.to_string().lines() {
writeln!(out, "//{}", line);
}
writeln!(out, "struct {} final {{", strct.name.cxx);
let alignment = if let Some(Alignment::Align(x)) = strct.alignment {
format!("alignas({}) ", x)
} else {
String::from("")
};
writeln!(out, "struct {}{} final {{", alignment, strct.name.cxx);

for field in &strct.fields {
for line in field.doc.to_string().lines() {
Expand Down
16 changes: 13 additions & 3 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use crate::syntax::qualified::QualifiedName;
use crate::syntax::report::Errors;
use crate::syntax::symbol::Symbol;
use crate::syntax::{
self, check, mangle, Api, Doc, Enum, ExternFn, ExternType, Impl, Lifetimes, Pair, Signature,
self, check, mangle, Alignment, Api, Doc, Enum, ExternFn, ExternType, Impl, Lifetimes, Pair, Signature,
Struct, Trait, Type, TypeAlias, Types,
};
use crate::type_id::Crate;
use crate::{derive, generics};
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::mem;
use syn::{parse_quote, punctuated, Generics, Lifetime, Result, Token};
Expand Down Expand Up @@ -144,6 +144,7 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types)
fn expand_struct(strct: &Struct) -> TokenStream {
let ident = &strct.name.rust;
let doc = &strct.doc;
let alignment = &strct.alignment;
let attrs = &strct.attrs;
let generics = &strct.generics;
let type_id = type_id(&strct.name);
Expand All @@ -167,11 +168,20 @@ fn expand_struct(strct: &Struct) -> TokenStream {
}
};

let mut repr = quote! { #[repr(C)] };
if let Some(Alignment::Align(x)) = alignment {
// Suffix isn't allowed in repr(align)
let x = Literal::u32_unsuffixed(*x);
repr = quote! {
#repr
#[repr(align(#x))]
}
}
quote! {
#doc
#attrs
#derives
#[repr(C)]
#repr
#struct_def

unsafe impl #generics ::cxx::ExternType for #ident #generics {
Expand Down
23 changes: 5 additions & 18 deletions syntax/attrs.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::Atom::{self, *};
use crate::syntax::repr::Repr;
use crate::syntax::{Derive, Doc, ForeignName};
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use syn::parse::{Nothing, Parse, ParseStream, Parser as _};
use syn::{Attribute, Error, LitStr, Path, Result, Token};
use syn::{Attribute, LitStr, Path, Result, Token};

// Intended usage:
//
Expand All @@ -29,7 +29,7 @@ use syn::{Attribute, Error, LitStr, Path, Result, Token};
pub struct Parser<'a> {
pub doc: Option<&'a mut Doc>,
pub derives: Option<&'a mut Vec<Derive>>,
pub repr: Option<&'a mut Option<Atom>>,
pub repr: Option<&'a mut Option<Repr>>,
pub namespace: Option<&'a mut Namespace>,
pub cxx_name: Option<&'a mut Option<ForeignName>>,
pub rust_name: Option<&'a mut Option<Ident>>,
Expand Down Expand Up @@ -178,21 +178,8 @@ fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Der
Ok(derives)
}

fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
let begin = input.cursor();
let ident: Ident = input.parse()?;
if let Some(atom) = Atom::from(&ident) {
match atom {
U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize if input.is_empty() => {
return Ok(atom);
}
_ => {}
}
}
Err(Error::new_spanned(
begin.token_stream(),
"unrecognized repr",
))
fn parse_repr_attribute(input: ParseStream) -> Result<Repr> {
input.parse::<Repr>()
}

fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> {
Expand Down
6 changes: 6 additions & 0 deletions syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod parse;
mod pod;
pub mod qualified;
pub mod report;
pub mod repr;
pub mod resolve;
pub mod set;
pub mod symbol;
Expand All @@ -46,6 +47,10 @@ pub use self::names::ForeignName;
pub use self::parse::parse_items;
pub use self::types::Types;

pub enum Alignment {
Align(u32),
}

pub enum Api {
Include(Include),
Struct(Struct),
Expand Down Expand Up @@ -92,6 +97,7 @@ pub struct ExternType {
pub struct Struct {
pub doc: Doc,
pub derives: Vec<Derive>,
pub alignment: Option<Alignment>,
pub attrs: OtherAttrs,
pub visibility: Token![pub],
pub struct_token: Token![struct],
Expand Down
23 changes: 21 additions & 2 deletions syntax/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use crate::syntax::attrs::OtherAttrs;
use crate::syntax::discriminant::DiscriminantSet;
use crate::syntax::file::{Item, ItemForeignMod};
use crate::syntax::report::Errors;
use crate::syntax::repr::Repr;
use crate::syntax::Atom::*;
use crate::syntax::{
attrs, error, Api, Array, Derive, Doc, Enum, EnumRepr, ExternFn, ExternType, ForeignName, Impl,
attrs, error, Alignment, Api, Array, Derive, Doc, Enum, EnumRepr, ExternFn, ExternType, ForeignName, Impl,
Include, IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Ptr, Receiver, Ref,
Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant,
};
Expand Down Expand Up @@ -57,6 +58,7 @@ pub fn parse_items(
fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> Result<Api> {
let mut doc = Doc::new();
let mut derives = Vec::new();
let mut repr = None;
let mut namespace = namespace.clone();
let mut cxx_name = None;
let mut rust_name = None;
Expand All @@ -66,13 +68,20 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) ->
attrs::Parser {
doc: Some(&mut doc),
derives: Some(&mut derives),
repr: Some(&mut repr),
namespace: Some(&mut namespace),
cxx_name: Some(&mut cxx_name),
rust_name: Some(&mut rust_name),
..Default::default()
},
);

let alignment = if let Some(Repr::Align(x)) = repr {
Some(Alignment::Align(x))
} else {
None
};

let named_fields = match item.fields {
Fields::Named(fields) => fields,
Fields::Unit => return Err(Error::new_spanned(item, "unit structs are not supported")),
Expand Down Expand Up @@ -170,6 +179,7 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) ->
Ok(Api::Struct(Struct {
doc,
derives,
alignment,
attrs,
visibility,
struct_token,
Expand All @@ -190,7 +200,7 @@ fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api {
let mut variants_from_header = None;
let attrs = attrs::parse(
cx,
item.attrs,
item.attrs.clone(),
attrs::Parser {
doc: Some(&mut doc),
derives: Some(&mut derives),
Expand All @@ -214,6 +224,15 @@ fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api {
cx.error(where_clause, "enum with where-clause is not supported");
}

let repr = match repr {
Some(Repr::Atom(atom)) => Some(atom),
Some(Repr::Align(_)) => {
cx.error(&item, "repr(align) on enums is not supported");
None
},
None => None,
};

let mut variants = Vec::new();
let mut discriminants = DiscriminantSet::new(repr);
for variant in item.variants {
Expand Down
45 changes: 45 additions & 0 deletions syntax/repr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::syntax::Atom::{self, *};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::{Ident, LitInt};

#[derive(Copy, Clone, PartialEq)]
pub enum Repr {
Align(u32),
Atom(Atom),
}

impl Parse for Repr {
fn parse(input: ParseStream) -> Result<Self> {
let begin = input.cursor();
let ident: Ident = input.parse()?;
if let Some(atom) = Atom::from(&ident) {
match atom {
U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize if input.is_empty() => {
return Ok(Repr::Atom(atom));
}
_ => {}
}
} else if ident == "align" {
let content;
syn::parenthesized!(content in input);
let alignment: u32 = content.parse::<LitInt>()?.base10_parse()?;
if !alignment.is_power_of_two() {
return Err(Error::new_spanned(
begin.token_stream(),
"invalid `repr(align)` attribute: not a power of two",
));
}
if alignment > 2u32.pow(29) {
return Err(Error::new_spanned(
begin.token_stream(),
"invalid `repr(align)` attribute: larger than 2^29",
));
}
return Ok(Repr::Align(alignment));
}
Err(Error::new_spanned(
begin.token_stream(),
"unrecognized repr",
))
}
}
5 changes: 5 additions & 0 deletions tests/ffi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ pub mod ffi {
a: [i32; 4],
}

#[repr(align(4))]
pub struct StructWithAlignment4 {
b: [u8; 4],
}

#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct StructWithLifetime<'a> {
s: &'a str,
Expand Down
2 changes: 2 additions & 0 deletions tests/ffi/tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ extern "C" bool cxx_test_suite_r_is_correct(const tests::R *) noexcept;

namespace tests {

static_assert(4 == alignof(StructWithAlignment4), "expected 4 byte alignment");

static constexpr char SLICE_DATA[] = "2020";

C::C(size_t n) : n(n) {}
Expand Down
5 changes: 5 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ fn test_enum_representations() {
assert_eq!(2021, ffi::Enum::LastVal.repr);
}

#[test]
fn test_struct_align_repr() {
assert_eq!(4, std::mem::align_of::<ffi::StructWithAlignment4>());
}

#[test]
fn test_debug() {
assert_eq!("Shared { z: 1 }", format!("{:?}", ffi::Shared { z: 1 }));
Expand Down
9 changes: 9 additions & 0 deletions tests/ui/enum_align_unsupported.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[cxx::bridge]
mod ffi {
#[repr(align(2))]
enum Bad {
A,
}
}

fn main() {}
8 changes: 8 additions & 0 deletions tests/ui/enum_align_unsupported.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
error: repr(align) on enums is not supported
--> $DIR/enum_align_unsupported.rs:3:5
|
3 | / #[repr(align(2))]
4 | | enum Bad {
5 | | A,
6 | | }
| |_____^
20 changes: 20 additions & 0 deletions tests/ui/struct_align.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#[cxx::bridge]
mod ffi {
#[repr(align(3))]
struct SharedA {
b: [u8; 4],
}

// 1073741824 = 2^30
#[repr(align(1073741824))]
struct SharedB {
b: [u8; 4],
}

#[repr(align(-2))]
struct SharedC {
b: [u8; 4],
}
}

fn main() {}
17 changes: 17 additions & 0 deletions tests/ui/struct_align.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error: invalid `repr(align)` attribute: not a power of two
--> $DIR/struct_align.rs:3:12
|
3 | #[repr(align(3))]
| ^^^^^^^^

error: invalid `repr(align)` attribute: larger than 2^29
--> $DIR/struct_align.rs:9:12
|
9 | #[repr(align(1073741824))]
| ^^^^^^^^^^^^^^^^^

error: invalid digit found in string
--> $DIR/struct_align.rs:14:18
|
14 | #[repr(align(-2))]
| ^