The GC Rooting Guide and the guide for Exact Stack Rooting give a first overview, but there's still a lot to figure out. For the upgrade to ESR24 I've decided to avoid this topic because it's way to complicated and extensive to do it at the same time as the upgrade, but I'd still like to make some notes about important things I figure out.

Use cases and examples

Implicit conversion to JS::Value

Implicit conversion from JS::RootedValue, JS::HandleValue and JS::MutableHandle to JS::Value allows us using a rooted type at the top of a chain of functions that are not migrated yet. This does not work with JS::Value pointer arguments unfortunately. The following code is valid:

void bar(JS::Value val)
{   
}

void foo(JSContext* cx, JS::HandleValue handleVal, JS::MutableHandleValue mutableHandleVal)
{
    JS::RootedValue val(cx);
    bar(val);
    bar(handleVal);
    bar(mutableHandleVal);
}

Heap rooting

Rooting on the heap is much different than rooting on the stack. On the stack, new rooted types (like JS::Rooted<T>) are always constructed and destructed in LIFO order. This behaviour gets used for optimization and is essential for all stack rooting types. Data on the heap can be rooted and unrooted in arbitrary order and needs different types and different approaches.

The following sections are an attempt to describe the important differences as well as advantages and disadvantages of the available heap rooting approaches.

The JS::Heap<T> type

Wrapping the value in JS::Heap<T> only protects the pointer from becoming invalid when the GC thing it points to gets moved (moving GC). It does not protect the GC thing from being collected by the GC! This type always has to be used in combination with a rooting approach that protects against GC!

Using JS::Heap<T>, JS::Add*Root and JS::Remove*Root

Using JS::Add*Root is one way of protecting a GC thing from being collected. Later there needs to be a call to JS::Remove*Root to enable collection again. I haven't yet figured out a clear advantage of this approach compared to the other two approaches. It looks like this approach gets used rarely in Firefox and they are replacing it in favour of the other two approaches.

TODO: are they going to remove it from the API? Are there use cases where this rooting approach is superior?

Characteristics:

  • If no RAII approach is used, it can easily cause leaks if the call to Remove*Root() is missing.
  • In some Gecko use cases this can't be used because it can cause cycles and the cycle collector won't know about them (this is not relevant for us because we don't need a cycle collector).
  • It bloats up the root set. --> What does this mean in practice?
  • Add*Root is slow --> What does this mean in practice?

Guideline:

  • Try to use the other approaches for the moment. We still have to figure out when/if this approach should be used.

Example

Check the attached main.cpp for a full working example (including the base class).

#ifndef HEAPROOTINGTEST_H_INCLUDED
#define HEAPROOTINGTEST_H_INCLUDED

using namespace std;

class CHeapRootingTest : CTestClassBase
{
public:
    
    CHeapRootingTest(JSRuntime* rt, JSContext* cx) : CTestClassBase(rt, cx)
    {
        m_HeapValue.setObject(*InitObjectInternal());
        JS::AddValueRoot(m_cx, &m_HeapValue);
    }
    
    ~CHeapRootingTest()
    {
        JSAutoRequest rq(m_cx);
        JS::RemoveValueRoot(m_cx, &m_HeapValue);
    }
    
    void PrintUsecount()
    {
        JSAutoRequest rq(m_cx);
        if (!m_HeapValue.isObject())
            cerr << "Error: m_HeapValue is not an object!" << endl;
        
        JS::RootedObject objStack(m_cx, &m_HeapValue.toObject());
        PrintUsecountBase(objStack);
    }
    
private:
     JS::Heap<JS::Value> m_HeapValue;
};

#endif // HEAPROOTINGTEST_H_INCLUDED

Using JS::Heap<T> in combination with custom tracers

Implementing tracers and adding them via JS_AddExtraGCRootsTracer is one way of protecting against GC. Each tracer that gets added needs to be removed again later with JS_RemoveExtraGCRootsTracer.

Note: There are other ways to add a specified trace function (I haven't tested those yet).

Characteristics:

  • Obviously it's faster to add (and later remove) one function that gets called during GC and loops over many objects than adding (and removing) many objects to the GC root set in a loop.
  • Less memory overhead because storing a pointer to a trace function requires less memory than storing a large number of pointers to GC things.

Guideline:

  • This approach should be used if large numbers of GC things need to be rooted.

Example

Check the attached main.cpp for a full working example (including the base class).

#ifndef HEAPROOTINGTEST_H_INCLUDED
#define HEAPROOTINGTEST_H_INCLUDED

class CHeapRootingTest : CTestClassBase
{
public:
    
    static void Trace(JSTracer *trc, void *data)
    {
        reinterpret_cast<CHeapRootingTest*>(data)->TraceMember(trc);
    }
    
    void TraceMember(JSTracer *trc)
    {
        // The JS_Call<T>Tracer family of functions (those without the "Heap" in the name) will be deprecate in the future.
        JS_CallHeapValueTracer(trc, &m_HeapValue, "m_HeapValue");
    }

    CHeapRootingTest(JSRuntime* rt, JSContext* cx) : CTestClassBase(rt, cx)
    {
        m_HeapValue.setObject(*InitObjectInternal());
        JS_AddExtraGCRootsTracer(rt, CHeapRootingTest::Trace, this);
    }
    
    ~CHeapRootingTest()
    {
        JS_RemoveExtraGCRootsTracer(m_rt, CHeapRootingTest::Trace, this);
    }
    
    void PrintUsecount()
    {
        JSAutoRequest rq(m_cx);
        if (!m_HeapValue.isObject())
            cerr << "Error: m_HeapValue is not an object!" << endl;
        JS::RootedObject objStack(m_cx, &m_HeapValue.toObject());
        PrintUsecountBase(objStack);
    }
    
private:
     JS::Heap<JS::Value> m_HeapValue;
};

#endif // HEAPROOTINGTEST_H_INCLUDED

Using JS::PersistentRooted<T>

Characteristics:

  • This type both keeps the reference to the GC thing valid when it gets moved and it protects it against garbage collection.
  • Costs a small memory and CPU performance overhead (stores GC things to be rooted in a linked list internally).
  • It's obviously easier to use, the code looks cleaner and it's less error prone.

Guideline:

  • This approach should be used for small numbers of objects that don't get rooted and unrooted often.

Example

Check the attached main.cpp for a full working example (including the base class).

#ifndef HEAPROOTINGTEST_H_INCLUDED
#define HEAPROOTINGTEST_H_INCLUDED

class CHeapRootingTest : CTestClassBase
{
public:
    
    CHeapRootingTest(JSRuntime* rt, JSContext* cx) : m_HeapValue(cx), CTestClassBase(rt, cx)
    {
        m_HeapValue = JS::ObjectValue(*InitObjectInternal());
    }
    
    void PrintUsecount()
    {
        if (!m_HeapValue.get().isObject())
            cerr << "Error: m_HeapValue is not an object!" << endl;
        JS::RootedObject objStack(m_cx, &m_HeapValue.get().toObject());
        PrintUsecountBase(objStack);
    }
    
private:
     JS::PersistentRootedValue m_HeapValue;
};

#endif // HEAPROOTINGTEST_H_INCLUDED

Testing Rooting

JS_GC_ZEAL (increased GC frequency)

This is a debugging feature to increase the frequency of garbage collections. It should reveal issues that would only show up in rare cases under normal circumstances. If the feature is enabled in the SpiderMonkey build (--enable-gczeal), you can set the environment variable JS_GC_ZEAL to configure debugging. Set it to -1 to print a table of possible settings (or look up that table in jsgc.cpp).

The most useful settings probably are:

2: GC every F allocations (default: 100)"
7: Collect the nursery every N nursery allocations"

You can append a number separated by a comma to specify F or N respectively (like "2,1" to GC after every allocation or "7,10" to do a minor GC every 10 nursery allocations). With some settings the program gets extremely slow which makes it nearly impossible to use this feature to test a whole replay or even a normal game with maximum GC frequency.

Static rooting analysis

There's a separate wiki page for that: StaticRootingAnalysis

Last modified 10 years ago Last modified on Aug 21, 2014, 9:47:39 PM

Attachments (2)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.