Blink‎ > ‎

Garbage Collection for Blink C++ objects (a.k.a. Oilpan)


Introduction

Terminology

  • Heap
    In Oilpan context, "heap" means memory to store objects which support garbage collection.

How to write code with GC objects

Basic rules

If you'd like to make a class T garbage-collected, you should do
  1. Include platform/heap/Handle.h. (We just include Handle.h instead of including other header files in platform/heap/.)
  2. Inherit GarbageCollected<T> or GarbageCollectedFinalized<T> as the first base class.
    1. If, and only if, the destructor T::~T must be called, use GarbageCollectedFinalized<T>.
  3. Members of T:
    1. Garbage-collected data members: Use Member<U> m_name, and must be traced. 
    2. References to non-garbage-collected objects use OwnPtr, RefPtr, etc. require T to be GarbageCollectedFinalized<T>.
  4. Define T::trace(Visitor*) methods using {DEFINE,DECLARE}_TRACE macros, and trace the garbage-collected data members.
  5. Update references to instances of T:
    1. If it is a data member of
      1. A garbage-collected class, e.g. Composer: Add Member<T> to it and trace it in Composer::trace(Visitor*).
      2. A non-garbage-collected class: Use Persistent<T>.
    2. Use T* if it is a local variable or a function argument.

Destructor

Destructors are called for GarbageCollectedFinalized. However we can't access other garbage-collected objects in destructors. We can't manage destruction order of objects, and we can't know when collectable objects are destructed.
We recommend to make destructor implementations empty.

Part classes

If a class P is always used as a data member of another class O, P can't be garbage-collected. If P has a data member of a garbage-collected class, we should do the followings:
  • Make the data member Persistent<> if O is not garbage-collected
  • If O is garbage-collected,
    • Make the data member Member<>
    • Mark P DISALLOW_ALLOCATION()
    • Add P::trace()
    • Trace P in O::trace()
class P { DISALLOW_ALLOCATION(); public: DEFINE_INLINE_TRACE() { visitor->trace(m_data); } private: Member<T> m_data; }; class O : public GarbageCollected<O> { public: DEFINE_INLINE_TRACE { visitor->trace(m_p); } private: P m_p; };

Stack-only classes

If a class S is always allocated on a stack, and S has a data member of a garbage-collected class, we should do
  • Mark S STACK_ALLOCATED()
  • Make the data member Member<>
  • Not add S::trace()

WeakMember<>

It becomes nullptr when garbage collection is executed and the pointed object can be collectable.

HeapHashSet<WeakMember<T>> and HeapHashMap<WeakMember<T>, U> have special behavior. When a T object stored in them is collected, HashSet/HashMap entry for it is automatically removed.

Multiple inheritance of garbage-collected classes, and USING_GARBAGE_COLLECTED_MIXIN(type)

A class can't inherit multiple classes which inherit GarbageCollected or GarbageCollectedFinalized. In such case, one of the base classes should inherit GarbageCollected or GarbageCollectedFinalized, and other base classes should inherit GarbageCollectedMixin. The derived class should have USING_GARBAGE_COLLECTED_MIXIN(DerivedClass).

class Base1 : public GarbageCollected<Base1> { DECLARE_VIRTUAL_TRACE(); }; class Interface : public GarbageCollectedMixin { DECLARE_VIRTUAL_TRACE(); }; class DerivedClass : public Base1, public Interface { USING_GARBAGE_COLLECTED_MIXIN(DerivedClass); public: DEFINE_INLINE_VIRTUAL_TRACE() { Base1::trace(visitor); Interface::trace(visitor); } };

Collections, and ALLOW_ONLY_INLINE_ALLOCATION()

Collections of references to garbage-collected class T should be
  • "Heap" version of collection classes.  e.g. HeapVector, HeapHashSet, HeapHashMap
  • Component type should be Member<T> or WeakMember<T>.  Do not use T*.

Stack-allocated heap collections are automatically traced.  Data members of heap collections needs to be traced in trace(Visitor*).

Collections of class T which contains references to garbage-collected classes should be
  • Normal collection of T, and T contains Persistent<>.
It works.  But we'd like to avoid Persistent<>. We recommend:
  • "Heap" version of collection classes
  • Component type should be T as is.
  • T should have ALLOW_ONLY_INLINE_ALLOCATION(), Member<>s, and trace() function.
ALLOW_ONLY_INLINE_ALLOCATION() means the class can't be used outside of collections. If T has ALLOW_ONLY_INLINE_ALLOCATION() and T is put in HeapVector, then T may additionally need a custom vector trait specialization using WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS() and similar.

// Not recommended: class T { private: Persistent<G> m_t; }; Vector<T> list; // Recommended:
namespace blink {

class Component { ALLOW_ONLY_INLINE_ALLOCATION(); public: DEFINE_INLINE_TRACE() { visitor->trace(m_g); } private: Member<G> m_g; };

} // namespace blink
WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::Component);
HeapVector<T> list; 

Trivially constructible classes do not need the trait specialization. If your class isn't trivial in that sense, you'll run into a static assert when compiling, saying something like:

 HeapVectorBacking doesn't support objects that cannot be initialized with memset.

Threading

A garbage-collected object is owned by a thread which allocated it, and the object is destructed in the owner thread.  Objects in other threads can have references to the object.  All of references to the object must be cleared before the owner thread termination, and the object can't outlive the owner thread.

A Persistent<T> object is also owned by a thread which allocate it. It must be destructed in the owner thread.  We can destruct CrossThreadPersistent<T> in non-owner thread.  However it doesn't mean we can transfer the ownership of an object pointed by a CrossThreadPersistent<T> to another thread.

DEFINE_TRACE macros

T::trace() methods are defined via DEFINE_TRACE helper macros.

By using DEFINE_TRACE macros, we can define the two variants of trace() methods (the fast path T::trace(InlinedGlobalMarkingVisitor), and slow path T::trace(Visitor*)) at once.

 Macro Expansion 
class X : public GarbageCollected {
public:
    DECLARE_TRACE();
};
class X : public GarbageCollected {
public:
    void trace(Visitor*);
    void trace(Visitor);
};
class X : public GarbageCollected {
public:
    DECLARE_TRACE();
};
class X : public GarbageCollected {
public:
    virtual void trace(Visitor*);
    virtual void trace(InlinedGlobalMarkingVisitor);
};
class X : public GarbageCollected {
public:
    DECLARE_INLINE_TRACE() { visitor->trace(m_a); }

private:
    Member<Y> m_a;
};
class X : public GarbageCollected {
public:
    void trace(Visitor* visitor) { visitor->trace(m_a); }
    void trace(InlinedGlobalMarkingVisitor visitor) { visitor->trace(m_a); }

private:
    Member<Y> m_a;
};
class X : public GarbageCollected {
public:
    DECLARE_VIRTUAL_INLINE_TRACE()
    { visitor->trace(m_a); }

private:
    Member<Y> m_a;
};
class X : public GarbageCollected {
public:
    virtual void trace(Visitor* visitor)
    { visitor->trace(m_a); }

    virtual void trace(InlinedGlobalMarkingVisitor visitor)
    { visitor->trace(m_a); }


private:
    Member<Y> m_a;
};
DEFINE_TRACE(X)
{
  visitor->trace(m_a);
}
void X::trace(Visitor* visitor)
{
  visitor->trace(m_a);
}

void X::trace(InlinedGlobalMarkingVisitor visitor)

{
  visitor->trace(m_a);
}

After: class X { DECLARE_TRACE(); }
Before: class X { virtual void trace(Visitor*); }

Before: class X { virtual void trace(Visitor*) {...} };
After: class X { DEFINE_INLINED_TRACE(virtual,) { ... } };

Before: void X::trace(Visitor* visitor) { ... }
After: DEFINE_TRACE(X) { ... }



Transition to GC from reference counting

Transition table

Old New Transition  
class Foo : public ScriptWrappable, public RefCounted<Foo> { class Foo : public GarbageCollectedFinalized<Foo>, public ScriptWrappable {
RefCountedWillBeGarbageCollectedFinalized If we need to run the destructor of one of the parent classes, we should  make this class "Finalized."
We should put GarbageCollected(Finalized) first if the class uses multiple inheritance.
class Foo : public RefCounted<Foo> {
public:
    ~Foo() { /* meaningful code here */ }

class Foo : public GarbageCollectedFinalized<Foo> {
public:
    ~Foo() { ... }
RefCountedWillBeGarbageCollectedFinalized If we need to run the destructor of this class, we should make this class "Finalized."
class Foo : public RefCounted<Foo> {
public:
    ~Foo() { }
    OwnPtr<Bar> m_bar;

class Foo : public GarbageCollectedFinalized<Foo> {
public:
    ~Foo() { }
    OwnPtr<Bar> m_bar;
RefCountedWillBeGarbageCollectedFinalized If we need to run destructors of data members, we should make this class "Finalized."
class Foo : public RefCounted<Foo> { class Foo : public GarbageCollected<Foo> {
RefCountedWillBeGarbageCollected If we don't need to run the destructor at all, we should make this class "GarbageCollected."
class Foo : public RefCounted<Foo> { ... };
class Bar : public Foo { ... };
class Foo : public GarbageCollected<Foo> { ... };
class Bar : public Foo { ... };
  We don't need to make derived classes GarbageCollected.

Old New Transition  
class Foo {
    RefPtr<Bar> m_bar;
};
class Bar : RefCounted<Bar> { ... };
class Foo {
    Persistent<Bar> m_bar;
};
class Bar : GarbageCollected<Bar> { ... };

RefPtrWillBePersistent A data member pointing an on-heap object from off-heap object should be Persistent.
class Foo : RefCounted<Foo> {
    RefPtr<Bar> m_bar;
};
class Bar : RefCounted<Bar> { ... };
class Foo : GarbageCollectedFinalized<Foo> {
    RefPtr<Bar> m_bar;
};
class Bar : RefCounted<Bar> { ... };
  No changes for data members pointing off-heap objects from on-heap objects.
class Foo : RefCounted<Foo> {
    RefPtr<Bar> m_bar;
};
class Bar : RefCounted<Bar> { ... };
class Foo : GarbageCollected<Foo> {
    Member<Bar> m_bar;
    DEFINE_INLINE_TRACE() { visitor->trace(m_bar); }
};
class Bar : GarbageCollected<Bar> { ... };
RefPtrWillBeMember A data member pointing an on-heap object from another on-heap object should be a Member.
Also, the data member should be traced.
class Foo {
    Vector<RefPtr<Bar>> m_bar;
};
class Bar : RefCounted<Bar> { ... };
class Foo {
    PersistentHeapVector<Member<Bar> > m_bar;
};
class Bar : GarbageCollected<Bar> { ... };
 WillBePersistentHeapVector< RefPtrWillBeMember<>> Collections of on-heap objects must be either of HeapFoo or PersistentHeapFoo. PersistentHeapVector should be used in this case because the field is a member of an off-heap object.
class Foo : RefCounted<Foo> {
    HashSet<RefPtr<Bar> > m_bar;
    HashSet<Baz> m_baz;
};
class Bar : RefCounted<Bar> { ... };
class Foo : GarbageCollected<Foo> {
    HeapHashSet<Member<Bar> > m_bar;
    HashSet<Baz> m_baz;
    void trace(Visitor* visitor) { visitor->trace(m_bar); }
};
class Bar : GarbageCollected<Bar> { ... };
WillBeHeapHashSet< RefPtrWillBeMember<>> Collections of on-heap objects must be either of HeapFoo or PersistentHeapFoo. HeapHashSet should be used in this case because the field is a member of an on-heap object.

Old New Transition  
void f()
{
    RefPtr<Foo> protector(this);
    ....
void f()
{
    ....
RefPtrWillBeRawPtr Local pointers are automatically traced.
PassRefPtr<Foo> create()
{
    return adoptRef(new Foo);
}

Foo* create() {
    return new Foo;
}

RefPtrWillBeRawPtr, adoptRefWillBeNoop Return values and arguments of functions are automatically traced.

void f()
{
    Vector<RefPtr<Foo> > fooList;

void f()
{
    HeapVector<Member<Foo> > fooList;

WillBeHeapVector< RefPtrWillBeMember<>> Collections of on-heap objects must be either of HeapFoo or PersistentHeapFoo. HeapVector should be used in this case because local variables are never persistent.

Comments