Malloc, garbage collection. Liveness is undecidable, so collectors compute reachability instead.
A global pointer that moves up. Allocation is constant time, memory is never reused, and the heap grows until the kernel refuses. Selfie's own compiler runs on this and never frees a byte; for a compiler that is fine.
Add free and you need a free list, a fit policy, coalescing against fragmentation, and metadata per block: the book's tour from first-fit to slab allocators. Every design is a different notation for the same meaning, a function from requests to disjoint blocks, with a different cost profile.
And the read-modify-write of _bump is last week's race. Two threads can receive the same block. That is the assignment.
A block is live if the program will use it again and dead if it will not. Whether a program will ever use a given address is a property of its future behaviour: Rice, exactly. No collector decides liveness.
A block is reachable if some chain of pointers leads to it from a variable. Reachability is a property of the current state, computed by a graph traversal in time linear in the heap. Unreachable implies dead; the converse fails, and the difference is the memory a collector keeps although it is dead.
So every garbage collector computes the decidable over-approximation of the undecidable property. Safe, never precise. The design principle of the class again: bound what you cannot decide.
The roots are the global variables in the data segment and the locals and parameters on every stack. Mark everything reachable from them, transitively; sweep everything unmarked. Mark-and-sweep, and selfie's collector does this on the emulator's view of the guest's memory, or as a library inside the program, or both at once.
Which words are pointers? C* does not say; a word is a number. A conservative collector treats every word that could be a heap address as if it were one: an over-approximation of an over-approximation, still safe, still a bound. Boehm's collector made that fast; selfie's makes it readable.
Self-collecting selfie: the emulator's collector collects the memory of a guest that is running its own collector on the memory of its guest. Try -gc at two levels and read the profile.
Mark-and-sweep stops the program for a time linear in the heap. Fine for a compiler; not for a game, a pacemaker, or a kernel. Every collector since is a way to spread that time out.
Most objects die young: collect the young ones often. Do a little marking per allocation. Mark while the program runs, with a barrier that tells the collector what changed. Each is a bound traded for a cost.
Count pointers instead of tracing; free at zero. No pause, but cycles are never freed: an under-approximation of unreachability. Safe in the other direction: leaks, never dangling pointers.
Managed languages are languages whose runtime does this for you, and the price is paid on every allocation. The Cost chapter's trade, once more.
The last systems assignment, treiber-stack, builds a lock-free stack on the same two instructions. And rotor-bounds turns a model checker on your sequential systems code, because rotor does not yet model threads. Both are introduced in week 10.