November 11, 2021
On 2021-11-08 22:43, Paul Backus wrote:
> On Tuesday, 9 November 2021 at 01:14:24 UTC, tsbockman wrote:
>> 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 `free`ing 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."
> 
> I believe it is also possible to make this `@safe` by doing borrow checking at runtime, although it would introduce some overhead, and make the API less ergonomic.
> 
> Maybe the best compromise we can reach in the current language is to offer a `@safe` borrow-checked interface alongside an unchecked `@system` interface.

This is interesting. Maybe you could add a little detail to the bug report or to this thread?
November 11, 2021
On 2021-11-09 0:30, H. S. Teoh wrote:
> On Mon, Nov 08, 2021 at 10:38:59PM -0500, Steven Schveighoffer via Digitalmars-d wrote:
>> On 11/8/21 8:04 PM, deadalnix wrote:
>>> 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.
>>
>> The reference count cannot be in the head, it has to be in the block.
>> So this conversion is actually unsound.
> [...]
> 
> It could work if the block has a mutable counter at the top and the rest
> is immutable/const.  But the current type system cannot express such a
> thing; if the head is const, then it's const all the way down.

The mutable counter can be at a negative address that is not accessible to the "normal" type system. There's nothing wrong with having a piece of mutable data next to a piece of immutable data.

November 11, 2021

On Thursday, 11 November 2021 at 10:06:44 UTC, Ola Fosheim Grøstad wrote:

>

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

>

I have not yet encountered cases where it would be necessary that aren't "I'm implementing the standard library".

Not so sure about that. Arena allocators would be done with malloc, but you probably should wrap it with RAII.

Allocating directly from the OS (bypassing malloc) can in some cases be advantageous. But again, you are usually better off creating a RAII wrapper for it.

You also have dynamic stack allocation which can boost performance significantly. Sadly, Walter is against having this as a feature.

So use a different allocator with your vector/smart pointer/what have you.

November 12, 2021

On Thursday, 11 November 2021 at 16:09:46 UTC, Stanislav Blinov wrote:

>

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

>

I have not yet encountered...
I think that...
I don't think this is a problem.
I wouldn't care about it either.
Me, ~99.9% of the time. I definitely don't miss...

This all amounts to "I don't need it, therefore nobody should either". That's not a very good metric.

I don't think that's an accurate description of what I wrote.

>

It's 2021. Your footnote on "nobody" simply does not apply.

Agree to disagree.

> >

My algorithm:

  1. Write code
  2. Did I notice it being slow or too big? Go to 4.
  3. Move on to the next task.
  4. Profile and optimise, go to 2.

That's an interesting way of doing science. How would you notice anything without a baseline, which you haven't even set yet because you're not at (4)?

This is my opinion, not fact: if I didn't get to 4, then it doesn't matter (again, to me). I don't care if something runs in 1us or 1ms*. I start caring when it's >20ms, because that's when I start noticing it.

  • usually
November 12, 2021
I think that we just came to an agreement that the metadata stuff should be handled separately potentially using different memory.

I suggested this as an escape hatch with my custom reference type back in my second comment!

https://forum.dlang.org/post/smc9r2$7nh$1@digitalmars.com
November 12, 2021
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.
- All ``extern(D)`` classes today cannot be in read only memory due the monitor field.
- 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.
November 12, 2021
On Friday, 12 November 2021 at 01:34:05 UTC, rikki cattermole wrote:
> - Any escape hatch must acknowledge that memory lifetimes is not the same thing as the memory containing data of a given type.

I'm sorry, can you expound on this one a little more?  I don't follow.
November 12, 2021
On 12/11/2021 2:49 PM, Greg Strong wrote:
> On Friday, 12 November 2021 at 01:34:05 UTC, rikki cattermole wrote:
>> - Any escape hatch must acknowledge that memory lifetimes is not the same thing as the memory containing data of a given type.
> 
> I'm sorry, can you expound on this one a little more?  I don't follow.

So the reference count needs to be heap allocated.
The value of a given type must be heap allocated as well.

These two memory addresses need not be next to each other.

I.e.

struct ReferenceCount {
	shared(int) count;
}

struct Node(Type) {
	Type value;
}

struct Reference(Type) {
	private {
		Node* data;
		ReferenceCount* refCount;
	}
}

Rather than:

struct Reference(Type) {
	private	Storage!type* storage;
}

struct Storage(Type) {
	Type value;
	shared(int) refCount;
}

The idea is that the value may be in read only memory, but the reference cannot be due to requirement of mutation.
November 12, 2021

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".

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!
}
November 12, 2021

On Thursday, 11 November 2021 at 10:06:44 UTC, Ola Fosheim Grøstad wrote:

>

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

>

wrote code for a microcontroller once with 1k with addressable bits. Good times. Sorta.

That's a lot! As a teen I did a project on Motorola 6800. I think it was launched with a whopping 128 bytes of static RAM,

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