Cook Computing

Adding Ref-Counting to Rotor

June 13, 2002 Written by Charles Cook

After reading Chris Sell's announcement about Adding Ref-Counting to Rotor and the subsequent discussion on the dotnet list, I've been thinking about how it might work. I might be totally wrong because I'm don't know much about the CLR but the following seems plausible.

Each reference type object will have an associated refcount. This will be stored alongside the object in the garbage collected heap.

No changes are planned to IL. Instead the JITter will be enhanced so that IL instructions which potentially change the effective ref count on an object will generate extra code which modifies the ref count stored alongside the object.

If this code detects that the ref-count has gone to zero, the object's finalizer is called (if it has one).

The following code illustrates what might happen:


class A { }

class _
{
  static A CreateA()
  {
    A m = new A();
    A n = m;
    n = null; 
    return m;
  }
  static void Main(string[] args)
  {
    CreateA();
  }
}

The IL for method CreateA is:


.method private hidebysig static class A 
        CreateA() cil managed
{
 // Code size       16 (0x10)
 .maxstack  1
 .locals init ([0] class A m,
          [1] class A n,
          [2] class A CS$00000003$00000000)
 IL_0000: newobj     instance void A::.ctor()   // green
 IL_0005: stloc.0
 IL_0006: ldloc.0  // green
 IL_0007: stloc.1
 IL_0008: ldnull
 IL_0009: stloc.1  // red
 IL_000a: ldloc.0  // green
 IL_000b: stloc.2
 IL_000c: br.s       IL_000e
 IL_000e: ldloc.2  // green
 IL_000f: ret  // red
} // end of method _::CreateA

The instructions marked in green are where the ref count of the instance of class A is incremented and red where it is decremented. So in this example there is a net increase of 3 refs before the end of the method. Two of these are held in in the locals so the ret instruction must trigger two decrements as the locals go out of scope. The remaining reference is ok because it is the return value and is passed back on the stack.

The relevant IL in Main is:


 IL_0000:  call      class A _::CreateA()
 IL_0005:  pop  // red

The return value is not used and so pop is used to remove the object from the stack. The object reference is not stored anywhere so this results in the object's ref-count going to zero and the JITted code calls the finalizer of the object (if it has one).

Of course there is a lot more to it than this sample, for example handling member object references, exceptions, etc; and there are many more IL instructions which affect ref-counts than the handful mentioned above. But this could be how it is intended to work in principle. It remains to be seen what the effect on performance will be but this can be minimized by optimizing out the ref-counting where the JITter can determine it is not required.

Lack of deterministic finalization is the most common complaint I hear against .NET. It would be great to see this fixed in a future version of .NET.