Skip to content

Add support for #[repr(align(x))] on bridge structs #902

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

Merged
merged 2 commits into from
Aug 9, 2025

Conversation

mxxo
Copy link
Contributor

@mxxo mxxo commented Jul 21, 2021

Add support for #[repr(align(x))] on bridge structs, closing #835. For the example code:

#[cxx::bridge]
mod ffi {
    #[repr(align(4))]
    struct S {
        b: [u8; 4],
    }
}

the following output was obtained using the cxx code generators:

// -- snip 
#[repr(C)]
#[repr(align(4))]
pub struct S {
    pub b: [u8; 4],
}
// -- snip
#include <array>
#include <cstdint>
#include <type_traits>

struct S;

#ifndef CXXBRIDGE1_STRUCT_S
#define CXXBRIDGE1_STRUCT_S
struct alignas(4) S final {
  ::std::array<::std::uint8_t, 4> b;

  using IsRelocatable = ::std::true_type;
};
#endif // CXXBRIDGE1_STRUCT_S

I believe it would be straightforward to extend this implementation for #[repr(packed)] as well.

I was wondering whether to add support for enums but decided against it for now and made it an "unsupported" error for the time being. enum alignas(X) is accepted by all the C++ compilers I tried, but I also found a defect report from 2017 (2534) that removes the ability to apply alignas to enums: https://wg21.cmeerw.net/cwg/issue2354.

@dyangelo-grullon
Copy link

@dtolnay is there any feedback on this change? What is the path to getting this merged? I’m contemplating closing the last mile or putting up a bounty for this specific feature.

Copy link
Owner

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not sufficient for alignment values that are smaller than the alignment of one of the fields, which Rust allows and C++ does not: "If the strictest (largest) alignas on a declaration is weaker than the alignment it would have without any alignas specifiers (that is, weaker than its natural alignment or weaker than alignas on another declaration of the same object or type), the program is ill-formed."

It needs to expand to something like:

struct alignas(max(
  N,
  alignof(Field1),
  alignof(Field2),
  ...
)) Struct final {

Comment on lines +241 to +246
let alignment = if let Some(Alignment::Align(x)) = strct.alignment {
format!("alignas({}) ", x)
} else {
String::from("")
};
writeln!(out, "struct {}{} final {{", alignment, strct.name.cxx);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let alignment = if let Some(Alignment::Align(x)) = strct.alignment {
format!("alignas({}) ", x)
} else {
String::from("")
};
writeln!(out, "struct {}{} final {{", alignment, strct.name.cxx);
write!(out, "struct");
if let Some(Alignment::Align(align)) = strct.alignment {
write!(out, " alignas({})", align);
}
writeln!(out, " {} final {{", strct.name.cxx);

Comment on lines +181 to 183
fn parse_repr_attribute(input: ParseStream) -> Result<Repr> {
input.parse::<Repr>()
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary function, it can use attr.parse_args::<Repr>() above instead of attr.parse_args_with(parse_repr_attribute).

Comment on lines +50 to +52
pub enum Alignment {
Align(u32),
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This enum is not serving any purpose. An alignment can be stored directly as u32 or LitInt below.

@@ -92,6 +97,7 @@ pub struct ExternType {
pub struct Struct {
pub doc: Doc,
pub derives: Vec<Derive>,
pub alignment: Option<Alignment>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub alignment: Option<Alignment>,
pub alignment: Option<LitInt>,

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be clarified that this is a limitation of C++ enums and not cxx.

Suggested change
cx.error(&item, "repr(align) on enums is not supported");
cx.error(&item, "C++ does not support custom alignment on an enum");

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the error should point to just the align attribute, not the entire item.

use syn::parse::{Error, Parse, ParseStream, Result};
use syn::{Ident, LitInt};

#[derive(Copy, Clone, PartialEq)]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these trait impls are used anywhere.

Suggested change
#[derive(Copy, Clone, PartialEq)]

} else if ident == "align" {
let content;
syn::parenthesized!(content in input);
let alignment: u32 = content.parse::<LitInt>()?.base10_parse()?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would accept a suffixed integer literal such as #[repr(align(2int))]. That should not be allowed.

"invalid `repr(align)` attribute: not a power of two",
));
}
if alignment > 2u32.pow(29) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a guarantee that C++ compilers support alignments in this whole range. For example Clang:

struct alignas(1<<19) A {};
struct alignas(1<<29) B {};
auto a() { return alignof(A); } // 524288
auto b() { return alignof(B); } // 1

This needs to use a smaller limit until somebody needs otherwise. As is, this could lead to C++ passing an insufficiently aligned reference to Rust which would be UB.

}
_ => {}
}
} else if ident == "align" {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to give a better error message if a repr attribute is written with a non-literal expression, since C++ allows this but Rust does not. #[repr(align(1 << 6))]

@dtolnay dtolnay merged commit 1b4f7e2 into dtolnay:master Aug 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants