April 30, 2014
On Wednesday, 30 April 2014 at 21:17:23 UTC, H. S. Teoh via Digitalmars-d wrote:
> On Wed, Apr 30, 2014 at 02:13:32PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:
>> On 4/30/14, 2:09 PM, Timon Gehr wrote:
>> >On 04/30/2014 10:58 PM, Andrei Alexandrescu wrote:
>> >>On 4/30/14, 1:56 PM, Timon Gehr wrote:
>> >>>
>> >>>struct S{
>> >>>     ~this(){ /* ... */ }
>> >>>     /* ... */
>> >>>}
>> >>>
>> >>>class C{
>> >>>     S s;
>> >>>}
>> >>>
>> >>>?
>> >>
>> >>By hand, as I mentioned. -- Andrei
>> >>
>> >
>> >I meant, is it going to be deprecated too?
>> 
>> No, that will continue to be allowed. -- Andrei
>
> Then what is it going to do? S.~this will never get called?
>
It gets called when the gc runs.

>
> T

April 30, 2014
On 4/30/14, 2:15 PM, H. S. Teoh via Digitalmars-d wrote:
> On Wed, Apr 30, 2014 at 02:13:32PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:
>> On 4/30/14, 2:09 PM, Timon Gehr wrote:
>>> On 04/30/2014 10:58 PM, Andrei Alexandrescu wrote:
>>>> On 4/30/14, 1:56 PM, Timon Gehr wrote:
>>>>>
>>>>> struct S{
>>>>>      ~this(){ /* ... */ }
>>>>>      /* ... */
>>>>> }
>>>>>
>>>>> class C{
>>>>>      S s;
>>>>> }
>>>>>
>>>>> ?
>>>>
>>>> By hand, as I mentioned. -- Andrei
>>>>
>>>
>>> I meant, is it going to be deprecated too?
>>
>> No, that will continue to be allowed. -- Andrei
>
> Then what is it going to do? S.~this will never get called?

That is correct. -- Andrei

April 30, 2014
On 4/30/14, 2:24 PM, deadalnix wrote:
> On Wednesday, 30 April 2014 at 20:57:26 UTC, Andrei Alexandrescu wrote:
>> I don't remember a proposal being made that made slices of structs
>> with destructors distinct from other slices.
>
> So the RC slice thing is what make all that worthwhile ?

I can't discuss this as I'm not sure what past discussions it refers.

>>> Also, RC is good on top of GC, so
>>> you can collect loop, especially if we are going to RC automagically.
>>> That is a major issue.
>>
>> That stays, and it's useful. I'm talking destructors here.
>>
>
> Then the RCSlice do not provide any guarantee at all.

The guarantee it provides is once the refcount goes down to 0, elements will be destroyed.

>> We need to improve the language to allow for such. Did I mention it's
>> not going to be easy?
>>
>
> If I understand what you mentioned, RCSlice is what make to you the
> ditching of destructor worthwhile.

No, they are somewhat loosely related. Destructors are not called for arrays of struct _today_, so in that regard we're looking at improving a preexisting issue.

> The fact that this is not doable in
> current D should be a showstopper for this whole thread as all that is
> discussed here is dependent of what solution we choose for this problem
> (right now we have none).

This thread is a good place to discuss language improvements that make RCSlice possible.


Andrei

April 30, 2014
On 4/30/14, 2:26 PM, sclytrack wrote:
> It gets called when the gc runs.

No. No calls whatsoever while the gc runs. GC is notoriously bad for handling non-memory resources, and I see no reason to continue attempting it to do so after so much evidence to the contrary has been, um, collected :o).

Andrei

April 30, 2014
On Wednesday, 30 April 2014 at 20:57:26 UTC, Andrei Alexandrescu wrote:
>> Finally, immutable is sharable accross thread. That mean, even if we
>> bypass the type system, that RC must be atomic for immutable.
>> As they
>> convert automatically for co,st, that mean that all const code will be
>> full of atomic increment/decrement. They are bad for the CPU, and cannot
>> be optimized away.
>
> Good point. I see that as a problem, albeit a solvable one.

How? Having lock; instructions implicitly appearing in normal looking slice code is unacceptable.
April 30, 2014
On 4/30/14, 2:47 PM, John Colvin wrote:
> On Wednesday, 30 April 2014 at 20:57:26 UTC, Andrei Alexandrescu wrote:
>>> Finally, immutable is sharable accross thread. That mean, even if we
>>> bypass the type system, that RC must be atomic for immutable.
>>> As they
>>> convert automatically for co,st, that mean that all const code will be
>>> full of atomic increment/decrement. They are bad for the CPU, and cannot
>>> be optimized away.
>>
>> Good point. I see that as a problem, albeit a solvable one.
>
> How? Having lock; instructions implicitly appearing in normal looking
> slice code is unacceptable.

I'm thinking e.g. non-interlocked refcounts go like 1, 3, 5, ... and interlocked refcounts go like 2, 4, 6, ...

Then you do an unprotected read of the refcount. If it's odd, then it's impossible to having originated as an interlocked one. So proceed with simple increment. If it's even, do an interlocked increment.


Andrei

April 30, 2014
On Wednesday, 30 April 2014 at 21:51:17 UTC, Andrei Alexandrescu wrote:
> On 4/30/14, 2:47 PM, John Colvin wrote:
>> On Wednesday, 30 April 2014 at 20:57:26 UTC, Andrei Alexandrescu wrote:
>>>> Finally, immutable is sharable accross thread. That mean, even if we
>>>> bypass the type system, that RC must be atomic for immutable.
>>>> As they
>>>> convert automatically for co,st, that mean that all const code will be
>>>> full of atomic increment/decrement. They are bad for the CPU, and cannot
>>>> be optimized away.
>>>
>>> Good point. I see that as a problem, albeit a solvable one.
>>
>> How? Having lock; instructions implicitly appearing in normal looking
>> slice code is unacceptable.
>
> I'm thinking e.g. non-interlocked refcounts go like 1, 3, 5, ... and interlocked refcounts go like 2, 4, 6, ...
>
> Then you do an unprotected read of the refcount. If it's odd, then it's impossible to having originated as an interlocked one. So proceed with simple increment. If it's even, do an interlocked increment.
>
>
> Andrei

I don't think I fully understand.

Either all RC changes for a given type need to be atomic or none do, and that information is given by the type (everything that is immutable/const/shared). I don't see any feasible way of escaping this, or any advantage to a runtime convention like the odd/even trick above.
April 30, 2014
On 04/30/2014 10:57 PM, Andrei Alexandrescu wrote:
>>
>>
>> RCSlice!(const T) has nothing to do with const(RCSlice!T) as far as the
>> compiler is concerned.
> ...

There's always this hack:

struct RCSlice(T){
    static if(!is(T==const))
        RCSlice!(const(StripImmutable!T)) upcast(){
            return cast(typeof(return))this;
        }
    alias upcast this;
    // ...
}

> We need to improve the language to allow for such. Did I mention it's
> not going to be easy?

Well, actually an easy way would be to have the compiler infer safe coercions.

(A 'coercion' here is meant to denote a no-op implicit conversion, for example, conversion to const.)

It would be enabled with an attribute on the template. Coercions between different instantiations of the same templated type would be safe and allowed if the memory layout does not change between them and the compiler can prove the coercion safe field-wise. There needn't be restrictions on non-field members. Types annotated like this would get the top-level mutability specifier stripped off if possible, during IFTI / when cast().

In particular, this would work:

class C{}
class D:C{}

@infer_coercions
struct Slice(T){
    size_t length;
    T* ptr;
}

void foo(T)(T arg){ /* ... */ }

void main()@safe{
    auto a=[new D,new D];
    auto s=Slice!D(a.length,a.ptr);
    Slice!(const(C)) sc=s; // ok, D* coercible to const(C)*
    const(Slice!C) cs=s; // ok, D* coercible to n-m-ind const(C*)
    sc=cs; // ok, D* is coercible to n-m-ind const(C)*
    // (length works because const(size_t) and size_t can be
    //  freely corced between each other.)

    foo(cs); // actually calls foo!(Slice!(const(C))),

    // ==> no more magic in T[]
}

I.e. slices can be implemented in the library under this proposal.
(The above just forwards the magical behaviour of T* to a user-defined type.)

I think this fixes the issue in general, for example, for ranges:

const rng1 = [1,2,3].map!(a=>2*a);
const rng2 = rng1.filter!(a=>!!(a&1)); // ok



Probably this should be slightly generalized, eg:

@infer_coercions
struct S(T){
    T field;
}

void foo(T)(T arg){}

void main(){
    S!(T[]) s;
    foo(s); // foo!(S!(const(T)[]))
}

However, then, whether to do const(S!T) => S!(const(T)) or const(S!T) => S!(TailConst!T) should maybe be specified on a per-parameter basis, because this is in general not easy to figure out for the compiler.


April 30, 2014
On 05/01/2014 12:24 AM, Timon Gehr wrote:
>
> I think this fixes the issue in general, for example, for ranges:
>
> const rng1 = [1,2,3].map!(a=>2*a);
> const rng2 = rng1.filter!(a=>!!(a&1)); // ok
>
>
>
> Probably this should be slightly generalized, eg:
> ...

(The generalization is actually needed for the range example to work.)

April 30, 2014
On Wednesday, 30 April 2014 at 22:08:23 UTC, John Colvin wrote:
> I don't think I fully understand.
>
> Either all RC changes for a given type need to be atomic or none do, and that information is given by the type (everything that is immutable/const/shared). I don't see any feasible way of escaping this, or any advantage to a runtime convention like the odd/even trick above.

If you CPU is decent you have some cache coherency protocol in place. This won't ensure that thing appears sequentially consistent, but you don't care here.

You can proceed as follow in pseudo assembly :

count = load count_addr
need_atomic = count & 0x01
brtr atomic
count = count + 2
store count count_addr
br follow_up

atomic:
atomic_add count_addr 2

follow_up:
// Code after increment goes here

Note that is working as count may not be the correct number in case of sharing, but will always have the same parity, so even reading the wrong value will make you branch properly and the value of count is not used to increment in the atomic block.

I'm not happy with this solution, because:
 - You still have an atomic in there, and the compiler can't remove it. This reduce greatly the capability of the compiler to optimize. For instance, the compiler cannot optimize away redundant pairs of increment/decrement.
 - You have a branch in there. Atomic are expensive, but branch as well. Especially since both are storing (and one atomically), so it can't be speculated.
 - If we start using that all over the place, the codegen will ends up being quite fat. That means less friendly cache behavior.

That odd/even solution surely works, but ultimately do not solve the issue: if you want full speed, you'll have to provide both a const and a mutable version of the code, which defeat the purpose of const. Note that the exact same issue exists with inout.