summaryrefslogtreecommitdiffstats
path: root/xpcom/docs/cc-macros.rst
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /xpcom/docs/cc-macros.rst
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xpcom/docs/cc-macros.rst190
1 files changed, 190 insertions, 0 deletions
diff --git a/xpcom/docs/cc-macros.rst b/xpcom/docs/cc-macros.rst
new file mode 100644
index 0000000000..7877b3aabe
--- /dev/null
+++ b/xpcom/docs/cc-macros.rst
@@ -0,0 +1,190 @@
+=======================================
+How to make a C++ class cycle collected
+=======================================
+
+Should my class be cycle collected?
+===================================
+
+First, you need to decide if your class should be cycle
+collected. There are three main criteria:
+
+* It can be part of a cycle of strong references, including
+ refcounted objects and JS. Usually, this happens when it can hold
+ alive and be held alive by cycle collected objects or JS.
+
+* It must be refcounted.
+
+* It must be single threaded. The cycle collector can only work with
+ objects that are used on a single thread. The main thread and DOM
+ worker and worklet threads each have their own cycle collectors.
+
+If your class meets the first criteria but not the second, then
+whatever class uniquely owns it should be cycle collected, assuming
+that is refcounted, and this class should be traversed and unlinked as
+part of that.
+
+The cycle collector supports both nsISupports and non-nsISupports
+(known as "native" in CC nomenclature) refcounting. However, we do not
+support native cycle collection in the presence of inheritance, if two
+classes related by inheritance need different CC
+implementations. (This is because we use QueryInterface to find the
+right CC implementation for an object.)
+
+Once you've decided to make a class cycle collected, there are a few
+things you need to add to your implementation:
+
+* Cycle collected refcounting. Special refcounting is needed so that
+ the CC can tell when an object is created, used, or destroyed, so
+ that it can determine if an object is potentially part of a garbage
+ cycle.
+
+* Traversal. Once the CC has decided an object **might** be garbage,
+ it needs to know what other cycle collected objects it holds strong
+ references to. This is done with a "traverse" method.
+
+* Unlinking. Once the CC has decided that an object **is** garbage, it
+ needs to break the cycles by clearing out all strong references to
+ other cycle collected objects. This is done with an "unlink"
+ method. This usually looks very similar to the traverse method.
+
+The traverse and unlink methods, along with other methods needed for
+cycle collection, are defined on a special inner class object, called a
+"participant", for performance reasons. The existence of the
+participant is mostly hidden behind macros so you shouldn't need
+to worry about it.
+
+Next, we'll go over what the declaration and definition of these parts
+look like. (Spoiler: there are lots of `ALL_CAPS` macros.) This will
+mostly cover the most common variants. If you need something slightly
+different, you should look at the location of the declaration of the
+macros we mention here and see if the variants already exist.
+
+
+Reference counting
+==================
+
+nsISupports
+-----------
+
+If your class inherits from nsISupports, you'll need to add
+`NS_DECL_CYCLE_COLLECTING_ISUPPORTS` to the class declaration. This
+will declare the QueryInterface (QI), AddRef and Release methods you
+need to implement nsISupports, as well as the actual refcount field.
+
+In the `.cpp` file for your class you'll have to define the
+QueryInterface, AddRef and Release methods. The ins and outs of
+defining the QI method is out-of-scope for this document, but you'll
+need to use the special cycle collection variants of the macros, like
+`NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION`. (This is because we use
+the nsISupports system to define a special interface used to
+dynamically find the correct CC participant for the object.)
+
+Finally, you'll have to actually define the AddRef and Release methods
+for your class. If your class is called `MyClass`, then you'd do this
+with the declarations `NS_IMPL_CYCLE_COLLECTING_ADDREF(MyClass)` and
+`NS_IMPL_CYCLE_COLLECTING_RELEASE(MyClass)`.
+
+non-nsISupports
+---------------
+
+If your class does **not** inherit from nsISupports, you'll need to
+add `NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING` to the class
+declaration. This will give inline definitions for the AddRef and
+Release methods, as well as the actual refcount field.
+
+Cycle collector participant
+===========================
+
+Next we need to declare and define the cycle collector
+participant. This is mostly boilerplate hidden behind macros, but you
+will need to specify which fields are to be traversed and unlinked
+because they are strong references to cycle collected objects.
+
+Declaration
+-----------
+
+First, we need to add a declaration for the participant. As before,
+let's say your class is `MyClass`.
+
+The basic way to declare this for an nsISupports class is
+`NS_DECL_CYCLE_COLLECTION_CLASS(MyClass)`.
+
+If your class inherits from multiple classes that inherit from
+nsISupports classes, say `Parent1` and `Parent2`, then you'll need to
+use `NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MyClass, Parent1)` to
+tell the CC to cast to nsISupports via `Parent1`. You probably want to
+pick the first class it inherits from. (The cycle collector needs to be
+able to cast `MyClass*` to `nsISupports*`.)
+
+Another situation you might encounter is that your nsISupports class
+inherits from another cycle collected class `CycleCollectedParent`. In
+that case, your participant declaration will look like
+`NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MyClass,
+CycleCollectedParent)`. (This is needed so that the CC can cast from
+nsISupports down to `MyClass`.) Note that we do not support inheritance
+for non-nsISupports classes.
+
+If your class is non-nsISupports, then you'll need to use the `NATIVE`
+family of macros, like
+`NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(MyClass)`.
+
+In addition to these modifiers, these different variations have
+further `SCRIPT_HOLDER` variations which are needed if your class
+holds alive JavaScript objects. This is because the tracing of JS
+objects held alive by this class must be declared separately from the
+tracing of C++ objects held alive by this class so that the garbage
+collector can also use the tracing. For example,
+`NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(MyClass,
+Parent1)`.
+
+There are also `WRAPPERCACHE` variants of the macros which you need to
+use if your class is wrapper cached. These are effectively a
+specialized form of `SCRIPT_HOLDER`, as a cached wrapper is a
+JS object held alive by the C++ object. For example,
+`NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(MyClass,
+Parent1)`.
+
+There is yet another variant of these macros, `SKIPPABLE`. This
+document won't go into detail here about how this works, but the basic
+idea is that a class can tell the CC when it is definitely alive,
+which lets the CC skip it. This is a very important optimization for
+things like DOM elements in active documents, but a new class you are
+making cycle collected is likely not common enough to worry about.
+
+Implementation
+--------------
+
+Finally, you must write the actual implementation of the CC
+participant, in the .cpp file for your class. This will define the
+traverse and unlink methods, and some other random helper
+functions. In the simplest case, this can be done with a single macro
+like this: `NS_IMPL_CYCLE_COLLECTION(MyClass, mField1, mField2,
+mField3)`, where `mField1` and the rest are the names of the fields of
+your class that are strong references to cycle collected
+objects. There is some template magic that says how many common types
+like RefPtr, nsCOMPtr, and even some arrays, should be traversed and
+unlinked. There’s also a variant `NS_IMPL_CYCLE_COLLECTION_INHERITED`,
+which you should use when there’s a parent class that is also cycle
+collected, to ensure that fields of the parent class are traversed and
+unlinked. The name of that parent class is passed in as the second
+argument. If either of these work, then you are done. Your class is
+now cycle collected. Note that this does not work for fields that are
+JS objects.
+
+However, if that doesn’t work, you’ll have to get into the details a
+bit more. A good place to start is by copying the definition of
+`NS_IMPL_CYCLE_COLLECTION`.
+
+For a script holder method, you also need to define a trace method in
+addition to the traverse and unlink, using
+`NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN` and other similar
+macros. You'll need to include all of the JS fields that your class
+holds alive. The trace method will be used by the GC as well as the
+CC, so if you miss something you can end up with use-after-free
+crashes. You'll also need to call `mozilla::HoldJSObjects(this);` in
+the ctor for your class, and `mozilla::DropJSObjects(this);` in the
+dtor. This will register (and unregister) each instance of your object
+with the JS runtime, to ensure that it gets traced properly. This
+does not apply if you have a wrapper cached class that does not have
+any additional JS fields, as nsWrapperCache deals with all of that
+for you.