December 16, 2022

On Friday, 16 December 2022 at 04:54:21 UTC, Siarhei Siamashka wrote:

>

Another problem is illustrated by the example below:

T enforce(string msg, T)(T cond) {
    if (!cond) {
        static immutable e = new Exception(msg);
        throw e;
    }
    return cond;
}

void main() {
    try {
        enforce!"trigger an exception"(1 == 2);
    } catch (Exception e) {
        assert(0, "if we reach here, then it's probably a compiler bug");
    } catch (immutable Exception e) {
        // the proper place to handle it is here
        assert(e.msg == "trigger an exception");
    }
}

If it's an immutable exception, then the compiler should probably only catch it as immutable?

This pull disallows throwing an immutable object:

https://github.com/dlang/dmd/pull/14706

You can still throw a const object though, which would work for your enforce.

December 16, 2022

On Friday, 16 December 2022 at 13:25:25 UTC, Nick Treleaven wrote:

>

On Friday, 16 December 2022 at 04:54:21 UTC, Siarhei Siamashka wrote:

>

Another problem is illustrated by the example below:

T enforce(string msg, T)(T cond) {
    if (!cond) {
        static immutable e = new Exception(msg);
        throw e;
    }
    return cond;
}

void main() {
    try {
        enforce!"trigger an exception"(1 == 2);
    } catch (Exception e) {
        assert(0, "if we reach here, then it's probably a compiler bug");
    } catch (immutable Exception e) {
        // the proper place to handle it is here
        assert(e.msg == "trigger an exception");
    }
}

If it's an immutable exception, then the compiler should probably only catch it as immutable?

This pull disallows throwing an immutable object:

https://github.com/dlang/dmd/pull/14706

You can still throw a const object though, which would work for your enforce.

Personally I think it should always just be implied const like:

catch (Exception e) should imply catch (const e) that way both mutable and immutable will work.

December 16, 2022

On Friday, 16 December 2022 at 13:56:19 UTC, bauss wrote:

>

Personally I think it should always just be implied const like:

catch (Exception e) should imply catch (const e) that way both mutable and immutable will work.

Shouldn't that be like in C++ where the it is recommended to use

catch(const MyException& e)

so in in D it would be

catch(const ref MyException e)
December 16, 2022

On Wednesday, 14 December 2022 at 08:01:47 UTC, cc wrote:

>

How do you instantiate one using malloc?

Just came up with this, seems to work:

class C
{
    int i;
}

void main()
{
    import std.experimental.allocator;
    import std.experimental.allocator.mallocator;

    TypedAllocator!Mallocator a;
    C c = a.make!C;
    c.i++;
    a.dispose(c);
}
December 16, 2022

On Friday, 16 December 2022 at 13:25:25 UTC, Nick Treleaven wrote:

>

This pull disallows throwing an immutable object:

https://github.com/dlang/dmd/pull/14706

You can still throw a const object though, which would work for your enforce.

Personally, I would prefer to have this resolved by:

  1. Actually placing immutable data in read only data sections. Why isn't the compiler doing it already?
  2. Having any immutable exception just fall through mutable catch blocks without matching any of them.

The modification of immutable exception data can be suppressed by defining a custom no-op Throwable.TraceInfo, something like this:

class EmptyTraceInfo : Throwable.TraceInfo {
    int opApply(scope int delegate(ref const(char[]))) const {
        return 0;
    }
    int opApply(scope int delegate(ref size_t, ref const(char[]))) const {
        return 0;
    }
    override string toString() const {
        return "sorry, no backtrace here";
    }
}

class ImmutableException : Exception {
    static immutable empty_trace_info = new EmptyTraceInfo;

    @nogc @trusted pure nothrow this(string msg, string file = __FILE__,
        size_t line = __LINE__, Throwable nextInChain = null)
    {
        super(msg, file, line, nextInChain);
        info = cast(Throwable.TraceInfo)empty_trace_info;
    }

    @nogc @trusted pure nothrow this(string msg, Throwable nextInChain,
        string file = __FILE__, size_t line = __LINE__)
    {
        super(msg, file, line, nextInChain);
        info = cast(Throwable.TraceInfo)empty_trace_info;
    }
}

T enforce(string msg, T)(T cond) {
    if (!cond) {
        static immutable e = new ImmutableException(msg);
        throw e;
    }
    return cond;
}

The default implementation is the source of GC allocations itself: https://github.com/dlang/dmd/blob/v2.101.1/druntime/src/rt/deh.d#L13-L21

This way immutable exceptions can work perfectly fine without any rogue GC allocations. But losing backtraces isn't nice and I'm trying to see what can be done. Maybe druntime can use its per-thread non-GC allocated storage for this: https://github.com/dlang/dmd/blob/v2.101.1/druntime/src/rt/dwarfeh.d#L146-L165 ?

Using const instead of immutable is just hiding the problem and I don't like this.

December 16, 2022

On Friday, 16 December 2022 at 15:02:55 UTC, Siarhei Siamashka wrote:

>

Using const instead of immutable is just hiding the problem and I don't like this.

Shouldn't the actual implementation of the exception handling be hidden as much as possible towards the programmer, at least on the receiving end. Exposing it too much might lead to that any change in the implementation might not be possible in the future.

When you catch an exception, is it then important for the programmer to know how the exception was thrown or can this be done auto magically under the hood?

December 16, 2022

On Friday, 16 December 2022 at 14:05:37 UTC, IGotD- wrote:

>

On Friday, 16 December 2022 at 13:56:19 UTC, bauss wrote:

>

Personally I think it should always just be implied const like:

catch (Exception e) should imply catch (const e) that way both mutable and immutable will work.

Shouldn't that be like in C++ where the it is recommended to use

catch(const MyException& e)

so in in D it would be

catch(const ref MyException e)

Classes are reference types in D, so I think the ref is implicit

December 16, 2022

On Friday, 16 December 2022 at 15:23:45 UTC, IGotD- wrote:

>

On Friday, 16 December 2022 at 15:02:55 UTC, Siarhei Siamashka wrote:

>

Using const instead of immutable is just hiding the problem and I don't like this.

Shouldn't the actual implementation of the exception handling be hidden as much as possible towards the programmer, at least on the receiving end. Exposing it too much might lead to that any change in the implementation might not be possible in the future.

Hiding the actual implementation is good, but hiding a memory corruption bug is bad. The code from https://forum.dlang.org/post/cmtaeuedmdwxjecpcrjh@forum.dlang.org can be successfully compiled by D compilers at least from GDC 9 and up to the most recent versions. But what actually happens is that the "immutable" data gets corrupted (the field 'info' is overwritten by the exception handling code from druntime) and also the catch block can receive it in a mutable form and modify it there or pass it around and later modify somewhere else. All of this despite the @safe attribute. Not to mention the GC allocations despite the @nogc attribute too.

It was known at least since https://issues.dlang.org/show_bug.cgi?id=12118 (mentioned in Nick Treleaven's pull request). Replacing "immutable" with "const" doesn't change anything on a fundamental level, the hidden corruption still remains there.

But I'm not happy about just disallowing to throw immutable exceptions instead of fixing them and without a good replacement ("const" is not a good replacement).

>

When you catch an exception, is it then important for the programmer to know how the exception was thrown or can this be done auto magically under the hood?

It's important to know that there are no hidden bugs.

December 16, 2022
On 12/16/2022 5:23 AM, Tejas wrote:
> It already is...

Good!

December 17, 2022
On Thursday, 15 December 2022 at 12:55:22 UTC, Ola Fosheim Grøstad wrote:
>
> Anyway, concurrent collectors that don't stop the world are not as bad if you add hardware capabilities that prevents the caches for being flushed and the data-bus from being saturated. You could have a separate hardware-cache for book-keeping tasks and just slowly scan memory in the background rather than the hit-and-run approach.

I see concurrent GC (or at least something that doesn't stop other threads) as the only choice. The more I look into the D garbage collector the more surprised I become.

In order to get tracing GC to work you need to.

1. Scan the stack from the stack pointer and upwards.
2. Scan all the registers, making the algorithm non portable.
3. Scan all the globally allocated memory
4. Scan all the thread local storage. This usually live on the stack for the libraries that are loaded at startup. This information must be read out before or after the thread starts.
5. The threads that were interrupted, the context must be read out including the position of the stack pointer.

6. All this requires that the D runtime must keep track on all threads, something that you otherwise don't need since the kernel/other base runtime do this for you.

7. You need metadata for the tracing graph. D uses typeinfo in order to reduce the scanning of objects, this is nice but at the same increases the complexity.

8. You need to have a functionality that suspends the execution, that is on all CPUs. In order to to that all the CPUs needs to be interrupted. This operation itself takes time as it needs to save the context, go through the interrupt service routine, probably do something in the scheduler, also needs to report back to the requesting CPU. The code in D seems to stop the threads one by one so this operation itself takes time, more if there are more threads running on other CPUs. After the GC is done with its operation, it's the same story again but resuming the threads. A "short GC pause" is really a relative term.

9. There is probably more that I don't know about that would surprise me, not in positive way I'm afraid.

I must give the D project the credit for having the stamina implementing such infrastructure in the standard library. I would have given up and looked elsewhere. Also, I'm surprised that the operating systems offer such particular interfaces for making this possible. It's not completely straight forward and suspending all threads require quite different approaches on each system. What if they didn't?

So the question if GC is built into the standard library is positive or negative, then my answer is that in the case of the D it certainly increase the complexity quite a lot. What if there was a simpler path?