Skip to content

Improve struct alignment with padding bytes#468

Merged
bors-servo merged 1 commit into
rust-lang:masterfrom
flier:padding-bytes
Feb 7, 2017
Merged

Improve struct alignment with padding bytes#468
bors-servo merged 1 commit into
rust-lang:masterfrom
flier:padding-bytes

Conversation

@flier

@flier flier commented Feb 2, 2017

Copy link
Copy Markdown
Contributor

I known, it is a little tricky or dirty solution, I have to do a lot of calculate for alignment.

For example

typedef struct {
  long long __clang_max_align_nonce1
      __attribute__((__aligned__(__alignof__(long long))));
  long double __clang_max_align_nonce2
      __attribute__((__aligned__(__alignof__(long double))));
} max_align_t;

will be generated as

#[repr(C)]
#[derive(Debug, Copy)]
pub struct _bindgen_ty_1 {
    pub __clang_max_align_nonce1: ::std::os::raw::c_longlong,
    _padding_0: u64,
    pub __clang_max_align_nonce2: f64,
    _padding_1: u64,
}
#[test]
fn bindgen_test_layout__bindgen_ty_1() {
    assert_eq!(::std::mem::size_of::<_bindgen_ty_1>() , 32usize);
    assert_eq! (0usize , unsafe {
                & ( * ( 0 as * const _bindgen_ty_1 ) ) .
                __clang_max_align_nonce1 as * const _ as usize });
    assert_eq! (16usize , unsafe {
                & ( * ( 0 as * const _bindgen_ty_1 ) ) .
                __clang_max_align_nonce2 as * const _ as usize });
}

It may generate wrong layout when use template or multi virtual inheritance.

At least, it is a begining, pass all the test cases, and some complex struct in wild.

@highfive

highfive commented Feb 2, 2017

Copy link
Copy Markdown

warning Warning warning

  • These commits modify unsafe code. Please review it carefully!

@emilio

emilio commented Feb 3, 2017

Copy link
Copy Markdown
Contributor

Just a quick update, I'm reviewing this, it's just kind of involved to do so :)

@fitzgen fitzgen mentioned this pull request Feb 3, 2017
@bors-servo

Copy link
Copy Markdown

☔ The latest upstream changes (presumably #471) made this pull request unmergeable. Please resolve the merge conflicts.

@emilio emilio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, looks pretty good! I have a few comments and questions here and there, but overall looks good, thanks!

Comment thread src/codegen/mod.rs Outdated
}
}

struct StructLayout<'a, 'ctx: 'a> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's move this to a helper module, maybe src/codegen/struct_layout.rs? That way it's easier to audit :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, StructLayout isn't a super descriptive name, maybe StructLayoutTracker?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sure, codegen::struct_layout::StructLayoutTracker will be fine

Comment thread src/codegen/mod.rs Outdated
};

if padding_bytes >= align || layout.align > mem::size_of::<* mut ()>()
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Brace on its own line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

move to use rustfmt on save

Comment thread src/codegen/mod.rs Outdated
pub fn saw_base(&mut self, base_ty: &Type) {
if let Some(base_layout) = base_ty.layout(self.ctx) {
self.latest_offset += self.padding_bytes(base_layout) +
base_ty.calc_size(self.ctx).unwrap_or(base_layout.size);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there any reason for not trusting base_layout.size here? If so, please add a comment, since it doesn't seem obvious to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, we could trust base_layout.size here. In fact, I use base_ty.calc_size because I have tried to fix those virtual inheritance vtable padding issues, but I remove those code before submit, because it is a little complex, hard to cover everything in one patch :(

Comment thread src/codegen/mod.rs Outdated
)
};

let check_field_offset = if item.is_opaque(ctx) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add a FIXME comment pointing to #465 so people can understand the base_members check. It's probably worth moving this condition to its own variable. Something like:

let should_skip_field_offset_checks = item.is_opaque(ctx) || ...;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done.

Comment thread src/codegen/mod.rs Outdated
field.name().and_then(|name| {
field.offset().and_then(|offset| {
let field_offset = offset / 8;
let field_name = aster::AstBuilder::new().id(ctx.rust_mangle(name).into_owned());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd expect just:

let field_name = ctx.rust_mangle(name);

to work, doesn't it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ctx.rust_mangle(name) is a string, but we need a ident, or

             assert_eq! (0usize , unsafe {
-                        & ( * ( 0 as * const Test ) ) . "helper" as * const _
-                        as usize });
+                        & ( * ( 0 as * const Test ) ) . helper as * const _ as
+                        usize });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

right, you can use ctx.rust_ident(&name) then :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It works, thanks!

Comment thread src/ir/comp.rs
let field_layout = ctx.resolve_type(field.ty)
.layout(ctx);
/// Compute the layout of this type.
pub fn calc_layout(&self, ctx: &BindgenContext) -> Option<Layout> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am unsure what's the purpose of this method. Are we getting better info that libclang? If so, please state above that we may be able to compute our layout even when libclang doesn't provide it.

@flier flier Feb 4, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

libclang will give the struct size after alignment, but sometime we need padding bytes at the end, because Rust doesn't support alignment more than 8 bytes.

For example, in the layout_mbuf example, we need 24 padding bytes to ensure the rte_mbuf has the correct size.

struct rte_mbuf {
	MARKER cacheline0;
       ...
	/** Timesync flags for use with IEEE1588. */
	uint16_t timesync;
} __rte_cache_aligned;
#[repr(C)]
#[derive(Debug, Copy)]
pub struct rte_mbuf {
    pub cacheline0: MARKER,
  ...
    _padding_0: [u64; 3usize],
}

CompInfo::calc_layout will do the dirty work to find out how many bytes we need padding at the end.

Comment thread src/ir/comp.rs Outdated
if cur.kind() != CXCursor_FieldDecl {
if let Some((ty, _)) = maybe_anonymous_struct_field {
let field = Field::new(None, ty, None, None, None, false);
let field = Field::new(None, ty, None, None, None, false, None);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not right, we should be able to know the field offset and track it in maybe_anonymous_struct_field.

Does the offset function return something when either called on the struct declaration (where we create the maybe_anonymous_struct_field?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sure, added the offset to maybe_anonymous_struct_field tuple

        let mut maybe_anonymous_struct_field = None;
        cursor.visit(|cur| {
            if cur.kind() != CXCursor_FieldDecl {
                if let Some((ty, _, offset)) =
                    maybe_anonymous_struct_field.take() {
                    let field =
                        Field::new(None, ty, None, None, None, false, offset);
                    ci.fields.push(field);
                }
            }
...

Comment thread src/ir/ty.rs Outdated
TypeKind::Alias(inner) |
TypeKind::TemplateAlias(inner, _) |
TypeKind::TemplateRef(inner, _) => ctx.resolve_type(inner).calc_size(ctx),
_ => None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please list the other types explicitly here so when we add more people don't forget to add it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done.

@emilio emilio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, so I think this is good to merge with that last comment addressed, and a squash so the merge commits go away.

I'll need to test this with Gecko just to double-check our C++ structs aren't completely broken with this patch, but I don't expect a lot of problems.

Thanks for this patch once again, it's really awesome! I don't love the extra complexity, but I think the improvements are quite worth it :)

Comment thread src/ir/ty.rs Outdated
TypeKind::Named |
TypeKind::Function(..) |
TypeKind::ObjCInterface(..) |
TypeKind::UnresolvedTypeRef(..) => None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe you can assume safely that all functions are pointer-sized, and use an unreachable! branch for UnresolvedTypeRef.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, it should be like this?

            TypeKind::Function(..) |
            TypeKind::ObjCInterface(..) => Some(mem::size_of::<*mut ()>()),
            ...
            TypeKind::Void | TypeKind::Named => None,
            TypeKind::UnresolvedTypeRef(..) => unreachable!(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe that's fine. Not totally sure about Objective C interfaces (@scoopr maybe knows?). I believe you can't have one in a field, only a pointer to it, so whatever it returns it's probably harmless.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, its illegal to have Objective-C objects as anything but pointers

@emilio

emilio commented Feb 4, 2017

Copy link
Copy Markdown
Contributor

Also, maybe @fitzgen wants to take a quick look at it?

@flier

flier commented Feb 4, 2017

Copy link
Copy Markdown
Contributor Author

Still adding more test cases from DPDK headers, there still a little layout error, I will back to it tomrrow :)

@flier

flier commented Feb 5, 2017

Copy link
Copy Markdown
Contributor Author

Finally, it can pass all the 214 layout tests cases for DPDK :)

@emilio

emilio commented Feb 7, 2017

Copy link
Copy Markdown
Contributor

This looks great, thanks for it! I tested it locally with Gecko and caught just two field offset failures (I think because of #111).

I've got a few improvements on top of it (and I think perf regressed slightly, which is somewhat expected, but we can improve caching the layout, maybe).

This is pretty much appreciated, thanks!

@bors-servo r+

@bors-servo

Copy link
Copy Markdown

📌 Commit 7fd7070 has been approved by emilio

@bors-servo

Copy link
Copy Markdown

⌛ Testing commit 7fd7070 with merge f5394b3...

bors-servo pushed a commit that referenced this pull request Feb 7, 2017
Improve struct alignment with padding bytes

I known, it is a little tricky or dirty solution, I have to do a lot of calculate for alignment.

For example
```c++
typedef struct {
  long long __clang_max_align_nonce1
      __attribute__((__aligned__(__alignof__(long long))));
  long double __clang_max_align_nonce2
      __attribute__((__aligned__(__alignof__(long double))));
} max_align_t;
```
will be generated as
```rust
pub struct _bindgen_ty_1 {
    pub __clang_max_align_nonce1: ::std::os::raw::c_longlong,
    _padding_0: u64,
    pub __clang_max_align_nonce2: f64,
    _padding_1: u64,
}
fn bindgen_test_layout__bindgen_ty_1() {
    assert_eq!(::std::mem::size_of::<_bindgen_ty_1>() , 32usize);
    assert_eq! (0usize , unsafe {
                & ( * ( 0 as * const _bindgen_ty_1 ) ) .
                __clang_max_align_nonce1 as * const _ as usize });
    assert_eq! (16usize , unsafe {
                & ( * ( 0 as * const _bindgen_ty_1 ) ) .
                __clang_max_align_nonce2 as * const _ as usize });
}
```
It may generate wrong layout when use template or multi virtual inheritance.

At least, it is a begining, pass all the test cases, and some [complex struct](https://github.com/servo/rust-bindgen/compare/master...flier:padding-bytes?expand=1#diff-eda352138aed047149ebeec72d19979d) in wild.
@emilio

emilio commented Feb 7, 2017

Copy link
Copy Markdown
Contributor

Well, actually there is a regression where we pad too much, but I'll work it out after this merges.

@bors-servo

Copy link
Copy Markdown

☀️ Test successful - status-travis
Approved by: emilio
Pushing f5394b3 to master...

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.

5 participants