Consistency, Templates, Constructors, and D3

Era Scarecrow rtcvb32 at yahoo.com
Wed Aug 29 14:15:47 PDT 2012


On Wednesday, 29 August 2012 at 15:07:42 UTC, F i L wrote:
> Actually, now that I think about it, there's an potentially 
> better way. Simply have static analysis do the work for us:
>
> class A
> {
>   int a;
>   this new() {
>     // if 'this = ...' is found before 'this.whatever' then
>     // the automatic allocation is overriden. So we have no need
>     // for any kind of @noalloc/@alloc() distinction.
>
>     // More importantly, because allocation is type specific, we
>     // strip this out when calling it from a derived class (see 
> B)
>
>     this = GC.alloc(A); // this stripped when called from 
> B.new()
>     this.a = ...;
>   }
> }
>
> class B : A
> {
>   int b;
>   this new() {
>     super.new(); // use A.new() except for allocation
>     this.b = ...;
>   }
> }
>
>
> Basically what's happening is two functions are built out for 
> each class constructor which defines a 'this = ...': one with 
> the allocation stuff, and one without. When a derived class 
> calls the super classes constructor, it's calling the one built 
> without the allocation stuff.
>
> There could also be some kind of cool tricks involved. For 
> instance of you use 'typeof(this)' with 'GC.alloc()' (instead 
> of 'A'), then it could keep the allocation stuff and the 
> super.new() constructor and use the allocation logic, but still 
> allocate the size appropriate for type 'B' when it's called:
>
> class A
> {
>   this new() {
>     if (condition) {
>       this = GC.alloc(typeof(this));
>     }
>     else {
>       this = malloc(typeof(this));
>     }
>     ...
>   }
> }
>
> class B
> {
>   this new() {
>     super.new(); // same allocation rules as A
>     ...
>   }
> }
>
> However, that last part's just a side thought, and I'm not sure 
> if it would really work, or what the implementation costs would 
> be.

  By this form of definition that's all suggested, you'd be mixing 
if it was heap or non-heap allocated. I'm not talking about Class 
A & B, I'm talking about things they contain.

Assume class B was defined instead.

class B : A {
   C something;
   this new() {
     super.new(); // same allocation rules as A
     ...
     something = new C(); //and however it's made
   }
}

  Now you have the following:

  1) Sometimes A/B is heap and sometimes not
  2) Class C may or may not be heap allocated we don't know (It's 
an implementation detail)

  If A/B happens to be stack allocated then when it leaves the 
frame (or abandoned), no harm done (C is abandoned and will be 
picked up by the GC later safely)


  Let's reverse it so C is the outer class, and let's assume A is 
defined to use something like... alloca (stack). Now you have:

  class C {
    B mysteryNew;
    this new() {
      mysteryNew = new B();
    }
  }

  Oops! Now leaving new (or the constructor, or whatever) 
mysteryNew is now an invalid object! So if there's another new 
option you can decide 'maybe' which one you may want to use.

  int currentCounter;
  B[10] globalReservedB;

  class C {
    B mysteryNew;
    this new1() @alloc {
      if (currentCounter < globalReservedB) {
          //because it's faster? And we all know faster is better!
          globalReservedB[currentCounter] = new1 B();
          mysteryNew = globalReservedB[currentCounter++];
      } else
        assert(0);
    }
  }

  Whew! Wait! No!!!! globalReservedB still is just a reference 
pointer and not preallocated space, meaning you'd have to do 
fancy low level magic to actually store stuff there. mysteryNew 
now points to a global reference holder that holds an invalid 
pointer.

  Say the authors of zlib make a D class that does compression at 
a low level because D is better than C. By default they used the 
standard new.

  LATER they decide that zlib compression should only happen on 
the stack because you only need to compress very very small 
buffers (for something like chat text where you only have maybe 
4k to worry about), so they override new with their own that uses 
alloca. They don't want to change it to a struct because it's 
already a class.

  Now what?? There's no guarantees anymore at all! If you update a 
library you'd need to read every implementation detail to make 
sure the updates don't break current code and maybe add checks 
everywhere. What a headache!

   interface IZlib {
     void compress(string input);
     string decompress(string input);
     ubyte[] flush();
     void empty();
   }

   //intended use.
   ubyte[] compressText(string text) {
     Zlib zText = new Zlib();
     zText.compress(text);
     return zText.flush();
   }

   //our fancy function wants to do multiple reads/writes.
   Zlib compressText(Zlib zText, string text) {
     if (zText is null)
       zText = new Zlib();
     zText.compress(text);
     return zText;
   }

   Assuming it allocates on the heap and where we assumed it would 
always go, the function probably would work fine. If Zlib changed 
to alloca since it should never be used otherwise (and they even 
make an explicit note), but it could break previously compiled 
code.

   class Zlib : IZlib {
     ...
   }

   //could contain mystery allocator
   IZlib ztext = compressText(null, "Hello world");

   //could crash, if it was pre-compiled code to call a library
   //this is now unavoidable and was shipped to millions of 
customers
   compressText(ztext, " Hello Hello!");


   Now let's assume you aren't allocating on the stack (because 
they think the stack is a stupid place to store stuff) but we 
don't want to use the GC. Let's assume Zlib was used with it's 
own new to use malloc so it's compatible with their C 
interface(s).

   class Something {
     Zlib zText;
     void clear() {zText = null;}
   }

   Something s = new Something();
   s.zText = new Zlib();

   So far so good. Later..

   //s isn't needed anymore
   //but we don't know if s was still used by something else
   //no destructor is called on zText, but we assume the GC
   //can pick up the abandoned object and cleanly handle it later.
   s.clear();
   s = null;

   Now zText is abandoned, and will never call it's destructor 
since it wasn't registered in the GC. If it is registered, then 
it MIGHT call the destructor, or it may only search the area and 
manage to free the ubyte[] buffer that it contained still leaving 
you with a floating leaked block of memory (no matter how small).

  (May be ranting, feel free to ignore me from here on)

   Let's assume you have space limitations and you have to do 
space efficiently. So we decide to do something Apple/Mac people 
did and use a two level management for memory (Or sort of, 
pointerless pointers, I read about this so...)

  struct sizeBlock{
    void* ptr;
    int size;
  }

  void* rawMemory;
  sizeBlock[1_024] blockRef; //1k item limit! Maybe we have 64k 
only, embedded devices

  int allocate(int size); //suspiciously similar to malloc somehow

  //simple, but more aptly used for arrays like strings
  void deallocate(int index) {
    blockRef[index].size = 0;
  }

  Object isObject(int index) {
    return cast(Object) *blockRef[index].ptr;
  }

  All allocators now need to register their pointers in blockRef. 
Should memory need to be shifted, only blockRef is updated and we 
can save as many bytes as we need.

  class Managed {
    int new() @alloc {
      int block = allocate(Managed.sizeof); //or however the size 
is calculated
      this = isObject(block);

      //initialize

      return block;
    }
  }

  Now if allocate cannot find a block of appropriate size, it can 
just shift everything over until the end has room and none of the 
classes or references (as long as it honors blockRef) is safe; 
Even if rawMemory gets resized/moved later it should be able to 
handle it.

  int someObject = new Managed(); //so far so good right?

  Object o = isObject(someObject); //actively work on/with it
  //safe until we decide to allocate something.
  //Then we need to refresh our instance of o.

  int somethingElse = new Managed();
  o = isObject(someObject); //may have moved so we refresh o

//destructor never called! and memory in jeopardy! Worse is any 
contents
//are likely abandoned and a larger memory leak issue has 
appeared!
  deallocate(someObject);


More information about the Digitalmars-d mailing list