CLI与Rotor中JIT(x86)的函数调用协定

CLI Calling Conventions [in ECMA-335]

CLI函数调用协定精确地描述了托管代码函数调用方Caller与被调用方Callee之间是如何利用堆栈来传递调用参数的。由于CLI采用了完全基于堆栈的简易虚拟机模型,因此,托管函数调用中所有的实参均通过CLI堆栈来传递。下面给出了在调用call指令之前,托管堆栈帧的构造步骤及示例:

1. this引用压栈,如果是方法调用的话
2. 函数调用实参从左到右进栈
3. 如果最后一个实参是可变数量参数的话,仅需其数组引用进栈即可
4. call/calli/callvirt等call指令返回之后,返回值或引用处于栈顶,函数调用参数全部退栈

CLI与Rotor中JIT(x86)的函数调用协定_第1张图片


JIT Calling Conventions of x86
[in Rotor 1.x]

JIT函数调用协定主要适用于JIT生成的本地代码(Jitted Native Code)之间、及其与执行引擎内部函数之间的参数传递过程。事实上,在真实的本地代码执行过程中并不存在着一个优美而又简单的CLI虚拟堆栈。在进行即时编译时,JIT Compiler需要将抽象的CLI函数调用协议转换成为具体的面向x86本地代码的JIT函数调用方式。在Rotor 1.x与Rotor 2.0中采用了不同的JIT调用协定,后者比前者更为简洁、高效。Rotor 1.x中的JIT调用协定不仅饶舌,而且让人看起来非常费解,其设计者David Stutz在公开演讲中曾多次提到了这一点,他指出:之所以采用这种令人困惑的参数传递方式,主要是为了尽可能避免和减少对执行引擎中已有的非托管代码部分(如Stub/JIT Prestub等处)的修改,貌似有偷懒之虞...

Rotor 1.x中的JIT函数调用协定主要分为两种类型,其步骤与示例分别如下:

A.无可变数量参数时:
1. 函数调用实参从左到右进栈
2. 将栈中的实参进行重排(Reordering)
  2.1 挑选出调用参数列表中最左边的两个可放置在4字节寄存器中的实参(如类型为byte/int的变量等)
  2.2 将其值分别存进ECX和EDX寄存器
  2.3 从堆栈中删除挑选出的实参,并压缩堆栈
  2.4 EDX进栈,然后ECX进栈
3. 函数返回缓冲区指针(Return buffer ptr)进栈,如果需要的话

B.存在可变数量参数时:
1. 函数调用实参从左到右进栈
2. 栈中实参无需进行Reordering
3. 将可变数量实参的实际参数数量压栈
4. 将可变数量实参的元数据类型Token压栈
5. Return buffer ptr进栈,如果需要有的话
6. this指针进栈,如果是对象方法调用的话

CLI与Rotor中JIT(x86)的函数调用协定_第2张图片


JIT Calling Conventions of x86
[in Rotor 2.0]

Rotor 2.0对JIT函数调用协定进行了再次的整理,其参数传递方式类似于__fastcall的改版,同时还利用了寄存器和浮点堆栈来实现整型及浮点类型的返回。相对整洁的流程及寄存器的使用令整个JIT函数调用过程得到了进一步的优化。其基本流程和2个实例如下:

1. this指针进栈(如果是方法调用的话),然后Return buffer ptr进栈(如果需要的话)
2. 所有非可变实参以从左至右的次序进栈
3. 如果存在可变数量参数,则所有可变实参从左至右进栈,然后再将可变实参的类型Token压栈
4. 从上述压栈次序中挑选出最前面两个可放置在4字节寄存器中的参数,分别存进ECX、EDX并将它们从栈中删除,然后压缩堆栈
5. 如果返回值为浮点数,该返回值将放置在FP堆栈的栈顶返回(非调用栈栈顶!)
6. 如果返回值为32位整数(或者是可扩充为32bit的类型),则通过EAX寄存器返回
7. 如果返回值为64位整数,则通过EAX:EDX寄存器返回
8. 其他返回类型一律通过Return buffer ptr返回

CLI与Rotor中JIT(x86)的函数调用协定_第3张图片

你可能感兴趣的:(JIT)