-
Notifications
You must be signed in to change notification settings - Fork 700
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
Improve struct alignment with padding bytes #468
Conversation
Just a quick update, I'm reviewing this, it's just kind of involved to do so :) |
☔ The latest upstream changes (presumably #471) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
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!
src/codegen/mod.rs
Outdated
} | ||
} | ||
|
||
struct StructLayout<'a, 'ctx: 'a> { |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
src/codegen/mod.rs
Outdated
}; | ||
|
||
if padding_bytes >= align || layout.align > mem::size_of::<* mut ()>() | ||
{ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :(
src/codegen/mod.rs
Outdated
) | ||
}; | ||
|
||
let check_field_offset = if item.is_opaque(ctx) || |
There was a problem hiding this comment.
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) || ...;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 });
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works, thanks!
src/ir/comp.rs
Outdated
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> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/ir/comp.rs
Outdated
@@ -532,7 +578,7 @@ impl CompInfo { | |||
cursor.visit(|cur| { | |||
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); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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);
}
}
...
src/ir/ty.rs
Outdated
TypeKind::Alias(inner) | | ||
TypeKind::TemplateAlias(inner, _) | | ||
TypeKind::TemplateRef(inner, _) => ctx.resolve_type(inner).calc_size(ctx), | ||
_ => None, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
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 :)
src/ir/ty.rs
Outdated
TypeKind::Named | | ||
TypeKind::Function(..) | | ||
TypeKind::ObjCInterface(..) | | ||
TypeKind::UnresolvedTypeRef(..) => None, |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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!(),
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Also, maybe @fitzgen wants to take a quick look at it? |
Still adding more test cases from DPDK headers, there still a little layout error, I will back to it tomrrow :) |
Finally, it can pass all the 214 layout tests cases for DPDK :) |
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+ |
📌 Commit 7fd7070 has been approved by |
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.
Well, actually there is a regression where we pad too much, but I'll work it out after this merges. |
☀️ Test successful - status-travis |
I known, it is a little tricky or dirty solution, I have to do a lot of calculate for alignment.
For example
will be generated as
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.