.Net8的JIT是如何计算函数内存空间大小的?

前言

内存空间是程序的灵魂,没有了它,这个程序只能是一堆废代码。本篇来以.Net8的JIT第一个加载的C#函数StelemRef(它在System.Private.CoreLib.dll)为例,看下.Net8 PreView3里面是如何分配内存空间的大小的。.

概括

1.分配要素
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的调用。这些都是分配的内存空间的必要元素。

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

五.画像

它的整体画像如下所示:

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个字节)

3.观察机器码的分配

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。