10.18 Garbage collection for dummies (LilyPond Contributor’s Guide)

### 10.18 Garbage collection for dummies

Note: Reading this section is strongly recommended before attempting complex C++ programming.

Within LilyPond, interaction with Guile is ubiquitous. LilyPond is written in C++ and Guile Scheme. Even in C++, most of the code uses Guile APIs to interface with the outside Scheme world, both with user and internal Scheme code.

Scheme is a garbage-collected language. This means that once in a while, a so-called garbage collector scans the memory for values that are no longer being used, and reclaims them. This process ensures that the memory is given back to the computer and made available for other uses. The garbage collector implementation used in Guile 2 and later is the Boehm-Demers-Weiser garbage collector (BDWGC).

C++, on the other hand, usually frees values at determined points of time (although most of the time they remain implicit, through the use of the famous “RAII” or “scope-bound resource management” technique). It has no direct support for garbage collection. This can make memory management of Scheme values in C++ a challenge (or a headache). Whenever you are using a value whose memory is managed by Guile, you must keep an eye on its lifetime.

To be more precise, the garbage collector works in a mark phase and a sweep phase. During marking, the collector scans values that the program is currently using, then asks these values for containing references to other values, and continues following references until all reachable objects have been found. Objects that are unreachable can logically no longer be used in the program, so they are freed in the sweep phase.

In Schemeland, the interpreter takes care of marking values for you. For instance, if you store a list in a variable, then during garbage collection, this list is automatically marked, and this causes all elements of the list to be marked in turn, which ensures they remain alive. In Cppland, you need to be very careful to keep values allocated on Guile’s heap as visible to the garbage collector if they cannot be reached from the Scheme side.

#### Understanding which values are under garbage-collected management

To begin with, which values are allocated on the Guile heap? The basic Guile API type is the SCM type, which represents a value boxed for usage in Scheme. The SCM type is pointer-sized piece of data. It is either a pointer to Scheme data structures (e.g. pair, double pair, etc.) – in this case, the pointer is 64-bit aligned and has its lower bits set to 0 –, or it is an immediate value (short integer, boolean, `'()`, etc.) – in which case the lower order bits are non-zero. Smobs, vectors, strings and many other Scheme data structures are represented as pairs, where the car holds a tag value (non-aligned, lower order bits set) and the cdr holds the pointer to data. From the scheme side, the fact that these types are represented using pairs is invisible.

Thus, for immediate SCM values, all the value is contained in the SCM itself. There is no concept of freeing these values, as they are never heap-allocated: they just keep being copied around, and dropped by normal C++ lifetime mechanisms when done (such as dropping local variables of a function when it returns). On the other hand, all other values point to memory allocated on the Guile heap. It is the lifetime of this memory that you need to care about.

LilyPond adds its own object types to Guile as well. They as called “smobs”, which depending on sources means “Scheme objects” or “small objects”. Smobs come in two flavors:

“Simple smobs” are objects that can be passed around by copy without changing the meaning. Their classes derive from `Simple_smob`. `Pitch` and `Duration` are good examples. The usual way to create them is just like a normal C++ object (e.g., `Smob_type variable (constructor parameters);`). When created in this way, simple smobs are allocated on the stack like any other C++ automatic variable, and dropped in the same way too. When you need to send a simple smob to Schemeland, you should call the member function `smobbed_copy ()`. This calls the smob’s copy constructor to make a copy under garbage collection control, packed in an SCM value.

“Complex smobs” are objects with an identity, such as `Music`, `Context` and `Grob`. Their classes derive from `Smob`. They are always created via the C++ `new` operator. After allocating, their memory is put under the control of the garbage collector. A complex smob has a field containing its SCM identity, which points back to itself. You can access this field using the member function `self_scm ()`.

The function to convert a SCM value back into the C++ smob type is `unsmob<Smob_type *> (value)` (which returns a null pointer if the SCM was not a value of the smob type in question). Because of the dual nature of simple smobs, you need to be mindful that if `Smob_type` derives from `Simple_smob`, the memory referred to by the result of `unsmob<Smob_type>` (if non-null) may either be on the stack or on the Guile heap, even though most of the time it will be on the Guile heap. On the other hand, for a complex smob, it will systematically be on the Guile heap.

#### How values are protected

When the garbage collector starts a collection, it first scans all memory being used by the program at the current point of time. This is called the root set. For Scheme, it includes all global variables of all modules and local variables of the function being executed. C++ adds everything that is on the stack and in registers (FIXME: investigate global variables). The dependencies of these values are then marked, etc.

#### Marking roots

The marking of the C++ function stack is very simple: scan the stack and treat every value as a possible pointer. This principle is called “conservative garbage collection”, and has a few consequences. One is that there may be some false positives, if random values on the stack happen to look the same as pointers to memory in the Guile heap. These values will be held longer than necessary, which is harmless.

Another, much more nasty consequence is that values are only kept alive while they have an SCM presence on the stack. Here is an example of what not to do:

>     Complex_smob_type *
>     func ()
>     {
>       Complex_smob_type *object = new Complex_smob_type ();
>       object->unprotect ();
>       return object;
>     }

When the caller of this function receives the object pointer, there is no reason for the object’s SCM identity (what would be returned by its `self_scm ()` method) to be present on the stack or in registers. Only the pointer to the C++ object is. This does not work to protect the object from garbage collection. The object could be freed if a GC pass occurs. The fix is to unprotect later if possible, at a point where the object’s `self_scm ()` is placed in a long-lived reachable Scheme data structure. Alternatively, if this is impractical, return an SCM to keep the object protected. The `unprotect ()` method actually returns the SCM for convenience.

>     SCM
>     func ()
>     {
>       Complex_smob_type *object = new Complex_smob_type ();
>       return object->unprotect ();
>     }

A different, even nastier trap can be illustrated with this example:

>     LY_DEFINE (ly_func, "ly:func",
>                1, 0, 0, (SCM param),
>                R"(
>     Doc
>                )")
>     {
>       Smob_type *object = unsmob<Smob_type> (param);
>       // do some stuff here, including
>       scm_cons (a, b)
>       // ...
>       return to_scm (object->some_field_);
>     }

At first glance, this looks fine. The SCM value `param` should remain on the stack until the end of the function, keeping the smob protected. This is not always true, however. If the compiler does a clever optimization, it might reuse the memory of the `param` variable for something else. If this happens, the object is unprotected while the memory of the cons cell is being allocated, which could cause the smob to be collected. The access `object->some_field_` is then use-after-free.

The solution to this is to use `scm_remember_upto_here`, which allows to forcefully keep the object alive:

>     LY_DEFINE (ly_func, "ly:func",
>                1, 0, 0, (SCM param),
>                R"(
>     Doc
>                )")
>     {
>       Smob_type *object = unsmob<Smob_type> (param);
>       // do some stuff here, including
>       scm_cons (a, b)
>       // ...
>       SCM field = to_scm (object->some_field_);
>       scm_remember_upto_here (param);
>       return field;
>     }

#### GC marking for smobs

Guile automatically marks the elements contained in compound values of the types it provides, like lists and vectors. LilyPond’s smobs must do the same in order to keep elements they refer to alive while they are themselves alive. This is done by implementing the member function `SCM mark_smob () const`. This function must call `scm_gc_mark` on every Scheme value that needs to be kept alive with the object. It can return an SCM value, which is marked in the same way. (This dates back to Guile 1, which used the C++ function stack to mark objects. It was necessary to keep the stack depth constant when marking objects such as lists, or stack overflows would have easily ensued. It is no longer very relevant in Guile 2.)

For many smob types, `mark_smob` needs to add marking to the implementation of the superclass. This is usually done using a `derived_mark` method. This is the case for translators, for example. The child class should thus just implement `derived_mark` and not override `mark_smob`.

For simple smobs allocated as automatic variables, i.e., outside of Guile’s control, `mark_smob` is not called during garbage collection. In this case, the only marking that the object receives is conservative scanning of the stack. This has the strong implication that a simple smob must contain all SCM values it refers to in its memory image on the stack. Anything that needs more complex marking behavior should be a complex smob. For example, it’s not OK for a simple smob to contain an `std::vector<SCM>`. On the other hand, that would be OK for a complex smob as long as its `mark_smob` function iterates over the vector to mark each element. The simplest solution is storing a Guile vector, of SCM type, which is OK even in simple smobs because the memory image on the stack is an SCM vector value, which during marking causes the marking of all vector elements, unlike an `std::vector<SCM>`.

#### Initial protection for complex smobs

When you create a complex smob, it receives an initial GC protection, which should be removed with its `unprotect ()` method once the complex smob enters an area where it is protected by other means.

There is no such protection for a `smobbed_copy ()` of a simple smob because those tend to be more short-lived and are often just returned to Scheme after being created.

TODO: expand on smob constructors, especially the need for Preinit classes. See `lily/include/smobs.hh`.

TODO: explain the quirks of finalization (non-)ordering. See commit `6555b3841a`.

#### LilyPond Contributor’s Guide v2.26.0 (stable-branch).

- 1 Introduction to contributing
  - 1.1 Help us
  - 1.2 Overview of work flow
  - 1.3 Summary for experienced developers
  - 1.4 Mentors
- 2 Quick start
  - 2.1 LilyDev
    - Installing LilyDev in VirtualBox
    - Configuring LilyDev in VirtualBox
  - 2.2 Compiling with LilyDev
  - 2.3 Now start work!
- 3 Working with source code
  - 3.1 Setting up
    - 3.1.1 Installing Git
    - 3.1.2 Creating a GitLab account and setting up SSH
    - 3.1.3 Cloning and forking the repository
    - 3.1.4 Configuring Git
  - 3.2 Git cheat sheet
  - 3.3 Lifecycle of a merge request
    - 3.3.1 Uploading a patch for review
    - 3.3.2 Automated testing
    - 3.3.3 Patch countdown
    - 3.3.4 Merging to master
    - 3.3.5 Abandoned patches
  - 3.4 Writing good commit messages
  - 3.5 Commit access
  - 3.6 Further Git documentation resources
  - 3.7 Repository directory structure
- 4 Compiling
  - 4.1 Overview of compiling
  - 4.2 Requirements
    - 4.2.1 Requirements for running LilyPond
    - 4.2.2 Requirements for compiling LilyPond
      - Fedora
      - Linux Mint
      - OpenSUSE
      - Ubuntu
      - Other
    - 4.2.3 Requirements for building documentation
  - 4.3 Getting the source code
  - 4.4 Configuring `make`
    - 4.4.1 Build modes
    - 4.4.2 Running `autogen.sh`
    - 4.4.3 Running `configure`
      - Configuration options
      - Checking build dependencies
      - Configuring target directories
  - 4.5 Compiling LilyPond
    - 4.5.1 Using `make`
    - 4.5.2 Saving time with the `-j` option
    - 4.5.3 Useful `make` variables
  - 4.6 Post-compilation options
    - 4.6.1 Installing LilyPond from a local build
    - 4.6.2 Generating documentation
      - Documentation editor’s edit/compile cycle
      - Building documentation
      - Building a single document
      - Saving time with `CPU_COUNT`
      - Installing documentation
      - Building documentation without compiling
    - 4.6.3 Testing LilyPond binary
  - 4.7 Problems
  - 4.8 Concurrent stable and development versions
    - 4.8.1 Replacing the notation fonts in development versions
  - 4.9 Build system
- 5 Documentation work
  - 5.1 Introduction to documentation work
  - 5.2 `\version` in documentation files
  - 5.3 Documentation suggestions
  - 5.4 Texinfo introduction and usage policy
    - 5.4.1 Texinfo introduction
    - 5.4.2 Documentation files
    - 5.4.3 Sectioning commands
    - 5.4.4 Menus
    - 5.4.5 LilyPond formatting
    - 5.4.6 Text formatting
    - 5.4.7 Syntax survey
      - Comments
      - Cross-references
      - External links
      - Fixed-width font
      - Indexing
      - Lists
      - Special characters
      - Miscellany
    - 5.4.8 Other text concerns
  - 5.5 Documentation policy
    - 5.5.1 Books
    - 5.5.2 Section organization
    - 5.5.3 Checking cross-references
    - 5.5.4 General writing
    - 5.5.5 Technical writing style
  - 5.6 Tips for writing documentation
    - 5.6.1 Working on subsections
    - 5.6.2 Searching
  - 5.7 Scripts to ease documentation work
    - 5.7.1 Building only one section of the documentation
    - 5.7.2 Updating documentation with `convert-ly`
  - 5.8 Documentation strings in the Internals Reference
  - 5.9 Translating the documentation
    - 5.9.1 Getting started with documentation translation
      - Translation requirements
      - Which documentation can be translated
      - Starting translation in a new language
    - 5.9.2 Documentation translation details
      - Files to be translated
      - Translating the website and other Texinfo documentation
      - Adding a Texinfo manual
    - 5.9.3 Documentation translation maintenance
      - Check state of translation
      - Updating documentation translation
      - Updating translation committishes
      - Maintaining without updating translations
    - 5.9.4 Technical background
- 6 Website work
  - 6.1 Introduction to website work
  - 6.2 Uploading website
  - 6.3 Debugging website and docs locally
  - 6.4 Translating the website
- 7 LilyPond Wiki work
  - 7.1 Introduction to the LilyPond Wiki
  - 7.2 Adding and editing snippets
  - 7.3 Snippet administration
  - 7.4 The `makelsr.pl` script
  - 7.5 LilyPond Wiki to Git
- 8 Issues
  - 8.1 Introduction to issues
  - 8.2 Triaging bugs
  - 8.3 Issue classification
  - 8.4 Adding issues to the tracker
- 9 Regression tests
  - 9.1 Introduction to regression tests
  - 9.2 Precompiled regression tests
  - 9.3 Compiling regression tests
  - 9.4 Regtest comparison
  - 9.5 Pixel-based regtest comparison
  - 9.6 Finding the cause of a regression
  - 9.7 MusicXML tests
- 10 Programming work
  - 10.1 Overview of LilyPond architecture
  - 10.2 LilyPond programming languages
  - 10.3 Programming without compiling
  - 10.4 Finding functions
  - 10.5 Code style
    - 10.5.1 Languages
    - 10.5.2 Filenames
    - 10.5.3 Code formatting
    - 10.5.4 Naming Conventions
    - 10.5.5 Broken code
    - 10.5.6 Code comments
    - 10.5.7 Handling errors
    - 10.5.8 Localization
  - 10.6 Warnings, Errors, Progress and Debug Output
  - 10.7 Debugging LilyPond
    - 10.7.1 Debugging overview
    - 10.7.2 Debugging C++ code
    - 10.7.3 Debugging Scheme code
    - 10.7.4 Debugging scoring algorithms
    - 10.7.5 Debugging skylines
  - 10.8 Tracing object relationships
  - 10.9 Tracing processing time
  - 10.10 Adding or modifying features
    - 10.10.1 Write the code
    - 10.10.2 Write regression tests
    - 10.10.3 Write convert-ly rule
    - 10.10.4 Automatically update documentation
    - 10.10.5 Manually update documentation
    - 10.10.6 Edit changes.tely
    - 10.10.7 Verify successful build
    - 10.10.8 Verify regression tests
    - 10.10.9 Post patch for comments
    - 10.10.10 Push patch
    - 10.10.11 Closing the issues
  - 10.11 Iterator tutorial
  - 10.12 Engraver tutorial
    - 10.12.1 Useful methods for information processing
    - 10.12.2 Translation process
    - 10.12.3 Listening to music events
    - 10.12.4 Acknowledging grobs
    - 10.12.5 Engraver declaration/documentation
  - 10.13 Callback tutorial
  - 10.14 Understanding pure properties
    - 10.14.1 Purity in LilyPond
    - 10.14.2 Writing a pure function
    - 10.14.3 How purity is defined and stored
    - 10.14.4 Where purity is used
    - 10.14.5 Case studies
    - 10.14.6 Debugging tips
  - 10.15 LilyPond scoping
  - 10.16 Regular expressions
  - 10.17 Scheme->C interface
    - 10.17.1 Comparison
    - 10.17.2 Conversion
  - 10.18 Garbage collection for dummies
  - 10.19 LilyPond miscellany
    - 10.19.1 Spacing algorithms
    - 10.19.2 Info from Han-Wen email
    - 10.19.3 Music functions and Guile debugging
    - 10.19.4 Articulations on EventChord
- 11 Release work
  - 11.1 Development phases
  - 11.2 Release checklist
  - 11.3 Major release checklist
- 12 Modifying the Emmentaler font
  - 12.1 Overview of the Emmentaler font
  - 12.2 Font creation tools
  - 12.3 Adding a new font section
  - 12.4 Adding a new glyph
  - 12.5 Building the changed font
  - 12.6 METAFONT formatting rules
- 13 Administrative policies
  - 13.1 LilyPond is GNU Software
  - 13.2 Environment variables
  - 13.3 Performing yearly copyright update (“grand-replace”)
- Appendix A GNU Free Documentation License