Reputation: 3383
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
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:
Upvotes: 0
Reputation: 6610
The stack's elements are not all of the same size, and can include value types (struct
s) 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 integerint32
, a 4-byte signed integernative int
, a signed integer of either 4 or 8 bytes, whichever is more convenient for the target architectureF
, a floating point value (float32
,float64
, or other representation supported by the underlying hardware)&
, a managed pointerO
, 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