Введение

Реализация языка программирования Pascal

IP Pascal – это реализация языка программирования Pascal, использующая платформу переносимости IP, систему, поддерживающую множество машин, операционных систем и языков программирования. Она реализует язык "Pascaline" (названный в честь калькулятора Блеза Паскаля) и успешно прошла тестирование в рамках набора тестов для проверки соответствия стандарту Pascal (Pascal Validation Suite).

Обзор

IP Pascal реализует язык "Pascaline" (названный в честь калькулятора Блеза Паскаля), который является значительно расширенным супермножеством ISO 7185 Pascal. Он добавляет модульность с контролем пространств имен, включая концепцию параллельного монитора задач, динамические массивы, перегрузку и переопределение, объекты и множество других незначительных расширений языка. IP реализует платформу переноса, включающую в себя набор виджетов, библиотеку TCP/IP, библиотеку MIDI и звука, а также другие функции, позволяющие как программам, написанным на IP Pascal, так и самому IP Pascal, переноситься на различные операционные системы и аппаратные платформы. IP Pascal – одна из немногих существующих реализаций Pascal, прошедших проверку Pascal Validation Suite, обширного набора тестов, предназначенного для подтверждения соответствия стандарту ISO 7185 Pascal. Несмотря на то, что Pascaline расширяет ISO 7185 Pascal, он не ослабляет типобезопасность Pascal (в отличие от многих других диалектов Pascal, использующих так называемые "обходы типов"). Функциональность языка сопоставима с C# (который реализует язык, подобный C++, но без присущих ему проблем с типобезопасностью), и Pascaline может использоваться везде, где применимы управляемые программы (хотя он основан на языке, который на 30 лет старше C#).

Открытое или закрытое положение

Автор языка Pascaline заявил о своем нежелании сохранять его проприетарным. IP Pascal продается как реализация Pascaline, однако сам язык должен быть открытым и иметь качественные реализации. С этой целью полная спецификация Pascaline будет опубликована в сети Интернет, а в долгосрочной перспективе планируется создание версии компилятора-интерпретатора Pascal P5 с открытым исходным кодом (версия ISO 7185 компилятора-интерпретатора Pascal P4 Вирта), реализующей соответствие Pascaline. Эта версия будет известна как компилятор Pascal P6 и также будет опубликована и распространена с открытым исходным кодом. Ценность IP Pascal как коммерческого продукта будет определяться ресурсами IDE и кодировщиком компилятора этой системы. Данная статья основана на достаточно старой версии Pascaline. Более новая версия Pascaline существует как Pascal P6, являясь частью серии Pascal P. См. ссылки ниже.

Юникод

IP Pascal может генерировать программы либо в режиме ISO 8859-1 (с 8-битными символами), либо в режиме Unicode простым переключателем во время компиляции (в отличие от многих других языков, исходный код Unicode и не-Unicode программ не различается). В текстовых файлах используется формат UTF-8, совместимый с ASCII, и эти файлы читаются и записываются как 8-битные, так и 16-битные символы внутри программы (верхние 128 символов ISO 8859-1 преобразуются в формат UTF-8 и обратно в текстовых файлах даже в программе, использующей 8-битную кодировку символов).

Постоянная для верхнего предела символов

Как и maxint, у Pascaline есть maxchr, который представляет собой максимальный символ, существующий в наборе символов (и необязательно имеет графическое представление). Диапазон типа char определяется как от 0 до maxchr включительно. Это важное дополнение для работы с типами, такими как "множество char", и облегчает работу с различными вариантами наборов символов (например, ISO 8859-1 или Unicode).

Реализация Z80

Компилятор был запущен в 1980 году на дисковой операционной системе Micropolis, но быстро перенесен на CP/M, работающую на Z80. Исходная система была написана на языке ассемблера Z80 и выдавала прямой машинный код для Z80. Это был однопроходный компилятор без компоновщика, включавший в себя библиотеку системной поддержки и перемещавший её в генерируемый код, записывая в исполняемый дисковый файл. Сразу после запуска компилятора, почти в канун Нового 1980 года, был написан сопроводительный ассемблер на языке Pascal, а затем и компоновщик на языке ассемблера Z80. Такое необычное сочетание было обусловлено расчетом, показавшим, что таблицы компоновщика станут проблемой для ограниченного объемом памяти в 64 КБ Z80, поэтому компоновщик должен был быть как можно меньше. Это было использовано для переноса исходного кода компилятора и компоновщика Z80 с ассемблера Micropolis (который был ассемблером без компоновщика, создающим единый выходной двоичный файл) в новую систему ассемблера-компоновщика. После этого компилятор был перенастроен для вывода в формат компоновщика, а библиотека поддержки была перемещена в отдельный файл и подключена. В 1981 году компилятор был значительно переработан для добавления оптимизаций, таких как распределение регистров, преобразование логических выражений в переходы, удаление мертвого кода, подстановка констант и другие оптимизации. Это создало реализацию Pascal, которая показывала лучшие результаты в тестах производительности, чем любые существующие компиляторы Z80, а также большинство компиляторов 8086. К сожалению, занимая 46 КБ, он был сложен в использовании, поскольку мог скомпилировать лишь несколько страниц исходного кода, прежде чем переполнить свои таблицы (это была распространенная проблема для большинства реализаций Pascal на процессорах с небольшим адресным пространством). Возможность использования системы в основном объяснялась решением создать компактный компоновщик, позволяющий создавать большие системы из этих небольших объектных файлов. Несмотря на это, исходная реализация IP Pascal использовалась в качестве компилятора общего назначения до 1987 года. На этом этапе IP Pascal был похож на C по своей модульной структуре. Каждый исходный файл представлял собой модуль и состоял из некоторой комбинации модуля "программы", типов, констант, переменных, процедур или функций. Они были в "свободном формате". Процедуры, функции, типы, константы и переменные могли находиться вне любого блока и в любом порядке. Процедуры, функции и переменные из других файлов ссылались с помощью "внешних" объявлений, а процедуры, функции и переменные из текущего файла объявлялись "глобальными". Каждый файл компилировался в объектный файл, а затем все они компоновались вместе. Проверка типов между объектными файлами не выполнялась. В составе исходного компилятора был создан модуль ввода-вывода для терминала, независимый от конкретного устройства, позволяющий использовать любой последовательный терминал (аналогичный модулю CRT в Turbo Pascal), который используется и по сей день. В 1985 году была предпринята попытка переписать компилятор на Pascal. Новый компилятор должен был быть двухпроходным с использованием промежуточного представления, что было разработано для решения проблем с памятью, связанных с первым компилятором. Фронтенд компилятора был создан и протестирован без возможности генерации промежуточного кода (только синтаксический анализ). В 1987 году система Z80, использовавшаяся для IP, была заменена на IBM PC с процессором 80386, и работа над проектом была прекращена. С тех пор использовалось несколько других компиляторов, соответствующих стандарту ISO 7185, завершившись компилятором SVS Pascal, 32-битной реализацией на основе расширителя DPMI.

Внедрение 80386

К 1993 году компиляторы, совместимые с ISO 7185 и обеспечивающие высокое качество 32-битного кода, практически исчезли. В этот момент оставалось два варианта: прекратить использование Pascal или возродить прежний проект IP Pascal и модернизировать его как компилятор для 80386. К тому времени на IBM PC существовал только работоспособный парсер Pascal и ассемблер для Z80. С 1993 по 1994 год ассемблер был сделан модульным для поддержки нескольких процессоров, включая 80386, был создан линковщик, заменяющий линковщик языка ассемблера Z80, и завершен фронтенд компилятора Pascal, генерирующий промежуточный код. Затем был создан симулятор промежуточного кода, написанный на Pascal, для проверки работоспособности системы. В 1994 году с помощью этого симулятора язык "ядра" ISO 7185 IP Pascal был расширен за счет добавления таких функций, как динамические массивы. В 1995 году был создан "проверяющий кодировщик" для генерации машинного кода 80386, а также программа-конвертер, преобразующая выходные объектные файлы в файл "Portable Executable" для Windows. Системная библиотека для IP Pascal была создана на самом IP Pascal. Это был необычный шаг, предпринятый, чтобы избежать последующей перекодировки библиотеки из ассемблера или другого диалекта Pascal в IP Pascal, однако это означало, что генератор кода 80386 и библиотека должны были отлаживаться совместно. В начале 1996 года первоначальная целевая платформа Windows NT была заменена на Windows 95, и IP Pascal стал полностью функционирующим компилятором 80386 под Windows. Система успешно самозагрузилась, а оставшийся код на Pascal был портирован из SVS Pascal в IP Pascal для завершения процесса самозагрузки. Этому процессу значительно способствовала возможность запуска SVS Pascal, основанного на DPMI, под Windows 95, что позволило избежать необходимости переключения между DOS и Windows 95.

Внедрение Linux

В 2000 году была создана версия Linux (Red Hat) только для текстового режима. Эта реализация напрямую использует системные вызовы и избегает использования glibc, что позволяет создавать более компактные исполняемые файлы, чем при использовании полной системы поддержки, необходимой для языка C, но за счет снижения переносимости. Планируется создать версию текстовой библиотеки, использующую информацию termcap, и графическую библиотеку под X11.

Шаги к "напиши один раз, беги куда угодно"

В 1997 году версия библиотеки терминалов из оригинального IP Pascal 1980 года была портирована на Windows, и начата разработка финального кодировщика для 80386. Однако основная причина, требовавшая улучшения кодировщика – скорость выполнения – в значительной степени потеряла актуальность из-за увеличения производительности процессоров IBM PC. В результате новый кодировщик был завершен только в 2003 году. В 2001 году была создана вспомогательная программа для IP Pascal, предназначенная для преобразования C-заголовочных файлов в Pascal-заголовочные файлы. Это должно было заменить ручной способ создания интерфейсов операционной системы для IP Pascal. В 2003 году для IP Pascal был разработан полностью графический модуль, не зависящий от операционной системы. В 2005 году был добавлен комплект для управления окнами и виджетами.

Паскалин

Примерно в 2015 году спецификация языка для IP Pascal была собрана и доработана. Результатом стал язык под названием "Pascaline". Значительная часть спецификации Pascaline была реализована в IP Pascal. Однако было решено сначала реализовать большую часть языка на кодовой базе компилятора Pascal P6. Серия Pascal P – это оригинальный компилятор из ETH Zurich, разработанный студентами Вирта, и существует сегодня как Pascal P4 (название, выбранное Никлаусом Виртом). Он был преобразован в ISO 7185 Pascal в Pascal P5 и сейчас обновляется до языка Pascaline в Pascal P6. Параллельно с этим, Pascal P6 дорабатывается как полноценный компилятор, начиная с генерации кода для процессорной модели AMD64, с целью расширения поддержки и на другие процессоры, такие как ARM и RISC V. Цель – достичь версии Pascal P6 1.0 после полной реализации спецификации Pascaline.

Пети-Ами

Примерно в 2019 году библиотека поддержки IP Pascal была переписана на языке C. Это было сделано с двумя целями: во-первых, сделать библиотеку полезной для всех языков программирования, а не только для Pascal, а во-вторых, упростить и ускорить разработку и отладку на новых платформах. Petit Ami теперь поддерживается для Windows, Linux, а также имеется частичная реализация для Mac OS и BSD Unix.

Уроки

Оглядываясь назад, самой большой ошибкой в версии Z80 была её однопроходная структура. Для этого не было реальных оснований; предыдущий компилятор автора (Basic) использовал многопроходную схему с промежуточным хранением. Единственным аргументом в пользу однопроходной компиляции была предполагаемая скорость. Однако однопроходная компиляция оказалась неудачным решением для небольших машин и вряд ли способствовала продвинутым оптимизациям, характерным для больших машин. Более того, однопроходный подход замедлил или сделал невозможным запуск компилятора из ассемблера Z80 и переход на сам Pascal. Поскольку компилятор был монолитным, переход на Pascal нельзя было выполнить поэтапно, а требовалось произвести полную замену. К моменту начала замены проект пережил саму машину. Наибольшим преимуществом двухпроходной компиляции в реализации I80386 стало ведение стандартного каталога промежуточных инструкций, обеспечивающего взаимодействие между фронтендом и бэкендом компилятора. Этот хорошо понятный "этап" компиляции снизил общую сложность. Интуитивно понятно, что при тесном взаимодействии двух программ одинакового размера сложность не является аддитивной, а мультипликативной, поскольку связи между половинами программы бесконтрольно разрастаются. Еще один урок, извлеченный из опыта работы с Z80 и учтенный в компиляторе 80386, заключался в том, чтобы писать как можно больше кода непосредственно на Pascal, включая библиотеку поддержки. Благодаря тому, что код поддержки 80386 был полностью написан на Pascal, он стал настолько модульным и переносимым, что большая его часть была перемещена из области, специфичной для операционной системы, в раздел библиотеки "общего кода", предназначенный для кода, который не изменяется для каждой машины и операционной системы. Даже "системный" код требует лишь незначительных изменений при переходе от одной реализации к другой. Это позволило значительно сократить объем работы при портировании системы. Наконец, ошибкой было начинать второй этап оптимизации до запуска компилятора. Хотя улучшение генерируемого кода было значительным, вызванное этим увеличение сложности компилятора привело к проблемам с ограниченным адресным пространством. В то время считалось, что более оптимизированный код облегчит запуск компилятора на Pascal. Оглядываясь назад, оставшиеся участки кода, написанные на ассемблере, и были проблемой, и их следовало устранить как можно скорее. Иными словами, проблемы с памятью могли быть временными, но наличие значительных разделов программы, написанных на ассемблере, – это серьезная и долгосрочная проблема.