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

Projects
None yet
@durka
Copy link
Contributor

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

This comment has been minimized.

Copy link
Collaborator

rust-highfive commented Feb 4, 2016

r? @brson

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

@durka durka force-pushed the durka:clone-copy branch from 69cb84b to 0aaacb3 Feb 4, 2016

@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"))() => {

This comment has been minimized.

@nagisa

nagisa Feb 4, 2016

Contributor

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

}
}

_ => cx.span_bug(span, "#[derive(Clone)] on trait item or impl item")

This comment has been minimized.

@nagisa

nagisa Feb 4, 2016

Contributor

So this branch is not trigerred for non-trait/impl items? Consider: functions, type aliases, mod items, etc?

This comment has been minimized.

@durka

durka Feb 4, 2016

Author Contributor

AFAIK those should never reach this path because they error out here: https://github.com/rust-lang/rust/blob/master/src/libsyntax_ext/deriving/mod.rs#L122

This comment has been minimized.

@durka

durka Feb 4, 2016

Author Contributor

Oh, I misinterpreted what you meant. There are only three choices for Annotatable: http://manishearth.github.io/rust-internals-docs/syntax/ext/base/enum.Annotatable.html

This comment has been minimized.

@durka

durka Feb 4, 2016

Author Contributor

I'm wrong again because it could be a different kind of Item. So it should just ICE with "... on non-struct/enum item"

This comment has been minimized.

@nagisa

nagisa Feb 4, 2016

Contributor

Right, I’m surprised this branch is necessary, when any sort of item “files” just fine behind the Item variant.

This comment has been minimized.

@durka

durka Feb 4, 2016

Author Contributor

No, I'm going back to my second comment. There's a nested match. This unreachable catchall arm is in the outer one, which differentiates between Item vs TraitItem/ImplItem. The inner one does the check to see which kind of Clone we can derive.

This comment has been minimized.

@nagisa

nagisa Feb 4, 2016

Contributor

I'm wrong again because it could be a different kind of Item. So it should just ICE with "... on non-struct/enum item"

I think you could just have something like

match *item {
    Annotatable::Item(ast::Item { node: ast::ItemStruct(...) }) |
    Annotatable::Item(ast::Item { node: ast::ItemEnum(...) })
    if /* the other conditions */ => {
         // this is copy branch
    }
    _ => /* the clone branch */
}

This comment has been minimized.

@durka

durka Feb 5, 2016

Author Contributor

It doesn't work because it's enum Annotatable { Item(P<Item>), ... } and you can't match through a P. At least, I can't. I tried, with #![feature(box_patterns)] but the ptr field is private so I can't get to the inner Box.

@bluss

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

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

This comment has been minimized.

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 durka force-pushed the durka:clone-copy branch from 0aaacb3 to fe59b7c Feb 5, 2016

@durka

This comment has been minimized.

Copy link
Contributor Author

durka commented Feb 5, 2016

Rebased.

@nagisa

This comment has been minimized.

Copy link
Contributor

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 }

This comment has been minimized.

@brson

brson Feb 5, 2016

Contributor

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

This comment has been minimized.

@durka

durka Feb 5, 2016

Author Contributor

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

@brson brson added the relnotes label Feb 5, 2016

@brson

This comment has been minimized.

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

This comment has been minimized.

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 durka force-pushed the durka:clone-copy branch from fe59b7c to 298ddac Feb 6, 2016

@durka

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

Manishearth commented Apr 22, 2016

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

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

@retep998

This comment has been minimized.

Copy link
Member

retep998 commented Apr 23, 2016

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

@durka

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor

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 durka force-pushed the durka:clone-copy branch from 6712c02 to 5d800cb Apr 24, 2016

@durka

This comment has been minimized.

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

This comment has been minimized.

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 durka force-pushed the durka:clone-copy branch from e54419a to 33c1c86 Apr 25, 2016

@durka

This comment has been minimized.

Copy link
Contributor Author

durka commented Apr 25, 2016

Breaking change removed. Performance gains retained.

@durka durka force-pushed the durka:clone-copy branch from 33c1c86 to 64d22d4 Apr 25, 2016

@@ -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

This comment has been minimized.

@alexcrichton

alexcrichton Apr 25, 2016

Member

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

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

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.

This comment has been minimized.

@alexcrichton

alexcrichton Apr 25, 2016

Member

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

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

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

This comment has been minimized.

@alexcrichton

alexcrichton Apr 25, 2016

Member

Nah it's fine to just remove these comments

This comment has been minimized.

@durka

durka Apr 26, 2016

Author Contributor

Fixed.

fn clone(&self) -> A {
match *self {
A { a: ref __self_0_0 } => {
::clone::assert_receiver_is_clone(&(*__self_0_0));

This comment has been minimized.

@alexcrichton

alexcrichton Apr 25, 2016

Member

I think we have src/test/pretty for this, right? Always just good to avoid run-make wherever possible :)

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

Hmm what does src/test/pretty actually do? This test asserts that the pretty expansion is exactly as intended but src/test/pretty doesn't seem to have expected output.

This comment has been minimized.

@eddyb

eddyb Apr 25, 2016

Member

There's one .pp file in there which is the expected output for one test. If nobody has removed it yet for being painful to update, that is.

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

Oh, so there is. I can migrate this test to src/test/pretty then. But
there are more run-make tests of the same class (pretty-expanded,
pretty-expanded-hygiene). I guess they can be migrated in a separate PR.

On Mon, Apr 25, 2016 at 2:04 PM, Eduard-Mihai Burtescu <
notifications@github.com> wrote:

In src/test/run-make/expand-derive/input_pp.rs
#31414 (comment):

+// minimal junk
+#![feature(no_core)]
+#![no_core]
+
+struct A {

  • a: i32,
    +}
    +#[automatically_derived]
    +#[allow(unused_qualifications)]
    +impl ::clone::Clone for A {
  • #[inline]
  • fn clone(&self) -> A {
  •    match *self {
    
  •        A { a: ref __self_0_0 } => {
    
  •            ::clone::assert_receiver_is_clone(&(*__self_0_0));
    

There's one .pp file in there which is the expected output for one test.
If nobody has removed it yet for being painful to update, that is.


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#r60958975

This comment has been minimized.

@alexcrichton

alexcrichton Apr 25, 2016

Member

Yeah I'd also be fine just leaving this test out, it'd end up being pretty brittle :(

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

I'm trying to think of a way to test the shallowness of a Clone impl without pp-exactness.

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

I think this test will do the job.

This comment has been minimized.

@durka

durka Apr 25, 2016

Author Contributor

In other words this is a behavior change, but it is now OKed by accepted rust-lang/rfcs#1521.

This comment has been minimized.

@alexcrichton

alexcrichton Apr 25, 2016

Member

Yeah that seems like a good test to me.

This comment has been minimized.

@durka

durka Apr 26, 2016

Author Contributor

OK, I deleted the pretty-printing test and added the Innocent(Liar) test.

@durka durka force-pushed the durka:clone-copy branch from b47ffb4 to 1e82084 Apr 26, 2016

@alexcrichton

This comment has been minimized.

Copy link
Member

alexcrichton commented Apr 26, 2016

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

@durka durka force-pushed the durka:clone-copy branch from c8ae7ee to 34b7575 Apr 26, 2016

@durka

This comment has been minimized.

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)

shallow Clone for #[derive(Copy,Clone)]
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.

@durka durka force-pushed the durka:clone-copy branch from 34b7575 to 9249e6a Apr 26, 2016

@alexcrichton

This comment has been minimized.

Copy link
Member

alexcrichton commented Apr 26, 2016

bors added a commit that referenced this pull request Apr 26, 2016

Auto merge of #31414 - durka:clone-copy, r=alexcrichton
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

This comment has been minimized.

Copy link
Contributor

bors commented Apr 26, 2016

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

@bors bors merged commit 9249e6a into rust-lang:master Apr 27, 2016

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details

@mcarton mcarton referenced this pull request Oct 8, 2016

Closed

Improved `Clone` derive #2

3 of 3 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.