Stack Frame Format in the Shared Source CLI

When executing just-in-time (JIT) compiled code in the Shared Source Common Language Infrastructure (SSCLI), there is one simple format for the stackframe and a one calling convention. All arguments are located on the stack and nothing is passed in registers.  In addition, the this pointer and the return buffer pointer, if present, are always in a fixed spot; and the prolog and epilog are identical from method to method. Finally, only the ESI register is saved. Other details to take into account are described below.

Note: In non-JIT-compiled code, such as helper stubs, other calling conventions are at work, so do not expect to see a completely homogenous stack. All calls to or from JIT-compiled code follow the calling convention, but calls between non-JIT-compiled components can follow other conventions.

Parameter Reordering

Create a stack frame as follows: In the caller's code, up to and including the call instruction, arguments to the method are pushed into the pushed argument area. After this, the return address is pushed automatically as part of the call instruction. Arguments are pushed onto the stack in right-to-left order (using the "CIL calling convention"). For a function void MyFunc( a, b, c ), parameter a is pushed first, then b, and then c. By the time the call instruction is encountered in the CIL stream, the arguments have been pushed onto the stack; now the JIT compiler will look for the first two arguments whose size is 4 bytes or smaller, first considering a, then b, and so on. Once two arguments matching the 4-byte size criterion are found, they are extended to 4 bytes (if necessary), and then hoisted into registers ECX and EDX. After this, the rest of the arguments on the stack are moved to take up the freed space, and finally, EDX is pushed back onto the stack, followed by ECX.

The following is an example of this sequence, for the function:
void MyFunc(int32 a, int64 b, int64 c, int32 d, int32 e)
Before IL Call instructionBefore X86 call
 
Top of Stack

e - int32
d - int32 
c - int64
b - int64
a - int32

(The two 4 byte arguments found are 'a' and 'd'.)
Top of stack

a - int32
d - int32
e - int32
c - int64
b - int64

Implied Parameters

The this pointer is a special argument which is not listed as an argument of the function in CIL, but is always present in method calls on objects. If the this pointer is present, it will always be the first __cdecl argument. The "return buffer pointer" is also a special argument that does not appear in the CIL function signature. The return buffer pointer will be the second __cdecl argument if the "this pointer" is present; otherwise, it is the first argument.

The following examples show these implied parameters.

 
ValueClass MyFunc(int32 a, int32 b)

Top of stack

ret. buffer  - int32
a            - int32
b            - int32


ValueClass MyFunc(int64 a, int32 b)

Top of stack

ret. buffer  - int32
b            - int32
a            - int64


ValueClass MyFunc(int64 a, int64 b)

Top of stack

ret. buffer  - int32
b            - int64
a            - int64


ValueClass MyFunc(int32 this_pointer, int64 b, int64 c)

Top of stack

this pointer - int32
ret. buffer  - int32
c            - int64
b            - int64

FCALL Matching

The order of parameters to an FCALL (see fcall.h) has to match the JIT calling convention described above, instead of matching the function signature as it appears in the framework classes. In fcall.h there are a number of macros to help in converting an CIL-style signature to the JIT calling convention.

Variable-length Parameter Lists

For functions with a variable number of parameters, a VarArg token is passed along with arguments. The signature of each call to a vararg function is described in the metadata, and the VarArg token can be used to look at the signature. The function signature does not describe the this pointer and the return buffer pointer, but the garbage collector expects to find them at a fixed offset, so they are pushed onto the stack after the VarArg token. All other arguments are pushed onto the stack before the VarArg token from left to right, and there is no reordering to pull out 4-byte arguments.

The following two examples show variable length parameter lists.

 
instance vararg void MyFunc(int64 a, int32 b, ValueClass c)

Top of stack

this pointer - int32
vararg token - int32
c            - sizeof( c )
b            - int32
a            - int64


instance vararg ValueClass MyFunc(int64 a, int32 b, ValueClass c)

Top of stack

this pointer - int32
ret. buffer  - int32
vararg token - int32
c            - sizeof( c )
b            - int32
a            - int64


Copyright (c) 2002 Microsoft Corporation. All rights reserved.