16
•
Попытаться ис править код после того, как он сгенерирован.
Это понятие «щелевой» оптимизации. Основная идея в том, что известно какие
комбинации инструкций компилятор собирается произвести и также известно
которые из них «плохие» (такие как код для числа -1). Итак, все что нужно
сделать — просканировать полученный код, найти такие комбинации инструкций
и заменить их на более «хорошие». Это вид макрорасширений наоборот и
прямой пример метода сопоставления с образцом. Единственная сложность в
том, что может существовать множество таких комбинаций. Этот метод
называется «щелевой» оптимизацией просто потому, что оптимизатор работает
с маленькой группой инструкций. «Щелевая» оптимизация может драматически
влиять на качество кода и не требует при этом больших изменений в структуре
компилятора. Но все же за это приходится платить скоростью, размером и
сложностью компилятора. Поиск всех комбинаций требует проверки множества
условий, каждая из которых является источником ошибки. И, естественно, это
требует много времени.
В классической реализации «щелевого» оптимизатора, оптимизация
выполняется как второй проход компилятора. Выходной код записывается на
диск и затем оптимизатор считывает и обрабатывает этот файл снова.
Фактически, оптимизатор может быть даже отдельной от компилятора
программой. Так как оптимизатор только обрабатывает код в маленьком «окне»
инструкций (отсюда и название), лучшей реализацией было бы буферизировать
несколько срок выходного кода и сканировать буфер каждый раз после EmitLn.
•
Попытаться сразу генерировать лучший код.
В этом методе выполняется проверка дополнительных условий перед выводом
кода. Как тривиальный пример, мы должны были бы идентифицировать нуль и
выдать CLR вместо загрузки, или даже совсем ничего не делать, как в случае с
прибавлением нуля, например. Конкретней, если мы решили распознавать
унарный минус в процедуре Factor вместо Expression, то мы должны
обрабатывать —1 как обычную константу, а не генерировать ее из
положительных. Ни одна из этих вещей не является слишком сложной для
реализации, просто они требуют включения дополнительных проверок в код,
поэтому я не включил их в программу. Как только мы дойдем до получения
работающего компилятора, генерирующего полезный выполнимый код, мы
всегда сможем вернуться и доработать программу для получения более
компактного кода. Именно поэтому в мире существует «Версия 2.0».
Существует еще один, достойный упоминания, способ оптимизации, обещающий
достаточно компактный код без излишних хлопот. Это мое «изобретение», в том смысле,
что я нигде не видел публикаций по этому методу, хотя я и не питаю иллюзий что это
придумано мной.
Способ заключается в том, чтобы избежать частого использования стека, лучше
используя регистры центрального процессора. Вспомните, когда мы выполняли только
сложение и вычитание, то мы использовали регистры D0 и D1 а не стек? Это работало,
потому для этих двух операций стек никогда не использовал более чем две ячейки.
Хорошо, процессор 68000 имеет восемь регистров данных. Почему бы не
использовать их как стек? В любой момент своей работы синтаксический анализатор
«знает» как много элементов в стеке, поэтому он может правильно ими манипулировать.
Мы можем определить частный указатель стека, который следит, на каком уровне мы
находимся и адресует соответствующий регистр. Процедура Factor, например, должна