November 12, 2021

On Friday, 12 November 2021 at 08:41:41 UTC, Dom DiSc wrote:

>

How is 1k bits "a lot" compared to 128 bytes (which is exactly the same amount)?!?

I thought he meant 1KiB with bit level addressing. :-P

November 12, 2021
On 12.11.21 02:34, rikki cattermole wrote:
> 
> I think I have summarized the main points that has been brought up in this thread. If I have forgotten something please add.
> 
> - ``const``/``immutable`` memory can be in read only memory, attempting to modify this will result in the termination of a program.

This is not an issue at all given an explicit escape hatch.

> - All ``extern(D)`` classes today cannot be in read only memory due the monitor field.

The monitor field should be removed.

> - Both ``const`` and ``immutable`` today provide guarantees that what memory is pointed to is readable. But it may not be writable and therefore cannot be written to.
> - Any escape hatch must acknowledge that memory lifetimes is not the same thing as the memory containing data of a given type.
> - For a value reference type, with the outer type being ``const`` is equivalent to just the template argument type being ``const``. ``struct Type(const T)`` is equivalent to ``const(Type!T)`` and ``const(Type!(const(T)))``.
> - Runtime checks are required for if the metadata or data storage is null.
> - Atomic operations are expensive, to lesson this the compiler should elide calls to reference counting methods with the help of something like ARC and scope.

`immutable` data should not be implicitly shared.

- We have to define a complete set of allowed equivalence transformations based on type qualifiers. This is necessary so that there is a specification of what @trusted code may do. It is a precondition for any escape hatch like __mutable/__metadata.
November 12, 2021
On Thursday, 11 November 2021 at 21:46:29 UTC, Andrei Alexandrescu wrote:
> I keep on thinking of proposing opFunCall() that is called whenever an object is passed by value into a function. The lowering for such objects would be:
>
> fun(obj);
>
> ===>
>
> fun(obj.opFunCall());

This has little to do with function calls. The same problem will exist for assignment for instance. Now you could also override opAssign, but you see where I'm going with that.

Not only does the head const needs to be dropped, but this needs to happen implicitly AND aggressively.

By implicitly, I meant hat the conversion doesn't require extra syntax, as in:
```d
const int[] a;
const(int)[] b = a; // OK
```

And by aggressively, I mean that it happens as soon as possible, not simply when a conversion is needed. For instance:
```d

void foo(T)(T t) {
    writeln(T.stringof);
}

void main() {
    const int[] a;
    foo(a); // Prints const(int)[]
}
```
November 12, 2021
On 2021-11-11 22:41, tsbockman wrote:
> On Thursday, 11 November 2021 at 23:17:53 UTC, Andrei Alexandrescu wrote:
>> On 2021-11-08 20:14, tsbockman wrote:
>>> 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."
>>
>> This I'm not worried about. `@trusted` should be fine in low-level code, no? `destroy` is not safe. What am I missing?
> 
> In the program below, if `Owner.__dtor` is `@system`, then `@safe` code like `main` can't instantiate `Owner`.
> 
> But, if `Owner.__dtor` is `@trusted`, then memory safety can be violated with a "use after free".
> 
> ```D
> module app;
> 
> struct Owner {
>      import core.stdc.stdlib : malloc, free;
> 
>      private int* x;
> 
>      this(const(int) x) @trusted {
>          this.x = cast(int*) malloc(int.sizeof);
>          *(this.x) = x;
>      }
>      inout(int*) borrow() return inout @safe {
>          return x; }
>      ~this() @trusted {
>          free(x);
>          x = null;
>      }
> }
> 
> void main() @safe {
>      import std.stdio : writeln;
> 
>      Owner owner = 1;
>      scope x = owner.borrow;
>      writeln(*x);
>      destroy!false(owner); // owner.__dtor(); // is also @safe
>      writeln(*x += 1); // use after free!
> }
> ```

Interesting, thanks. I submitted https://issues.dlang.org/show_bug.cgi?id=22507.
November 12, 2021
On Thu, Nov 11, 2021 at 06:17:53PM -0500, Andrei Alexandrescu via Digitalmars-d wrote:
> On 2021-11-08 20:14, tsbockman wrote:
[...]
> > **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 im`pure`.
> 
> Yah, my thoughts exactly. My money is on the latter option. The reference counter is metadata, not data, and should not be treated as part of the data even though implementation-wise it is.
> 
> Eduard and I stopped short of being able to formalize this.

This sounds like it's a good idea.  But how would that interact with `pure`?  Will it be just part of life that RC types are necessarily impure?


[...]
> > 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.
> 
> It's not nonsense. Or if it is a lot other things can be categorized as nonsense, such as the GC recycling memory of one type and presenting it as a different type etc.

As I said elsewhere, it's a different level of abstraction. The GC operates at a lower level of abstraction, and therefore does "dirty" things like casting to/from immutable, pointer arithmetic, etc.. But the interface it presents to user code forms a higher level of abstraction where these low-level manipulations are hidden away under the hood.


T

-- 
Give me some fresh salted fish, please.
November 12, 2021
On Thursday, 11 November 2021 at 23:17:53 UTC, Andrei Alexandrescu wrote:
> Walter would argue that his work on scope makes that possible too. The short of it is getting an answer to "can it be done" is important in and of itself because it gives us hints on what needs to be done.
>

But it cannot be done. This because scope do not compose, so as soon as there is one indirection, all information is gone.

November 12, 2021
On Friday, 12 November 2021 at 17:01:57 UTC, H. S. Teoh wrote:
> As I said elsewhere, it's a different level of abstraction. The GC operates at a lower level of abstraction, and therefore does "dirty" things like casting to/from immutable, pointer arithmetic, etc.. But the interface it presents to user code forms a higher level of abstraction where these low-level manipulations are hidden away under the hood.
>
>
> T

I think it is fair to expect any RC system to be able to operate at that same level.
November 12, 2021
On Fri, Nov 12, 2021 at 05:23:33PM +0000, deadalnix via Digitalmars-d wrote:
> On Friday, 12 November 2021 at 17:01:57 UTC, H. S. Teoh wrote:
> > As I said elsewhere, it's a different level of abstraction. The GC operates at a lower level of abstraction, and therefore does "dirty" things like casting to/from immutable, pointer arithmetic, etc.. But the interface it presents to user code forms a higher level of abstraction where these low-level manipulations are hidden away under the hood.
[...]
> I think it is fair to expect any RC system to be able to operate at that same level.

Yes.  So actually, this *could* be made to work if the RC payload is only allowed to be allocated from an RC allocator.  The allocator would allocate n+8 bytes, for example, and return a pointer to offset 8 of the allocated block, which is cast to whatever type/qualifier(s) needed. Offset 0 would be the reference count.  The copy ctor would access the reference count as *(ptr-8), which is technically outside the const/immutable/whatever payload.  When the ref count reaches 0, the allocator knows to deallocate the block starting from (ptr-8), which is the start of the actual allocation.


T

-- 
Tech-savvy: euphemism for nerdy.
November 12, 2021
On 12.11.21 18:44, H. S. Teoh wrote:
> Yes.  So actually, this*could*  be made to work if the RC payload is
> only allowed to be allocated from an RC allocator.  The allocator would
> allocate n+8 bytes, for example, and return a pointer to offset 8 of the
> allocated block, which is cast to whatever type/qualifier(s) needed.
> Offset 0 would be the reference count.  The copy ctor would access the
> reference count as *(ptr-8), which is technically outside the
> const/immutable/whatever payload.  When the ref count reaches 0, the
> allocator knows to deallocate the block starting from (ptr-8), which is
> the start of the actual allocation.

You still need language support. Reaching mutable data through an immutable pointer violates transitivity assumptions.
November 12, 2021

On Thursday, 11 November 2021 at 09:15:54 UTC, Atila Neves wrote:

> >

Who, using a SYSTEMS language, should not have a reason to care about their memory?

Me, ~99.9% of the time.

This explains a lot and why D is stuck with a miserable GC