GC: Is "liveness" of objects really needs to be defined?
Created by: wks
Currently the Mu spec defines "live object" as being reachable from roots.
An alternative definition is to define heap objects as always live, but implementations can "cheat". We define:
A memory location has a lifetime. An internal reference (iref) to a memory location is valid as long as the memory location's lifetime has not expired. Specifically,
- The lifetime of a memory location in the heap begins when the
NEWHYBRIDthat allocates the heap object is executed. It never expires.
- The lifetime of a memory location in the global memory begins when the bundle that defines it is loaded. It never expires.
- The lifetime of a memory location in the stack begins when the
ALLOCAHYBRIDthat allocates the stack cell is executed. It expires when the function activation which the stack cell is allocated is destroyed by either returning, throwing an exception, or killing the stack.
If the Mu spec no longer define liveness by the "root set" and transitive reachability, the Mu implementation must infer those reachability rules from other parts of the spec. I believe a carefully defined spec implies the same rules as explicitly defined reachability rules.
Examples and corner cases
When an object is unreachable from the roots (previously defined as "dead"): Mu can reclaim the object. Since it cannot be reached, the client and Mu IR programs will never find out Mu is cheating about the lifetime of heap objects, which were defined as "forever". (You can kill an immortal if nobody can see him/her again.)
Object pinning: Since an address is exposed during the period of pinning, the GC must not collect the object otherwise the native code will find Mu is cheating. In other words, pinning keeps an object alive.
Weak references and finalisers
Weak reference: We must change the meaning of "weak references" because we no longer define "reachable". We can define it as:
- At any time, Mu may atomically set the values of some weak references to NULL if "after doing so, no one can prove that their referred objects can otherwise be reached". (This is not formal at all. Maybe weak references are really meaningless.) In this way, a Mu implementation that never clear weak references is a valid implementation. But an implementation that does so may legally do so.
Finaliser: It was not defined because of it is not guaranteed to be caught. But in order to allow the Mu implementation cheat, I define it as:
- Any object may have a "prevent-one-death" flag (set when a finalisable object is created).
- There is a queue maintained by Mu. (finalising queue. The client implements a finalising thread watching the queue.)
- At any time, Mu may atomically remove the "prevent-one-death" flag of an object and put it in the queue mentioned above, provided that "the only way to get a reference to that object is via the queue". (This does not sound very formal, either.) In this way, a Mu implementation that never call any finaliser is a valid implementation. But an implementation that does so may legally do so.