GC垃圾回收的GCInfo编码过程

前言

这个GCInfo存储的是GC垃圾回收的时候,需要被标记的对象。这些对象通过奇特的编码形式,写入到GCInfo。当GC的时候,会通过奇特的解码模式把这些对象解码出来,进行标记。本篇来看下这个过程。.

概括

一:编码:
比如有一个对象在栈的rbp-8的位置。这个-8的十六进制64位是:0xFFFFFFFFFFFFFFF8,以n表示。-8的十六进制会通过以下编码形式存入到GCInfo。

0.
第一步申请一个地址,可以以变量m_pCurrentSlot表示。

1.
在栈对象写入到GCInfo的时候,它会有一个固定的栈标记编码插槽:STACK_SLOT_ENCBASE。这个编码代表的是0x6,以base表示。

2.
CLR会把常量值1左移STACK_SLOT_ENCBASE位,然后-1。再&上-8,如下:
(n &((1<< base)-1))==0x38,这种操作的结果是0x38.

3.
把2步骤重复一边,只不过base加上1再左移,&的则是2步骤计算的结果。
0x38 &(1<<(base+1) -1) == 0x38,这种操作的结果同样是0x38。

4.
把步骤3计算的结果进行左移(0x40-0x27)位。这个0x40是64位系统的sizeof(size_t)*8得来的,因为64位系统的一个地址存储的值的长度是64个bit位8字节。0x27则是当前计算的这个-8在第0步申请的地址的二进制偏移的位置。0x40-0x27也就是-8经过编码之后,所放置的bit位的位置。
0x38 << = (0x40-0x27) == 0x0000000070000000。

5.
放置-8编码后的结果0x0000000070000000。
*m_pCurrentSlot |= 0x0000000070000000

二:解码

通过上面的过程,把0x0000000070000000解码到0x38其实非常简单,只要把0x0000000070000000右移(0x40-027)位即可,但是要把0x38解码到-8,该怎么办呢?
记得上面的栈标记编码插槽STACK_SLOT_ENCBASE嘛,这个东西派上了用场。
首先把0x38左移(0x40-0x06(这个0x06就是STACK_SLOT_ENCBASE)),得到:E000 0000 0000 0000 。然后在把这个得到的结果:E000 0000 0000 0000右移(0x40-0x06),结果得到:FFFF FFFF FFFF FFF8。也就是-8。

三:原理

为什么会出现,把0x38先左移0x3A后右移0x3A就能得到-8。实质上你看到它二进制00111000就知道了。把00111000左移0x3A(0x40-0x27),也就是把它前面的bit位两个00去掉了,变成了111开头后加一个0也就是十六进制的E位最高位。然后又右移了0x3A,则是把0xE低bit位的两个1后面补两个0,其它高位则是用1填充,结果为-8,十六进制:FFFF FFFF FFFF FFF8。