March 26, 2008
Walter Bright wrote:
> I suspect that having a granular level of specifying safe/unsafe is the wrong approach. Doing it at the module level is easy to understand, and has the side effect of encouraging better modularization of safe/unsafe code.

That would mean that modules in Phobos would be either Safe or Unsafe.

Or[/and] that some modules would have to have two versions, one Safe and the other Unsafe.

More practical would be (especially if the compiler has access to info/hints to the safety of individual functions) to have it per function.

Then the compiler could discriminate, depending on if the user had used the -Safe switch or not.

Personally, I'd advocate having Safety on App Level. Either an app is SafeD compliant, or not. I have a hard time seeing anything in between.

March 26, 2008
> > Finally, would SafeD have to disallow destructors? If you're accessing garbage collected memory in a destructor, you're asking for trouble. It's not always as simple as directly disallowing access these fields. Calling functions can indirectly cause the memory to be accessed. However, if you're not accessing GC memory in a destructor, you're probably using some lower-level functions, which are generally untrustworthy.
> 
> I thought the garbage collector only freed memory after the destructor had been run.
> 
> DMD 1.00 spec document, page 104 says "The garbage collector calls the destructor when the object is deleted."
> 
> Did this change?  I haven't checked for an update to my copy of the spec document in some time.
> 
> -- the "other" Chris Miller

http://www.digitalmars.com/d/2.0/class.html#Destructor

"When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects. This is because that the garbage collector does not collect objects in any guaranteed order, so there is no guarantee that any pointers or references to any other garbage collected objects exist when the garbage collector runs the destructor for an object."

-- 
Chris Miller <chris@dprogramming.com>
March 26, 2008
Chris Miller Wrote:
> 
> http://www.digitalmars.com/d/2.0/class.html#Destructor
> 
> "When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects. This is because that the garbage collector does not collect objects in any guaranteed order, so there is no guarantee that any pointers or references to any other garbage collected objects exist when the garbage collector runs the destructor for an object."

Oh, well then you should just move the code you wanted to run in the parent object's destructor to the sub-object's destructor, though it does leave my wondering why the garbage collector would have killed the sub-object first, since if there is code in the parent object's destructor that uses the sub-object, that should count as a reference, so the sub-object should still be in scope - I think.  Or does code in a destructor not count towards keeping heap references in scope?

-- the "other" Chris Miller

March 26, 2008
Chris Miller wrote:
> Chris Miller Wrote:
>> http://www.digitalmars.com/d/2.0/class.html#Destructor
>>
>> "When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects. This is because that the garbage collector does not collect objects in any guaranteed order, so there is no guarantee that any pointers or references to any other garbage collected objects exist when the garbage collector runs the destructor for an object."
> 
> Oh, well then you should just move the code you wanted to run in the parent object's destructor to the sub-object's destructor, though it does leave my wondering why the garbage collector would have killed the sub-object first, since if there is code in the parent object's destructor that uses the sub-object, that should count as a reference, so the sub-object should still be in scope - I think.  Or does code in a destructor not count towards keeping heap references in scope?
> 
> -- the "other" Chris Miller

'code' never holds references, strictly data.  The question you have, I believe, is when obj1 holds a reference to obj2 (be it a pointer or a reference), what happens when the last reference to obj1 disappears. Both objects become 'dead' and the order of cleanup is undefined.  In that case, you can question the choice, but consider any cycle of references where together they are all referenced.  When the last external reference to the cycle disappears the entire set is collectable with no 'right' order.  Based on the fact that in some cases there's no 'right' order, the collector takes the approach of 'no order can be assumed for any object being collected'.  The result being that no references to other collectable memory can be referenced from a destructor.  Because of the collector, you don't need to either. Destructors only need to manage non-collectable entities.

Later,
Brad
March 26, 2008
Walter Bright wrote:
> Lutger wrote:
>> Sounds great. Would it be possible to compile SafeD code to Java bytecode? iirc it isn't possible to do that with D.
> 
> It possibly could be.

Hurray!
Will it also have mandatory braces? :-)

Ciao
-- 
Roberto Mariottini, http://www.mariottini.net/roberto/
SuperbCalc, a free tape calculator: http://www.mariottini.net/roberto/superbcalc/
March 26, 2008
I think polishing D 1.0 is definitely more important.
March 26, 2008
Brad Roberts Wrote:

> Chris Miller wrote:
> > Chris Miller Wrote:
> >> http://www.digitalmars.com/d/2.0/class.html#Destructor
> >>
> >> "When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects. This is because that the garbage collector does not collect objects in any guaranteed order, so there is no guarantee that any pointers or references to any other garbage collected objects exist when the garbage collector runs the destructor for an object."
> > 
> > Oh, well then you should just move the code you wanted to run in the parent object's destructor to the sub-object's destructor, though it does leave my wondering why the garbage collector would have killed the sub-object first, since if there is code in the parent object's destructor that uses the sub-object, that should count as a reference, so the sub-object should still be in scope - I think.  Or does code in a destructor not count towards keeping heap references in scope?
> > 
> > -- the "other" Chris Miller
> 
> 'code' never holds references, strictly data.  The question you have, I believe, is when obj1 holds a reference to obj2 (be it a pointer or a reference), what happens when the last reference to obj1 disappears. Both objects become 'dead' and the order of cleanup is undefined.  In that case, you can question the choice, but consider any cycle of references where together they are all referenced.  When the last external reference to the cycle disappears the entire set is collectable with no 'right' order.  Based on the fact that in some cases there's no 'right' order, the collector takes the approach of 'no order can be assumed for any object being collected'.  The result being that no references to other collectable memory can be referenced from a destructor.  Because of the collector, you don't need to either. Destructors only need to manage non-collectable entities.


Ah, so you're saying that reference counting is run according to scope relative to the sequential process line as it propagates down from main().   That makes sense, since those objects are no longer referenced by anything from the running scope, they're all garbage collectible.

So yes, then the only solution would be to introduce a new rule to the garbage collector stipulating that objects with the least number of references from other objects in the collectable scope should be deleted first.  This would inherently slow down the garbage collector, thus presenting you with a tradeoff.

What about something like scope(exit) object.doThis(); ?  Sort of like Tango's FileConduit?  Would that work to be able to manually force a ordered deletion?  It wouldn't be automatic by the garbage collector.  In my experience a scope statement is a lot easier than the C++ way of carefully discovering when something is finally out of scope!  Just a thought.  I don't know, but it's a very interesting problem to think about!
March 31, 2008
== Quote from Chris Miller (lordSaurontheGreat@gmail.com)'s article
...
> > assumed for any object being collected'.  The result being that no references to other collectable memory can be referenced from a destructor.  Because of the collector, you don't need to either. Destructors only need to manage non-collectable entities.
>
> Ah, so you're saying that reference counting is run according to scope
> relative to the sequential process line as it propagates down from
> main().   That makes sense, since those objects are no longer referenced
> by anything from the running scope, they're all garbage collectible.
> So yes, then the only solution would be to introduce a new rule to the
> garbage collector stipulating that objects with the least number of
> references from other objects in the collectable scope should be deleted
> first.  This would inherently slow down the garbage collector, thus
> presenting you with a tradeoff.
>
> What about something like scope(exit) object.doThis(); ?  Sort of like Tango's FileConduit?  Would that work to be able to manually force a ordered deletion?  It wouldn't be automatic by the garbage collector. In my experience a scope statement is a lot easier than the C++ way of carefully discovering when something is finally out of scope!  Just a thought.  I don't know, but it's a very interesting problem to think about!

Garbage collection can be done (to a degree) with reference counting, but systems which use GC more often use an algorithm called "mark / sweep" (or a variation of it.)  This has various advantages over reference counting.  First, if you have circular pointers, such as blocks A and B pointing to each other, they would never be collected in a reference counting system; secondly, every time a reference counting system needs to copy or overwrite a pointer, a count has to be adjusted somewhere, and this means that a lot of algorithms run more slowly -- for example, copying an array of pointers requires all the pointed-to blocks to be "touched" in order to increase their refcounts.

The way mark/sweep collection works is that the program stack is scanned, along with all global variables, and the stacks of any threads and thread local storage, plus the actual machine registers.  This scan looks for pointers or (in a "conservative" design) things which *might* be pointers.

Any of the blocks of (dynamically allocated) memory that is pointed to by one of these (stack, global, or register) pointers is then considered to also be "live".  This block is then scanned as well for pointers, and so on.  Basically, if there is a series of pointers from a live area to a specific block, that block is also live, recursively.  Any other area is considered "garbage", because if you don't have a pointer to it anywhere in the live set, you can't still be using it.

Mark/sweep doesn't have the problem of circular reference counts, but on the other hand, there is no way to figure out which blocks were parents of which others, so that destruction order is essentially random.  There is no way to fix this reliably, especially since the circular links can mean that the objects are both parents of each other -- so what order do they get destroyed in?

In languages like D and C++, the garbage collection is conservative, and this means that any pointer-sized block will be considered a pointer if it contains a value that is an address of any area that might still be live.

This means that a few "garbage" blocks can be kept around because they are in a memory area which is pointed to by some random integer. This also means that in practice, even for sets of memory blocks that are not circular and might have an obvious destruction order could not be guaranteed to be destroyed in the right order, because a random integer in any of them might make them seem circular, so relying on any policy that tried to detect circularity would be unreliable at best.

There are some ways to work around this though -- If object A needs to call B.close(), then a reference to object B can be stored in a global (or static) variable as well as in object A.  After object A's destructor calls B.close(), then it should remove B from the global table, thus making B garbage (B will not actually get freed until the next GC cycle.)  (Make sure the table doesn't need to synchronize for the "remove" step since that could cause deadlock, so an associative array is probably a bad idea.)

Kevin
March 31, 2008
On Mon, 31 Mar 2008 05:13:50 +0200, Kevin Bealer <kevinbealer@gmail.com> wrote:

> In languages like D and C++, the garbage collection is conservative, and
> this means that any pointer-sized block will be considered a pointer if
> it contains a value that is an address of any area that might still be
> live.
>This means that a few "garbage" blocks can be kept around because they
> are in a memory area which is pointed to by some random integer. This
> also means that in practice, even for sets of memory blocks that are
> not circular and might have an obvious destruction order could not be
> guaranteed to be destroyed in the right order, because a random integer
> in any of them might make them seem circular, so relying on any policy
> that tried to detect circularity would be unreliable at best.

In D, an allocated block can be marked as containing no pointers, and thus
will not be scanned for things looking pointers. I don't know how good the
GC/compiler is at understanding these things on its own, but at least a
programmer can make it more informed.

--Simen
March 31, 2008
Simen Kjaeraas Wrote:

> On Mon, 31 Mar 2008 05:13:50 +0200, Kevin Bealer <kevinbealer@gmail.com> wrote:
> 
> > In languages like D and C++, the garbage collection is conservative, and this means that any pointer-sized block will be considered a pointer if it contains a value that is an address of any area that might still be live.
> >This means that a few "garbage" blocks can be kept around because they
> > are in a memory area which is pointed to by some random integer. This also means that in practice, even for sets of memory blocks that are not circular and might have an obvious destruction order could not be guaranteed to be destroyed in the right order, because a random integer in any of them might make them seem circular, so relying on any policy that tried to detect circularity would be unreliable at best.
> 
> In D, an allocated block can be marked as containing no pointers, and thus will not be scanned for things looking pointers. I don't know how good the GC/compiler is at understanding these things on its own, but at least a programmer can make it more informed.
> 
> --Simen

Yes, and I forgot to mention that at one point the D compiler got an upgrade that made it more "precise" (the opposite of "conservative" in GC jargon).  It now knows that a dynamically allocated array of a primitive type (such as an int[] array or one of the string types) is not a pointer.

So if you are dealing with lots of large matrices or lots of string data, you should suffer much less from the effects of the accidental retention of blocks. I don't think it affects for structures, classes, or stack data, though it would probably be straightforward to do this for structures that had no pointers.

Kevin