October 05, 2020
On Monday, 5 October 2020 at 10:33:46 UTC, IGotD- wrote:
> Ouch, so this mean that how 'in' works must be a defined standard that all D compilers adhere to. Also this standard must be defined for each CPU architecture.

It breaks for one compiler too because of templating. If all unit tests only trigger ref passing, and it is used with a smaller template param then it will run with value passing...

October 05, 2020
On Monday, 5 October 2020 at 08:42:21 UTC, Mathias LANG wrote:
> On Monday, 5 October 2020 at 08:34:37 UTC, Iain Buclaw wrote:
>> On Monday, 5 October 2020 at 07:55:57 UTC, Walter Bright wrote:
>>> On 10/4/2020 7:19 AM, Iain Buclaw wrote:
>>>> [...]
>>>
>>> The problem is not dmd compiled code calling gdc/ldc compiled code. The problem is code that works with one compiler fails with another, because the "to ref or not to ref" decision is *implementation defined*.
>>
>> Furthermore, NRVO is implementation defined, which results in the same dmd/gdc/ldc making different decisions for "to ref or not to ref", and yet I see no one complaining about that.
>
> I cannot resist the bait: https://issues.dlang.org/show_bug.cgi?id=20752

Yeah, but isReturnOnStack and NRVO are two different things.  NRVO avoids a copy before passing. :-)
October 05, 2020
On Monday, 5 October 2020 at 09:53:37 UTC, Walter Bright wrote:
> On 10/5/2020 1:34 AM, Iain Buclaw wrote:
>> Furthermore, NRVO is implementation defined, which results in the same dmd/gdc/ldc making different decisions for "to ref or not to ref", and yet I see no one complaining about that.
>
> I don't see that as a to ref or not decision. It is an issue of how many copies are made, there shouldn't be dangling references to those copies, or it shouldn't NRVO it.
>
> I don't recall any case of NRVO breaking code since I invented it 30 years ago, other than something that relied on the number of copies.

I don't consider there to be any difference between the two as far as parameter passing is concerned.  As I understood from the review, the point of ref passing is to elide copies.  Because this is allowed as an optimization only, none of what it does should spill out into user code.  If people notice then something has gone wrong in the implementation.
October 05, 2020
On Monday, 5 October 2020 at 09:49:21 UTC, Walter Bright wrote:
> On 10/4/2020 11:37 PM, Iain Buclaw wrote:
>> I don't think __restrict__ is a good way to reason with expected behaviour.  The spec only makes three things clear: No clobber; No escape; Copy elision if possible.
>> 
>> In is not ref, and is not restrict.  In is in - a value goes in and doesn't come out.
>
> https://dlang.org/spec/function.html#parameters says:
>
> "in The parameter is an input to the function. Input parameters behaves as if they have the const scope storage classes. Input parameters may be passed by reference by the compiler. Unlike ref parameters, in parameters can bind to both lvalues and rvalues (such as literals). Types that would trigger a side effect if passed by value (such as types with postblit, copy constructor, or destructor), and types which cannot be copied, e.g. if their copy constructor is marked as @disable, will always be passed by reference. Dynamic arrays, classes, associative arrays, function pointers, and delegates will always be passed by value, to allow for covariance. If the type of the parameter does not fall in one of those categories, whether or not it is passed by reference is implementation defined, and the backend is free to choose the method that will best fit the ABI of the platform."
>
> The salient points are "may be passed by reference", and "whether or not it is passed by reference is implementation defined". The trouble with passing by reference is when there are other live mutable references to the same memory object. Whether mutating through those references mutates the in argument is "implementation defined".
>
> That's the problem with `in`. It's not an issue of a shortcoming in DMD.

I think we can all agree that the wording needs to be improved.

Regarding the last sentence (If the type does not fall in one of those categories...), if there's no explicit saying so, I take that to mean do nothing unless you can guarantee that there'd be no change in program behaviour.  Which may as well be as good as being equal to do nothing.

There's no advantage to passing the remaining types not explicitly named in the spec using ref semantics anyway.  "In" parameters could be forced in memory, but again with a dubious benefit of doing so.
October 05, 2020
On 10/5/20 4:42 AM, Mathias LANG wrote:
> On Monday, 5 October 2020 at 08:34:37 UTC, Iain Buclaw wrote:
>> On Monday, 5 October 2020 at 07:55:57 UTC, Walter Bright wrote:
>>> On 10/4/2020 7:19 AM, Iain Buclaw wrote:
>>>> I've also skimmed past a passing concern that the ABI between compilers would be different.  Well, let me rest assure you that DMD, GDC and LDC have never been compatible in the first place, so there's no point worrying about that now.
>>>
>>> The problem is not dmd compiled code calling gdc/ldc compiled code. The problem is code that works with one compiler fails with another, because the "to ref or not to ref" decision is *implementation defined*.
>>
>> Furthermore, NRVO is implementation defined, which results in the same dmd/gdc/ldc making different decisions for "to ref or not to ref", and yet I see no one complaining about that.
> 
> I cannot resist the bait: https://issues.dlang.org/show_bug.cgi?id=20752

So now it's up to us to decide whether we want less of that or more of that.
October 05, 2020
On Monday, 5 October 2020 at 12:04:51 UTC, Andrei Alexandrescu wrote:
>
> So now it's up to us to decide whether we want less of that or more of that.

There are a number of NRVO tests that I've fixed up in the testsuite because one type that is passed in memory on x86_64 is passed in registers on SPARC64.  Or the DMD x86_64 ABI was at the time incomplete and wrongly passed a certain type in memory. :-)
October 05, 2020
On Monday, 5 October 2020 at 07:56:00 UTC, Iain Buclaw wrote:
> Correct me if I'm wrong, but it looks like we'll have three competing implementations:
>
> DMD: `const scope`, with `ref` applied on types usually passed in memory.
> LDC: `const scope ref @restrict`
> GDC: `const scope` with `ref` applied on types usually passed by invisible reference.

Non-PODs are always by-ref, so wrt. PODs only:

DMD: Currently (2.094) `ref` for all types > 2 machine words, with a little exception for x87 `real` on Win64 (ref). To be improved.

LDC: With https://github.com/ldc-developers/ldc/pull/3578, `ref` for all types which would be passed by invisible ref (Win64, AArch64), or passed on the stack and larger than 2 machine words (Posix x86_64, 32-bit x86). For all other ABIs (I'm not really familiar with): `ref` if larger than 2 machine words.

No `@restrict` (IIUC, the LLVM semantics are more strict and wouldn't allow the same arg to be passed as 2 `@restrict` refs, even if both are const).

---

Wrt. the concerns about differing ref/value decisions for PODs across compilers/platforms and thus implementation-dependent potential aliasing issues for lvalue args: a possible approach could be leaving everything as-is ABI-wise, but have the compiler create and pass a temporary in @safe callers if the callee takes a ref, unless it can prove there's no way the arg can be aliased. E.g., assuming x87 `real` for Win64:

void callee(in real x); // e.g., by-ref for Win64, by-value for Posix x86_64

void safeCaller1(ref x) @safe
{
    callee(x); // x might be aliased by global state
    // for Win64:    auto tmp = x, callee(tmp);
    // Posix x86_64: by-value, so simply `callee(x)`
}

void safeCaller2() @safe
{
    real x = 1;
    callee(x); // x cannot be aliased, fine to pass directly by-ref for Win64
}

void safeCaller3(in real x) @safe
{
    callee(x); // safe to forward directly by-ref for Win64
}
October 05, 2020
On Monday, 5 October 2020 at 13:27:00 UTC, kinke wrote:
>
> Wrt. the concerns about differing ref/value decisions for PODs across compilers/platforms and thus implementation-dependent potential aliasing issues for lvalue args: a possible approach could be leaving everything as-is ABI-wise, but have the compiler create and pass a temporary in @safe callers if the callee takes a ref, unless it can prove there's no way the arg can be aliased. E.g., assuming x87 `real` for Win64:
>
> void callee(in real x); // e.g., by-ref for Win64, by-value for Posix x86_64
>
> void safeCaller1(ref x) @safe
> {
>     callee(x); // x might be aliased by global state
>     // for Win64:    auto tmp = x, callee(tmp);
>     // Posix x86_64: by-value, so simply `callee(x)`
> }

So then `in` would come with its own semantic, that requires new code to handle, rather than piggy-backing off of `ref`?
October 05, 2020
On 10/5/2020 4:52 AM, Iain Buclaw wrote:
> I think we can all agree that the wording needs to be improved.
> 
> Regarding the last sentence (If the type does not fall in one of those categories...), if there's no explicit saying so, I take that to mean do nothing unless you can guarantee that there'd be no change in program behaviour.  Which may as well be as good as being equal to do nothing.
> 
> There's no advantage to passing the remaining types not explicitly named in the spec using ref semantics anyway.  "In" parameters could be forced in memory, but again with a dubious benefit of doing so.

POD types that "wrap" a basic type need to work in the ABI like the basic type. An obvious example is using:

    struct Array { size_t length; void* ptr; }

to match dynamic arrays. Another is:

    T, struct S { T t; }, T[1]

should all pass the same way.
October 05, 2020
On 10/5/2020 12:56 AM, Iain Buclaw wrote:
> Actually, I think there is zero mention of aliasing in the language spec, so the following can only be interpreted as being valid and precisely defined to work in D.
> ---
> float f = 1.0;
> bool *bptr = cast(bool*)&f;
> bptr[2] = false;
> assert(f == 0.5);
> ---

@safe code won't allow such a cast. In @system code, the result will depend on the layout of the memory, more specifically big/little endianness. I think we can agree that the `in` semantics cannot be restricted to @system code only.


> If this gets addressed, then we can use aliasing rules as a measure for how we treat -preview=in.  If you are interested in defining some aliasing rules for D, I'd be more than happy to spin off a new thread to discuss them, and I will implement that in GDC and report back the success/failures of applying such rules. :-)

The plan for aliasing rules is @live.


> Correct me if I'm wrong, but it looks like we'll have three competing implementations:
> 
> DMD: `const scope`, with `ref` applied on types usually passed in memory.
> LDC: `const scope ref @restrict`
> GDC: `const scope` with `ref` applied on types usually passed by invisible reference.

The problems come from:

1. the user not knowing if `in` is passing by ref or not

2. being "implementation defined" meaning that the user simply cannot know (1) because it can change from version to version, or with changes in compiler switch settings. Not just in switching from one compiler to another (although that's bad enough)

3. the user would have to look at the disassembly to determine (1) or not, and this is unreasonable

4. if the function is a template with `in T t` as a parameter, the user cannot know if `t` is passed by ref or not