В вычислительной технике генерация кода является частью цепочки процессов компилятора и преобразует промежуточное представление исходного кода в форму (например, машинный код ), которая может быть легко выполнена целевой системой.
Сложные компиляторы обычно выполняют несколько проходов по различным промежуточным формам. Этот многоэтапный процесс используется, потому что многие алгоритмы оптимизации кода проще применять по одному за раз, или потому что входные данные для одной оптимизации зависят от завершенной обработки, выполненной другой оптимизацией. Такая организация также облегчает создание единого компилятора, который может быть нацелен на несколько архитектур, поскольку только последний из этапов генерации кода (бэкенд ) должен меняться от цели к цели. (Для получения дополнительной информации о проектировании компилятора см. Компилятор .)
Входные данные для генератора кода обычно состоят из дерева разбора или абстрактного синтаксического дерева . [1] Дерево преобразуется в линейную последовательность инструкций, обычно на промежуточном языке , таком как трехадресный код . Дальнейшие этапы компиляции могут или не могут называться «генерацией кода», в зависимости от того, подразумевают ли они существенное изменение представления программы. (Например, проход оптимизации peephole вряд ли будет называться «генерацией кода», хотя генератор кода может включать проход оптимизации peephole.)
Помимо базового преобразования из промежуточного представления в линейную последовательность машинных инструкций, типичный генератор кода пытается каким-то образом оптимизировать сгенерированный код.
Задачи, которые обычно являются частью фазы «генерации кода» сложного компилятора, включают в себя:
Выбор инструкций обычно осуществляется путем рекурсивного обхода в обратном порядке абстрактного синтаксического дерева, сопоставляя конкретные конфигурации дерева с шаблонами; например, дерево W := ADD(X,MUL(Y,Z))
может быть преобразовано в линейную последовательность инструкций путем рекурсивной генерации последовательностей для t1 := X
и t2 := MUL(Y,Z)
, а затем выдачи инструкции ADD W, t1, t2
.
В компиляторе, использующем промежуточный язык, может быть два этапа выбора инструкций — один для преобразования дерева синтаксического анализа в промежуточный код, а второй этап гораздо позже для преобразования промежуточного кода в инструкции из набора инструкций целевой машины. Этот второй этап не требует обхода дерева; он может быть выполнен линейно и обычно включает простую замену операций промежуточного языка на соответствующие им коды операций . Однако, если компилятор на самом деле является транслятором языка (например, тот, который преобразует Java в C++ ), то второй этап генерации кода может включать построение дерева из линейного промежуточного кода.
Когда генерация кода происходит во время выполнения , как при компиляции just-in-time (JIT), важно, чтобы весь процесс был эффективным с точки зрения пространства и времени. Например, когда регулярные выражения интерпретируются и используются для генерации кода во время выполнения, часто генерируется недетерминированный конечный автомат вместо детерминированного, потому что обычно первый может быть создан быстрее и занимает меньше места в памяти, чем второй. Несмотря на то, что он обычно генерирует менее эффективный код, генерация кода JIT может использовать преимущества профилирующей информации, которая доступна только во время выполнения.
Фундаментальная задача получения входных данных на одном языке и создания выходных данных на нетривиально другом языке может быть понята в терминах основных трансформационных операций формальной теории языка . Следовательно, некоторые методы, которые изначально были разработаны для использования в компиляторах, стали использоваться и другими способами. Например, YACC (Yet Another Compiler-Compiler ) принимает входные данные в форме Бэкуса-Наура и преобразует их в синтаксический анализатор на языке C. Хотя изначально он был создан для автоматической генерации синтаксического анализатора для компилятора, yacc также часто используется для автоматизации написания кода, который необходимо изменять каждый раз при изменении спецификаций. [3]
Многие интегрированные среды разработки (IDE) поддерживают некоторую форму автоматической генерации исходного кода , часто используя алгоритмы, общие с генераторами кода компилятора, хотя обычно менее сложные. (См. также: Преобразование программ , Преобразование данных .)
В общем, синтаксический и семантический анализатор пытается извлечь структуру программы из исходного кода, в то время как генератор кода использует эту структурную информацию (например, типы данных ) для создания кода. Другими словами, первый добавляет информацию, а второй теряет часть информации. Одним из последствий этой потери информации является то, что отражение становится трудным или даже невозможным. Чтобы противостоять этой проблеме, генераторы кода часто встраивают синтаксическую и семантическую информацию в дополнение к коду, необходимому для выполнения.
генерация кода.