.NET IL/MSIL 评估堆栈基础知识

3

似乎找不到这些问题的好答案。

以下是我认为自己已经了解的和我模糊的知识。

  • 评估堆栈是一个类似于C风格堆栈的内存缓冲区(它是本机int / size_t的堆栈吗)?
  • 评估堆栈元素可以是32位或64位(如何在单个堆栈中混合这些元素?)
  • Ldloc_0将局部变量存储在评估堆栈上,但如果它大于64位怎么办?
  • Ldloc_0是否只在评估堆栈上存储指向局部变量的指针?
  • 在评估堆栈上存储的对象是否总是指针或原始值?
  • 如果.maxsize为8,那么是否意味着(8 * size_t)?如果是这样,那么如果我读到文档说明它是32位或64位怎么办?

以下面的示例为例。这个局部变量会通过指针引用存储在评估堆栈上吗?

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”将结构体明确存储到计算堆栈中,但它比64位还要大。这里是否存储了引用?

"
.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

不要忘记,评估堆栈是抽象的。CIL是JIT编译的,因此当代码实际执行时,值可能存储在寄存器或内存位置中。 - IS4
@llidanS4,我已经想出来了。我正在制作一个IL到C的转换器,只需预测哪些本地变量或字段变量将被设置,然后修改“Br”/转到位置。这样我就能得到C级别的优化了。 - zezba9000
2个回答

2
堆栈的元素不全是相同的大小,可以包括任意大小的值类型(struct)。来自ECMA-335,第I.12.3.2.1节。
评估堆栈由可以容纳任何数据类型的插槽组成,包括一个值类型的非装箱实例。
虽然一些JIT编译器可能会更详细地跟踪堆栈上的类型,但CLI仅要求值为以下之一:
- int64,8字节有符号整数 - int32,4字节有符号整数 - native int,4或8字节的有符号整数,以目标架构为便利 - F,浮点值(float32、float64或底层硬件支持的其他表示方式) - &,托管指针 - O,对象引用 - *,“瞬态指针”,只能在单个方法体内使用,指向已知在非托管内存中的值(有关详细信息,请参见CIL指令集规范。*类型是在CLI内部生成的;它们不是用户创建的)。 - 用户定义的值类型
稍早在I.12.1节中提到:
用户定义的值类型可以出现在内存位置或堆栈上,并且没有大小限制。
因此,在您的情况下,ldloc.0指令将整个值类型实例(包括其三个数据字段)加载到堆栈上。
感谢this answer指向这些ECMA章节的答案。那个问题和其他答案表明为什么堆栈可以用插槽而不是字节来测量:因为JIT编译器已经评估如何将MSIL转换为本机指令,因此它必须知道每个指令上堆栈中值的类型。

我已经阅读了你从ECMA-335发布的一些内容,但我猜困惑来自于不理解如何否定评估堆栈?JIT肯定不会做所有这些额外的复制吧? - zezba9000

0
如果 .maxsize 是8,这是指(8 * size_t)吗? .maxstack 指示符并不对应运行时评估堆栈的实际大小。相反,它提示分析工具其中有多少项同时驻留在堆栈中。如果设置 .maxstack 不正确(例如,过小),则该方法被认为是不可验证的,在低信任情况下可能会导致问题(但您阅读的是CIL而不是编写代码,因此这不应该成为您的问题)。
例如,让我们考虑一个简单的 Add 方法,它接受两个 int 参数,将它们加在一起,将结果存储在名为 sum 的类字段中,并返回该字段的值。
.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
}

在方法的评估堆栈上,同时永远不会有超过3个项目。


来源:

ECMA-335,第III.1.7.4节


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接