zezba9000
zezba9000

Reputation: 3383

.NET IL / MSIL Evaluation Stack fundamentals

Can't seem to find a good answer for these questions.

Here are what I think I know and what I'm fuzzy on.

Take the example below. Does this local variable get stored on the evaluation stack by a ptr reference?

public struct MyStruct
{
    public long x, y, z;

    public static MyStruct Foo()
    {
        MyStruct c;
        c.x = 1;
        c.y = 2;
        c.z = 3;
        return c;   
    }
}

"ldloc.0" clearly stored the struct onto the evaluation stack BUT its also much larger than 64bits. Is the reference stored instead?

.class public sequential ansi sealed beforefieldinit MyStruct
    extends [mscorlib]System.ValueType
{
    // Fields
    .field public int64 x
    .field public int64 y
    .field public int64 z

    // Methods
    .method public hidebysig static 
        valuetype MyStruct Foo () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 34 (0x22)
        .maxstack 2
        .locals init (
            [0] valuetype MyStruct,
            [1] valuetype MyStruct
        )

        IL_0000: nop
        IL_0001: ldloca.s 0
        IL_0003: ldc.i4.1
        IL_0004: conv.i8
        IL_0005: stfld int64 MyStruct::x
        IL_000a: ldloca.s 0
        IL_000c: ldc.i4.2
        IL_000d: conv.i8
        IL_000e: stfld int64 MyStruct::y
        IL_0013: ldloca.s 0
        IL_0015: ldc.i4.3
        IL_0016: conv.i8
        IL_0017: stfld int64 MyStruct::z
        IL_001c: ldloc.0// What is actually stored here?
        IL_001d: stloc.1
        IL_001e: br.s IL_0020

        IL_0020: ldloc.1
        IL_0021: ret
    } // end of method MyStruct::Foo

} // end of class MyStruct

Upvotes: 3

Views: 1118

Answers (2)

Thomas Flinkow
Thomas Flinkow

Reputation: 5105

If .maxsize is 8 does that mean (8 * size_t)?

The .maxstack directive does not correlate to the actual size of the evaluation stack at runtime. Instead, it hints to analyzation tools how many items reside on the stack at the same time. Setting .maxstack incorrectly (as in, too small), the method is considered not verifiable, which can lead to problems in low-trust scenarios (but this should not be a problem for you, as you're reading CIL, not writing).

For example let us consider a simple Add method that accepts to int parameters, adds those together, stores the result in a class field called sum and returns the value of that field.

.method private hidebysig instance
    int32 Add (
        int32 value1,
        int32 value2
    ) cil managed
{
    .maxstack 3 // At most, there are three elements on the stack.

    ldarg.0                   // 1 item on the stack
    ldarg.1                   // 2 items on the stack
    ldarg.2                   // 3 items on the stack
    add                       // 2 items on the stack
    stfld    int32 Foo::sum   // 0 items on the stack
    ldarg.0                   // 1 item on the stack
    ldfld    int32 Foo::sum   // 1 item on the stack
    ret
}

There are never more than 3 items on the method's evaluation stack at the same time.


Sources:

ECMA-335, Section III.1.7.4

Upvotes: 0

Joe Sewell
Joe Sewell

Reputation: 6610

The stack's elements are not all of the same size, and can include value types (structs) of any size. From ECMA-335, section I.12.3.2.1:

The evaluation stack is made up of slots that can hold any data type, including an unboxed instance of a value type.

[...]

While some JIT compilers might track the types on the stack in more detail, the CLI only requires that values be one of:

  • int64, an 8-byte signed integer
  • int32, a 4-byte signed integer
  • native int, a signed integer of either 4 or 8 bytes, whichever is more convenient for the target architecture
  • F, a floating point value (float32, float64, or other representation supported by the underlying hardware)
  • &, a managed pointer
  • O, an object reference
  • *, a “transient pointer,” which can be used only within the body of a single method, that points to a value known to be in unmanaged memory (see the CIL Instruction Set specification for more details. * types are generated internally within the CLI; they are not created by the user).
  • A user-defined value type

A little earlier, in section I.12.1:

User-defined value types can appear in memory locations or on the stack and have no size limitation

So in your case the ldloc.0 instruction is loading the entirety of the value type instance - with its three data fields - onto the stack.

Thanks to this answer for pointing me toward these ECMA sections. That and the other answers on that question indicate why the stack can be measured in slots rather than bytes: because the JIT compiler is already evaluating how to convert the MSIL into native instructions, so it has to know the types of the values on the stack at every instruction.

Upvotes: 2

Related Questions