Uh oh!
There was an error while loading. Please reload this page.
Default for arrays via const generics - #74254
Conversation
rust-highfive
commented
Jul 11, 2020
r? @dtolnay (rust_highfive has picked a reviewer for you, use r? to override) |
Well, I think this PR works and can be reviewed. |
dtolnay
left a comment
There was a problem hiding this comment.
I haven't reviewed closely yet, but the generated code looks quite a bit worse than before so I would be concerned about the performance and code size implications of this.
https://rust.godbolt.org/z/3dxGE5
externcrate core;use core::mem::{self,MaybeUninit};use core::ptr;typeT = String;constN:usize = 2;pubfnbefore() -> [T;N]{Default::default()}pubfnafter() -> [T;N]{structWrapper{data:MaybeUninit<[T;N]>,init:usize,}implDropforWrapper{#[inline]fndrop(&mutself){debug_assert!(self.init <= N);let ptr = self.data.as_mut_ptr()as*mutT;let initialized_part = ptr::slice_from_raw_parts_mut(ptr,self.init);unsafe{
ptr::drop_in_place(initialized_part);}}}letmut w = Wrapper{data:MaybeUninit::uninit(),init:0};let array_pointer = w.data.as_mut_ptr()as*mutT;for i in0..N{assert!(N <= isize::MAXasusize);unsafe{let elem_ptr = array_pointer.add(i);
elem_ptr.write(T::default());}
w.init += 1;}let data = unsafe{ w.data.as_ptr().read()};
mem::forget(w);
data
}example::before:movrax,rdimov qword ptr [rdi],1 vxorps xmm0,xmm0,xmm0vmovups xmmword ptr [rdi+8],xmm0mov qword ptr [rdi+24],1vmovups xmmword ptr [rdi+32],xmm0retexample::after:subrsp,56mov qword ptr [rsp],1 vxorps xmm0,xmm0,xmm0vmovups xmmword ptr [rsp+8],xmm0mov qword ptr [rsp+24],1vmovups xmmword ptr [rsp+32],xmm0movrax,rdimov qword ptr [rsp+48],2movrcx, qword ptr [rsp]mov qword ptr [rdi],rcxvmovupsxmm0, xmmword ptr [rsp+8]vmovups xmmword ptr [rdi+8],xmm0movrcx, qword ptr [rsp+24]mov qword ptr [rdi+24],rcxmovrcx, qword ptr [rsp+16]mov qword ptr [rdi+16],rcxmovrcx, qword ptr [rsp+24]mov qword ptr [rdi+24],rcxvmovupsxmm0, xmmword ptr [rsp+32]vmovups xmmword ptr [rdi+32],xmm0addrsp,56retdtolnay
commented
Jul 11, 2020
@bors try |
bors
commented
Jul 11, 2020
⌛ Trying commit 3d3a7bd02207ae5d2be3bd3ac776b4b0ea15e1e1 with merge 8205f8afb8c63490368efc0d9db81e4e2b5830d8... |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
bors
commented
Jul 12, 2020
☀️ Try build successful - checks-actions, checks-azure |
lcnr
commented
Jul 12, 2020
The test failures seems like a problem with winnowing. The bound in
|
There was a problem hiding this comment.
The previous version did not have inline attr
Minimized: fndefault_array<T,constN:usize>() -> [T;N]where[T;N]:Default{Default::default()}fnmain(){let _:[u8;4] = default_array();} |
There was a problem hiding this comment.
This is effectively implementing FromIterator for arrays. I think extracting this making it reusable across other places would make sense.
There was a problem hiding this comment.
Well, I imagine something like:
// in core::mempub(crate)structPartialBuffer<T,constN>{buf:[MaybeUninit<T>;N],init:usize}implPartialBuffer<_,_>{pub(crate)fnnew() -> Self{..}pub(crate)unsafefnpush(&mutself,value:T){..}// or maybe pub(crate) unsafe fn next_place(&mut self) -> &mut MaybeUninit<T> {..} to reduce copyingpub(crate)unsafefnassume_init(self) -> [T;N]{..}pub(crate)fnget_ref(&self) -> &[T]{..}pub(crate)fnget_mut(&mutself) -> &mut[T]{..}}There was a problem hiding this comment.
Why not make the guard type itself hold the MaybeUninit?
There was a problem hiding this comment.
Previous version did so.
I tried keeping MaybeUninit outside of Guard to see whether it simplifies code and whether it optimizes generated ASM.
dtolnay
left a comment
There was a problem hiding this comment.
Newest version of the code, in godbolt: https://rust.godbolt.org/z/Pc6175.
example::before:movrax,rdimov qword ptr [rdi],1 vxorps xmm0,xmm0,xmm0vmovups xmmword ptr [rdi+8],xmm0mov qword ptr [rdi+24],1vmovups xmmword ptr [rdi+32],xmm0retexample::after:subrsp,48movrax,rdimov qword ptr [rsp],1 vxorps xmm0,xmm0,xmm0vmovups xmmword ptr [rsp+8],xmm0mov qword ptr [rsp+24],1vmovups xmmword ptr [rsp+32],xmm0movrcx, qword ptr [rsp]mov qword ptr [rdi],rcxvmovupsxmm0, xmmword ptr [rsp+8]vmovups xmmword ptr [rdi+8],xmm0movrcx, qword ptr [rsp+24]mov qword ptr [rdi+24],rcxmovrcx, qword ptr [rsp+16]mov qword ptr [rdi+16],rcxmovrcx, qword ptr [rsp+24]mov qword ptr [rdi+24],rcxvmovupsxmm0, xmmword ptr [rsp+32]vmovups xmmword ptr [rdi+32],xmm0addrsp,48retIt appears to me that the difference is entirely a consequence of "return value optimization" not kicking in for the MaybeUninit for some reason, which seems fixable in the compiler. Changing the following two lines fixes the performance issue and gets exactly the same machine code as before (but isn't correct).
- let mut data: MaybeUninit<[T; N]> = MaybeUninit::uninit();+ let mut data: [T; N] = unsafe { mem::uninitialized() }; // unsound- unsafe { data.assume_init() }+ dataexample::before:movrax,rdimov qword ptr [rdi],1 vxorps xmm0,xmm0,xmm0vmovups xmmword ptr [rdi+8],xmm0mov qword ptr [rdi+24],1vmovups xmmword ptr [rdi+32],xmm0retexample::after:movrax,rdimov qword ptr [rdi],1 vxorps xmm0,xmm0,xmm0vmovups xmmword ptr [rdi+8],xmm0mov qword ptr [rdi+24],1vmovups xmmword ptr [rdi+32],xmm0retlcnr
commented
Jul 12, 2020
@MikailBag thanks for this PR, we ended up talking about this implementation and the winnowing problem on zulip: https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/good.20example.20for.20const.20generics/near/203642562 Do you want to try and incorporate that impl in your PR or should I open a new one for it? /// A trait implemented by all arrays which are either empty or contain a type implementing `Default`.#[unstable(feature = "array_default_internals", reason = "implementation detail", issue = "none")]#[marker]pubtraitArrayDefault{}#[unstable(feature = "array_default_internals", reason = "implementation detail", issue = "none")]impl<T>ArrayDefaultfor[T;0]{}#[unstable(feature = "array_default_internals", reason = "implementation detail", issue = "none")]impl<T:Default,constN:usize>ArrayDefaultfor[T;N]{}traitDefaultHack{fndefault_hack() -> Self;}impl<T>DefaultHackforT{defaultfndefault_hack() -> Self{unreachable!();}}impl<T:Default>DefaultHackforT{fndefault_hack() -> Self{Default::default()}}#[stable(since = "1.4.0", feature = "array_default")]impl<T,constN:usize>Defaultfor[T;N]where[T;N]:ArrayDefault{// ...} |
MikailBag
commented
Jul 12, 2020
@dtolnay thanks for your wonderful investigation!
|
dtolnay
commented
Jul 12, 2020
3 seems best if possible. |
shepmaster
commented
Jul 12, 2020
If we are going to add back the |
MikailBag
commented
Jul 12, 2020
@dtolnay I implemented third approach, tests pass (wow 😄 ). |
I do not feel comfortable with the current approach.
#![feature(const_generics)]fnbreak_me<T,constN:usize>() -> [T;N]where[T;N]:Default{Default::default()}fnmain(){let _:[u8;35] = break_me();}I personally would like to try and perf what happens if we just use const generics for all impls and
While these impls are bound on an unstable trait, they are still instantly stable. |
lcnr
commented
Jul 12, 2020
The current impl also fails for #![feature(const_generics)]structFoo<constN:usize>([u8;N]);impl<constN:usize>Foo<N>{fnnew() -> Self{Foo(Default::default())}}fnmain(){}with |
@lcnr thanks, it's really serious argument against this combined "macro + const-generics" approach, especially the last example. Let me summarize how I see situation. |
shepmaster
commented
Jul 12, 2020
I feel like this is the best final solution, and it would probably benefit multiple other cases. |
MikailBag
commented
Oct 10, 2020
Unfortunately, PR does not play well with I currently changed the feature from |
RalfJung
commented
Oct 10, 2020
Please do not. |
Yeah, I understand that using non-min specialization is only workaround. I have zero experience with trait system, so I can be wrong here. // We allow specializing on explicitly marked traits with no associated// items.And Default does not have associated types. That's why I now think I should add this specialization marker to Default and UPD: no, build fails: |
RalfJung
commented
Oct 11, 2020
It is backwards-incompatible to add this marker to an existing trait. And it is unsound to do specialization on a trait unless the trait has that marker -- a trait that is used for specializing other traits needs to pass some stricter checks, which most existing traits will not pass. I don't know of a good way out of this, but I am also not a specialization expert. Cc @matthewjasper |
Dylan-DPC-zz
commented
Oct 31, 2020
This is backwards incompatible and looks highly likely won't be merged as is. If you find a different way to solve this, it is better to create a new PR. Thanks for taking the time to contribute |
@Dylan-DPC can you please describe in what way this PR breaks compatibility? I don't know if the desired behavior can be achieved with |
nbdd0121
commented
Jun 19, 2021
Can't we just introduce a new #[rustc_unsafe_specialization_marker]traitMarkerDefault:Default{}impl<T:Default>MarkerDefaultforT{}traitDefaultHack{fndefault_hack() -> Self;}impl<T>DefaultHackforT{defaultfndefault_hack() -> T{unreachable!();}}impl<T:MarkerDefault>DefaultHackforT{fndefault_hack() -> T{Default::default()}} |
RalfJung
commented
Jun 19, 2021
We should IMO not introduce more |
crlf0710
commented
Jun 19, 2021
There's a specialization-free approach, waiting for the feature |
Sorry, I created PR too early :)
It is WIP currently.
UPD: no longer WIP, but blocked on codegen issues.