Поиск

Microsoft Intermediate Language и компиляторы JITter

Для облегчения перевода языков в среду .NET в Microsoft разработан промежуточный язык — Microsoft Intermediate Language (MSIL). Чтобы откомпилировать приложение для .NET, компиляторы берут исходный код и создают из него MSIL-код. MSIL — это полноценный язык, пригодный для написания приложений. Однако, как в случае с ассемблерным языком, вам вряд ли придется этим заниматься, кроме каких-то особых обстоятельств. Каждая группа разработчиков компилятора решает, в какой мере он будет поддерживать MSIL. Но если создатели компиляторов захотят, чтобы их язык полноценно взаимодействовал с другими языками, им придется ограничить себя рамками, определяемыми спецификациями CLS.

Результатом компиляции приложения, написанного на С# или другом языке, который отвечает правилам CLS, является MSIL-код. Потом, при первом запуске приложения в среде CLR, MSIL-код компилируется в машинные команды, специфичные для данного процессора. (На самом деле компилируются только функции, вызываемые впервые.)

Поскольку мы все пока новички в этой технологии, а книга наша посвящена С#, посмотрим по порядку, что же происходит с кодом.

  1. Вы пишете исходный код на С#.
  2. Затем вы компилируете его с помощью компилятора языка С# в ЕХЕ-файл.
  3. Компилятор создает MSIL-код и помещает в раздел "только-на-чте-ние" выходного файла стандартный РЕ-заголовок (признак машино-независимой выполняемой программы для Win32). Пока все хорошо. Но здесь появляется очень важная деталь: при создании выходного файла компилятор импортирует из CLR функцию _CorExeMain.
  4. Когда приложение начинает выполняться, ОС загружает этот РЕ (впрочем, как и обычный РЕ), а также все нужные DLL, в частности, библиотеку, которая экспортирует функцию _CorExeMain (mscoree.dll).
  5. Загрузчик ОС выполняет переход в точку входа РЕ, устанавливаемую компилятором. Это ничем не отличается от процедуры загрузки в Windows любого другого РЕ. Однако так как ОС не в состоянии выполнить MSIL-код, то фактически в точке входа содержится заглушка, в которой установлена команда перехода к функции jCorExeMain из mscoree.dll.
  6. Функция JCorExeMain переходит к выполнению MSIL-кода, помещенного в РЕ.
  7. Так как MSIL-код не может быть выполнен непосредственно (ведь это не машинный код), CLR компилирует его с помощью оперативного (just-in-time, или JIТ) компилятора (его еще называют JITter) в команды процессора. Эта компиляция выполняется только для непосредственно вызываемых методов программы. Откомпилированный выполняемый код сохраняется на машине и перекомпилируется только в случае изменения исходного кода. Для преобразования MSIL в настоящий машинный код можно применить один из следующих JIТ-компиляторов.
  • Генератор кода при установке (Install-time code generation) Выполняет компиляцию всей сборки в двоичный код, специфичный для данного процессора, подобно тому, как это делает компилятор С#. Сборка (assembly) — это комплект модулей кода, посылаемый компилятору. (О сборках подробнее я расскажу ниже в разделе "Развертывание".) Эта компиляция выполняется в ходе установки, когда обработка Сборки ЛТ-компилятором меньше всего заметна для конечного пользователя. Достоинство этого типа генерации двоичного кода в том, что компиляция всей сборки выполняется один раз еще до запуска приложения. Поскольку код скомпилирован, то о потере производительности при первом вызове метода приложения можно не беспокоиться. Это аналогично тому, как квартиросъемщик, чтобы каждый месяц не болела голова, где взять деньги на очередную выплату, платит вперед сразу за весь период. Вопрос о целесообразности применения этой утилиты решается в зависимости от размера конкретной системы и среды, в которой происходит ее развертывание. Если вы, как это часто бывает, собираетесь создать для своей системы установочное приложение, используйте этот ЛТ-компилятор, чтобы у пользователя была уже полностью оптимизированная версия системы "с иголочки".
  • JIT Стандартный JITter, вызываемый при выполнении приложения каждый раз для впервые активизируемого метода (в порядке, описанном выше). Это напоминает плату в намеченный срок. Данный режим работает по умолчанию, если вы не запускаете явно компилятор РrеJIТ.
  • EconoJIT Включается во время выполнения приложения и предназначен специально для систем, которые имеют ограниченные ресурсы, например, для портативных устройств с малым размером памяти. Основное отличие этого компилятора от обычного JITter — в объединении кодовых фрагментов (code pitching). Благодаря разбивке кода на фрагменты EconoJIT может удалить сгенерированный (т. е. откомпилированный) код, если памяти для запуска системы недостаточно. Достоинство этого компилятора в экономии памяти, а недостаток в том, что если фрагментированный код загружается вновь, он должен быть опять перекомпилирован как код, который еще никогда не вызывался.