diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /xpcom/docs/cc-macros.rst | |
parent | Initial commit. (diff) | |
download | thunderbird-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.rst | 190 |
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. |