• .Net8的JIT是如何计算函数内存空间大小的?
  • 发布于 2个月前
  • 323 热度
    0 评论
前言
内存空间是程序的灵魂,没有了它,这个程序只能是一堆废代码。本篇来以.Net8的JIT第一个加载的C#函数StelemRef(它在System.Private.CoreLib.dll)为例,看下.Net8 PreView3里面是如何分配内存空间的大小的。

概括
一.分配要素
StelemRef的C#原型(为了便于阅读,代码经过提炼):
[DebuggerHidden]
[StackTraceHidden]
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private unsafe static void StelemRef(Array array, IntPtr index, object obj)
{
  ref object ptr = ref Unsafe.As<CastHelpers.ArrayElement[]>(array)[(int)(checked((IntPtr)(unchecked((long)index))))].Value;
  void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
  if (obj == null) {//省略} 
  if (elementType != (void*)RuntimeHelpers.GetMethodTable(obj) && !(array.GetType() == typeof(object[]))) {//省略}
 CastHelpers.WriteBarrier(ref ptr, obj);
}
它的IL代码(为了便于阅读,代码经过提炼):
.method private hidebysig static void  StelemRef(class System.Array 'array',native int index,object obj) cil managed
{
  .locals (object& V_0,
           void* V_1,
           bool V_2,
           bool V_3,
           bool V_4)
  //为了避免过多代码干扰,此处上下省略一万行
  IL_0002:  call       !!0 
  IL_0016:  call       valuetype 
  IL_002d:  call       valuetype 
  IL_0040:  call       void 
  IL_0050:  callvirt   instance class System.Type 
  IL_005a:  call       class System.Type 
  IL_005f:  call       bool System.Type::op_Equality(class 
  IL_006f:  call       void 
  IL_0074:  nop
  IL_0075:  ret
} // end of method CastHelpers::StelemRef
可以看到这个StelemRef函数的IL代码有三个参数,五个本地变量(.locals)。以及8个call的调用。这些都是分配的内存空间的必要元素。

二.分配的方式
1.参数
首先这个三个参数,array数组类型,index整型,obj的object类型,它们在JIT里面不参与内存空间的计算,所以被排除了。
2.本地变量
五个本地变量,object& V_0是个object类型,占8个字节,V_1是void* 类型占8字节,V_2是bool占四个字节,V_3是bool占四个字节,V_4是bool占四个字节。那么本地变量的占据的空间是8+8+4+4+4=0x1C(28)个字节。
3.call
再来看下8个call占用的空间,第一个call和最后一个call分别分配了4字节的空间2*4==8,中间的六个call每个8字节空间6*8==48,那么8个call总共占据的空间是56个字节。
4.预留空间
在第一个call和第二个call之间预留了8字节,在最后一个call(也就是第8个call)后面预留了44个字节。

5.画像

它的整体画像如下所示:
28         (0-7索引,三个参数及五个本地变量的空间) + 
4           (9索引,因为前面三个参数和五个本地变量占据了8(0-7)个索引,而第八个索引为空,所以此处是9,(第一个call占据的空间))  + 
8           (无索引,第一个call和第二个call中间的预留空间) +
48         (a-f索引 第二个call和第七个call占据的空间,总共六个call,每个八字节) + 
4           (0x10 index(索引) 最后一个call占据的空间) +
(4 +32 +8)  (这后面的全都是预留的空间)
那么28+4+8+48+4+4+32+8=136(0x88个字节)

三.观察机器码的分配
000002255E3020B8 55                   push        rbp  
000002255E3020B9 57                   push        rdi  
000002255E3020BA 48 81 EC 88 00 00 00 sub         rsp,88h  
000002255E3020C1 48 8D AC 24 90 00 00 00 lea         rbp,[rsp+90h]  
000002255E3020C9 C5 D8 57 E4          vxorps      xmm4,xmm4,xmm4 
000002255E3020CD C5 F9 7F 65 A0       vmovdqa     xmmword ptr [rbp-60h],xmm4
以上是StelemRef函数的机器头部代码,第三行 sub rsp,88h。这个88h就是上面计算的内存空间0x88,它进入的时候给StelemRef函数分配内存的栈空间的大小0x88。
用户评论