.NET IL / MSIL 评估堆栈基础
Posted
技术标签:
【中文标题】.NET IL / MSIL 评估堆栈基础【英文标题】:.NET IL / MSIL Evaluation Stack fundamentals 【发布时间】: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 级优化。 【参考方案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】:堆栈的元素大小不一,可以包含任意大小的值类型 (struct
s)。
来自 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 肯定不会做所有这些额外的副本?以上是关于.NET IL / MSIL 评估堆栈基础的主要内容,如果未能解决你的问题,请参考以下文章