November 09
On 11/9/2024 6:38 PM, Richard (Rikki) Andrew Cattermole wrote:
> But it does tell us, that as a language feature it is dependent upon it, to be working correctly, so can't be turned on until then.

By setting the source to S.init after the move, it will work safely.

November 10
On 11/9/24 23:44, Walter Bright wrote:
> I'm not sure it's a problem or a danger.
> 
> Timon mentioned the related problem with:
> 
> ```
> callee(__rvalue s, __rvalue s);
> ```
> 
> where s would be destroyed twice. This isn't always detectable:
> ```
> S* ps = ...;
> callee(__rvalue *s, __rvalue(*s));
> ```
> But can be rendered benign with the blit of S.init after the destructor call.

I think the main potential trouble is that there is usually an assumption that there is no aliasing between rvalue arguments.

For example, if a compiler backend assumes no aliasing, undefined behavior might be introduced if one of the arguments is modified and then the other is read.

Of course, we can instead specify that the aliasing is legal (but it may still be surprising).
November 10
On 11/10/24 00:01, Walter Bright wrote:
> On 11/9/2024 9:37 AM, kinke wrote:
>> Oh, there's at least one problem with the `this(T)` move-ctor signature - C++ interop. C++ doesn't destroy the parameter, because it's an rvalue-ref. The proposed by-value signature in D however includes the destruction of the value-parameter as part of the move- construction. The same applies to move-assignment via `opAssign(T)`. So after calling a C++ move ctor/assignOp with an `__rvalue(x)` argument, the rvalue wasn't destructed, and its state is as the C++ callee left it. Automatically reset-blitting to `T.init` would be invalid in that case, as the moved-from lvalue might still have stuff to destruct.
> 
> We could disallow __rvalue arguments for call to C++ functions?

How do you even call a C++ function that accepts an rvalue reference from D?

If `extern(C++) this(T)` magically matches the C++ move constructor, it seems that additional magic has to be added to all calls in any case to deal with the mismatch.
November 11

On Saturday, 9 November 2024 at 22:39:33 UTC, Walter Bright wrote:

>

I suggest the most pragmatic implementation of your ideas is to append to the destructor calls to rvalue parameters a blit of the .init value. It is only necessary if the rvalue has a destructor. The callee cannot know if an rvalue was passed using __rvalue, so it has to defensively do this anyway.

I'm not too fond of that, as that means doing the blit for every value parameter with a dtor, not just in the (presumably way less) call sites using __rvalue. Adding a cleanup-scope (finally) for the call shouldn't be too hard, reset-blitting all arguments that were __rvalue'd. Incl. PODs and non-PODs without dtor, to get the T.init-state guarantee in all cases, required to make this feature half-way safe in cases where the compiler cannot prove that the original lvalue isn't accessed later.

November 11

On Sunday, 10 November 2024 at 17:42:27 UTC, Timon Gehr wrote:

>

On 11/10/24 00:01, Walter Bright wrote:

>

On 11/9/2024 9:37 AM, kinke wrote:

>

Oh, there's at least one problem with the this(T) move-ctor signature - C++ interop. C++ doesn't destroy the parameter, because it's an rvalue-ref. The proposed by-value signature in D however includes the destruction of the value-parameter as part of the move- construction. The same applies to move-assignment via opAssign(T). So after calling a C++ move ctor/assignOp with an __rvalue(x) argument, the rvalue wasn't destructed, and its state is as the C++ callee left it. Automatically reset-blitting to T.init would be invalid in that case, as the moved-from lvalue might still have stuff to destruct.

We could disallow __rvalue arguments for call to C++ functions?

How do you even call a C++ function that accepts an rvalue reference from D?

We can't without rvalue-ref complications in D, but we could definitely special-case move ctors and assignment operators, just need to match the C++ mangle. And match the same semantics obviously, which is the crux. - We can already interop with the main C++ lifetime member functions - regular constructors, copy constructors, destructors. It'd IMO be a shame not being able to use the original C++ move ctor and assignOp too, having to re-implement them in D for a complete binding.

November 11

On Sunday, 10 November 2024 at 17:36:25 UTC, Timon Gehr wrote:

>

On 11/9/24 23:44, Walter Bright wrote:

>

I'm not sure it's a problem or a danger.

Timon mentioned the related problem with:

callee(__rvalue s, __rvalue s);

where s would be destroyed twice. This isn't always detectable:

S* ps = ...;
callee(__rvalue *s, __rvalue(*s));

But can be rendered benign with the blit of S.init after the destructor call.

But that's at least already invalid/undefined in your proposal. I've used callee(lval, __rvalue(lval)) to show that the aliasing problem can occur in valid code too - lval isn't accessed lexically after __rvalue'ing it. __rvalue'ing a global variable and checking that the global isn't accessed in the callee is even harder.

>

I think the main potential trouble is that there is usually an assumption that there is no aliasing between rvalue arguments.

It's not just an assumption, it's an implicit guarantee - a by-value parameter is analogous to a local in high-level terms, so of course with its own distinct private memory. Well, until now. :)

>

For example, if a compiler backend assumes no aliasing, undefined behavior might be introduced if one of the arguments is modified and then the other is read.

This isn't just a problem with compiler optimizations, but in general:

void callee(const ref S x, S y) {
    y.bla = x.bla - 1;
    assert(y.bla != x.bla, "have changed ref via value alias!");
}
6 days ago
On 11/11/24 12:31, kinke wrote:
> On Sunday, 10 November 2024 at 17:36:25 UTC, Timon Gehr wrote:
>> On 11/9/24 23:44, Walter Bright wrote:
>>> I'm not sure it's a problem or a danger.
>>>
>>> Timon mentioned the related problem with:
>>>
>>> ```
>>> callee(__rvalue s, __rvalue s);
>>> ```
>>>
>>> where s would be destroyed twice. This isn't always detectable:
>>> ```
>>> S* ps = ...;
>>> callee(__rvalue *s, __rvalue(*s));
>>> ```
>>> But can be rendered benign with the blit of S.init after the destructor call.
> 
> But that's at least already invalid/undefined in your proposal. I've used `callee(lval, __rvalue(lval))` to show that the aliasing problem can occur in valid code too - `lval` isn't accessed lexically after __rvalue'ing it. __rvalue'ing a global variable and checking that the global isn't accessed in the callee is even harder.
> ...

Well I would rather not consider this valid as the last use of the original `lval` may be within the callee after the move. My favorite design would be making `__rvalue` a low-level `@system` operation by default and having the high-level `move` operation actually ensure these things cannot happen.

>> I think the main potential trouble is that there is usually an assumption that there is no aliasing between rvalue arguments.
> 
> It's not just an assumption, it's an implicit guarantee - a by-value parameter is analogous to a local in high-level terms, so of course with its own distinct private memory. Well, until now. :)
> ...

Yup.

>> For example, if a compiler backend assumes no aliasing, undefined behavior might be introduced if one of the arguments is modified and then the other is read.
> 
> This isn't just a problem with compiler optimizations, but in general:
> 
> ```D
> void callee(const ref S x, S y) {
>      y.bla = x.bla - 1;
>      assert(y.bla != x.bla, "have changed ref via value alias!");
> }
> ```

Yup.
6 days ago

On Saturday, 9 November 2024 at 22:59:34 UTC, Walter Bright wrote:

>

On 11/9/2024 8:15 AM, Richard (Rikki) Andrew Cattermole wrote:

>
  1. We'll need to introduce a swap builtin, since we have no way to say describe moves between parameters. This can come later, as it is an addition.

Doesn't a swap function get arguments passed by ref?

>
  1. I have the concern that existing code that is not designed to accept a move, will have a move into it. White listing via an attribute @move to say that this constructor/opAssign is designed to handle a move in would be valuable.

This can work, but if the users have to proactively add this attribute, I'm afraid we've failed.

>
  1. Optimizing of eliding of destructors should be done with type state analysis, it does not need its own dedicated DFA.

The two are the same, aren't they?

When will I be able to measure performance for different data types and usage scenarios?

Thus, we can concretely show the performance advantages of the __rvalue keyword. In my opinion, the performance impact of transport semantics in real-world applications should be analyzed in detail, and its effects on large data structures and frequently used objects should be examined.

We have no time to lose...

SDB@79

2 days ago
On 11/10/2024 9:36 AM, Timon Gehr wrote:
> I think the main potential trouble is that there is usually an assumption that there is no aliasing between rvalue arguments.
> 
> For example, if a compiler backend assumes no aliasing, undefined behavior might be introduced if one of the arguments is modified and then the other is read.
> 
> Of course, we can instead specify that the aliasing is legal (but it may still be surprising).

The problem exists anyway.

https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1021.md

This has been incorporated, but is only turned on with a switch.
7 hours ago
On 11/19/24 07:50, Walter Bright wrote:
> On 11/10/2024 9:36 AM, Timon Gehr wrote:
>> I think the main potential trouble is that there is usually an assumption that there is no aliasing between rvalue arguments.
>>
>> For example, if a compiler backend assumes no aliasing, undefined behavior might be introduced if one of the arguments is modified and then the other is read.
>>
>> Of course, we can instead specify that the aliasing is legal (but it may still be surprising).
> 
> The problem exists anyway.
> 
> https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1021.md
> 
> This has been incorporated, but is only turned on with a switch.

Well, aliasing between `ref` parameters is an expected thing that can occur. Backends and users are aware of this possibility. Check out the implementation of std.algorithm.swap.

Aliasing between non-`ref` parameters (or across `ref`-ness) is a different thing. This can be rather surprising and I think it would sometimes lead to undefined behavior with current backends.

So the question is how `__rvalue` will interact with `@safe`, and if it is sometimes unsafe, whether there will be a safe variant that conservatively moves rvalues in memory to avoid aliasing situations if they cannot be precluded.
1 2
Next ›   Last »