首页 > 解决方案 > MSIL 代码和机器代码比较 (.NET)

问题描述

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

我希望从不同的角度对两者进行比较,例如:操作/指令的数量有何不同?机器代码通常有更多的行吗?除了平台独立性(至少在 cpu 体系结构独立性和基于 Windows 的平台独立性的意义上)、元数据样式代码以及作为许多高级编程语言的某种“共同基础”语言之外,中间/MSIL 还能做什么?代码允许?如果比较一些 MSIL 代码和相应的机器代码,最显着的差异可能是什么?

我真的很感激一个主要是高层次的比较,但也许有一些简单而具体的例子。

标签: .netcil

解决方案


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

将 MSIL 代码编译到某些特定机器时进行了哪些简化?

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

我之前认为机器代码没有基于堆栈的操作,并且 MSIL 中所有基于堆栈的操作都被转换为具有所需推/出堆栈结果的大量数据移动操作,因此机器代码通常比 MSIL 长得多代码。

堆栈是每个 CPU 架构实际上都需要的核心概念(有/曾经有一些没有堆栈的 CPU 架构,但我认为这是一个相当罕见的情况)。如果没有工作堆栈,许多操作将变得不切实际地复杂。

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

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

但这似乎并非如此,所以这让我想知道 - 机器代码与 MSIL 代码有何不同,在哪些方面?

让我们使用 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机器代码是一项非常艰巨的任务。

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

操作/指令的数量有何不同?

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

机器代码通常有更多的行吗?

我必须有可用的硬数据来比较;但是,根据上述关于指令复杂性的内容,我会说是的。

除了平台独立性和元数据样式代码之外,中间/MSIL 代码还允许什么?

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

如果比较一些 MSIL 代码和相应的机器代码,最显着的差异可能是什么?

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

除了这些功能之外,MSIL 还能让什么变得简单?MSIL 语言有哪些自然结构/特性可以让一些事情变得更容易?

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

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

推荐阅读