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

Use fix point analysis to implement can_derive_debug #824

Merged
merged 1 commit into from Jul 20, 2017
Merged

Use fix point analysis to implement can_derive_debug #824

merged 1 commit into from Jul 20, 2017

Conversation

photoszzt
Copy link
Contributor

It's failing about 30 tests now and most of them is related to template. I'm also not so sure about the place to call compute_can_derive_debug in gen.
Fix: #767

r? @fitzgen

@highfive
Copy link

Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @fitzgen (or someone else) soon.

Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

This looks right to me, will have to take a look at the errors to get a better idea of what is going wrong.

let ty_kind = ty.map(|ty| ty.kind());

match ty_kind {
Some(&TypeKind::Void) |
Copy link
Member

Choose a reason for hiding this comment

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

Since we only care about types here, we should exit the function early with non-types, which will get rid of these annoying Some(...)s and the unwraps:

let ty = match item.as_type() {
    None => return false,
    Some(ty) => ty
};

match ty.kind() {
    ...
}

@@ -0,0 +1,269 @@
use super::analysis::MonotoneFramework;
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: need a module doc comment, or the tests will fail with --features testing_only_docs:

//! Determining which types for which we can emit `#[derive(Debug)]`.

use ir::comp::CompKind;

#[derive(Debug, Clone)]
pub struct CanDeriveDebugAnalysis<'ctx, 'gen>
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: and a doc comment here.

See the doc comment above the UsedTemplateParameters type; the doc comment should help readers understand how this analysis works, why its domain is a finite height lattice and we will therefore definitely reach a fixed point, an overview of the constraining operations for different items/types are, etc...

@@ -1652,6 +1659,21 @@ impl<'ctx> BindgenContext<'ctx> {
pub fn need_bindegen_complex_type(&self) -> bool {
self.generated_bindegen_complex.get()
}

/// compute whether we can derive debug
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: please capitalize and punctuate sentences:

/// Compute whether we can derive `Debug` for each type.

self.can_derive_debug = Some(analyze::<CanDeriveDebugAnalysis>(self));
}

pub fn lookup_item_id_can_derive_debug(&self, id: ItemId) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: this will also need a doc comment.

src/ir/item.rs Outdated
.map_or(true, |l| l.opaque().can_derive_debug(ctx, ()))
} else {
ty.can_derive_debug(ctx, ())
ctx.options().derive_debug &&
Copy link
Member

Choose a reason for hiding this comment

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

When ctx.options().derive_debug, the whole rest of the method should just be call to lookup_item_id_can_derive_debug now.

Some(&TypeKind::Pointer(inner)) => {
let inner_type = self.ctx.resolve_type(inner);
if let TypeKind::Function(ref sig) =
*inner_type.canonical_type (self.ctx).kind() {
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: no space between the method name and parentheses:

inner_type.canonical_type(self.ctx)

@fitzgen
Copy link
Member

fitzgen commented Jul 18, 2017

Here is tests/headers/inner_template_self.hpp's test expectation diff:

--- expected: "/home/travis/build/servo/rust-bindgen/tests/expectations/tests/inner_template_self.rs"
+++ generated from: "/home/travis/build/servo/rust-bindgen/tests/headers/inner_template_self.hpp"
 /* automatically generated by rust-bindgen */
 
 
 #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
 
 
 #[repr(C)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Copy, Clone)]
 pub struct LinkedList {
     pub next: *mut LinkedList,
     pub prev: *mut LinkedList,
 }
 impl Default for LinkedList {
     fn default() -> Self { unsafe { ::std::mem::zeroed() } }
 }
 #[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Copy)]
 pub struct InstantiateIt {
     pub m_list: LinkedList,
 }

Pointers are always Debug, but it looks like the constrain function doesn't reflect that.

if self.can_derive_debug.contains(&inner) {
self.can_derive_debug.insert(id);
return true;
}
Copy link
Member

Choose a reason for hiding this comment

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

There should be no check whether the pointed-to inner type is in the set or not: pointers can always derive Debug (its just their address) so the non-function pointer case should simply be this:

self.can_derive_debug.insert(id);
return true;

@fitzgen
Copy link
Member

fitzgen commented Jul 18, 2017

I think that should fix most of the test failures. If not, then let's look at which tests are still failing after fixing that.

@photoszzt
Copy link
Contributor Author

After the above fix and move the consider edge a bit, it fails 15 tests now.

I'm not so sure about whether the edge I'm adding is correct or not. The next failure is

--- expected: "/Users/zzhu/Documents/rust-bindgen/tests/expectations/tests/crtp.rs"
+++ generated from: "/Users/zzhu/Documents/rust-bindgen/tests/headers/crtp.hpp"
 /* automatically generated by rust-bindgen */


 #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]


 #[repr(C)]
 #[derive(Debug, Default, Copy, Clone)]
 pub struct Base {
     pub _address: u8,
 }
 #[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Copy)]
 pub struct Derived {
     pub _address: u8,
 }
 #[test]
 fn bindgen_test_layout_Derived() {
     assert_eq!(::std::mem::size_of::<Derived>() , 1usize , concat ! (
                "Size of: " , stringify ! ( Derived ) ));
     assert_eq! (::std::mem::align_of::<Derived>() , 1usize , concat ! (
                 "Alignment of " , stringify ! ( Derived ) ));
 }
 impl Clone for Derived {
     fn clone(&self) -> Self { *self }
 }
 impl Default for Derived {
     fn default() -> Self { unsafe { ::std::mem::zeroed() } }
 }
 #[repr(C)]
 #[derive(Debug, Default)]
 pub struct BaseWithDestructor {
     pub _address: u8,
 }
 #[repr(C)]
-#[derive(Debug)]
 pub struct DerivedFromBaseWithDestructor {
     pub _address: u8,
 }
 #[test]
 fn bindgen_test_layout_DerivedFromBaseWithDestructor() {
     assert_eq!(::std::mem::size_of::<DerivedFromBaseWithDestructor>() , 1usize
                , concat ! (
                "Size of: " , stringify ! ( DerivedFromBaseWithDestructor ) ));
     assert_eq! (::std::mem::align_of::<DerivedFromBaseWithDestructor>() ,
                 1usize , concat ! (
                 "Alignment of " , stringify ! ( DerivedFromBaseWithDestructor
                 ) ));
 }
 impl Default for DerivedFromBaseWithDestructor {
     fn default() -> Self { unsafe { ::std::mem::zeroed() } }
 }
 #[test]
 fn __bindgen_test_layout_Base_instantiation() {
     assert_eq!(::std::mem::size_of::<Base>() , 1usize , concat ! (
                "Size of template specialization: " , stringify ! ( Base ) ));
     assert_eq!(::std::mem::align_of::<Base>() , 1usize , concat ! (
                "Alignment of template specialization: " , stringify ! ( Base )
                ));
 }
 #[test]
 fn __bindgen_test_layout_BaseWithDestructor_instantiation() {
     assert_eq!(::std::mem::size_of::<BaseWithDestructor>() , 1usize , concat !
                (
                "Size of template specialization: " , stringify ! (
                BaseWithDestructor ) ));
     assert_eq!(::std::mem::align_of::<BaseWithDestructor>() , 1usize , concat
                ! (
                "Alignment of template specialization: " , stringify ! (
                BaseWithDestructor ) ));
 }

true
},
&TypeKind::Opaque => {
if ty.layout(self.ctx)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the original implementation, the Opaque type is always true. Should I do that or uses the current implementation?

Copy link
Member

Choose a reason for hiding this comment

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

Let's try and translate exactly what master is doing into the constraint function.

match ty.kind() {
&TypeKind::Void |
&TypeKind::NullPtr |
&TypeKind::Int(..) |
Copy link
Member

Choose a reason for hiding this comment

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

To avoid all the repeated &s, we can match *ty.kind() { ... }

@fitzgen
Copy link
Member

fitzgen commented Jul 18, 2017

I'd suggest working on getting the smaller (and therefore usually simpler) test cases passing before working on larger ones. Often, the root cause of the issue will be the same in a larger test case, but easier to understand in a smaller setting that has less extra context, and then when you fix it for the small test case, the larger test case is also fixed.

It looks like template-param-usage-15.hpp is a good candidate to dig into next.

--- expected: "/home/travis/build/servo/rust-bindgen/tests/expectations/tests/template-param-usage-15.rs"
+++ generated from: "/home/travis/build/servo/rust-bindgen/tests/headers/template-param-usage-15.hpp"
 /* automatically generated by rust-bindgen */
 
 
 #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
 
 
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct BaseUsesT<T> {
     pub usage: *mut T,
     pub _phantom_0: ::std::marker::PhantomData<::std::cell::UnsafeCell<T>>,
 }
 impl <T> Default for BaseUsesT<T> {
     fn default() -> Self { unsafe { ::std::mem::zeroed() } }
 }
 #[repr(C)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Copy, Clone)]
 pub struct CrtpIgnoresU {
     pub _base: BaseUsesT<CrtpIgnoresU>,
     pub y: ::std::os::raw::c_int,
 }
 impl Default for CrtpIgnoresU {
     fn default() -> Self { unsafe { ::std::mem::zeroed() } }
 }

It looks like maybe the CrtpIgnoresU doesn't realize that its template instantiation of BaseUsesT should be able to derive debug?

@photoszzt photoszzt changed the title WIP use fix point analysis to implement can_derive_debug Use fix point analysis to implement can_derive_debug Jul 19, 2017
Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

This looks really great!

There are just a few things to change before landing, mostly nitpicks, see below.

Thanks a ton @photoszzt ! :)


/// An analysis that finds for each IR item whether debug can be derived.
///
/// TODO More comments before merge
Copy link
Member

Choose a reason for hiding this comment

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

TODO ;)


// The incremental result of this analysis's computation. Everything in this
// set can derive debug.
cant_derive_debug: HashSet<ItemId>,
Copy link
Member

Choose a reason for hiding this comment

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

The comment needs to be updated.

EdgeKind::TemplateDeclaration |
EdgeKind::TemplateParameterDefinition |
EdgeKind::InnerType |
EdgeKind::InnerVar => true,
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit surprised to see InnerType and InnerVar here. How can they affect whether a type can derive debug or not?

}

impl<'ctx, 'gen> CantDeriveDebugAnalysis<'ctx, 'gen> {
// Don't know where to call this method
Copy link
Member

Choose a reason for hiding this comment

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

It's called now, so I think this comment can be removed ;)

}
let consider_base = info.base_members()
.iter()
.all(|base| !self.cant_derive_debug.contains(&base.ty));
Copy link
Member

Choose a reason for hiding this comment

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

Each of the places where we do something like

let things_can_derive = things.all(|thing| !self.cant_derive_debug.contains(thing));

should become

let things_cant_derive = things.any(|thing| self.cant_derive_debug.contains(thing));

Its equivalent (de Morgan's) but the latter reads much more naturally.

Additionally, we should early return whenever we find something that would cause us to update the cant_derive_debug set, rather than continuing to consider (for example) fields even after we know we can't derive debug because of the bases.

That is, we should do something like this:

let bases_cant_derive = ...;
if bases_cant_derive {
    self.cant_derive_debug.insert(id);
    return true;
}

let fields_cant_derive = ...;
if fields_cant_derive {
    self.cant_derive_debug.insert(id);
    return true;
}

false

To avoid repeating the insert-into-the-set-and-then-return-true dance over and over, we can define a helper:

impl CantDeriveDebugAnalysis {
    ...
    fn insert(&mut self, id: ItemId) -> bool {
        let was_already_in = self.cant_derive_debug.insert(&id);
        assert!(
            !was_already_in,
            "We shouldn't try and insert twice because if it was already in the set, \
             `constrain` would have exited early."
        );
        true
    }
}

src/ir/ty.rs Outdated
inst.can_derive_debug(ctx, self.layout(ctx))
}
_ => true,
}
Copy link
Member

Choose a reason for hiding this comment

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

Are there any other CanDeriveDebug impls that are now unused and we can remove? I'd try commenting all of them out other than Item and ItemId and see if any call sites start failing to compile, and then look at each of those call sites and determine whether it would be easier to just use an Item/ItemId impl or to uncomment the impl that was being used. I expect most of them are handled by Item and ItemId.

We should at minimum be able to delete the detect_can_derive_debug_cycle member from CompInfo and its CanDeriveDebug impl as well.

@@ -1652,6 +1660,23 @@ impl<'ctx> BindgenContext<'ctx> {
pub fn need_bindegen_complex_type(&self) -> bool {
self.generated_bindegen_complex.get()
}

/// Compute whether we can derive debug
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: missing a period.

@@ -169,6 +170,11 @@ pub struct BindgenContext<'ctx> {

/// Whether we need the mangling hack which removes the prefixing underscore.
needs_mangling_hack: bool,

/// Set of ItemId that can't derive debug
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: missing a period.

TypeKind::Comp(ref info) => {
if info.has_non_type_template_params() {
if ty.layout(self.ctx).map_or(false,
|l| l.opaque().can_derive_debug(self.ctx, ())) {
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: funky indentation

@fitzgen
Copy link
Member

fitzgen commented Jul 20, 2017

@photoszzt, looks like there are compilation errors with the latest commit:

error[E0432]: unresolved import `super::can_derive_debug::CantDeriveDebugAnalysis`
  --> src/ir/context.rs:13:5
   |
13 | use super::can_derive_debug::CantDeriveDebugAnalysis;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find `can_derive_debug` in `super`
error[E0432]: unresolved import `super::can_derive_debug::CantDeriveDebugAnalysis`
  --> src/ir/context.rs:13:5
   |
13 | use super::can_derive_debug::CantDeriveDebugAnalysis;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find `can_derive_debug` in `super`
error: aborting due to previous error

@bors-servo
Copy link

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

Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

Thanks for sticking this one out!

@fitzgen
Copy link
Member

fitzgen commented Jul 20, 2017

@bors-servo r+

@bors-servo
Copy link

📌 Commit 01c609c has been approved by fitzgen

@bors-servo
Copy link

⌛ Testing commit 01c609c with merge 8f43c93...

bors-servo pushed a commit that referenced this pull request Jul 20, 2017
Use fix point analysis to implement can_derive_debug

It's failing about 30 tests now and most of them is related to template. I'm also not so sure about the place to call compute_can_derive_debug in gen.
Fix: #767

r? @fitzgen
@bors-servo
Copy link

☀️ Test successful - status-travis
Approved by: fitzgen
Pushing 8f43c93 to master...

emilio added a commit to emilio/rust-bindgen that referenced this pull request Jul 22, 2017
…derive debug analysis.

We have a special-case for them in codegen to generate a blob, that can derive
debug.

This is a regression from rust-lang#824, and hit stylo.
emilio added a commit to emilio/rust-bindgen that referenced this pull request Jul 22, 2017
…derive debug analysis.

We have a special-case for them in codegen to generate a blob, that can derive
debug.

This is a regression from rust-lang#824, and hit stylo.
bors-servo pushed a commit that referenced this pull request Jul 22, 2017
analysis: Account for template instantiations of opaque types in the derive debug analysis.

We have a special-case for them in codegen to generate a blob, that can derive
debug.

This is a regression from #824, and hit stylo.
tmfink pushed a commit to tmfink/rust-bindgen that referenced this pull request Aug 4, 2017
…derive debug analysis.

We have a special-case for them in codegen to generate a blob, that can derive
debug.

This is a regression from rust-lang#824, and hit stylo.
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.

None yet

4 participants