September 21, 2021
On Tuesday, 21 September 2021 at 20:47:41 UTC, H. S. Teoh wrote:
> Век живи - век учись. А дураком помрёшь.
:)

"Век живи  - век учись, всё равно дураком помрёшь."

is correct version. :)


September 22, 2021
On Tuesday, 21 September 2021 at 20:42:12 UTC, H. S. Teoh wrote:
> A sufficiently optimizing compiler may determine that since Main and stopper are independent, it is free to reorder the code such that the two lifetimes are independent, and therefore end up with the same situation as the first example.

In other words, compiler is trying to be smarter than a programmer :)
With a poor result...
But... it is **main** function, after all!
Maybe, main() should be an exception when performing
that 'smart' optimizations? ;)

Btw, is there any dmd option for turning all/some optimizations off?
Or some 'pragma/attribute'?

September 22, 2021

On Tuesday, 21 September 2021 at 20:28:33 UTC, jfondren wrote:

> >

Everything is Ok now,

I don't think this is reliably OK. If you're not using Stopper later in the function, and if there are no other references to it, then the GC can collect it. It just has no obligation to collect it, so minor differences like this might prevent that from happening for particular compilers/options/versions.

I saw a thread on this forum named
'Why are so many programmers do not like GC'
or something like that.

After this adventure I would add my 5 cents:
because (sometimes, ok) it behaves absolutely
unpredictable, depending on operation order,
"particular compilers/options/versions" etc.

September 22, 2021

On Wednesday, 22 September 2021 at 08:03:59 UTC, eugene wrote:

>

On Tuesday, 21 September 2021 at 20:28:33 UTC, jfondren wrote:

> >

Everything is Ok now,

I don't think this is reliably OK. If you're not using Stopper later in the function, and if there are no other references to it, then the GC can collect it. It just has no obligation to collect it, so minor differences like this might prevent that from happening for particular compilers/options/versions.

I saw a thread on this forum named
'Why are so many programmers do not like GC'
or something like that.

After this adventure I would add my 5 cents:
because (sometimes, ok) it behaves absolutely
unpredictable, depending on operation order,
"particular compilers/options/versions" etc.

Nondeterminism in heap collection is a very common complaint, but here we have data is that apparently on the stack that is collected nondeterministically. I can't say I like that.

September 22, 2021

On Wednesday, 22 September 2021 at 10:05:05 UTC, jfondren wrote:

>

Nondeterminism in heap collection is a very common complaint,

It is another kind of nondeterminism that is usually complained about
("sometime in the future GC will collect if it wants" or so)

>

but here we have data is that apparently on the stack that is collected nondeterministically. I can't say I like that.

Exactly, so we've finally come to an agreement, great! :)

September 22, 2021

On 9/21/21 4:17 PM, eugene wrote:

>

On Tuesday, 21 September 2021 at 19:42:48 UTC, jfondren wrote:

>

On Monday, 13 September 2021 at 17:18:30 UTC, eugene wrote:
There's nothing special about sg0 and sg1, except that they're part of Stopper. The Stopper in main() is collected before the end of main() because it's not used later in the function

Okay, but how could you explain this then

void main(string[] args) {

     auto Main = new Main();
     Main.run();

     auto stopper = new Stopper();
     stopper.run();

Here is what is happening. The compiler keeps track of how long it needs to keep stopper around.

In assembly, the new Stopper() call is a function which returns in a register.

On the very next instruction, you are calling the function stopper.run where it needs the value of the register (either pushed into an argument register, or put on the call stack, depending on the ABI). Either way, this is the last time in the function the value stopper is needed. Therefore, it does not store it on the stack frame of main. This is an optimization, but one that is taken even without optimizations enabled in some compilers. It's called dead store elimination.

Since the register is overwritten by subsequent function calls, there no longer exists a reference to stopper, and it gets collected (along with the members that are only referenced via stopper).

>

Now, change operation order in the main like this:

void main(string[] args) {

     auto Main = new Main();
     auto stopper = new Stopper();

     Main.run();
     stopper.run();
d-lang/edsm-in-d-simple-example-2 $ ./test | grep STOPPER
'STOPPER' registered 5 (esrc.Signal)
'STOPPER' registered 6 (esrc.Signal)
'STOPPER @ INIT' got 'M0' from 'SELF'
'STOPPER' enabled 5 (esrc.Signal)
'STOPPER' enabled 6 (esrc.Signal)

Everything is Ok now, stopper is not collected soon after start.
So the question is how this innocent looking change
can affect GC behavior so much?...

In this case, at the point you call Main.run, stopper is only in a register. Yet, it's needed later, so the compiler has no choice but to put stopper on the stack so it has access to it to call stopper.run. If it didn't, it's likely that Main.run will overwrite that register.

Once it's on the stack, the GC can see it for the full run of main. This is why this case is different.

Note that Java is even more aggressive, and might still collect it, because it could legitimately set stopper to null after the last use to signify that it's no longer needed. I don't anticipate D doing this though.

I recommend you read the blog post, it has details on how this is happening. How do you fix it? I have proposed a possible solution, but I'm not sure if it's completely sound, see here. It may be that this works today, but a future more clever compiler can potentially see through this trick and still not store the pinned value.

I think the spec is wrong to say that just storing something as a local variable should solve the problem. We should follow the lead of other GC-supporting languages, and provide a mechanism to ensure a pointer is scannable by the GC through the entire scope.

-Steve

September 22, 2021

On Wednesday, 22 September 2021 at 11:44:16 UTC, Steven Schveighoffer wrote:

>

Here is what is happening.

Many thanks for this so exhaustive explanation!

September 22, 2021

On Wednesday, 22 September 2021 at 11:44:16 UTC, Steven Schveighoffer wrote:

>

Once it's on the stack, the GC can see it for the full run of main. This is why this case is different.

Note that Java is even more aggressive, and might still collect it, because it could legitimately set stopper to null after the last use to signify that it's no longer needed.

And it follows that programming in GC-supporting languages
may be harder than in languages with manual memory
management, right?

September 22, 2021

On 9/22/21 8:22 AM, eugene wrote:

>

On Wednesday, 22 September 2021 at 11:44:16 UTC, Steven Schveighoffer wrote:

>

Once it's on the stack, the GC can see it for the full run of main. This is why this case is different.

Note that Java is even more aggressive, and might still collect it, because it could legitimately set stopper to null after the last use to signify that it's no longer needed.

And it follows that programming in GC-supporting languages
may be harder than in languages with manual memory
management, right?

Only when interfacing with C ;) Which admittedly is a stated goal for D.

It's telling that I've been using D for 14 years and never had or seen this problem.

-Steve

September 22, 2021

On Wednesday, 22 September 2021 at 12:26:53 UTC, Steven Schveighoffer wrote:

>

On 9/22/21 8:22 AM, eugene wrote:

>

And it follows that programming in GC-supporting languages
may be harder than in languages with manual memory
management, right?

I meant my this particular trouble...
I do not want to understand how and what
compiler generates, I just want to
get working program without any oddities.
Nevertheless, thank you again for your nice explanation!

>

Only when interfacing with C ;) Which admittedly is a stated goal for D.

I know.
And this is just fine to have the ability
of using libc (especially system calls) without
such things as c# 'marshaling' for ex.

>

It's telling that I've been using D for 14 years and never had or seen this problem.

Bond. James Bond. :) 25 years of C coding.
Now, imaging a shock I was under when
I 'discovered' that swapping two lines of code
can magically fix my prog and make GC do
the right thing. :)

Actually, D is a nice language per se
and I truly wish it to be as popular as java/python/etc.
But these GC ... mmm... 'features' may reduce
to zero any wish to learn D, that's about it.

When my C program crashes, I'm 100% sure I made something stupid

  • forget to initialize a pointer, easy to find and fix
  • did some memory corruption (worse, but then electric fence is my best friend)

But if a crash is caused by 'optimization' + GC...
It looks like a programmer must keep some
implicit/unwritten rules in order to write correctly...