User:Alphaglosined/ManagedMemory

From D Wiki
Jump to: navigation, search

Applying behavior to all heap based types

For each module provided to dmd that contains this module, include this as part of the RTInfo.

managed(T) {
    static assert(T.stringof.length > 0); // some random restriction
    // some data
    // opInc, opDec, opAllocate, this, ~this
}

Gets rewritten as:

struct Managed_xxxx(T) {

  static assert(T.stringof.length > 0); // some random restriction
  // some data
  // opInc, opDec, opAllocate, this, ~this

}

Optionally the managed block, may have a name.

managed Foo(T) {}

struct Foo(T) {}

A managed block is not declared as a struct, primarily because it cannot be initiated the same way. It must only be done by the compiler at runtime.

From within a managed block, using the variable __INSTANCE__, it is possible to work on an instance of T. It should be treated as if T is private. In other words the same module.

A managed block may provide operator overloads for e.g. opInc, opDec, opAllocate, this and ~this. The order of calling is:

  1. Types operator overloads
  2. Files: 1 .. N
    1. For file: Managed block declaration: 1 .. N

Memory representation

.sizeof remains as is (.init.length)

.init changes to include the RTInfo first, then the declaration.


For arrays the representation becomes:

length, ptrToData, ptrToRTInfo

Where ptrToRTInfo is ptrToData-RTInfo!(T[]).sizeof and contains a vtable of all the operator overloads that are relevant.


For classes, RTInfo will be prepended to the object data, making the first field RTInfo!T.sizeof offset.

Methods + Operator Overloads

  1. opAllocate
  Constructs T using the given arguments. Only one is valid, and is always the last one defined.
  1. opInc
  Hook point for ref counting.
  1. opDec
  Hook point for ref counting.

The methods this and ~this are special. They are the lifetime of the memory in general. For passing in arguments to the managed block, use constructor but with arguments. Both can co exist.

Managed attribute

The managed attribute informs the compiler that the given memory block has overloads that may not be defined by the type itself.

In essence it forces the compiler to ensure a type behaves like it has operator overloads. So the vtable will always contain an opInc/opDec and opAllocate.

The attribute managed cannot be casted away or to. It must be constructed in some form.

If a function argument does not escape, managed memory may be passed into it, without annotating it as such. Escaping includes passing the instance to another function. Primitives are the exception to this rule. Structs, classes, unions and arrays may not be however.

The only times the compiler is concerned about the size of a managed(T) changing, is at construction and calling. At calling the vtable is already known. During construction a vtable will be created (if not already done) for the given type with its memory construction arguments, within static memory and by doing so implicitly merging all operator overloads that are required.

To construct managed memory, use:

managed RefCounted(T) {

   IAllocator alloc;
   T opAllocate(int[] args...) {
       return alloc.make!T(args);
   }

}

managed(int[]) func(int x, IAllocator alloc=theAllocator()) {

   return managed(int[])>(RefCounted(alloc))(x, x + x, x * x);

}

To construct managed memory from another block of memory (doesn't have to be managed):

managed(int[]) func(int[] data, IAllocator alloc=theAllocator()) {

   return managed(int[])>(RefCounted(alloc))(data);

}

When the argument passed in for the value(data), matches the type (int[]) then it is copied as is. Postblit (this(this)) is then ran upon it, to duplicate it. It is up to the end user to worry about duplication child data.

Providing managed blocks are totally optional for creation. This makes the following code valid:

managed(int[]) func(int[] data) {

   return managed(int[])(data);

}

As shown, if no opAllocate is provided, the default (new) will used instead.