Первый этап работы над Вир-2 был техническим, по сути, это была проверка LLVM «на вшивость». LLVM эту проверку прошел. Понятно, что впереди множество частных проблем, но у меня теперь есть основания считать, что LLVM достаточно хорошо продуман и может быть использован в Вир-2.
Моя уверенность опирается на мой опыт, в 1994-1998 я был архитектором и ведущим разработчиком мульти-языковой, мульти-платформенной компилирующей системы XDS (https://www.excelsior-usa.com/history.html).
Новосибирк, 1996, 2-я Ершовская конференция, Никлаус Вирт, И.В. Поттосин и XDS команда около ВЦ СО РАН.
Технически 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, а дальше при сборке инструмента, компоненты или программы делается до-компиляция с оптимизацией и сборка. Таким образом все возможности оптимизации могут быть использованы.
Постоянная ссылка
Delphi тоже смотрит в сторону кросс-платформенной компиляции, готовят выпуск версии Delphi с компиляцией под Linux.
http://blog.marcocantu.com/blog/2017-february-natively-compiled-code.html
Постоянная ссылка
Чем описанное принципиально отличается от JVM, .NET и им подобным? То, что делается на другой «элементной базе» и затачивается под другой «языково нейтральный» язык, это понятно, а принципиальное отличие?
Постоянная ссылка
Вот здесь достаточно подробно описано:
http://digital-economy.ru/stati/tekhnologiya-razrabotki-multiplatformennykh-programm-na-osnove-yavnykh-skhem-programm