-
-
Notifications
You must be signed in to change notification settings - Fork 608
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
add __rvalue(expression) builtin #17050
base: master
Are you sure you want to change the base?
Conversation
Thanks for your pull request, @WalterBright! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#17050" |
0f383fd
to
60c0f9e
Compare
082c48d
to
6e0e44f
Compare
Can this |
Will the call to S s;
__rvalue(s)
use(s); be either
? I prefer option 2 and is the closest to what Rust does. Can it be implemented with constant-time overhead by adding a status bit to the (parameter) variable declaration indicating that its contents has been invalidated by a move? I recently saw a https://youtu.be/08gvuBC-MIE?t=1839 in which Jon Kalb admits that the standard committe made a mistake by forcing moved from object to in a so called "fully formed state" instead of a so called "partially formed state". As this prevents certain kinds of optimizations. For details see https://youtu.be/08gvuBC-MIE?t=1839. |
@nordlow I'll write a proper document for this after I figure out just what the end result will be! |
86b02a7
to
8ccedfb
Compare
Thanks. Please see updates to my comment at #17050 (comment). |
My opinion on move semantics is that once you've moved s to t, then s's lifetime is over, and it should be in the default initialized state (a concept C++ doesn't have). |
Is this realized by
|
Have you considered it making it a compiler error to access |
Yes, but it requires Data Flow Analysis, which is slow. |
Ok, thanks. Afaict, the complexity of supporting Rust-style r-value semantics depends on the context in which S use(S);
S x;
auto y = __rvalue(x)
use(y); // allowed
use(x); // disallowed such a analysis could be implemented in the compiler with negligible overhead using an extra status bit in the But in the general case I realize now that the compiler needs to recurse into all function calls that are passed l-values by reference as arguments. Do you have a good reference to which data flow analysis in general and its applications such as this one? |
I currently experimenting with using static if (__traits(compiles, { int x; const y = __rvalue(x); })) {
import core.stdc.string : memcpy;
T move(T)(return scope ref T source) @trusted {
scope(exit) {
static immutable init = T.init;
memcpy(&source, &init, T.sizeof);
}
return __rvalue(source);
}
void move(T)(ref T source, ref T destination) @trusted {
scope(exit) {
static immutable init = T.init;
memcpy(&source, &init, T.sizeof);
}
destination = __rvalue(source);
}
} else
public import core.lifetime : move;
/// unary move()
pure nothrow @nogc @safe unittest {
auto x = S(42);
assert(x == S(42));
const y = move(x);
assert(y == S(42));
assert(x == S.init);
}
/// binary move()
pure nothrow @nogc @safe unittest {
auto x = S(42);
assert(x == S(42));
S y;
move(x, y);
assert(y == S(42));
assert(x == S.init);
}
version(unittest) {
struct S { @disable this(this); int x; }
} a suitable rewrite of the |
__rvalue() is a bad name for a move() intrinsic, the answer you seek is yes, __rvalue is exactly a replacement for move(), and it should be named move(). I'll argue for this before it's merged, but we're making very good progress here! :) |
Also no, this can't be 'wrapped', it's an intrinsic; it needs to be renamed move, you can't wrap it in a function named move. |
We're playing around with deep foundation level initialisation code; this should be expected... it's a tough transformation to fight through no doubt, but it'll be the most valuable thing that can happen to the language in a long time. |
0dc145b
to
d081468
Compare
The only error message reported is:
How useless. |
d081468
to
2bae1af
Compare
Unfortunately, this PR has become too big and complex. I made some changes, and it is inexplicably broken. I'm going to have to start over. This is why I strongly dislike large PRs. |
2bae1af
to
1945786
Compare
Finally discovered why this is failing. |
a4fa8a0
to
244c56b
Compare
I redesigned the code that deals with overload resolution of copy and move constructors. Seems to be much better. |
244c56b
to
419609c
Compare
The buildkite error is:
Anybody have a clue? |
The vibe.d error seems to be the result of regarding a struct as "not POD" if it contains a move constructor, something it did not do before. |
419609c
to
bc64f7a
Compare
To get it to pass buildkite tests, dstruct.d isPOD() is changed:
|
@TurkeyMan ready for another try |
Well I guess C++ has a similar moment where they realised they didn't know what POD even meant anymore, and then redefined it to mean something useful; I think the final rules were:
I mean, the error you're seeing must have been introduced by something that already has a move constructor (formerly "rvalue constructor"), which previously was seen as POD, and now not seen as POD? There's no change in semantics... so why the change in POD-ness? Probably worth working out what POD actually means though, and then tightening the definition around that and correcting any broken code. I'll get back on with testing this out... |
So, now there's 2 branches |
...I went with |
I think I've reduced this as much as I can; it's weirdly elaborate! struct StringTest
{
alias toString this; // this MUST exist; I guess this affects the return expression in `fun` somehow...
const(char)[] toString() // can return anything other than `int`
=> null;
// must have both copy and move ctors
this(StringTest rhs) {}
this(ref StringTest rhs) {}
// UNCOMMENT THIS TO DO CRASH
// this(T)(int x) {} // crash! function has a template, param type (`int`) unrelated to construction
this(int x) {} // doesn't crash: no tempalte arg
this(T)(const(char)[] x) {} // doesn't crash: type matches alias this
~this() {} // only if there is a destructor! comment this: crash goes away!
}
StringTest fun(ref StringTest arg)
{
return arg; // I guess this crashes searching for the copy ctor and getting confused
}
void main()
{
StringTest st;
fun(st); // must CALL this function, even though it's not a template!
} You must uncomment the commented line to see the crash. The compiler shouldn't crash when un-commenting that line. Nothing calls that function, it should just be ignored in this program. |
The __rvalue branch is the main line now. |
It's another infinite recursion bug:
|
Hmmm, well it's very specific... the conditions that lead to it are super fragile, but it did appear in the wild instantly. |
Reduced example:
|
bc64f7a
to
c59b637
Compare
c59b637
to
e2e71bc
Compare
This adds the
__rvalue(expression)
builtin, which causesexpression
to be treated as an rvalue, even if it is an lvalue.