GC Rooting Guide

  • Revision slug: SpiderMonkey/GC_Rooting_Guide
  • Revision title: GC Rooting Guide
  • Revision id: 304473
  • Created:
  • Creator: terrence
  • Is current revision? No
  • Comment

Revision Content

Warning: this is still in heavy development.  No terms should be taken as final... yet.

SpiderMonkey API GC Rooting Guide

Definitions

  • GCThing - Storage allocated by SpiderMonkey's allocator.
  • GCPointer - A raw pointer type or tagged pointer type that may refer to a GCThing (JSObject *, JSString *, Value, jsid, etc)
  • GCHeap - The set of GCThings allocated by SpiderMonkey's allocator.  This set is interconnected by GCPointers stored in the GCThings.  The goal of SpiderMonkey's GC is to partition this set into "reachable" and "unreachable" sets.
  • RootSet - This is a set of GCPointers that will be considered de-facto to be alive by the GC.
  • LiveSet - The set of all GCThings that are reachable from Roots.
  • DeadSet - The set of all DeadObjects.
  • CHeap - The C/C++ program heap: e.g. memory allocated by malloc/calloc/realloc.
  • CStack - The C/C++ program stack.
  • Root - A GCPointer in the RootSet or a GCThing held live by a direct reference in the RootSet, depending on context.
  • rooted - A GCThing in the LiveSet is said to be "Rooted" even though it is not a Root as such, but is only reachable from Roots.  This guide will attempt to distinguish Live from rooted to avoid confusion.

Overview

The goal of this guide is to ensure that you, a SpiderMonkey API user, know what to do to keep your GCThings rooted.  Specifically, this guide will help you ensure your objects stay in the LiveSet while you are using them and only pass on to the DeadSet when you are ready.

This guide starts with the assumption that you have just used SpiderMonkey's APIs to reserve a GCThing in the GCHeap and that the API has returned to you a GCPointer.  Congratulations.  The GCPointer that we returned is not in the RootedSet.  This means the GCThing is not, at the moment, in the LiveSet.  It is, in fact, in the DeadSet.  If a GC occurs before you put it in the LiveSet, it will be discarded and its body poisoned.  If you had grand hopes and dreams for the future of this object, the GC will have just ruined your day.  Do not let this happen to you.

When you decide to add your GCPointer to the LiveSet, the process you follow will vary depending on where the GCThing is going to be stored.  GCPointers can be stored in a GCThing, on the CHeap, or in the CStack.  These three memory regions have different lifetime and overhead characteristics and thus require different management strategies for efficient space and CPU utilization.  C++ does not provide us with any reliable mechanism for determining the storage class of an object, therefore, we have to put the burden of this distinction on the user.  Fortunately, C++'s type system, TBPL, and this guide all help us to get this right, 100% of the time.

Storing a GCPointer in a GCThing

Storing your GCPointer inside of a GCThing that is already in the LiveSet is one of the easiest ways to add a new GCThing to the LiveSet.  GCPointers that reside in GCThings fall into one of two cases: storage in a slot or storage in the privates.  Do not store GCPointers in privates.  If you see a GCPointer stored in a private, please file a bug.

TODO: allocating extra named slots

TODO: example setProperty

Storing a GCPointer on the CHeap

TODO: Tracing and HeapPtr

Storing a GCPointer on the CStack

GCPointers stored on the stack are special.  Unlike other GCPointers, they are not traced as part of the GC.  Instead, every pointer on the CStack is considered part of the RootedSet.  To that end, we have a special "Rooting" mechanism for stack pointers that is very efficiently able to add and remove GCPointers from the RootedSet.

The downside of this approach is that GCPointers must be added to and removed from this RootedSet tracking in LIFO order.  Because of this restriction, it is very important that when you store a GCPointer on the stack, you must tell SpiderMonkey about it.  Conversely, you must also tell SpiderMonkey that it is no longer needed before the function exits.  All of this must follow the LIFO restriction.  Fortunately, SpiderMonkey has a convenient suite of C++ RAII classes to do this for you, intuitively called Rooted.   We have a specialized variant for each of the various GCThing types that the JS API exposes:

RootedObject obj(foo);

RootedString str(foo);

RootedFunction fun(foo);

RootedScript script(foo);

Note:  C++ insists that an initializing assignment (e.g. the default constructor followed by operator=) must have a copy constructor available, even if it is not used.  Since it is not valid to copy a Rooted (more on this in a second) we have deleted the copy constructor from these classes.  Therefore, we are forced to force you to initialize with constructor syntax.  Sorry.

Using the C++ type system to ensure safety

When the JS API returns a GCPointer, it returns it as a special Unrooted type.  This is to explicitly annotate the fact that the returned value must be rooted somehow.  Rooted, conveniently, has a constructor that takes an Unrooted.  Since Unrooted is opaque, the C++ type system will force you to root any GCPointer that comes out of the JS API before you can use it.

JS_SetProperty(cx, JS_NewObject(cx...), ...); // compile error

vs.

RootedObject tmp(JS_NewObject(cx, ...));

JS_SetProperty(cx, tmp, ...);

 

Revision Source

<div class="warning">
  <h2 id="Warning.3A_this_is_still_in_heavy_development..C2.A0_No_terms_should_be_taken_as_final..._yet.">Warning: this is still in heavy development.&nbsp; No terms should be taken as final... yet.</h2>
</div>
<h2 id="SpiderMonkey_API_GC_Rooting_Guide">SpiderMonkey API GC Rooting Guide</h2>
<h3 id="Definitions">Definitions</h3>
<ul>
  <li>GCThing - Storage allocated by SpiderMonkey's allocator.</li>
  <li>GCPointer - A raw pointer type or tagged pointer type that may refer to a GCThing (JSObject *, JSString *, Value, jsid, etc)</li>
  <li>GCHeap - The set of GCThings allocated by SpiderMonkey's allocator.&nbsp; This set is interconnected by GCPointers stored in the GCThings.&nbsp; The goal of SpiderMonkey's GC is to partition this set into "reachable" and "unreachable" sets.</li>
  <li>RootSet - This is a set of GCPointers that will be considered de-facto to be alive by the GC.</li>
  <li>LiveSet - The set of all GCThings that are reachable from Roots.</li>
  <li>DeadSet - The set of all DeadObjects.</li>
  <li>CHeap - The C/C++ program heap: e.g. memory allocated by malloc/calloc/realloc.</li>
  <li>CStack - The C/C++ program stack.</li>
  <li>Root - A GCPointer in the RootSet or a GCThing held live by a direct reference in the RootSet, depending on context.</li>
  <li>rooted - A GCThing in the LiveSet is said to be "Rooted" even though it is not a Root as such, but is only reachable from Roots.&nbsp; This guide will attempt to distinguish Live from rooted to avoid confusion.</li>
</ul>
<h3 id="Overview">Overview</h3>
<p>The goal of this guide is to ensure that you, a SpiderMonkey API user, know what to do to keep your GCThings rooted.&nbsp; Specifically, this guide will help you ensure your objects stay in the LiveSet while you are using them and only pass on to the DeadSet when you are ready.</p>
<p>This guide starts with the assumption that you have just used SpiderMonkey's APIs to reserve a GCThing in the GCHeap and that the API has returned to you a GCPointer.&nbsp; Congratulations.&nbsp; The GCPointer that we returned is <strong>not</strong> in the RootedSet.&nbsp; This means the GCThing is not, at the moment, in the LiveSet.&nbsp; It is, in fact, in the DeadSet.&nbsp; If a GC occurs before you put it in the LiveSet, it will be discarded and its body poisoned.&nbsp; If you had grand hopes and dreams for the future of this object, the GC will have just ruined your day.&nbsp; Do not let this happen to you.</p>
<p>When you decide to add your GCPointer to the LiveSet, the process you follow will vary depending on where the GCThing is going to be stored.&nbsp; GCPointers can be stored in a GCThing, on the CHeap, or in the CStack.&nbsp; These three memory regions have different lifetime and overhead characteristics and thus require different management strategies for efficient space and CPU utilization.&nbsp; C++ does not provide us with any reliable mechanism for determining the storage class of an object, therefore, we have to put the burden of this distinction on the user.&nbsp; Fortunately, C++'s type system, TBPL, and this guide all help us to get this right, 100% of the time.</p>
<h3 id="Storing_a_GCPointer_in_a_GCThing">Storing a GCPointer in a GCThing</h3>
<p>Storing your GCPointer inside of a GCThing that is already in the LiveSet is one of the easiest ways to add a new GCThing to the LiveSet.&nbsp; GCPointers that reside in GCThings fall into one of two cases: storage in a slot or storage in the privates.&nbsp; Do not store GCPointers in privates.&nbsp; If you see a GCPointer stored in a private, please file a bug.</p>
<p>TODO: allocating extra named slots</p>
<p>TODO: example setProperty</p>
<h3 id="CHeap">Storing a GCPointer on the CHeap</h3>
<p>TODO: Tracing and HeapPtr</p>
<h3 id="CStack">Storing a GCPointer on the CStack</h3>
<p>GCPointers stored on the stack are special.&nbsp; Unlike other GCPointers, they are not traced as part of the GC.&nbsp; Instead, every pointer on the CStack is considered part of the RootedSet.&nbsp; To that end, we have a special "Rooting" mechanism for stack pointers that is very efficiently able to add and remove GCPointers from the RootedSet.</p>
<p>The downside of this approach is that GCPointers must be added to and removed from this RootedSet tracking in LIFO order.&nbsp; Because of this restriction, it is very important that when you store a GCPointer on the stack, you must tell SpiderMonkey about it.&nbsp; Conversely, you must also tell SpiderMonkey that it is no longer needed before the function exits.&nbsp; All of this must follow the LIFO restriction.&nbsp; Fortunately, SpiderMonkey has a convenient suite of C++ RAII classes to do this for you, intuitively called <code>Rooted</code>.&nbsp;&nbsp; We have a specialized variant for each of the various GCThing types that the JS API exposes:</p>
<div class="note">
  <p><code>RootedObject obj(foo);</code></p>
  <p><code>RootedString str(foo);</code></p>
  <p><code>RootedFunction fun(foo);</code></p>
  <p><code>RootedScript script(foo);</code></p>
  <div class="warning">
    <p><strong>Note:</strong>&nbsp; C++ insists that an initializing assignment (e.g. the default constructor followed by operator=) must have a copy constructor available, even if it is not used.&nbsp; Since it is not valid to copy a Rooted (more on this in a second) we have deleted the copy constructor from these classes.&nbsp; Therefore, we are forced to force you to initialize with constructor syntax.&nbsp; Sorry.</p>
  </div>
</div>
<h4>Using the C++ type system to ensure safety</h4>
<p>When the JS API returns a GCPointer, it returns it as a special <code>Unrooted</code> type.&nbsp; This is to explicitly annotate the fact that the returned value must be rooted somehow.&nbsp; <code>Rooted</code>, conveniently, has a constructor that takes an <code>Unrooted</code>.&nbsp; Since <code>Unrooted</code> is opaque, the C++ type system will force you to root any GCPointer that comes out of the JS API before you can use it.</p>
<div class="note">
  <p><code>JS_SetProperty(cx, JS_NewObject(cx...), ...); // compile error</code></p>
  <p>vs.</p>
  <p><code>RootedObject tmp(JS_NewObject(cx, ...));</code></p>
  <p><code>JS_SetProperty(cx, tmp, ...);</code></p>
</div>
<p>&nbsp;</p>
Revert to this revision