-
-
Notifications
You must be signed in to change notification settings - Fork 706
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
[RFC] Fix move
#923
[RFC] Fix move
#923
Conversation
* also improve precondition skipping comment
|
While trying to make move work under CTFE I realized move can (and in fact already does in Phobos unittest) overwrite immutable/const fileds of structs without so much as a notice. WTF. Other then this - one more thing. Please move aliasing check &soource == &target to apply only for structs, it makes little sense to do this check for primitives. (I'd argue that some small structs could be fine wth plain assign and no aliasing check) |
|
Nitpick:
|
|
@monarchdodra's documentation change and @blackwhale's optimization are incorporated. |
|
to @monarchdodra:
Maybe. Will wait others to show their opinions here. |
|
Your new implementation of ˋT move(T)(ref T source)ˋ is awesome. It puts my consolidation change to shame... |
|
Hum... your approach to the context pointer issue is indeed a 180° approach from mine. I'm not entirelly sure this is what the standard had in mind about "an init value must be destroyable". In particular, I can't imagine any sane implementation of a destructor that starts with ˋif (this == init)ˋ I think this is especially relevent in the sense that the context pointer's value isn't really part of the object's state. Will wait for others to show their opinions. I think my context pointer fix is a valid one, so I'll keep my pull open. I may cherry pick some stuff from here though... hope you don't mind. |
|
Fixed my @blackwhale's optimization implementation. |
|
I like the way optimization looks, very simple and generic. But I'm still a bit uneasy about how compiler copies small structs (IRC it should just do a couple of moves but maybe..), does it work for odd-length structs e.g. 3-byte ones? Of course it would be extreamly useful if it was ever specified how compiler copies structs depending on size, something like: Looking at this kind of list I'd say that a should skip aliasing check but everything from b and c can afford it do it. By far the most interesting case is a struct that contains an array slice (2 words) and it would be great to make it be just 2 moves. Not sure if we can count on it being legal. Maybe we can get some help from compiler gurus.. |
|
The relevant code is cdstreq() in backend/cod2.c. ('streq' = 'struct equals' = struct assignment). It seems to basically do a rep movs as long as it is more than two registers long. I don't think it ever calls memcpy. Which makes sense since DMC's memcpy is not very fast, it just does rep movsd. If it is less than two registers I have a nasty feeling it just does a single movsd or movsw. Hope I'm wrong, that would be pretty awful. |
|
Thanks for the input. It is as I feared it's pretty much tied to backend. Peeked at LDC backend, appears to be transformed into memcpy or analoguos intrinsic. One last thing I'd love to know is if there is any danger at all of doing memcpy(&this, &this, this.sizeof); - self copying (aside from potentialy wasting cycles) w.r.t. to potential SIMD optimizations and what not. Keeping in mind that memcpy is typically an instrinsic. Looking at the code I see that this is a primary branch for small structs: if (numbytes <= REGSIZE * (6 + (REGSIZE == 4)))
{ while (numbytes >= REGSIZE)
{
c3 = gen1(c3,0xA5); /* MOVSW */
code_orrex(c3, rex);
numbytes -= REGSIZE;
}
while (numbytes--)
c3 = gen1(c3,0xA4); /* MOVSB */
}From what I understand it should do an unrolled sequence of: And that might be okay but... if I'm not mistaken it does: A lot of work to transport 1-2 words.
I'm afraid it is the case :( align(1)
struct C{
align(1) short abc;
align(1) short efg;
align(1) short hij;
}
static assert(C.sizeof == 6);
void main(){
C c = C(1, 2, 3);
C x;
x = c;
assert(x.abc == 1);
assert(x.efg == 2);
assert(x.hij == 3);
}
Generated asm: enter 10h, 0
push ebx
push esi
push edi
mov ax, 1
mov [ebp+var_10], ax
mov cx, 2
mov [ebp+var_E], cx
mov dx, 3
mov [ebp+var_C], dx
lea ebx, [ebp+var_8]
xor eax, eax
mov [ebx], eax
mov [ebx+4], ax
lea esi, [ebp+var_10]
lea edi, [ebp+var_8]
movsd
movsb
movsb
... //series of cmp/call assert |
|
To @blackwhale :
Why are you taking |
|
To @denis-sh I'm not even sure now;) The main argument was being able to make it as fast as possible. I believe you see that checking aliasing for small things (which memmove most definetly does) is not good enough. My line of reasoning was going like this:
So for now the case of smallish PODs is optimized and it's fine. Elaborate small structs are not for the reasons above. My lasy problem is with making move CTFE-able. With CTFE-ability requirement I don't see how to implement the bitwise move in the library. If we disallow moving structs with immutable fields it can be implemented as copy (given it's during compile-time I couldn't care less). I'd love to disallow it but we need a consensus esp as it breaks some obscure unittest in std.container. Anyway I think I got carried away way a bit. The focus should be on the pull request itself. |
|
To @blackwhale:
I don't know much about C's IMHO, it's very bad and is a source of bugs currently and will lead to more bugs in the future that we use C library (especially buggy Digital Mars one) without any reason. A already proposed to have our own implementation (my variant is called |
That's it. Comparing pointers if we copy say 4 bytes. Yeah, call me crazy but as we see generic code ends up doing this stuff and even worse. In fact, the call itself is too much but we can only hope on inliner.
I tried to rise the topic of over relience on C's mem* well probably folks are alright with it but not going to step in: The signature it uses allows it to optimize. The intended use is to cover both arrays of and just single structs. Also truth be told glibc has real fast memcpy with SIMD under the hood. On windows we have no such luck...
I recall that quite some time ago. Another important moment about it is that not knowning the type it can't optimize for small structs. In other words it's a great as fallback function to use when all special cases are handled. In my propsal of rawTransfer if there is no better way it should forward to something like this. |
|
I think I've tackled the CTFE problem, take a look : #936 |
|
Somehow it started failing the autotester. |
|
To @blackwhale:
I added dependencies on my other pulls. See Requires at the end of pull description. |
|
By the way, my |
|
There is a way to list them with some keyword so that auto-tester pulls them too. Can't remember the exact magic though... |
|
LGTM once it passes the tester |
|
It can't pass the tester as it requires functions from unmerged pulls. |
|
|
||
| Specifically: | ||
| $(UL | ||
| $(LI Do nothing if $(D &source is &target) (for the first overload only).) |
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.
»do« -> »does«, etc. for conformity.
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.
Issues fixed: * `move` is over-complicated * `move` doesn't work with qualified structs * `move` doesn't work with const/immutable non-struct types * for a nested struct, `move` tries to call destructor on possibly invalid struct filled by `init` except with a context pointer of `target` (such mix is unexpected and invalid e.g. if default constructor is disabled) * `move` fails do the correct mix of `init` and context pointer of `target` in previous point in some cases * lots of other issues I'm to tired to search in list here
|
@denis-sh Another matter is making it CTFE-able with yours rawCopy |
|
Failed to penetrate. More over issues mentioned in commit comments seem to be addressed in the meantime. |
It's better to analyze by commit and see commits comments for description.
And yes, I also dislike moving
const/immutabeobjects. But have no idea how to avoid it and not makemoveunusable.[EDITED] 3 times
My
move:I also dislike that
movedoesn't always setsourceto it'sinit. As it is documented now, it's not such a big problem as it was but it still looks inconsistent.So can I just tell
moveto writeinittosourceobligatory?And don't ask me to fill all fixed
movebug in bugzilla as it will take a full day at least and I will probably still miss something.Requires
setInitialStatefrom pull #928 anddestructfrom #929.