4 days ago
On 02/10/2024 3:59 PM, Atila Neves wrote:
> On Tuesday, 1 October 2024 at 03:07:02 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> On 01/10/2024 3:58 PM, Atila Neves wrote:
>>> On Monday, 30 September 2024 at 20:28:05 UTC, Richard (Rikki) Andrew Cattermole wrote:
>>>> I suspect that we're going in an entirely wrong direction with move constructors for two reasons:
>>>>
>>>> 1. Nobody has been able to answer what the purpose of them is, is it an optimization, is it ownership transfer system (which is better done with isolated).
>>>
>>> My understanding is that we need SSA in order to implement isolated. And so for now I'd say it's an optimisation mostly, but also bug prevention with move-only types.
>>
>> SSA? What? That has nothing to do with it.
> 
> Not according to Amaury.

He would be wrong to suggest that it is a requirement. It may make it easier to conceptualize it, and if you already have an IR that is SSA it may even allow you to do it cheaply. But it is not a requirement.

You can confirm what I have said by reading Wikipedia on the subject where SSA is indeed listed as an alternative approach to some problems. https://en.wikipedia.org/wiki/Data-flow_analysis

To construct an SSA IR will cost a lot of time, that quite frankly I can't see making the rest of the analysis faster. It would be a totally different situation if we could parallelize it (I went down that path with my semantic 4, but it makes more sense to do as part of semantic 3 for this). You basically need to make the CFG from the AST.

Isolated and with that unique ownership is based upon the data flow analysis of the tracked objects. Object tracking isn't too complex if you are limited to forward pass only.

For reference, good article by one of the creators of Midori, regarding isolated https://joeduffyblog.com/2016/11/30/15-years-of-concurrency/

4 days ago

On Tuesday, 1 October 2024 at 17:37:37 UTC, Walter Bright wrote:

>

On 10/1/2024 12:27 AM, Dukc wrote:

>

Why special syntax? move is a DRuntime function so it's expected that it can have special semantics, even without special syntax.

Converting to an rvalue is an enabler of other functions, too, not just move. __rvalue is a building block, not a complete function.

move is the __rvalue building block, at least in principle. It accepts an lvalue and returns a rvalue, without executing the destructor or move constructor of the type in question.

It does set the lvalue to it's .init value, but this can be elided by the optimiser if the lvalue is not used again, assuming the destructor doesn't side-effect in any way.

4 days ago

On Tuesday, 1 October 2024 at 17:41:34 UTC, Walter Bright wrote:

>

On 10/1/2024 12:25 AM, Dukc wrote:

>

You can make it to work with a compiler instrinsic. The intrinsic would either be an UDA applied to move that allows the compiler to make those assumptions, or an intrinsic function that works along the lines of nove and moveEmplace. The public DRuntime functions would forward or alias to the instrinsic function.

move() is an unhappy maze of templates. We need something a lot simpler.

If we'll have a compiler intrinsic function, move can be made much simpler by having it simply call the compiler intrinsic, or alias to it.

4 days ago
I feel like we discussed exactly this at dconf fairly extensively, with pictures and diagrams and stuff...

Just write `T move(ref T)` as an intrinsic.
There's potentially more work to do than this operation; the intrinsic will
also (in the future) include internal logic to end the lifetime of the
memory region, and possibly logic to record the transfer of ownership for
the value. This is all not expressible in-language... it's most appropriate
as an intrinsic; this is mega-fundamental stuff. Remember, the goal is to
murder core.lifetime with fire.

We don't want a function for this, no call/ret, do not want to pollute the program flow in that way and definitely don't want to create weird program flow while stepping through code while debugging. Intrinsic that initially just strips off ref, and can be enhanced with lifetime management logic in future.

On Tue, 1 Oct 2024, 02:11 Walter Bright via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> I've been implementing move constructors. They are working, at least on my
> test
> cases so far. They are distinguished by a copy constructor takes its
> argument by
> ref, and a move constructor by value:
>
> ```
> struct S
> {
>      this(ref S); // copy constructor
>      this(S);     // move constructor
> }
> ```
> So far, so good. Consider:
> ```
> void phone(S s)
> {
>      S t = s;  // copy constructor
> }
> ```
> But what if we want to initialize `t` via a move constructor? Somehow, `s`
> has
> to be converted from an lvalue to an rvalue. This is done via the function
> rvalue():
> ```
> S rvalue(ref S s) { return s; }
>
> S t = rvalue(s);
> ```
> Unfortunately, rvalue() gives us this:
> ```
> S __copytmp = 0;
> this(&__copytmp,&s);  // copy construction of s
> *__H1D1 = __copytmp;
> return __H1D1;
> ```
> which is correct, but not what we want, which is:
> ```
> return &s;
> ```
> and also not pass an extra hidden parameter for the return value, aka
> __H1D1.
>
> I have thought of several ways to do this, none of which seem particularly
> attractive as they are all not expressible in conventional D. I won't say
> what
> they are in order to not bias anyone.
>
> So, does anyone have any brilliant ideas for how to make the compiler
> treat an
> lvalue as an rvalue?
>
> P.S. C++ uses the std::move function to do it:
>
>
> https://learn.microsoft.com/en-us/cpp/standard-library/utility-functions?view=msvc-170#move
>
> which relies on rvalue references:
>
>
> https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170
>
> which is a major feature which I prefer to avoid.
>


4 days ago
On Tue, 1 Oct 2024, 03:51 Dukc via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote:
> > So, does anyone have any brilliant ideas for how to make the compiler treat an lvalue as an rvalue?
>
> Doesn't `core.lifetime.move` already do this?
>

That entire file is a malignant cancer and it must be murdered in the fires of hell.

>


4 days ago
On Tue, 1 Oct 2024, 03:31 Bastiaan Veelo via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Monday, 30 September 2024 at 16:05:16 UTC, Walter Bright wrote: [...]
> > P.S. C++ uses the std::move function to do it:
> >
> >
> https://learn.microsoft.com/en-us/cpp/standard-library/utility-functions?view=msvc-170#move
> >
> > which relies on rvalue references:
> >
> >
> https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170
> >
> > which is a major feature which I prefer to avoid.
>
> I suppose `-preview=rvaluerefparam` is not relevant here, right?
>

It's essential for this design to work; the logic in that preview allows appropriate selection of copy/move overloads.


4 days ago
On Tue, 1 Oct 2024, 17:03 Walter Bright via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 9/30/2024 8:29 PM, Richard (Rikki) Andrew Cattermole wrote:
> > Perhaps an attribute, like ``@move`` in core.attributes would be
> sufficient for
> > this task ;)
>
>
> It's an interesting idea, but a lot of details would need to be worked
> out. For
> instance, the core.attributes.move is a version of the move assignment
> operator,
> not move construction. Then there is the problem of distinguishing between
> a
> move constructor and a copy constructor - how do they overload against
> each
> other? There's what a default move constructor should be. There's what
> happens
> when a field of a struct has a move constructor.
>
> And so on.
>
> BTW, I took a look at core.lifetime.move. It's hard to figure out just
> what it
> does, as there are vacuous forwardings to other templates.
>
> For example:
> ```
> void move(T)(ref T source, ref T target)
> {
>      moveImpl(target, source);
> }
> ```
> Why?


Because D is so fundamentally broken in this department. Work your way through a clear understanding of core.lifetime, and you should become convinced how important this work you are doing actually it's!


3 days ago
On 10/1/2024 1:15 PM, Timon Gehr wrote:
> I guess the new implementation you have in mind is something like the following?
> 
> ```d
> auto move(T)(ref T arg)=>__rvalue(arg);
> ```

Not exactly. __rvalue would also convert an rvalue to an rvalue.
3 days ago
On 10/1/2024 1:06 PM, Timon Gehr wrote:
> I think in case we did go the function route, I think any implementation of `move` that is much more complex than the following is a failure:
> 
> ```d
> auto move(T)(@moved T arg)=>arg;
> ```

The major difference between @move and __rvalue is the former is attached to the parameter, and the latter is attached to the argument. This might seem a distinction without a difference, but this has large implications with how overloading works.

For example, how do we distinguish a move constructor from a copy constructor?
```
this(ref S); // copy constructor
this(S);     // move constructor
S s = t;     // calls copy constructor (lvalue)
S s = f();   // calls move constructor (rvalue)
```
The current overloading rules work out of the box, an rvalue goes for the move constructor, and an lvalue goes to the copy constructor.

The problem here is when I want to move t into s. How do I get it to call the move constructor?
```
S move(ref S s) { return s; } // convert argument from lvalue to rvalue
S s = move(t);
```
This works, however, it creates a copy of s and then moves the copy! There needs to be a way to tell the compiler to use the move construct, hence:
```
S s = __rvalue(t);
```
All __rvalue does is flag the expression in ( ) as an rvalue. Then the rest of the semantics go from there. Note that a struct parameter with a move constructor will always pass by ref regardless, which is what we want here. Also, move semantics will only work on structs. Not classes, integers, pointers, arrays, etc. If move semantics are desired for them, they'll need to be wrapped in a struct, or use a template to wrap it for you.

Consider:
```
this(ref S);   // copy constructor
this(@move S); // move constructor
```
I don't know how to make overloading work with this.
3 days ago
On 10/2/24 20:00, Walter Bright wrote:
> On 10/1/2024 1:06 PM, Timon Gehr wrote:
>> I think in case we did go the function route, I think any implementation of `move` that is much more complex than the following is a failure:
>>
>> ```d
>> auto move(T)(@moved T arg)=>arg;
>> ```
> 
> The major difference between @move and __rvalue is the former is attached to the parameter, and the latter is attached to the argument. This might seem a distinction without a difference, but this has large implications with how overloading works.
> ...

I don't think it affects how overloading works. You just treat it as a by-value argument as far as overloading and lifetime handling is concerned. In terms of ABI however, you pass the argument by reference.

Some sort of NRVO would make the occurrence of `arg` above a move.
This would be useful in general. Consider this program:

```d
import std.stdio;
struct S{
    this(ref S){ writeln("copy"); }
    ~this(){ writeln("destructor"); }
}
S foo(S s)=>s;

void main(){
    S s=foo(S());
    // ...
}

```

This prints:

```
copy
destructor
destructor
```

There is actually no reason for this to make a copy, and the compiler would in principle be able to figure it out.

> For example, how do we distinguish a move constructor from a copy constructor?
> ```
> this(ref S); // copy constructor
> this(S);     // move constructor
> S s = t;     // calls copy constructor (lvalue)
> S s = f();   // calls move constructor (rvalue)
> ```
> The current overloading rules work out of the box, an rvalue goes for the move constructor, and an lvalue goes to the copy constructor.
> 
> The problem here is when I want to move t into s. How do I get it to call the move constructor?
> ```
> S move(ref S s) { return s; } // convert argument from lvalue to rvalue
> S s = move(t);
> ```
> This works, however, it creates a copy of s and then moves the copy! There needs to be a way to tell the compiler to use the move construct, hence:
> ```
> S s = __rvalue(t);
> ```
> All __rvalue does is flag the expression in ( ) as an rvalue. Then the rest of the semantics go from there. Note that a struct parameter with a move constructor will always pass by ref regardless, which is what we want here. Also, move semantics will only work on structs. Not classes, integers, pointers, arrays, etc. If move semantics are desired for them, they'll need to be wrapped in a struct, or use a template to wrap it for you.
> ...

`__rvalue` makes sense and is compatible with something like `@move`. However, I think it is in principle not needed if there is `@move`, as an lvalue `x` that is passed to a `@move` parameter would be treated just like `__rvalue(x)`.

> Consider:
> ```
> this(ref S);   // copy constructor
> this(@move S); // move constructor
> ```
> I don't know how to make overloading work with this.

Well, those are constructors. You use one of them for copies and the other for moves.

In general, `@move` would be incompatible with `ref`. If `ref` is overloaded with `@move`, lvalues go to the `ref` overload and rvalues go to the `@move` overload. (So overloading works just as if there was no `@move` on the parameter.)

If you have an lvalue that should go to the `@move` overload, you explicitly call `move`.

Of course, with DIP1040, in principle the last use of an lvalue could prefer the `@move` overload instead.


Of course another question one might ask is what happens if you have:

```d
void foo(T t);
void foo(@move T t);

T t;
foo(t);
```

I think that would just be ambiguous, just as if there were no `@move`.


A benefit of explicit `@move` is that the move constructor ABI is not an invisible special case, and is also not magic.

A drawback of explicit `@move` is that people might do:

```d
this(ref S);
this(S); // (no @move)
```

And it is a priori not so clear what this should do. Maybe it should be disallowed, like it is now.