Does SWI-Prolog have storage local compounds

I took the opportunity to dig a little deeper into nb_setarg/3.
From what I see from the source code the duplicate term is
triggered if the given compound is storage global.

Isn’t this always the case, or are there also storage
local compounds in SWI-Prolog?

Yes. Atoms, small integers, and variables that are no reference pointers. In all these cases we can simply assign and there is no need to protect anything. In other cases we must avoid resetting the top of the stack to a point below where the term (or string, bigint or rational) lives. That is what freezeGlobal() does. A trick I learned from Bart Demoen.

Ok, it checks the 3rd argument and not the 2nd argument.
I misread the source code of the primitive built-in nb_setarg/3.

Does SWI-Prolog implement a generational garbage collection
scheme? Something like young and old objects? Would
this even apply to primitive built-ins such as nb_setarg/3 ?

No. Still on the TODO. GC closely follows the SICStus paper on that that you can find in the references. It has a couple of refinements and necessary extensions to deal with destructive (backtrackable) assignment. The paper describes briefly how to add generational GC. The need was never big enough to really implement it. I don’t know whether this algorithm would be affected by destructive assignment. Possibly, as you can find all pointers from old data to the current generation on the trail of the current generation, except for those created by non-backtrackable assignment. That would imply either generational GC becomes more expensive, or we need to add some sort of dummy entries to the trail, wasting trail space and slowing down unwinding the trail.

So far, proper scheduling of GC seems enough to avoid the need for generational GC. Scheduling, i.e., making the right decision between GC and expanding the stacks is crucial. This is currently based (in part) on the CPU time required for recent GC calls and the time used to do real work.

An alternative instead of a generational GC, would be an incremental GC.
So there would be only one generation, but the marking would be done
incremental. Actually its even possible to combine the two, i.e. provide

a garbage collector that is both incremental and generational. PyPy just
found a 10 years old bug in their incremental and generational GC:

Fixing a Bug in PyPy’s Incremental GC
Carl Friedrich Bolz-Tereick - 2024-03-26
https://www.pypy.org/posts/2024/03/fixing-bug-incremental-gc.html