-
-
Notifications
You must be signed in to change notification settings - Fork 609
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. |
The end goal is to completely delete core.lifetime. Don't try to shoehorn this in there; that stuff is all dead. |
What about the binary overload of I'm asking because this MR needs to include the druntime modifications to It's important to note that current behaviour of
I'm not sure this is in line with the behavior of See https://dlang.org/phobos/core_lifetime.html#.move for details. Luckily druntime is now part of the dmd repo. |
I agree with @nordlow that |
Nevertheless, I personally believe it is highly preferable to make all the overloads of Btw, I'm working on migrating |
Such certainly looks tempting, but it falls short as soon as the flow control becomes non-trivial. The flow analysis I know comes from class notes in a class I took on optimizations. |
emplace() will be replaced with the placement new operator, I posted a DIP on it in the development forum. |
Currently it will get destructed twice. |
This work is heavily based on Timon Gehr's and Manu Evans' contributions. |
Another case naturally resolved with caller-destruction! |
8ccedfb
to
192c872
Compare
I've started working with this branch and trying to build out the peripheral stuff. struct S
{
this(typeof(this) s) { writeln("move"); }
this(ref typeof(this) s) { writeln("copy"); }
}
This error is erroneous; it is not only valid, but it is indeed the entire point of this work. Given the struct above, we should be able to write: void main()
{
S x;
S a = x;
S b = __rvalue(x);
} And this should emit the output:
|
There's also still this issue: struct S
{
int i, j, k;
this(S s)
{
S* a = &s; // if i assign the addresses to locals...
S* b = &x;
assert(a is b); // this works
assert(&s is &x); // this doesn't work
}
}
__gshared S x;
void main()
{
// S t = __rvalue(x); // this doesn't work; it should call the constructor, but it doesn't
S t2 = S(__rvalue(x)); // this does call the constructor...
} It's weird that if I assign the pointers to locals and compare them, it's fine, but if I compare the Also, the first statement in main doesn't call the constructor like it should. |
I can't do anything with |
Ok I will check them out. |
It leads to reopening https://issues.dlang.org/show_bug.cgi?id=19871 and https://issues.dlang.org/show_bug.cgi?id=23036 |
Okay, well we'll need to work thorough those properly... is that change made in your branch, or can you commit a branch with that change so I can build it? |
|
The problem is it goes into infinite recursion and runs off the end of the stack |
Okay, well I'm sure that's fixable; just some semantic or terminal condition isn't geared quite right. |
What about this one: S t = __rvalue(x); // this doesn't work; it should call the constructor, but it doesn't And also that odd one where I can compare the pointers if I first assign them to locals, but I can't compare the |
Okay, I'm seeing the nature of the issue. The rules surrounding when default ctors should be generated need to be updated in relation to move semantics, and that spec update should inhibit the issues you're seeing. I'm having a bit of a poke to see if I can implement the changes, but it might be a bit too broad for my level of compiler knowledge... What I'm trying to do:
dsymbolsem.d:
sd.hasCopyCtor = buildCopyCtor(sd, sc2);
+++ sd.hasMoveCtor = buildMoveCtor(sd, sc2);
sd.postblit = buildPostBlit(sd, sc2); Because a default move constructor will become a concept, and then some rules regarding when constructors are synthesised need adjustment, something like:
The reason that it should fall-back to move construction or forward to move construction, is that we can't know if there's any 'special sauce' in the copy that performs special record keeping, which would need to be mirrored in a move constructor; so we can't safely synthesise a move ctor in the presence of a copy ctor. We must defer to the user-implemented logic. So; no additional logic needs to be added to the compiler if There's a secondary conversation about similarly synthesising default opAssign's, but there are 2 or 3 reasonable options, and that conversation can wait for later.... |
@WalterBright I don't really know how to diagnose compiler issues without a lot of trouble when they become complicated; is there some dev logging, or anything like that which emits the flow of of the compiler doing it's work? I don't see how to easily follow what it's doing without intensively time-consuming stepping through the debugger, which illustrate the program flow but mostly obscuring the context of the source code or expressions that are being evaluated at any given moment. |
I just add custom, targeted printf's as needed, and when I solve the problem, I delete them. Enabling generic logging results in a hopeless mass of irrelevant detail. |
The cause of the infinite loop is this line:
in the function:
I have yet to figure out how to fix it. |
This adds the
__rvalue(expression)
builtin, which causesexpression
to be treated as an rvalue, even if it is an lvalue.