Вир-2. Интерфейсы

Первый этап работы над Вир-2 был техническим, по сути, это была проверка LLVM «на вшивость». LLVM эту проверку прошел. Понятно, что впереди множество частных проблем, но у меня теперь есть основания считать, что LLVM достаточно хорошо продуман и может быть использован в Вир-2.

Моя уверенность опирается на мой опыт, в 1994-1998 я был архитектором и ведущим разработчиком мульти-языковой, мульти-платформенной компилирующей системы XDS (https://www.excelsior-usa.com/history.html).

Новосибирк, 1996, 2-я Ершовская конференция, Никлаус Вирт, И.В. Поттосин и XDS команда около ВЦ СО РАН.

psi96

Технически LLVM очень похожа на XDS, которая состояла из:

  • языковых парсеров (front-ends), строящих языково-независимое внутреннее представление (IR)
  • оптимизатора на основе SSA формы (Single Statement Assignment),
  • и кодо-генераторов (back-ends) для платформ x86, m68k, PowerPC, SPARC и ANSI C.

Так что я могу оценить решения, используемые в LLVM, и вижу ход мысли разработчиков, которые привели к таким решениям.

В ходе первого этапа, я реализовал подмножество языка и проверил его перевод в LLVM IR, потом в код и исполнение кода. Следующий этап: постепенная реализация всего языка, и, в первую очередь, вызова процедур (команд).

Замечание: я использую термины «команда», «процедура», «функция» как синонимы. Говоря о Вире, предпочитаю использовать «команда», остальные использую для того, чтобы сделать текст более понятным.

Из специфики Вира, для реализации вызова нужен доступ к интерфейсу вызываемой команды. Дело в том, в Вире единицей компиляции является команда (процедура/функция). Даже при разработке инструмента (инструмент — это штука, похожая на DLL и на Класс), составляющие инструмент команды компилируются отдельно, а потом собираются в код инструмента.

Соответственно вызов команды — это всегда вызов из другой единицы компиляции. В этом отношении языки Вира (А0 и А1) похожи на Фортран, что забавно: ничто не ново под Луной.

Итого, для вызова (внешней) команды компилятор должен получить её интерфейс. Как и в каком виде?

Естественно, что глупости, типа использование чего-то похожего на include, я сразу отбрасываю. Интерфейс должен быть языково-нейтральным, так как в идеале компоненты программы должны писаться на разных языках. Собственно, определение интерфейса — это большая проблема, на которой легли уже и Байрон, и Рембо. О, извините, всего лишь всякие DCOMы …

К счастью, мы можем опереться на LLVM и возможность решать проблему по частям. На первом уровне, вполне достаточно описывать интерфейсы команд в терминах LLVM типов, например:

i32 ( i32, i32* )

Это функция возвращающая целое, с параметрами: целое и указатель на целое.

И если дальше нам понадобится типизация более верхнего уровня, например, мы захотим проверять статически, что i32, который передается в функцию, в действительности должен быть типа BITSET, то надо будет в интерфейс добавлять «уточнения». Но это потом. Пока же мне достаточно использование LLVM типов.

Для описания интерфейса я использую JSON, что достаточно удобно, хотя, думаю, что при переходе к промышленному использованию надо будет перейти на бинарный формат, например, CBOR.

Пример интерфейса команды:

  • {
  •                «defs»: [
  •                               {
  •                                             «mode»: «proc»,
  •                                             «name»: «Выполнить»,
  •                                             «llvmtype»: «i32 ()»,
  •                                             «cconv»: «ccc»
  •                               }
  •                ]
  • }

Интерфейс строится при компиляции и сохраняется. Очевидно, что в записи не хватает timestamp, чтобы при сборке инструмента проверять, какие команды надо перекомпилировать, но его добавлю позже.

В поле defs могут быть описания типов, используемых в интерфейсе команды. А при сборке инструмента собирается интерфейс инструмента, состоящий из интерфейсов экспортированных команд.

Следствием раздельной компиляции на процедурном уровне является необходимость отдельно компилировать типы (не классы, а обычные типы). Для определения объекта (например, «список») отдельно должна быть описана и скомпилирована структура, и отдельно методы. Набор методов при этом не ограничен никакими синтаксическими рамками и может быть расширен в любой момент.

При этом должен быть изменен подход к защите внутренних (private) полей и методов. Защита не должна быть одноуровневой: разрешено (внутри класса) / запрещено всем остальным (кроме friends). Скорее она должна быть похожей на права доступа к файлам, то есть, группе «Ведущие разработчики компонент» разрешен полный доступ к полям, а группе «Пользователи» доступен только указатель на абстрактный тип данных.

Как очевидно понимает внимательный читатель, это движение к неклоповому ООП.

И последнее замечание об оптимизации: раздельная компиляция на уровне процедуры могла бы вести к неоптимальному коду, но не в нашем случае. Процедура компилируется в только до LLVM IR, а дальше при сборке инструмента, компоненты или программы делается до-компиляция с оптимизацией и сборка. Таким образом все возможности оптимизации могут быть использованы.

3 комментария


  1. Чем описанное принципиально отличается от JVM, .NET и им подобным? То, что делается на другой «элементной базе» и затачивается под другой «языково нейтральный» язык, это понятно, а принципиальное отличие?

    Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *