|
|||||||
Что такое бэкэнд компилятора (Compiler Backend)
Время создания: 13.05.2021 11:10
Текстовые метки: компилятор, теория, бэкэнд, compiler, backend
Раздел: Компьютер - Программирование - Теория программирования - Теория компиляции
Запись: xintrea/mytetra_syncro/master/base/1620893404mwi391ccv2/text.html на raw.github.com
|
|||||||
|
|||||||
Compiler Backend — один из двух ключевых компонентов компилятора, о котором можно сказать следующее:
Промежуточный кодПромежуточный код генерируется из Abstract Syntax Tree (AST) путём его обхода. Перед кодогенерацией AST должен быть полностью построен и проверен на семантическую корректность фронтендом компилятора. Основные инструменты при кодогенерации — паттерн Строитель “Builder” и шаблонизаторы. Строитель — это объект, предоставляет интерфейс для последовательного наполнения другого объекта свойствами. Например, Code Builder может последовательно наполнять список инструкций (это более высокоуровневый подход) либо список строк генерируемого кода (более грубое решение). Строитель может иметь внутреннее состояние, например, уровень отступа или флаг, указывающий на необходимость завершения текущего блока инструкцией возврата из функции. Шаблонизаторы получают на вход строки с якорями, параметры и создают новые строки. Таким способом можно генерировать и текстовые, и бинарные файлы (т.к. бинарный файл тоже представим как строка в однобайтовой кодировке). Простые шаблонизаторы разворачивают только переменные в строках, более сложные могут обрабатывать простые ветвления и циклы. Пример шаблона HTML-файла с переменными “TITLE” и “SCRIPT_LIST”: <head> <title>{TITLE}</title> {SCRIPT_LIST} </head> <body> <canvas width="800px" height="600px"> </canvas> </body> При генерации промежуточного кода из дерева пригождаются базовые приёмы программиста: рекурсия, стек, списки, массивы. Также пригодятся паттерн Visitor, идиома pattern matching (примером является даже обычный switch-case). Промежуточный код близок к ассемблеру, но абстрагирован от деталей конкретного процессора и может иметь свои ограничения в синтаксисе. Пример промежуточного кода (в формате LLVM-IR): define i32 @lambda(i8* %parent, i32 %x) { %1 = bitcast i8* %parent to i32* %2 = load i32* %1 ; %2 = a %3 = add i32 %x, %2 ret i32 %3 } При оптимизации крайне полезно, чтобы промежуточный код не имел переменных, а использовал назначение имён для вычислимых значений (value bindings). Иными словами, каждая “переменная” вычисляется только один раз и затем не меняется (схожий механизм не случайно появился в современном Javascript в виде ключевого слова let). Поддержка реальных переменных реализуется на базе модели памяти языка — например, мы один раз вычисляем адрес переменной на стеке, связываем это значение с именем и затем используем инструкции для записи/чтения по вычисленному адресу. Вы можете увидеть это в примере выше — в LLVM-IR каждое именованное значение присваивается только в одной строке (возможно, внутри цикла), и является регистром виртуальной машины LLVM (считается, что в LLVM бесконечно много регистров). ОптимизаторОптимизатор — это необязательный компонент бекенда. Промышленные компиляторы содержат оптимизатор, потому что в привычном цикле разработки используются отладочные (debug) и оптимизированные (release) конфигурации сборки.
Большинство проходов оптимизатора выполняется над промежуточным кодом, и делится на две категории:
Упражнение: приведите пример написанной программистом функции, которую всегда выгоднее встраивать (inline), а не вызывать явно. Генерация машинного кодаВ целом, промежуточный код превращается в машинный путём замены инструкций и распределения имён вычисляемых значений на регистры и на стек. Другими словами, промежуточный код — это просто универсальный ассемблер, соединяющий черты ассемблеров разных процессоров. Но на деле процессоры отличаются друг от друга:
Большинство различий стирается с помощью intrinsic-функций (или виртуальных инструкций) в промежуточном языке. Такие инструкции разворачиваются в одну инструкцию, если целевой процессор это позволяет, но может быть заменён на целую цепочку инструкций на более ограниченных платформах. Упражнение: напишите на C++ функцию, которая определяет ближайшую степень двойки, не превышающую переданное как параметр целое число. Затем найдите инструкцию ассемблера Intel x86, которая делает то же самое за один такт. Что надо знать до разработки бекендаДля реализации бекенда простейшего, процедурного языка понадобится:
Для реализации бекенда объектно-ориентированного языка также нужно:
|
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|