Лекция 10 • Генерация CIL • Генерация Java bytecode • Оптимизация Common Intermediate Language • Common Intermediate Language (CIL) — промежуточный язык, разработанный фирмой Microsoft для платформы .NET Framework. • JIT-компилятор CIL является частью так называемой CLR (Common Language Runtime) — общей среды выполнения языков .NET. • Ранее язык назывался Microsoft Intermediate Language (MSIL), однако был переименован для создания стандарта ECMA-335. Жизненный цикл кода .Net • Процесс выполнения: 1. Исходный код транслируется в CIL 2. CIL транслируется в объектный код и генерируется .NET assembly 3. В момент исполнения объектный код передается JIT-компилятору и транслируется в машинный код (native code). Возможна замена этапа на Aheadof-time compilation 4. Исполнение машинного кода Виртуальная машина CIL • Машина является стековой. Стек - статически типизирован (в каждой точке программы JIT-compiler может статически определить типы содержимого всех ячеек стека). В результате - невозможен код, пишущий в цикле значения на стек • Ячейки стека представлены как 4-байтовые или 8-байтовые знаковые целовые (обозначаемые как I4 и I8, соответственно; более короткие значения представляются как 4-байтовые); • Стек используется в основном для хранения промежуточных результатов вычисления (т.е. не рекомендуется хранить на стеке временные переменные, такие, как счетчик цикла и т.п.) • Большинство команд CIL получают свои аргументы на стеке, удаляют их со стека и помещают вместо них результат. • Машина является объектно-ориентированной: структура CIL отражает разбиение кода на классы, методы и т.п. Группы инструкций в CIL • • • • • • • • • Загрузка и хранение значений Арифметические Преобразование типов Создание и управление объектами Управление стеком операндов (push / pop) Контроль за исполнением (ветвление) Вызов методов и возврат значений Вызов исключений Синхронизация на основе Монитора Хранении переменных в СIL • в статической области памяти, существующей все время выполнения программы • в локальной области, которая выделяется при входе в метод; • внутри объекта, размещенного в куче. Пример кода • Пример программы Hello, World, написанной на CIL. Она выводит строку «Hello, world!». C# CIL JVM как backend • • • • Трансляция в язык Java Генерация в байт-кода напрямую в JVM Class Ассемблер Jasmin с трансляцией в JVM Class Генерация байт-кода через ASM framework или BCEL(Byte Code Engineering Library) Java Virtual Machine • Java Virtual Machine (Java VM, JVM) — виртуальная машина Java — основная часть исполняющей системы Java, так называемой Java Runtime Environment (JRE). • Java VM интерпретирует и исполняет Байткод Java (Java bytecode), предварительно созданный из исходного текста Javaпрограммы компилятором Java (javac) Java bytecode • Java bytecode - это инструкции, которые исполняются JVM • Каждая инструкция(опкод) длинной один байт. При наличии параметров – более 1 байта • Из 256 возможных опкодов 51 зарезервированы для будущего использования. 3 значения в реализации от Sun Microsystems считаются не допустимыми и не будут реализованы в будущем. Пример байт-кода public class Foo { private String bar; public String getBar(){ return bar; } public void setBar(String bar) { this.bar = bar; } } public class Foo extends java.lang.Object { public Foo(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public java.lang.String getBar(); Code: 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn public void setBar(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #2; //Field bar:Ljava/lang/String; 5: return } Группы инструкций в Java bytecode • • • • • • • • Загрузка и хранение значений (aload_0,istore) Арифметические и логические (ladd,fcmpl) Преобразование типов (i2b,d2i) Создание и управление объектами (new,putfield) Управление стеком (swap,dup2) Контроль за исполнением (ifeq,goto) Вызов методов и возврат значений (invokespecial,areturn) Дополнительно: синхронизация и исключения Префиксы операций для определения типа операнда Префикс/Суфикс Тип опреранда i integer l long s short b byte c character f float d double a reference Пример: Iadd – сложение двух целых чисел dadd – сложение двух вещественных Пример Способы генерации JVM-байткода • Трансляция в язык Java • Генерация в байт-кода в виде бинарного JVM Class файла • Ассемблер Jasmin с трансляцией в JVM Class • Генерация байт-кода через ASM framework или BCEL(Byte Code Engineering Library) Генерация в байт-кода в виде бинарного JVM Class файла • Необходимо строго соблюдать формат class-файлов, в котором каждый байт должен быть на своем месте и несет свои значения; • Кроме генерации последовательности опкодов необходимо также предусматривать описание самого класса, его полей, методов, переменных, а так же пула констант, внутри которого должны храниться все литералы, имена классов и методов; • Каждая из этих констант представляет из себя сложную структуру, содержащую внутри себя другие структуры; • Нужно работать не с ascii-представлением опкодов, а с их бинарными "номерами“; • Отладка кодогенерации затруднена. Ассемблер Jasmin • Jasmin - открытый опенсорс ассемблер, позволяющий создавать class-файлы с использованием синтаксиса наподобие x86ассемблера с помощью набора команд JVM; • Jasmin принимает ASCII описание JVM классов и генерирует для них бинарные JVM Class файлы, совместимые с средой Java Runtime; • Решает проблемы, связанные с константами, номерами оппкодов и сложной структурой файлов. Фреймворк ASM и библиотека BCEL • ASM – фреймворк для управление и анализа JB; • The Byte Code Engineering Library – библиотека, предоставляющая инструмента для генерации JVM Class в виде объектно-ориентированной модели; • Предоставляют удобные (особенно bcel) API для чтения, изменения, создания и модификации байткода для JSM; • Все это удобней, чем даже Jasmin, но интерфейсы есть только для Java, что не позволяет использовать при разработке транслятора написанного на других языках. Оптимизация • Общая схема фаз компиляции Исходная программа Токен Лексический анализатор запрос следующего Синтаксический анализатор Дерево разбора Семантический разбор Интерпретация Компиляция Оптимизация ПЯ Генерация ПЯ Оптимизация Исходная программа Пользователь оптимизирует программу Front-end Промежуточный код Компилятор усовершенствует циклы, вызовы процедур, производит вычисление Генерация кода Целевой код Компилятор оптимизирует выбор регистров, делает локальные преобразования Наилучшее преобразование программы – те, которые приводят к максимальному результату при минимальных усилиях Оптимизирующий компилятор Начальная стадия Анализ потока управления Оптимизатор кода Анализ потока данных Генератор кода Преобразование кода •Удаление общих подвыражений •Размножение копий кода •Оптимизация и раскрытие циклов •Удаление недоступного кода •Удаление не используемых констант • Оптимизация − это процесс, связанный с обработкой промежуточного или уже порожденного целевого кода и оказывающий существенное влияние на качество и эффективность результирующей программы. • Оптимизация: – на уровне исходного языка – машинно-зависимая – машинно-независимая o локальная (линейный участок, группа операторов) o глобальная (процедура целиком) o межпроцедурная Стадии оптимизации Низкоуровневая оптимизация • Оптимальный выбор (замена, объединение, разделение) инструкций • Переупорядочивание инструкций • Распределение регистров • Удаление цепочек переходов • Векторизация • Понижение силы операций Def-Use Chains Операторы: Ω={ ω1, ω2,…, ωn} - алфавит операторов I={i1,i2,…,ik} - алфавит входов O={o1,o2,…,om} - алфавит выходов input : Ω → 2I - входы операторов, output : Ω → 2O- выходы операторов ∀ ω1, ω2∈Ω ω1≠ω2 ⇒ input( ω1)∩input( ω2)=∅ & output( ω1)∩output( ω2)=∅ Def-Use разметка: DU: O → 2I ∀ω∈Ω def (ω)=output(ω) use(ω)= ∪i∈input(ω) {o∈O | i∈DU(o)} Объединение и разделение инструкций • Данный метод оптимизации состоит в замене одной или нескольких инструкций другим, но функционально эквивалентным набором, дающим выигрыш для целевой архитектуры. • Пример: Pentium MMX dec ecx jnz label1 80286 (x86) vs loop label1 Покадровая оптимизация Удаление пустого оператора Высокоуровневая оптимизация • Удаление недосягаемого («мёртвого») кода и неиспользуемых присвоений • Оптимизация множественных ветвлений • Развёртка, свёртка, объединение и разделение циклов • Вычисление инвариантов циклов, вынесение общих подвыражений и кода в ветвлениях, вынесение ветвлений из циклов • Переключение, объединение и разделение ветвлений • Предвыборка данных • Переупорядочевание функций • Встраивание и извлечение функций Глобальная оптимизация • Глобальная оптимизация основывается на глобальном потоковом анализе, который выполняется на графе программы и фактически представляет из себя преобразование этого графа. • Дополнительно может быть проведен анализ отдельных характеристик программы: – межпроцедурный анализ – межмодульный анализ – анализ областей жизни переменных Локальная оптимизация • Локальная оптимизация – обработка тела отдельных подпрограмм. – излишние загрузки/сохранения – недостижимый код – оптимизация потока управления – алгебраические упрощения – снижение стоимости вычислений