【问题标题】:.NET IL / MSIL Evaluation Stack fundamentals.NET IL / MSIL 评估堆栈基础
【发布时间】:2019-07-23 22:40:33
【问题描述】:

似乎无法为这些问题找到一个好的答案。

以下是我认为我知道的和我不清楚的。

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

以下面的例子为例。此局部变量是否通过 ptr 引用存储在评估堆栈中?

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 是 JITted,所以当代码实际执行时,值可能会存储在寄存器或内存位置。
  • @llidanS4 明白了。制作一个 IL 到 C 的翻译器并只向前分支预测将设置哪些本地或字段变量,然后修改“Br”/goto 位置。这样我就得到了 C 级优化。

标签: .net stack evaluation cil


【解决方案1】:

如果 .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, Section III.1.7.4

【讨论】:

    【解决方案2】:

    堆栈的元素大小不一,可以包含任意大小的值类型 (structs)。 来自 ECMA-335,第 I.12.3.2.1 节:

    评估堆栈由可以容纳任何数据类型的槽组成,包括值类型的未装箱实例

    [...]

    虽然一些 JIT 编译器可能会更详细地跟踪堆栈上的类型,但 CLI 只要求值是以下之一:

    • int64,8字节有符号整数
    • int32,4字节有符号整数
    • native int,4 或 8 字节的有符号整数,以更方便目标架构为准
    • F,浮点值(float32float64,或底层硬件支持的其他表示形式)
    • &,托管指针
    • O,一个对象引用
    • *,一个“瞬态指针”,只能在单个方法的主体中使用,它指向一个已知位于非托管内存中的值(有关更多详细信息,请参阅 CIL 指令集规范。* 类型是在 CLI 内部生成;它们不是由用户创建的)。
    • 用户定义的值类型

    稍早一点,在第 I.12.1 节中:

    用户定义的值类型可以出现在内存位置或堆栈上,并且没有大小限制

    因此,在您的情况下,ldloc.0 指令将整个值类型实例(及其三个数据字段)加载到堆栈中。

    感谢 this answer 将我指向这些 ECMA 部分。 这个问题和其他答案表明为什么堆栈可以在槽而不是字节中测量:因为 JIT 编译器已经在评估如何将 MSIL 转换为本机指令,所以它必须知道每条指令栈上的值的类型。

    【讨论】:

    • 我已经阅读了您从 ECMA-335 发布的一些内容,但我想困惑来自于不了解评估堆栈是如何被否定的? JIT 肯定不会做所有这些额外的副本?
    猜你喜欢
    • 2012-10-07
    • 2013-12-05
    • 1970-01-01
    • 2010-12-14
    • 2021-05-02
    • 2015-02-14
    • 2023-03-11
    • 2013-10-28
    • 1970-01-01
    相关资源
    最近更新 更多