MSIL 代码和机器代码比较 (.NET)

MSIL code and machine code comparison (.NET)

将 MSIL 代码编译到特定机器上时做了哪些简化?我以前认为机器代码没有基于堆栈的操作,并且 MSIL 中所有基于堆栈的操作都被转换为具有所需 push/pop 堆栈结果的大量数据移动操作,因此机器代码通常要长得多比 MSIL 代码。但这似乎并非如此,所以这让我想知道 - 机器代码与 MSIL 代码有何不同,在哪些方面?

我希望从不同的角度对两者进行比较,例如: operations/instructions 的数量有何不同?机器代码通常有更多行吗? 除了平台独立性(至少在 cpu 体系结构独立性和 windows 基于平台独立性的意义上)、元数据样式代码以及某种 "common ground" 语言之外还有什么级别的编程语言,intermediate/MSIL 代码允许吗?如果比较一些 MSIL 代码和相应的机器代码,最明显的区别可能是什么?

我非常希望能够进行高层次的比较,但也许可以使用一些简单而具体的示例。

首先,我们假设“机器码”是指x86-64指令集。对于其他架构,例如 ARM,特定方面可能略有不同。

What simplifications are made when compiling MSIL code to some specific machine?

这些并不是真正的简化。 MSIL 和典型的机器指令集(如 x86-64`)主要不同。

I previously thought that machine code has no stack based operations, and that all the stack based operations in MSIL are converted into numerous data movement operations that have the desired push/pop stack outcome, and as a result machine code is generally far longer than MSIL code.

堆栈实际上是每个 CPU 架构所必需的核心概念(有/曾经有一些 CPU 架构 without a stack,但我认为这种情况很少见)。如果没有工作堆栈,许多操作将变得不切实际地复杂。

但是:硬件CPU中的主要概念是寄存器。大多数计算和内存操作可以纯粹在寄存器中而不是在计算机的主内存中发生。将它们视为临时变量。此外,与主内存相比,它们的工作速度要快得多(即使中间有所有级别的缓存)。

也就是说,虽然 MSIL 指令 必须 遵循基于纯堆栈的数据处理方法(MSIL 中没有寄存器),但硬件 CPU s 必须使用寄存器。因此,这导致两种 不同的 方法将相同的表达式转换为相应的机器代码。

But this doesn't seem to be the case, so it makes me wonder - just how different is machine code to MSIL code, and in what aspects?

让我们使用 C# 表达式:a = b + c * d;,其中每个变量都是一个 int。

在 MSIL 中:

ldloc.1     // b — load from local variable slot 1
ldloc.2     // c — load from local variable slot 2
ldloc.3     // d — load from local variable slot 3
mul         // multiple two top-most values, storing the result on the stack
add         // add two top-most values, storing the result on the stack
stloc.0     // a — store top-most value to local variable slot 0

这个概念的一大优势是,为纯基于堆栈的机器代码编写代码生成器非常容易。

x86-64 程序集中:

mov   eax, dword ptr [c]   // load c into register eax
mul   dword ptr [d]        // multiply eax (default argument) with d
add   eax, dword ptr [b]   // add b to eax
mov   dword ptr [a], eax   // store eax to a

如您所见,在这个简单的例子中 x86-64 没有涉及堆栈。代码看起来也更短并且可能更易读。但是,生成真正的 x86-64 机器代码是一项 非常困难 的任务。

免责声明:汇编代码片段是我辛苦写的;请原谅我可能包含的错误。这些天写汇编不是我的日常工作:)

How does the amount of operations/instructions differ?

答案是:视情况而定。一些简单的运算比如算术运算有时是1:1,例如MSIL 中的 add 可以导致 x86-64 中的单个 add。另一方面,MSIL 可以利用定义更多高级操作的优势。例如,调用虚拟方法的 MSIL 指令 callvirtx86-64 中没有简单的对应指令:您需要几条指令来执行该调用。

Does machine code generally have lot more lines?

我有硬数据可供比较;但是,根据上面关于指令复杂性的说明,我会说是的。

What else besides platform independence and metadata-style code does the intermediate/MSIL code allow?

我认为问题应该是:机器代码还允许什么? MSIL 相当严格。 CLR 定义了许多有助于维护 MSIL 代码的一致性和正确性的规则。在机器代码中你有完全的自由——你也可以完全把事情搞砸。

What might be the most noticeable differences if one compared some MSIL code and the corresponding machine code?

在我看来,它是 CPU 的基于寄存器的架构,例如 x86-64

What does MSIL make easy besides these features? What are some natural structures/features of the MSIL language that make some things easier?

其实还有很多。首先,作为基于堆栈的体系结构,将 .NET 编程语言编译成 MSIL 更容易,正如我之前解释的那样。然后还有很多其他的小东西,比如:

  • MSIL 自然理解所有原始 CLR (.NET) 数据类型
  • MSIL 可以表达类型转换
  • MSIL 理解对象(类型实例),可以分配实例(newobj),调用方法包括虚方法调用(非常重要)
  • 手写 MSIL 的语法支持面向对象的代码结构,即 MSIL 支持表达高级 OO 概念
  • MSIL 提供对 boxing/unboxing
  • 的支持
  • MSIL 支持抛出和捕获异常(这也是一个大问题)
  • MSIL 具有基于互斥锁的同步(锁)指令