Сравнение кода MSIL и машинного кода (. NET) - PullRequest
2 голосов
/ 28 февраля 2020

Какие упрощения сделаны при компиляции кода MSIL для какой-либо конкретной c машины? Ранее я думал, что в машинном коде нет операций на основе стека, и что все операции на основе стека в MSIL преобразуются в многочисленные операции перемещения данных, которые имеют желаемый результат стека push / pop, и в результате машинный код обычно намного длиннее, чем MSIL. код. Но, похоже, это не так, поэтому я задаюсь вопросом - насколько машинный код отличается от кода MSIL и в каких аспектах?

Я был бы признателен за сравнение этих двух с разных точек зрения. Например, как отличается количество операций / инструкций? У машинного кода вообще больше строк? Что еще помимо независимости от платформы (по крайней мере, в смысле независимости архитектуры процессора и независимости платформ на основе windows), кода в стиле метаданных и того, чтобы быть своего рода языком «общего основания» для многочисленных языков программирования высокого уровня, выполняет промежуточное звено. / MSIL код разрешить? Какие могут быть наиболее заметные отличия, если сравнивать какой-то код MSIL и соответствующий машинный код?

Я бы действительно оценил сравнение в основном на высоком уровне, но, возможно, с некоторыми простыми и конкретными примерами.

1 Ответ

4 голосов
/ 28 февраля 2020

Прежде всего, давайте предположим, что «машинный код» означает x86-64 набор инструкций. С другими архитектурами, такими как ARM, отдельные аспекты могут немного отличаться.

Какие упрощения сделаны при компиляции кода MSIL для некоторой конкретной c машины?

Это не совсем упрощения. MSIL и типичный набор машинных инструкций, таких как x86-64`, принципиально различаются.

Ранее я думал, что в машинном коде нет операций на основе стека и что все операции на основе стека в MSIL преобразуются в многочисленные операции перемещения данных, которые имеют желаемый результат стека push / pop, и в результате машинный код, как правило, намного длиннее, чем код MSIL.

Стек - это базовая концепция, практически необходимая для каждой архитектуры ЦП ( Есть / были некоторые процессорные архитектуры без стека , но я думаю, что это довольно редкий случай). Многие операции были бы непрактично сложными без рабочего стека.

Однако: основная концепция в аппаратных процессорах - это регистры. Большинство вычислений и операций с памятью могут происходить исключительно в регистрах, а не в основной памяти компьютера. Думайте о них как о временных переменных. Кроме того, с ними намного, намного быстрее работать, чем с основной памятью (даже несмотря на все промежуточные уровни кэширования).

При этом, в то время как инструкции MSIL должны подчиняться чисто стековый подход к работе с данными (в MSIL нет регистров), при использовании аппаратных процессоров необходимо для использования регистров. Таким образом, это приводит к двум различным подходам к переводу одного и того же выражения в соответствующий машинный код.

Но, похоже, это не так, поэтому меня удивляет - насколько машинный код отличается от кода MSIL и в каких аспектах?

Давайте получим выражение C#: a = b + c * d;, где каждая переменная является целым числом.

В 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

Одним большим преимуществом этой концепции является то, что очень просто написать генератор кода для чистого машинного кода на основе стека.

In 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 машинного кода - очень сложная задача .

Отказ от ответственности: я написал фрагмент кода сборки наизусть; простите мои ошибки, которые это может содержать. Сейчас я не пишу на ассемблере:)

Чем отличается количество операций / инструкций?

Ответ: это зависит. Некоторые простые операции, такие как арифметические операции c, иногда бывают 1: 1, например, add в MSIL может привести к одному add в x86-64. С другой стороны, MSIL может использовать преимущество определения гораздо более высокоуровневых операций. Например, инструкция MSIL callvirt, которая вызывает виртуальный метод, не имеет простого аналога в x86-64: вам потребуется несколько инструкций для выполнения этого вызова.

В целом машинный код есть намного больше строк?

Мне нужны достоверные данные для сравнения; однако, в соответствии со сказанным выше относительно сложности инструкций, я бы сказал, скорее, да.

Что еще, кроме кода независимости от платформы и кода в стиле метаданных, допускает код промежуточный / MSIL?

Я думаю, что вопрос должен быть: что еще позволяет машинный код? MSIL довольно ограничительный. CLR определяет множество правил, которые помогают поддерживать последовательность и правильность кода MSIL. В машинном коде у вас есть полная свобода - и вы также можете все испортить.

Какие могут быть наиболее заметные различия, если сравнивать какой-то код MSIL и соответствующий машинный код?

С моей точки зрения, это архитектура процессоров на основе регистров, такая как x86-64.

Что MSIL облегчает помимо этих функций? Какие естественные структуры / особенности языка MSIL облегчают некоторые вещи?

На самом деле их много. Во-первых, будучи стековой архитектурой, гораздо проще скомпилировать язык программирования. NET в MSIL, как я объяснял ранее. Тогда есть много других мелких вещей, таких как:

  • MSIL, естественно, понимает все примитивные типы данных CLR (. NET)
  • MSIL может express преобразования типов
  • MSIL понимает объекты (экземпляры типов), может выделять экземпляры (newobj), вызывать методы, включая вызовы виртуальных методов (очень важно)
  • синтаксис для написания MSIL вручную поддерживает объектно-ориентированное структурирование код, т. е. поддержка MSIL, выражающая высокоуровневые концепции OO
  • MSIL поддерживает бокс / распаковку
  • MSIL поддерживает создание и перехват исключений (это тоже очень важно)
  • MSIL содержит инструкции для синхронизации на основе мьютекса (блокировки)
...