Как и прежде, никакими вспомогательными инструментами для автоматической генерации компиляторов я не пользовался. Такое упрямство может выглядеть странным, однако проект имел единственную цель — моё собственное удовольствие, и дополнительные инструменты послужили бы здесь лишь помехой. В этом смысле компилятор разрабатывался с чистого листа.
Пять шагов к самокомпиляции в Windows
Стоит сказать несколько слов об основных задачах, которые пришлось решить на пути от DOS к Windows:
Формирование заголовков и секций исполняемого файла. Помимо официального описания формата Portable Executable, отличным подспорьем на этом этапе стала статья Creating the smallest possible PE executable. Поскольку в заголовках и секциях требуются точные адреса процедур и переменных, а их можно узнать лишь после вычисления размера кода и глобальных данных, то компиляцию пришлось сделать трёхпроходной. При первом проходе строится граф вызовов процедур и отмечаются «мёртвые» процедуры; при втором — вычисляются адреса, размер кода и данных, заполняются заголовки; при третьем — генерируется код. Такой кунштюк весьма неизящен, особенно с учётом того, что при каждом проходе заново повторяются все этапы компиляции, начиная с лексического разбора. Однако он приводит к очень лаконичному исходному коду компилятора и не требует никакого промежуточного представления программы.
Новый генератор кода. Компиляция для Windows потребовала заменить пары регистров «сегмент — смещение» на 32-битные регистры смещений, а также удалить (а местами добавить) префиксы изменения длины операнда (66h) и длины адреса (67h).
Директива для объявления внешних функций Windows API. Все имена функций, объявленных с директивой external
, заносятся в таблицы секции импорта исполняемого файла. Поскольку эти функции требуют передачи аргументов справа налево, пришлось вручную инвертировать порядок аргументов в объявлении и вызовах всех таких функций. Тем самым отпала необходимость в инверсии средствами компилятора. Ради простоты все аргументы процедур и функций в XD Pascal передаются в виде 32-битных величин; к счастью, это правило справедливо и для функций Windows API, так что взаимодействие с системными библиотеками не привело к усложнению механизма передачи аргументов.
Удаление множеств и инфиксных строковых операций из исходного кода. Это требование связано с задачей самокомпиляции. Вычисление любых выражений в XD Pascal строится так, что все промежуточные результаты имеют длину 32 бита и сохраняются в стеке. Для строк и множеств Паскаля этот подход неприемлем. Точнее, он позволил бы иметь множества размером до 32 элементов, но такие множества оказались бы практически бесполезны.
Обёртки для некоторых процедур. Идея самокомпиляции привела к обёртыванию вызовов некоторых процедур стандартной библиотеки. Сигнатура обёртки едина для случаев компиляции внешним компилятором (Delphi/Free Pascal) и самокомпиляции; обёртываемые процедуры при этом различаются. Таким образом, вся специфика способа компиляции локализуется внутри нескольких обёрток. Паскаль пестрит процедурами, которые при ближайшем рассмотрении оказывается невозможно реализовать по правилам самого Паскаля: Read
, Write
, Move
и т.д. Для самых употребительных процедур, в том числе Read
и Write
, я сделал исключение и реализовал их нетипичными для грамматики языка, но привычными любому знатоку Паскаля. Для большинства остальных нетипичных процедур понадобились обёртки. Таким образом, XD Pascal не во всём совместим с Delphi или Free Pascal, однако большой беды в этом нет, поскольку даже сам Free Pascal в режиме совместимости с Delphi фактически остаётся несовместимым.
Компиляция программ с GUI
Задача самокомпиляции, несмотря на своё символическое значение, остаётся ограниченной: компилятор является консольной программой и поэтому выглядит не совсем полноправным обитателем мира Windows. Понадобилось ещё несколько нововведений на пути к компиляции программ с оконным интерфейсом:
Директива компилятору для установки типа интерфейса. Тип интерфейса (консольный или графический) должен быть указан в отдельном поле заголовка исполняемого файла. Как известно, в Delphi и Free Pascal для этого существует директива $APPTYPE
. Аналогичная директива $A
появилась и в XD Pascal.
Операция взятия адреса процедур и функций. В классическом Паскале нет полноценных указателей на процедуры и функции — их в некоторой мере заменяет процедурный тип. Этот тип в XD Pascal не реализован. Как бы то ни было, до сих пор применение операции @
к процедурам в моём скромном проекте казалось мне бесполезным. Однако обработка событий Windows API построена на обратных вызовах (callbacks), и здесь передача адреса вызываемой процедуры-обработчика вдруг стала насущной необходимостью.
Явное указание имён подключаемых библиотек. Для консольных программ было достаточно импорта функций Windows API из библиотеки KERNEL32.DLL
. Программы с GUI потянули за собой USER32.DLL
, GDI32.DLL
и т.д. Понадобилось расширить синтаксис директивы external
, добавив туда имя библиотеки.
Демонстрационная программа с GUI
Что в итоге
В результате получился очень простой самокомпилируемый компилятор для Windows. Вряд ли корректно сравнивать его с могучими коллективными проектами типа Free Pascal. Скорее он попадает в весовую категорию известного любительского BeRo Tiny Pascal. По сравнению с ним XD Pascal имеет заметные преимущества: более строго соблюдается грамматика Паскаля и контролируются ошибки, есть полноценный файловый ввод/вывод, поддерживается арифметика чисел с плавающей точкой, нет зависимости от внешнего ассемблера, допускается компиляция программ с оконным интерфейсом.
Далее мне предстоит разобраться с ложноположительным срабатыванием некоторых антивирусов — новой проблемой, о которой я и не задумывался в маленьком уютном мирке MS-DOS. Если повезёт, XD Pascal будет внедрён, наряду с BeRo Tiny Pascal, в лабораторный практикум по курсу конструирования компиляторов в МГТУ им. Н.Э. Баумана.
Комментариев нет:
Отправить комментарий