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

special-case #[derive(Copy, Clone)] with a shallow clone #31414

Merged
merged 1 commit into from
Apr 27, 2016

Conversation

durka
Copy link
Contributor

@durka durka commented Feb 4, 2016

If a type is Copy then its Clone implementation can be a no-op. Currently #[derive(Clone)] generates a deep clone anyway. This can lead to lots of code bloat.

This PR detects the case where Copy and Clone are both being derived (the general case of "is this type Copy" can't be determined by a syntax extension) and generates the shallow Clone impl. Right now this can only be done if there are no type parameters (see #31085 (comment)), but this restriction can be removed after specialization.

Fixes #31085.

@rust-highfive
Copy link
Collaborator

r? @brson

(rust_highfive has picked a reviewer for you, use r? to override)

@durka durka changed the title special-case #[derive(Copy, Clone)] with a shallow copy special-case #[derive(Copy, Clone)] with a shallow clone Feb 4, 2016
ast::ItemStruct(_, ast::Generics { ref ty_params, .. })
| ast::ItemEnum(_, ast::Generics { ref ty_params, .. })
if ty_params.is_empty()
&& (|| item.attrs.iter().any(|a| a.name() == "derive_Copy"))() => {
Copy link
Member

Choose a reason for hiding this comment

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

syntax::attr::contains_name(item.attrs, "derive_Copy")

@bluss
Copy link
Member

bluss commented Feb 4, 2016

According to #31085 (comment) this kind of improvement can improve compile times (that's a case with a lot of structs).

According to me @ #31085 (comment) using the Copy-based clone can improve codegen for loops calling .clone().

@bors
Copy link
Contributor

bors commented Feb 5, 2016

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

@durka
Copy link
Contributor Author

durka commented Feb 5, 2016

It occurs to me that the shallow Clone can also be done in this situation:

#[derive(Copy, Clone)]
struct S<T: Copy>(T);

i.e. when there are generic parameters but they are all already constrained to be Copy. Is it worth it to add this check as well?

Edit: seems like it would be somewhat unworkably unhygienic. We'd have to check for the literal name "Copy", since all this happens before resolve.

@durka
Copy link
Contributor Author

durka commented Feb 5, 2016

Rebased.

@nagisa
Copy link
Member

nagisa commented Feb 5, 2016

This PR LGTM, but I’d like an opinion from somebody more familiar with the code as well.


#[derive(Copy, Clone)]
struct B<T> { a: T }

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you put this in its own rmake test case instead of piggy-backing on this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll split the tests and revert my changes to the Makefile for this one.

@brson brson added the relnotes Marks issues that should be documented in the release notes of the next release. label Feb 5, 2016
@brson
Copy link
Contributor

brson commented Feb 5, 2016

Do we have any concrete numbers that this gives to either compile time or runtime? @retep998 said he worked around this on winapi because of bad compile times. Perhaps we can change winapi to use deriving again and see if this improves perf there.

@durka
Copy link
Contributor Author

durka commented Feb 5, 2016

Travis failed because I didn't run the full test suite locally. Will fix later tonight.

@durka
Copy link
Contributor Author

durka commented Feb 6, 2016

I split the tests and tried to fix the fallout in the rpass-full tests. I'm getting linker errors locally which I think will be solved by make clean, we'll see.

@durka
Copy link
Contributor Author

durka commented Feb 6, 2016

@brson @bluss here are the runtime benchmarks: gist

The performance of clone_from_slice on #[derive(Copy, Clone)] struct S(u8, u32); is doubled.

@durka
Copy link
Contributor Author

durka commented Feb 6, 2016

@brson @retep998 and compile time: gist

A #[derive(Copy, Clone)] struct with 1000 members compiles 13 times faster.

@durka
Copy link
Contributor Author

durka commented Feb 6, 2016

As a bonus, this extends the reach of #[derive(Clone)] -- you can now #[derive(Copy, Clone)] on structs containing types which are Copy but not Clone, such as [u8; 100]. gist

@bluss
Copy link
Member

bluss commented Feb 6, 2016

Compile time win for structs and enums might be the best thing with this. A bit unnerving that it enables deriving Clone on more things, though. That makes it hard to go back on this optimization.

@durka
Copy link
Contributor Author

durka commented Feb 6, 2016

@bluss I don't see why we'd want to go back on it. Plus, more motivation to fix the issue where types can be magically Copy but not Clone :)

@brson
Copy link
Contributor

brson commented Feb 7, 2016

Wow, great numbers.

I'm also uneasy about the semantic change this brings, making derive Copy/Clone work on types containing those that aren't clone. It exposes the optimization - semantically we may actually want derive Clone to be defined as if it was implemented the naive way.

I assume this doesn't magically make just derive(Clone) work for types containing types that don't implement Clone, but only derive(Copy, Clone), and that's super weird.

@durka
Copy link
Contributor Author

durka commented Feb 7, 2016

I assume this doesn't magically make just derive(Clone) work for types containing types that don't implement Clone, but only derive(Copy, Clone)

Correct. The derives just do their little syntactical shufflings and leave it to typeck to enforce the constraints later.

and that's super weird.

I agree, but it's only observable if you have a type that's Copy and not Clone, which isn't supposed to be possible. So it only comes up with corner cases like arrays over size 32 or tuples over size 12.

@durka
Copy link
Contributor Author

durka commented Feb 7, 2016

We could revert the semantic change by generating code like this:

impl Clone for Foo {
    fn clone(&self) -> Foo {
        if false { /* naive implementation */ }
        else { *self }
    }
}

but we'd of course lose the compile-time wins.

@sfackler
Copy link
Member

sfackler commented Feb 7, 2016

I think we optimize if false ... in the frontend so that branch would at least not make it to LLVM.

Can't we also do something like

struct Foo {
    bar: Bar,
}

// generated
impl Clone for Foo {
    fn clone(&self) -> Foo {
        fn is_clone<C: Clone>() {}
        is_clone::<Bar>();
        *self
    }
}

which should avoid the compile time sadness.

@sfackler
Copy link
Member

sfackler commented Feb 7, 2016

That does still leave the behavior change that bar's Clone impl isn't invoked obviously.

@durka
Copy link
Contributor Author

durka commented Feb 7, 2016

@sfackler I'm trying this. I still think it's insignificant since the existence of T: Copy + !Clone is a bug.

@eddyb
Copy link
Member

eddyb commented Apr 22, 2016

@durka Good question, it would likely make winapi worse, not better. Maybe replace #[derive(Copy, Clone)] with #[derive_CopyClone] or something like that?

@durka
Copy link
Contributor Author

durka commented Apr 22, 2016

@Manishearth sorry about my slowness. I still need to fix the spans so I
can make core::assert_receiver_is_clone unstable. And I was hoping if I
stared at the deriving code long enough I could remove the breakage, but I
don't see how. So punt on this if you want, otherwise I'll really try to
get it ready to land this weekend.
On Apr 22, 2016 17:36, "Eduard-Mihai Burtescu" notifications@github.com
wrote:

@durka https://github.com/durka Good question, it would likely make
winapi worse, not better. Maybe replace #[derive(Copy, Clone)] with
#[derive_CopyClone] or something like that?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31414 (comment)

@Manishearth
Copy link
Member

Nah, it's okay, we can wait a few days 😄

I wasn't sure what the status of this PR was.

@retep998
Copy link
Member

@eddyb I don't use #[derive(Clone)] at all in winapi.

@durka
Copy link
Contributor Author

durka commented Apr 23, 2016

@retep998 but you could in a parallel universe where #[derive(Clone)]
didn't slow things down and winapi didn't care about 1.x backcompat.
On Apr 22, 2016 21:51, "Peter Atashian" notifications@github.com wrote:

@eddyb https://github.com/eddyb I don't use #[derive(Clone)] at all in
winapi.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31414 (comment)

@retep998
Copy link
Member

retep998 commented Apr 23, 2016

@durka But I can't due some Copy types not being Clone such as function pointers and large arrays which winapi has quite a lot of.

@bluss
Copy link
Member

bluss commented Apr 23, 2016

How lovely, winapi needs the bugs that this PR takes care to not expose. 😄 Can't please them all

@durka
Copy link
Contributor Author

durka commented Apr 24, 2016

The stage1-rmake test passes now. And the stage2-rpass-full one. And I guess travis will try them all.

@durka
Copy link
Contributor Author

durka commented Apr 24, 2016

@eddyb and I are now certain we can do this without the breaking change. un-cc #31645 and my apologies for delaying the batch :)

@durka
Copy link
Contributor Author

durka commented Apr 25, 2016

Breaking change removed. Performance gains retained.

@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// pretty-expanded FIXME #23616
// no-pretty-expanded FIXME #23616 assert_receiver_is_clone
Copy link
Member

Choose a reason for hiding this comment

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

Instead of no-pretty-expanded I think you can just remove these directives altogether

Copy link
Contributor Author

@durka durka Apr 25, 2016

Choose a reason for hiding this comment

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

Oh is it opt-in? Ok. I thought it was opt-out. I also figured if I left the mention of the method name here, then someone in the future stabilizing or removing this hack might grep for it and realize they could re-enable the test. But it's fine with me to remove it.

Copy link
Member

Choose a reason for hiding this comment

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

Nah yeah it's fine to just remove everywhere, right now that phase of testing is opt-in

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. Does that mean when I write a new run-pass test I should try
to add "// pretty-expanded"? Do we have a policy on that?

On Mon, Apr 25, 2016 at 2:24 PM, Alex Crichton notifications@github.com
wrote:

In src/test/run-pass/issue-3121.rs
#31414 (comment):

@@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

-// pretty-expanded FIXME #23616
+// no-pretty-expanded FIXME #23616 assert_receiver_is_clone

Nah yeah it's fine to just remove everywhere, right now that phase of
testing is opt-in


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/rust-lang/rust/pull/31414/files/64d22d453d5b71b33b149dac3c28b2d626446d9f#r60962453

Copy link
Member

Choose a reason for hiding this comment

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

Nah it's fine to just remove these comments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@alexcrichton
Copy link
Member

Thanks again for the PR @durka! Everything looks good to me, wanna squash and I'll r+?

@durka
Copy link
Contributor Author

durka commented Apr 26, 2016

Squashed and wrote a more extensive commit message.

On Tue, Apr 26, 2016 at 1:06 PM, Alex Crichton notifications@github.com
wrote:

Thanks again for the PR @durka https://github.com/durka! Everything
looks good to me, wanna squash and I'll r+?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31414 (comment)

Changes #[derive(Copy, Clone)] to use a faster impl of Clone when
both derives are present, and there are no generics in the type.

The faster impl is simply returning *self (which works because the
type is also Copy). See the comments in libsyntax_ext/deriving/clone.rs
for more details.

There are a few types which are Copy but not Clone, in violation
of the definition of Copy. These include large arrays and tuples. The
very existence of these types is arguably a bug, but in order for this
optimization not to change the applicability of #[derive(Copy, Clone)],
the faster Clone impl also injects calls to a new function,
core::clone::assert_receiver_is_clone, to verify that all members are
actually Clone.

This is not a breaking change, because pursuant to RFC 1521, any type
that implements Copy should not do any observable work in its Clone
impl.
@alexcrichton
Copy link
Member

@bors: r+ 9249e6a

bors added a commit that referenced this pull request Apr 26, 2016
special-case #[derive(Copy, Clone)] with a shallow clone

If a type is Copy then its Clone implementation can be a no-op. Currently `#[derive(Clone)]` generates a deep clone anyway. This can lead to lots of code bloat.

This PR detects the case where Copy and Clone are both being derived (the general case of "is this type Copy" can't be determined by a syntax extension) and generates the shallow Clone impl. Right now this can only be done if there are no type parameters (see #31085 (comment)), but this restriction can be removed after specialization.

Fixes #31085.
@bors
Copy link
Contributor

bors commented Apr 26, 2016

⌛ Testing commit 9249e6a with merge 8f55218...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
relnotes Marks issues that should be documented in the release notes of the next release.
Projects
None yet
Development

Successfully merging this pull request may close these issues.