May 19
On Friday, 19 May 2017 at 19:46:07 UTC, nkm1 wrote:
> As someone who iis interested in @nogc (more precisely: in avoiding GC pauses), I like this proposal... But it looks like people are concerned about 'new' becoming contextual keyword (that in some contexts it allocates with GC and in others it does something else). So maybe different syntax can be used? How about "throw scope Exception"? ("scope" already does various things :)

This syntax was already proposed and mostly rejected by the community. See the original thread listed in the DIP.

> But memory management is not only about allocations, it's also about deallocations! Manually deallocating stuff (like freeing exception objects) is error prone and bothersome.

That's why we have the GC.

> Refcounted exceptions seems to me a pretty good solution (and I think the general plan for D is to use more reference counting? At least, that's my impression...)

That was my impression too. However Walter has made it clear that @safe ref-counting of classes is not planned as he doesn't think it's possible. I'm unclear on the details of that, so you'll have to ask him.
May 19
On Friday, 19 May 2017 at 19:46:07 UTC, nkm1 wrote:
> I like this proposal... But it looks like people are concerned about 'new' becoming contextual keyword (that in some contexts it allocates with GC and in others it does something else)

It *already* does that. `new` can be overloaded and modified with `scope`.

http://dlang.org/spec/expression.html

"NewExpressions are used to allocate memory on the garbage collected heap (default) or using a class or struct specific allocator. "

"If a NewExpression is used as an initializer for a function local variable with scope storage class, and the ArgumentList to new is empty, then the instance is allocated on the stack rather than the heap or using the class specific allocator. "


I also believe the compiler *should* be free to optimize it to a different allocation scheme if it proves it safely can.
May 19
On Friday, 19 May 2017 at 21:24:51 UTC, Adam D. Ruppe wrote:

> "NewExpressions are used to allocate memory on the garbage collected heap (default) or using a class or struct specific allocator. "
>
> "If a NewExpression is used as an initializer for a function local variable with scope storage class, and the ArgumentList to new is empty, then the instance is allocated on the stack rather than the heap or using the class specific allocator. "

IMHO, this has to go. Having alignment control now, and with DIP1000 solving reference escaping, there's absolutely no need in this special syntax; stack-allocated classes are possible as library types.

But it may be beneficial to reconsider the 'new (AllocatorOpts)' syntax, with more thought on interaction with the type system. As in, it's not necessary to have type-specific allocation functions. But some sort of type system flag is required if we want to make the language aware of our allocation schemes. To expand on my previous reply, something like this comes to mind:

// Delcaring class/struct not supporting being allocated by a non-@nogc allocator:
class [(nogc)] ClassName [(template parameters)] { ... }
struct [(nogc)] StructName [(template parameters)] { ... }

// Declaring arrays:
T[][(nogc)] arr;

So it could look like this:

class Allocator
{
    void[] allocate(size_t, TypeInfo ti = null) @nogc { ... }
    void deallocate(void[]) @nogc { ... }
}

Allocator myAllocator = /* however is desired */;

class (nogc) MyClass
{
    int[] gcArray;
    int[] (nogc) nonGCArray;

    // note the ctor itself is not @nogc, since it allocates
    // gcArray, so interoperability is possible
    this()
    {
        gcArray = [1, 2]; // fine
        nonGCArray = [1, 2]; // error
        nonGCArray = new (myAllocator) int[2];
    }

    ~this()
    {
        myAllocator.dispose(nonGCArray);
    }
}

auto a = new MyClass; // error, no allocator provided, GC assumed, MyClass cannot be allocated by GC

auto b = new (myAllocator) MyClass; // fine
auto c = new (theAllocator) MyClass; // error, theAllocator is not @nogc

---

Not a very pretty syntax, but I can't think of a way of making it any prettier...

Bringing this back to exceptions, it should "just work":

class (nogc) NoGCException : Exception { ... }

throw new (myAllocator) Exception("Argh!");

//...

catch (NoGCException e) {
} // error, e is not rethrown or disposed

catch (NoGCException e) {
    myAllocator.dispose(e);
} // fine, e was disposed


This, however, means teaching the language a few extra library constructs. And of course, there's the danger of deallocating with the wrong allocator, but being careful comes with the territory as far as memory management is concerned.
May 20
On Friday, 19 May 2017 at 15:45:28 UTC, Mike Parker wrote:
> DIP 1008 is titled "Exceptions and @nogc".
>
> https://github.com/dlang/DIPs/blob/master/DIPs/DIP1008.md
>
> All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on June 2 (3:59 AM GMT June 3), or when I make a post declaring it complete.
>
> At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round. Otherwise, it will be queued for the formal review and evaluation by the language authors.
>
> Extensive discussion of this DIP has already taken place in two threads, both linked from the document. You may find it beneficial to skim through those threads before posting any feedback here.
>
> Thanks in advance to all who participate.
>
> Destroy!

Like others, I do not like the special-casing of `throw new`. However, several designs have already been explored and found lacking. This proposal also has the advantage that it (hopefully) doesn't break existing code and all existing code gets the benefit for free. I think this benefit has been hugely underestimated; we must consider that large swathes of code that were previously non-@nogc due to exception allocation can now be *inferred automatically* to be @nogc. That's a huge advantage and may be worth the special case.

Because of the transitive nature of attributes, even one function in the call graph that is non-@nogc "paints" connecting nodes which in turn paint *their* connecting nodes, etc.
May 19
On Friday, May 19, 2017 3:45:28 PM PDT Mike Parker via Digitalmars-d wrote:
> DIP 1008 is titled "Exceptions and @nogc".
>
> https://github.com/dlang/DIPs/blob/master/DIPs/DIP1008.md
>
> All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on June 2 (3:59 AM GMT June 3), or when I make a post declaring it complete.
>
> At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round. Otherwise, it will be queued for the formal review and evaluation by the language authors.
>
> Extensive discussion of this DIP has already taken place in two threads, both linked from the document. You may find it beneficial to skim through those threads before posting any feedback here.
>
> Thanks in advance to all who participate.

Overall, I think that this is a decent proposal for making exceptions @nogc
- and exceptions are definitely one of the few things that prevents code
that
really should be @nogc from being @nogc. However, I do have two objections:

1. I thought that we were going to come up with a general solution for ref-counted classes, not something for just exceptions. If that's the case, then this makes a lot less sense. However, that being said, if this solution can be transparently transitioned to a more general ref-counting solution, then this is probably a good stop-gap solution (and if we can't get ref-counted classes for some reason, then at least we'd have it for exceptions).

2. This really isn't going to fix the @nogc problem with exceptions without either seriously overhauling how exceptions are generated and printed or by having less informative error messages. The problem is with how exception messages are generated. They take a string, and that pretty much means that either they're given a string literal (which can be @nogc but does not allow for customizing the error message with stuff like what the bad input was), or they're given a constructed string (usually by using format) - and that can't be @nogc.

And you can't even create an alternate constructor to get around the problem. Everything relies on the msg member which is set by the constructor. Code that wants the message accesses msg directly, and when the exception is printed when it isn't caught, it's msg that is used. Not even overiding toString gets around the issue. For instance, this code

class E : Exception
{
    this(int i, string file = __FILE__, size_t line = __LINE__)
    {
        super("no message", file, line);
        _i = i;
    }

    override string toString()
    {
        import std.format;
        return format("The value was %s", _i);
    }

    int _i;
}

void main()
{
    throw new E(42);
}

prints

foo.E@foo.d(20): no message
----------------
??:? _Dmain [0xd0d473ce]
??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFNlZv
[0xd0d526db]
??:? scope void rt.dmain2._d_run_main(int, char**, extern (C) int
function(char[][])*).tryExec(scope void delegate()) [0xd0d52607]
??:? scope void rt.dmain2._d_run_main(int, char**, extern (C) int
function(char[][])*).runAll() [0xd0d52684]
??:? scope void rt.dmain2._d_run_main(int, char**, extern (C) int
function(char[][])*).tryExec(scope void delegate()) [0xd0d52607]
??:? _d_run_main [0xd0d52577]
??:? main [0xd0d5040b]
??:? __libc_start_main [0xf798a3f0]

toString wasn't used anywhere. toString would only be used if the exception were caught and printed, e.g.

void main()
{
    import std.stdio;
    writeln(new E(42));
}

would print

The value was 42

whereas without the toString override, it would print

foo.E@foo.d(15): no message

So, as is stands, we need to pass strings to exception constructors, and if we want those messsages to be informative, they cannot be string literals and thus have to be GC-allocated if you don't want to risk memory leaks or accessing memory that's no longer valid. And Walter's proposal does nothing to fix that, so even with his proposal, it's not going to work well to have code that throws exceptions be @nogc and have the error messages be useful.

What we would probably need would be to change msg is a function which generates a message so that derived classes can override that rather than passing a message string. Then at least the allocation of the string would just happen when the exception was printed. And if it's okay to make getting the exception @system, then it could even return a const(char)[] to a member variable (be it a static array or a string) and potentially avoid allocating altogether. Alternatively, if we're willing to have only one output range type work with it (so that the function can be virtual), then we could replace msg with a function that took an output range.

Regardless, with how exceptions are currently constructed, I don't think that Walter's proposal goes far enough to actually fix the problem with @nogc and exceptions, even if it does solve a critical piece of the problem.

- Jonathan M Davis

May 19
On 5/19/2017 6:23 PM, Meta wrote:
> Like others, I do not like the special-casing of `throw new`. However, several
> designs have already been explored and found lacking. This proposal also has the
> advantage that it (hopefully) doesn't break existing code and all existing code
> gets the benefit for free. I think this benefit has been hugely underestimated;
> we must consider that large swathes of code that were previously non-@nogc due
> to exception allocation can now be *inferred automatically* to be @nogc. That's
> a huge advantage and may be worth the special case.

We'll be doing more invisible special casing in the future. For example,

  void foo(scope string s);
  ...
  string s;
  ...
  foo(s ~ "abc");

This array concatenation does not need to be done with the GC. It's not fundamentally different from what the compiler does now with:

  s = "abc" ~ "def";

not doing a GC allocation, either. The compiler also detects when it can allocate a closure on the stack rather than on the GC. We expect the compiler to do such transformations.

In general, if the compiler can determine the lifetime of an allocation, and can control "leakage" of any references to that allocation, then it is fair game to not use the GC for it.

The recent 'scope' improvements have uncovered a lot more opportunities for this, and DIP 1008 is the first fruit of it.
May 19
On Friday, May 19, 2017 11:35:54 AM PDT H. S. Teoh via Digitalmars-d wrote:
> I agree with this, and having looked at std.experimental.allocator recently, I think Andrei may even have made certain design decisions with this in mind.  I think the ideal goal (I'm not sure how achievable it is) would be to make theAllocator part of *druntime*, upon which you can actually use array concatenations, AA's, etc., (almost) freely, and the user would be able to control exactly how allocations would work. At least in the current state of things, I don't see this as being particularly possible due to various issues, the main one being that code written with GC in mind generally does not track lifetimes, which is a big obstacle to successfully being able to just assign a different allocator to theAllocator and have things Just Work(tm).

Because of the issue of lifetimes, some language features simply cannot be implemented without the GC, and I think don't see any point in trying to make it so that you can use all features of D without the GC. That simply won't work. By the very nature of the language, completely avoiding the GC means completely avoiding some features.

D's dynamic arrays fundamentally require the GC, because they do not manage their own memory. They're just a pointer and a length and literally do not care what memory backs them. As long as all you're doing is slicing them and passing them around (i.e. restrict yourself to the range-based functions), then the GC is not involved, and doesn't need to be, but as soon as you concatenate or append, the GC has to be involved. For that not to be the case, dynamic arrays would have to manage their own memory (e.g. be ref-counted), which means that they could not be what they are now. A different data structure would be required.

Similarly, stuff like closures require the GC. They need something to manage their memory. They're designed to be automatic.

Honestly, I think that this push for @nogc and manual memory mangement is toxic. Yes, we should strive to not require the GC where reasonable, but some things simply are going to require the GC to work well, and avoiding the GC very quickly gives you a lot of the problems that you have with languages like C and C++.

For instance, at dconf, Atila talked about the D wrapper for excel that he wrote. He decided to use @nogc and std.exception.allocator, and not only did that make it much harder for him to come up with a good, workable design, it meant that he suddenly had to deal with memory corruption bugs that you simply never have with the GC. He felt like he was stuck programming in C++ again - only worse, because he had issues with valgrind that made it so that he couldn't effectively use it to locate his memory corruption problems.

The GC makes it far easier to write clean, memory-safe code. It is a _huge_ boon for us to have the GC. Yes, there are cases where you can't afford to use the GC, or you have to limit its use in order for your code to be as performant as it needs to be, but that's the exception, not the norm. And avoiding the GC comes at a real cost.

I have no problem whatsoever telling folks that some features of D require the GC and that while D's GC is optional, if you avoid it, you avoid certain features. There is no free lunch.

We do need to make sure that we do not accidentally or erroneously require the GC so that code can work with @nogc when folks require that if it's reasonable for that code to be @nogc. Where reasonable, allocators should be an option so that folks can decide whether to use the GC or not for a particular piece of code. But sometimes, it's just not reasonable to expect the same functionality when not using the GC as you get when using the GC.

And the reality of the matter is that using the GC has real benefits, and trying to avoid it comes at a real cost, much as a number of C++ progammers want to complain and deride as soon as they hear that D has a GC. And honestly, even having @nogc all over the place won't make many of them happy, because the GC is still in the language.

- Jonathan M Davis

May 20
On 5/19/2017 8:54 PM, Jonathan M Davis via Digitalmars-d wrote:
> And the reality of the matter is that using the GC has real benefits, and
> trying to avoid it comes at a real cost, much as a number of C++ progammers
> want to complain and deride as soon as they hear that D has a GC. And
> honestly, even having @nogc all over the place won't make many of them
> happy, because the GC is still in the language.

Also, have a GC makes CTFE real nice.

May 20
On Saturday, 20 May 2017 at 07:02:10 UTC, Walter Bright wrote:
>
> Also, have a GC makes CTFE real nice.

Having to implement a GC for newCTFE won't be nice though :o)
I agree though being able to allocate memory makes ctfe much more useful then it would otherwise be.
May 20
On Saturday, 20 May 2017 at 02:25:45 UTC, Walter Bright wrote:

> We'll be doing more invisible special casing in the future. For example,
>
>   void foo(scope string s);
>   ...
>   string s;
>   ...
>   foo(s ~ "abc");
>
> This array concatenation does not need to be done with the GC. It's not fundamentally different from what the compiler does now with:
>
>   s = "abc" ~ "def";
>
> not doing a GC allocation, either.

string s = callCAPIAndAllocateString();
foo(s ~ "abc");

What will happen? The compiler will generate different code? Won't compile? The former means invisible performance gap. The latter means an unpleasant surprise. Neither are good. Do we really need such special cases?

> The compiler also detects when it can allocate a closure on the stack rather than on the GC. We expect the compiler to do such transformations.

You're saying "detects" as if it always does that, which is not true. And this avoids the issue, not addresses it. We have *no* control over how the closure is allocated. It's either none, or GC. Which means we can't have dynamically-allocated closures in @nogc code. Which forces user to write verbose code (i.e. structs with opCall), losing the benefit of anonymous functions altogether.

> In general, if the compiler can determine the lifetime of an allocation, and can control "leakage" of any references to that allocation, then it is fair game to not use the GC for it.

*If*. And if it can't, it won't compile. Which is:
a) Frustrating when it should've detected it.
b) Makes you think of allocating things manually from the start, to not deal with sudden failure to compile.

> The recent 'scope' improvements have uncovered a lot more opportunities for this, and DIP 1008 is the first fruit of it.

That is true, benefits of 'scope' are indeed huge. But please, consider how fragile all the "special-casing" is. It's based on preconditions and assumptions made by the compiler, and is beyond user control. It hides potential maintenance problems.
1 2 3 4 5 6 7