[GC/asm-generation] local variables go out of scope too early #8
Comments
Here is an example exposing that it indeed leads to something not expected: SuperStrict
Framework Brl.Retro
Type TParent
Field c:TChild
Method IsTrue:int()
return True
End Method
Method GetChild:TChild()
if not c then c = new TChild
return c
End Method
Method Delete()
'freeing the child - and potentially some "non-null"-data in there
'would create a segfault
print "TParent gets deleted - freeing the child now..."
c.free()
c = null
End Method
End Type
Type TChild
Field special:TSpecial = new TSpecial
Method free()
print "TChild gets freed"
special = null
End Method
Method Delete()
print "TChild gets deleted - free it"
free()
End Method
End Type
Type TSpecial
Field i:int = 123
End Type
Function WasteSomeTime()
For Local i:Int = 0 Until 10000
local v:string = Rand(0,10000)
Next
EndFunction
Local p:TParent = new TParent
if p.IsTrue()
local c:TChild = p.GetChild()
For local i:int = 0 to 3
'after deletion it becomes "0" instead of "123"
print "c.special.i = " + c.special.i
WasteSomeTime()
Next
'this would keep it alive
'p.IsTrue()
print "TParent goes out of scope now..."
endif Result (shortened):
You can see that "special" gets modified inbetween. |
Hi, I was reading about this and started debugging. After the fix, the program produces the expected output. Could you test the fix, too? |
Ahh sorry, yes my output was already based on "maxmods/brl.mod". And I did not know that your suggestion was already included there:
@ OS:
Edit: the expected output is the output of a "bugged" GC. |
Oh okay, I thought we were getting different outputs because of different OSes. |
Only read the first post, but this is correct behaviour. After the 's.IsTrue()' expression is evaluated, 's' is technically dead as it is never used again, and may be GC'd at any time. To test this, add something like 'Print s.IsTrue()' at/near the end of the program. This should keep 's' alive for longer, eg:
|
While I understand the logic behind it ("last use of something") I also would understand the logic to keep things if they are used in something "enclosing" (in this case Did not check if a variable can get collected in a Is there an example why the current behaviour is "better" (means: are there things bugging out if you would keep the variable until the enclosing Back to the sample: the problem with the GCing "too early" is that a child element could free things on "Delete()". Things which might still be in use. Developers might not not see a variable vanishing that early and are left puzzled then - like happening in the thread I linked in the OP. |
If you want something to be 'deleted' at a particular, predictable time, add a Discard() method or something. Finalizers are not the way to do this. IMO, finalizers should be avoided altogether but no one likes that theory! |
This bug still exists and is very real. After the MemExtend fix this issue is still there.
In release builds I get...
Which I see is wrong. At the very least 'c' and 'special' are still being accessed and need to kept alive. Am I missing somethng here? [edited] |
The example is confusing, but I think what's happening is:
This is all 'expected behaviour' so there is no issue here, just I think a misunderstanding of how GC/finalizers work under the hood. The cost of keeping all local variables 'live' until the end of block they were declared in (and why a block? kinda arbitrary but I assume it suits someone's use case...) would be high. There would be more register interference during register allocation and more register spillage to memory. Not gonna happen. Note that all this has nothing to do with the bbMemExtend issue as far as I can see, no idea how the 2 got conflated. That was a 'c' spec issue (and I suspect a glibc bug). |
I totally understand, thank you for making it clearer as to what is happening. Thanks! |
Excuse the "confusing example. I just tried to replicate what the forum thread was about (the db module). BTW: gccollect() did not collect it everytime...which is why in the thread that wastetime-function was added. @ c nulled Thanks for the clarification and information about the "costs" to keep a local var alive until an "endif". It is still a bit "unclear" when things can get deleted by GC ...some users might think in terms of "blocks". |
It is still a bit "unclear" when things can get deleted by GC
Strictly speaking, things can get GC'd if there are no 'live' references to
them. A live reference is a reference (in memory or a cpu reg) that the
compiler has determined *may* be used in future. Liveness of a reference
changes from moment to moment, depending on what code has been executed and
what code is yet to be executed. The compiler can only make a conservative
estimate of liveness, eg: in the case of heap references (ie: fields), bmx
assumes they always WILL be used in future so are always live, but a more
sophisticated compiler could theoretically do better.
But this issue is really only due to the use of finalizers (ie: method
Delete) and finalizers are evil. They may execute sooner (or later, or not
at all) than you expect, depending on the whims of the above algorithm
(which is affected by compiler implementation, optimizations etc), so are next to useless
for anything deterministic. IMO, they should ONLY be used for
'just in case' cleanups (if at all) in which case you can completely ignore
the question of when GC happens.
In reality, GC is *only* a memory management system, and it really only guarantees that memory will not be prematurely freed before you are finished with it (and finalizers are really just an ugly 'PreFree()' hack). Beyond that, there are no guarantees. Compiler implementation and optmizations have a major effect on how it works in practice, ie: when memory is actually alloced/freed, which makes it very hard to predict when finalizers will happen.
…On Fri, Mar 10, 2017 at 11:26 AM, Ronny Otto ***@***.***> wrote:
Excuse the "confusing example. I just tried to replicate what the forum
thread was about (the db module).
BTW: gccollect() did not collect it everytime...which is why in the thread
that wastetime-function was added.
@ c nulled
Yes I understand that it gets nulled and is not accessible any longer (or
not guaranteed as it is "release mode").
And if I got you right the problem is that GC is freeing the parent as
there is nothing "holding" it anymore. The delete() then dereferences "c"
which is no problem..but the c.free() before the dereferencing is creating
the trouble (by assuming it is safe now to clean up further things).
Thanks for the clarification and information about the "costs" to keep a
local var alive until an "endif". It is still a bit "unclear" when things
can get deleted by GC ...some users might think in terms of "blocks".
—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
<#8 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADU3QnhxXH46Ag7jruMPI0K15p-ZKYZTks5rkHyogaJpZM4Lq1XS>
.
|
Coming from this thread:
http://www.blitzmax.com/Community/posts.php?topic=107624#bottom
Brucey noted down that Blitzmax (v1.50) generates ASM-code which leads to a too early garbage collection of local variables.
Result:
If within
Delete()
children (created in the parent and only referenced there) would be reset/freed then within the "For-loop" any access to it would create a segfault.Local variables used in an "if condition" should not get deleted before reachin the "endif".
The text was updated successfully, but these errors were encountered: