November 08, 2021
On Monday, 8 November 2021 at 21:42:12 UTC, Andrei Alexandrescu wrote:
> 1. We could not make reference counting work properly in pure functions (a pure function does not mutate data, which goes against manipulating the reference count).

pure functions can mutate data passed to them.  And for a struct method (such as a copy constructor) this includes the struct's fields.
November 08, 2021
On Monday, 8 November 2021 at 22:07:46 UTC, Adam Ruppe wrote:
> On Monday, 8 November 2021 at 21:42:12 UTC, Andrei Alexandrescu wrote:
>> - work with qualifiers like T[] does, both RCSlice!(qual T) and qual(RCSlice!T)
>
> I think this is an incorrect requirement. A const(T) is not allowed to increase a reference count by definition.
>
> It in theory might still allow borrowing a reference, since there'd be no net change in reference count there and thus the data can remain immutable, but if isn't statically guaranteed to be balanced, you're directly in contradiction with the const rule.

You can do head-const with delegates.  immutable will be a problem, though.
November 08, 2021
On Mon, Nov 08, 2021 at 10:15:09PM +0000, deadalnix via Digitalmars-d wrote: [...]
> I think however, you missed several massive problems:
> 4. Type qualifier transitivity. const RCSlice!T -> const
> RCSlice!(const T) conversion needs to happen transparently.

The only way this can happen is via a language change.  The only way arrays get to enjoy such benefits is because the necessary implicit conversion rules are baked into the language.  User-defined types do not have such privileges, and there is currently no combination of language constructs that can express such a thing.


> 5. Head mutability. const RCSlice!(const T) -> RCSlice!(const T)
> conversion needs to happen transparently.
[...]

Ditto.

Basically, for something to be ref-counted, you need:

1) To attach a counter to the type that remains mutable in spite of the payload being possibly const or immutable.

2) Due to D's mutable/immutable implicit conversion to const, for a ref-counted type to avoid being cumbersome to use (e.g., pass mutable RefCounted!T to a function receiving RefCounted!(const T) without needing to cast / copy / any of various workarounds), a user-defined type needs to be able to declare implicit conversions of this sort. Current language rules do not permit this.


T

-- 
Fact is stranger than fiction.
November 09, 2021
On 09.11.21 01:12, H. S. Teoh wrote:
> On Mon, Nov 08, 2021 at 10:15:09PM +0000, deadalnix via Digitalmars-d wrote:
> [...]
>> I think however, you missed several massive problems:
>> 4. Type qualifier transitivity. const RCSlice!T -> const
>> RCSlice!(const T) conversion needs to happen transparently.
> 
> The only way this can happen is via a language change.  The only way
> arrays get to enjoy such benefits is because the necessary implicit
> conversion rules are baked into the language.  User-defined types do not
> have such privileges, and there is currently no combination of language
> constructs that can express such a thing.
> 
> 
>> 5. Head mutability. const RCSlice!(const T) -> RCSlice!(const T)
>> conversion needs to happen transparently.
> [...]
> 
> Ditto.
> 
> Basically, for something to be ref-counted, you need:
> 
> 1) To attach a counter to the type that remains mutable in spite of the
> payload being possibly const or immutable.
> ...

And to do that, you have to explicitly specify the semantics of qualifiers based on a set of allowed rewrites, and there needs to be a way to ensure low-level bookkeeping does not get elided. This was the __mutable proposal.
November 09, 2021
On Monday, 8 November 2021 at 22:38:27 UTC, Andrei Alexandrescu wrote:
>> I believe 4 and 5 to be impossible in D right now, 6 can be solved using a ton of runtime checks.
>
> To which I say, stop posting, start coding.

As I said, I believe 4 and 5 to be impossible at the moment.

In fact, I forgot but there is one more unsolvable problem if you want the whole thing to be safe: there is no way to prevent what's owned the the RCSlice from escaping.

There is a reason why, even though the value is clearly there, this endeavor has been on table table for 10 years+ and no real progress has been made. You can't just build on quicksand.

If I'm to be honest, I've wasted enough of my time trying to get any kind of progress on this front. I've written DIP, I've explains how other proposal won't fix the problem, it all proved to be a giant waste of time. I'd be thrilled to be able to contribute to improve this, but [this message from Walter](https://forum.dlang.org/post/slqbqm$2us2$1@digitalmars.com) is a string indicator that not much has changed and it would be a waste of my time.

At this point, short of some kind of intervention happening, I don't really see any significant progress being made on any of this.

So yes, I'm coding, every day. But not that, because I know the foundation required just aren't there, and I don't have the leverage to have anything move on the foundation front.
November 09, 2021
On Monday, 8 November 2021 at 22:38:27 UTC, Andrei Alexandrescu wrote:
>> I don't think this one is a real problem, as one can cast a function call to pure and go through this. Dirty as hell, but done properly it should work even in the face of compiler optimization based on purity. GCC or LLVM would have no problem optimizing this scafolding away.
>
> If you cast to pure then same compilers will be within their rights to optimize away calls - because they're pure.
>

I think you might be right here, unless one goes through some serious stunt by returning some kind of result that you'd use so the compiler cannot optimize it away.
November 09, 2021
On Tuesday, 9 November 2021 at 00:44:02 UTC, deadalnix wrote:
> 
> So yes, I'm coding, every day. But not that, because I know the foundation required just aren't there, and I don't have the leverage to have anything move on the foundation front.

Do you mean the technical foundation within the compiler to make the required analysis possible or something else?
November 09, 2021
On Monday, 8 November 2021 at 22:40:03 UTC, Andrei Alexandrescu wrote:
> On 2021-11-08 17:26, rikki cattermole wrote:
>> a reference counted struct should never be const
>
> So then we're toast. If that's the case we're looking at the most important challenge to the D language right now.

Not necessarily, but this is in fact the same problem as the head mutable problem.

If `const RCSlice!(const T)` can convert to `RCSlice!(const T)`, which it should to have the same semantic as slice.

The reference counting problem goes away if you can mutate the head.
November 09, 2021
On Monday, 8 November 2021 at 22:38:27 UTC, Andrei Alexandrescu wrote:
>> shared_ptr does atomic operation all the time. The reality is that on modern machines, atomic operation are cheap *granted there is no contention*. It will certainly limit what the optimizer can do, but all in all, it's almost certainly better than keeping the info around and doing a runtime check.
>
> In my measurements uncontested atomic increment are 2.5x or more slower than the equivalent increment.
>

Do you mind sharing this?

I find that curious, because load/stores on x86 are almost sequentially consistent by default, and you don't even need sequential consistency to increment the counter to begin with, so a good old `inc` instruction is enough.

I'd look to look at what's the compiler is doing here, because maybe we are trying to fix the wrong problem.
November 09, 2021

On Monday, 8 November 2021 at 21:42:12 UTC, Andrei Alexandrescu wrote:

>

Eduard Staniloiu (at the time a student I was mentoring) tried really hard to define a built-in slice RCSlice(T) from the following spec:

Your spec is very focused on homogenizing the API for GC and RC slices as much as (or rather, more than) possible.

But, it isn't possible to make a truly @safe, general-purpose RC slice in D at all right now, even without all the additional constraints that you are placing on the problem.

Borrowing is required for a general-purpose RC type, so that the payload can actually be used without a reference to the payload escaping outside the lifetime of the counting reference. But, the effective lifetime of the counting reference is not visible to the scope borrow checker, because at any point the reference's destructor may be manually called, potentially freeing the payload while there is still an extant borrowed reference.

With current language semantics, the destructor (and any other similar operations, such as reassignment) of the reference type must be @system to prevent misuse of the destructor in @safe code.
https://issues.dlang.org/show_bug.cgi?id=21981

The solution to this problem is to introduce some way of telling the compiler, "this destructor is @safe to call automatically at the end of the object's scope, but @system to call early or manually."

Also, the DIP1000 implementation is very buggy and incomplete; it doesn't work right for several kinds of indirections, including slices and class objects:
https://forum.dlang.org/thread/zsjqqeftthxtfkytrnwp@forum.dlang.org?page=1

>
  • work in pure code just like T[] does
  • work with qualifiers like T[] does, both RCSlice!(qual T) and qual(RCSlice!T)

I wrote a semi-@safe reference counting system for D recently, which includes slices, weak references, shared references with atomic counting, etc. It works well enough to be useful to me (way better than manual malloc and free), but not well enough to be a candidate for the standard library due to the above compiler bugs.

RCSlice!(qual T) is no problem; the reference count and the payload do not need to use the same qualifiers. Whether the count is shared or not can be tracked statically as part of the RC type, so the "const might be immutable, and therefore have a shared reference count, and therefore require expensive atomic operations" issue that you raise is easy enough to solve.

On the other hand, pure compatibility and a usable immutable(RCSlice!T) are mutually exclusive, I think:

Either the reference count is part of the target of the RC type's internal indirection, in which case it can be mutated in pure code, but is frozen by an outer, transitive immutable, or the reference count is conceptualized as an entry in a separate global data structure which can be located by using the address of the payload as a key, meaning that incrementing it does not mutate the target, but is impure.

I believe the former solution (compatible with pure, but not outer immutable) is preferable since it is the most honest, least weird solution, and therefore least likely to trip up either the compiler developers or the users somehow.

What you seem to be asking for instead is a way to trick the type system into agreeing that mutating a reference count doesn't actually mutate anything, which is nonsense. If that's really necessary for some reason, it needs to be special cased into the language spec, like how pure explicitly permits memory allocation.