This great answer explains how GC is able to collect local variables before the method finishes executing:
The jitter performs two important duties when it compiles the IL for a method into machine code. ... It also generates a table that describes how the local variables inside the method body are used. That table has an entry for each method argument and local variable with two addresses. The address where the variable will first store an object reference. And the address of the machine code instruction where that variable is no longer used. ... The "no longer used" address in the table is very important. It makes the garbage collector very efficient. It can collect an object reference, even if it is used inside a method and that method hasn't finished executing yet.
I'm curious about how JIT created internal tables look like, and how "no longer used" addresses are maintained in the real clr source code. Can anybody show me related code snippets in the recently open sourced coreclr source code?
Disclaimer: I'm no expert on the CLR or RyuJIT. I may be completely wrong about all of this.
I came across the following section in the RyuJIT chapter of the Book of the Runtime:
For lvlVars with tracked lifetimes, or for expression involving GC references, we report the range over which the reference is live. This is done by the emitter, which adds this information to the instruction group, and which terminates instruction groups when the GC info changes.
The structure that appears to store this information can be found in jit/jitgcinfo.h and looks like this:
struct varPtrDsc
{
varPtrDsc * vpdNext;
unsigned vpdVarNum; // which variable is this about?
unsigned vpdBegOfs ; // the offset where life starts
unsigned vpdEndOfs; // the offset where life starts
};
The paragraph I quoted above suggests that these fields are filled by "the emitter", by which I believe they mean jit/emit.cpp.
The start of the lifetime interval is set in emitter::emitGCvarLiveSet()
; the relevant excerpt is (blanks eliminated for brevity):
/* Allocate a lifetime record */
desc = new (emitComp, CMK_GC) varPtrDsc;
desc->vpdBegOfs = emitCurCodeOffs(addr);
#ifdef DEBUG
desc->vpdEndOfs = 0xFACEDEAD;
#endif
desc->vpdVarNum = offs;
desc->vpdNext = NULL;
The end of the lifetime is set in a similar manner, in emitter::emitGCvarDeadSet()
:
/* Record the death code offset */
assert(desc->vpdEndOfs == 0xFACEDEAD);
desc->vpdEndOfs = emitCurCodeOffs(addr);
Finally, the tables appear to be written in jit/gcencode.cpp, specifically in GCInfo::gcMakeVarPtrTable()
.
Hopefully this will serve as a starting point if you want to explore further.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With