在.net中的IL和堆栈实现?

Roy*_*mir 10 .net c# compiler-construction il

我写了一个简单的程序来研究IL的工作原理:

void Main()
{

 int a=5;
 int b=6;
 if (a<b) Console.Write("333");
 Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

IL:

IL_0000:  ldc.i4.5    
IL_0001:  stloc.0     
IL_0002:  ldc.i4.6    
IL_0003:  stloc.1     
IL_0004:  ldloc.0     
IL_0005:  ldloc.1     
IL_0006:  bge.s       IL_0012
IL_0008:  ldstr       "333"
IL_000D:  call        System.Console.Write
IL_0012:  call        System.Console.ReadLine
Run Code Online (Sandbox Code Playgroud)

我正在努力了解实施的效率:

  • 在#1行(IL代码),它将值5推送到堆栈上(4个字节,即int32)

  • 在第2行(IL代码),它从堆栈POP到局部变量.

接下来的两行也是如此.

然后,它将这些局部变量加载到堆栈上,然后进行评估bge.s.

问题#1

他为什么要将局部变量加载到堆栈中?值已经在堆栈中.但是为了把它们放在局部变量中,他将它们加以限制.这不是浪费吗?

我的意思是,为什么代码不能像:

IL_0000:  ldc.i4.5
IL_0001:  ldc.i4.6    
IL_0002:  bge.s       IL_0004
IL_0003:  ldstr       "333"
IL_0004:  call        System.Console.Write
IL_0005:  call        System.Console.ReadLine
Run Code Online (Sandbox Code Playgroud)

我的代码示例只有5行代码.那50,000,000行代码呢?IL会发出大量额外的代码

问题2

查看代码地址:

在此输入图像描述

  • IL_0009地址在哪里?它应该是顺序的吗?

ps我在+释放模式下使用Optimize标志

pax*_*blo 10

我可以很容易地回答第二个问题.说明书是可变长度的.例如,ldstr "333"ldstr(在地址处8)的操作码后跟表示字符串的数据(对用户字符串表中的字符串的引用)组成.

与之后的call语句类似- 您需要call操作码本身以及要调用的函数的信息.

将诸如4或6之类的小值推送到堆栈上的指令没有额外数据的原因是因为这些值被编码到操作码本身中.

请参阅此处获取说明和编码.

关于第一个问题,您可能希望查看C#开发人员之一Eric Lippert撰写的博客文章:

/ optimize标志不会改变我们的大量发射和生成逻辑.我们尝试始终生成简单,可验证的代码,然后依靠抖动在生成真实机器代码时进行大量优化.


SK-*_*gic 7

关于此级别的IL效率没有必要进行推理.

JIT将完全消除堆栈,将所有堆栈操作转换为中间三地址代码(并进一步转换为SSA).由于IL 永远不会被解释,因此堆栈操作不应该是高效和优化的.

例如,请参阅开源Mono实现.

  • @RoyiNamir,我的意思是你不能根据它的堆栈表示来推断你的代码效率.它仍然是一种高度抽象的中间语言,不能反映真正的本机代码. (2认同)
  • @SK在那里有一个观点.IL永远不会被执行(我猜的微框架除外).它只是生成本机二进制文件的中间代码.只要在生成本机代码时消除了所有低效率,优化IL就毫无意义(也就是浪费精力). (2认同)

svi*_*ick 6

他为什么要将局部变量加载到堆栈中?这些值已经在堆栈中.但他为了将它们置于局部变量而加入了它们.这不是浪费吗?

浪费了什么?您必须记住IL(通常)不会按原样执行,它由JIT编译器再次编译,JIT编译器执行大多数优化.一个使用"中间语言"的一点是,这样的优化可以在一个地方实现:JIT编译器和每种语言(C#,VB.NET,F#,...)不必从头再来实现它们.Eric Lippert在他的文章为什么IL?

IL_0009地址在哪里?它不应该是顺序的吗?

我们来看看ldstr指令的规范(来自ECMA-335):

III.4.16 ldstr- 加载文字字符串

格式:72 <T> [...]

ldstr指令将表示存储在元数据中的文字的新字符串对象推送为字符串(字符串文字).

对上面的元数据的引用和<T>意味着72指令的字节后面跟着一个元数据标记,它指向一个包含字符串的表.这样的象征有多大?从同一文件的第III.1.9节:

许多CIL指令后面跟着"元数据标记".这是一个4字节的值,用于指定元数据表中的行[...]

因此,在您的情况下,72指令的字节位于地址0008,并且令牌(在这种情况下为0x70000001,其中0x70字节表示用户字符串表)位于地址0009到000C.

  • @RoyiNamir,是的,你可以在前端优化它 - 但是在完成所有转换之后你也必须在后端优化它.你的前端会复杂得多,绝对没有优势,没有优势."堆栈"表示非常适合生成代码,但它不适合分析 - SSA要好得多,但是直接从源代码生成这样的代码会更复杂.当我不得不在IL级别进行一种寄存器分配时,我只有一个案例 - 生成的模式匹配代码对于JIT来说太大了. (2认同)