Прежде всего, давайте предположим, что «машинный код» означает 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 содержит инструкции для синхронизации на основе мьютекса (блокировки)