Saturday, April 27, 2013

Using ARC with non iOS compiler - possible?

Did you ever have the situation with 2 lists sharing the same objects and possibly one or both had the OwnsObjects property set to true? Especially when using interfaced lists like in Spring4d or DSharp you want to make use of reference couting and automatic memory management. But what if some instances are shared between different lists or moved from one list to the other. You cannot use the Delete or Remove method and then add the object to the other list of vice versa if the first list has OwnsObjects true because it will destroy the instance when it gets removed. That is why there is the Extract method which removes the instance but does not destroy it. This can get complicated and possibly leading to memleaks or exceptions quickly.

Using interfaced objects for simple data storages might not be a very good idea. It requires you to write interfaces with the properties just for that purpose and if you use some kind of RTTI based binding you cannot do that as there is no RTTI for interface properties (which are just syntax sugar anyway).

So what would we give for some easy memory management in these cases. How about using the concept we know from interfaces and other types for objects?

Of course we could write some TRefCountObject and inherit our data classes from that base class and handle these in our Notify method inside the list when items get added or removed. But that would be to easy and not magic at all. ;) And more seriously it does not always work to change the base type due to several reasons.

So what do we need? Basically just a new field inside our object to keep track of the reference count. Keep in mind we cannot do a full ARC implementation because that would include assignments and parameter passing which would need compiler support. We just want it for when objects are put into lists.

The method that is responsible for allocating the memory of new instances is TObject.NewInstance. So we need to replace that:

procedure InitializeARC;
var
  Buffer: array[0..4] of Byte;
begin
  Buffer[0] := $E9
  // redirect TObject.NewInstance
  PInteger(@Buffer[1])^ := PByte(@NewInstance) - (PByte(@TObject.NewInstance) + 5);
  WriteMemory(@TObject.NewInstance, @Buffer, 5);
end;

What this code does is place a jump instruction at the very beginning of the TObject.NewInstance method that redirects it to our NewInstance routine which looks like this:

function NewInstance(Self: TClass): TObject;
begin
  // get additional memory for the RefCount field
  GetMem(Pointer(Result), Self.InstanceSize + SizeOf(Integer));
  Result := InitInstance(Self, Result);
end;

It does basically the same as the original except that it allocates 4 bytes more for our RefCount field and then calls our version of InitInstance (which is responsable for initializing the object):

function InitInstance(Self: TClass; Instance: Pointer): TObject;
const
  Buffer: Pointer = @BeforeDestruction;
begin
  Result := Self.InitInstance(Instance);

  // initialize the RefCount field
  GetRefCountFieldAddress(Instance)^ := 0;

  // replace TObject.BeforeDestruction
  if PPointer(NativeInt(Self) + vmtBeforeDestruction)^ = @TObject.BeforeDestruction then
    WriteMemory(PPointer(NativeInt(Self) + vmtBeforeDestruction), @Buffer, SizeOf(Pointer));
end;

Since TObject.InitInstance just zeroes the memory the RTL knows about (obtained by calling InstanceSize) we need to set our field which sits on the last 4 bytes in our instance:

function GetRefCountFieldAddress(Instance: TObject): PInteger; inline;
begin
  // the RefCount field was added last
  Result := PInteger(NativeInt(Instance) + Instance.InstanceSize);
end;

Along with the reference couting we want to make sure that the instance is not getting destroyed when it is still managed by the RefCount (because it sits in some list). That is why the BeforeDestruction method gets replaced. Why not detour like NewInstance? The implementation in TObject is empty so there are not 5 bytes of available that we can overwrite to jump to our implementation. But as it is virtual we can replace it in the classes VMT. Like its implementation in TInterfacedObject it will raise an error when the RefCount is not 0.

procedure BeforeDestruction(Self: TObject);
begin
  if GetRefCount(Self) <> 0 then
    System.Error(reInvalidPtr);
end;

Implementing the actual AddRef and Release routines is pretty easy aswell:

function __ObjAddRef(Instance: TObject): Integer;
begin
  Result := InterlockedIncrement(GetRefCountFieldAddress(Instance)^);
end;

function __ObjRelease(Instance: TObject): Integer;
begin
  Result := InterlockedDecrement(GetRefCountFieldAddress(Instance)^);
  if Result = 0 then
    Instance.Destroy;
end;

The most important thing: You need to add the unit which contains this as the very first unit in your project  (or after ShareMem) so the NewInstance method gets patched as soon as possible.

Time to test if it does what it should:

implementation

{$R *.dfm}

uses
  DSharp.Collections,
  DSharp.Core.ARC;

type
  TList<T: class> = class(DSharp.Collections.TList<T>)
  protected
    procedure Notify(const Value: T; const Action: TCollectionChangedAction); override;
  end;

procedure TList<T>.Notify(const Value: T;
  const Action: TCollectionChangedAction);
begin
  case Action of
    caAdd: __ObjAddRef(Value);
    caRemove: __ObjRelease(Value);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  list1, list2: IList<TObject>;
begin
  list1 := TList<TObject>.Create;
  list2 := TList<TObject>.Create;

  list1.Add(TObject.Create);
  list1.Add(TObject.Create);
  list2.AddRange(list1);
  list1.Delete(1);
end;

initialization
  ReportMemoryLeaksOnShutdown := True;

end.

When we click the button both objects get added to both lists and the last list containing an object will cause it to get destroyed when removed (which happens if the list gets destroyed aswell).

So far this is more of a proof of concept but I think this can make some code easier and less complicated especially when working a lot with lists and moving around objects without knowing what list at the end owns the objects.

You can find that code in the svn repository and as always your feedback is welcome.

7 comments:

  1. A downside of the hack, is that it makes InstanceSize incorrect, which can matter if you have other libraries going for a similar approach of hidden fields, or with custom allocations (f.i. when pooling objects to avoid constructor & RTL initialization overhead).
    So rather than hack NewInstance to allocate extra bytes, it's safer to hack the InstanceSize directly.

    As an alternative, you can consider squatting the Monitor hidden field, since the Monitor functionality is broken in so many ways in most Delphi versions (at least up to XE2, it's useless thanks to a combination of race conditions and thread safety issues), it's no big loss (that's what I did for DWScript RefCountedObjects in Delphi)

    ReplyDelete
    Replies
    1. I was trying to use the InstanceSize first but then I would have to hack it for every class at startup time of the application (not talking about packages that get loaded later, and so on) since InstanceSize which just uses the vmtInstanceSize field (gets generated by the compiler, no?)

      I know there are some problems with TMonitor but I rather risk breaking something unofficial than using something official.

      Delete
  2. I'm just wondering...

    This will work only within the scope of the the DSharp lists.

    Such hacking at TObject level will make all object allocation slower, even if this class does not use the feature.

    I suspect it could make better sense to define your own class (e.g. TArcObject), from which to inherit. Then add the fields and methods you use, without any performance penalty, no VMT table to hack.

    If you really want to hack every TObject, you may also hook the vmtInstanceSize entry for each corresponding VMT class.

    And you will need to handle (zeroing) weak references... otherwise this is a POC but not something usable.

    ReplyDelete
    Replies
    1. The only performance penalty is initializing the refcount field and hacking the VMT if it is the first instance of that class - and in a release config that is close to zero.

      This will work on any list that has a modified Notify method (can even make TObjectList work with it) - either inherit or patch it.

      And no, it does not require zeroing weak references as there are no weak references in this context. It is like an improved OwnsObjects that works with multiple list instances owning an object. Does TObjectList zero any references to elements in the list when it destroys them? No

      Delete
    2. As soon as you have a circular reference, you will need weak pointers.

      In the real world, you will probably use the objects in the list, outside of it, right? (Otherwise this is useless)
      And here comes the potential need of weak refs.

      The performance penalty is real. But I suspect less than the regressions introduced in xe3 and xe4...

      Delete
    3. I am not sure you get the point. This is mostly about having objects in different lists without figuring out what list might have the ownership. In a normal situation if you have two lists one might not have OwnsObjects or you have to use Extract when moving from one list to another. And if both have OwnsObjects an object cannot be in both without having trouble with the list destroying it when removing or destroying the list.

      It does not solve the possible problem of having references of objects that are being freed by a list that has OwnsObjects as you have the same problem without ARC.

      And again, there is no performance penalty worth talking about. You are welcome to profile it.

      Delete
  3. Hi Mr. stefan Thanks for this post. I'm lookin for a solution to change the parent class of a class at runtime, is there a way it can be done?

    this is the case. Al control have to be derived from TControl, let be named TControl2 this is needed at least at design time so it can be use by the designer, but at runtime i really dont use most of the tControl feature because the control is only a wrapper to emit HTML output, I have also derived classes from thta TControl2 so I'm having alot of TControl memory overhead unused, and also all windoes messaging system is no used. So is there a way I can change the parent class at runtime so all the overhead of TControl can be replaced for another tha will only have the properties used by TControl2?

    ReplyDelete