Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Удаление устаревших элементов языка и компилятора #318

Open
12 tasks
Mazdaywik opened this issue Jul 12, 2020 · 2 comments
Assignees
Milestone

Comments

@Mazdaywik
Copy link
Member

Mazdaywik commented Jul 12, 2020

Мотивация

Проект развивается давно и эволюционно. А любое эволюционное развитие подразумевает накопление рудиментов и атавизмов вплоть до возвратно-гортанного нерва жирафа.

Но в отличие от живых существ, развитие которых осуществляется путём мутаций и отбора (генетическое программирование пока фантастика), программу можно пересмотреть и коренным образом переделать.

Поэтому предлагается пересмотреть язык и компилятор и удалить неадекватные его элементы.

Другой мотивацией может служить, как обычно, ускорение. Ускорение работы самого компилятора + ускорение сборки компилятора. С ускорением работы пока не очевидно, а сборка может ускориться из-за уменьшения объёма исходников.

Перечень устаревших элементов

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

$DRIVE, $INLINE, $SPEC, -OD, -OI, -OS —
замена на $OPT и -OT (#314)

Предлагается под капотом объединить различные виды древесных оптимизаций, как описано в #314. Да, для этого уже создана отдельная задача, здесь упомянуто, поскольку в тему.

$INCLUDE

А вот это уже интереснее! Казалось бы, полезная фича языка и зачем её удалять. Но, рассмотрим её преимущества и недостатки.

  • ✔ При использовании LibraryEx не нужно явно импортировать используемые функции. В большинстве случаев, это Map, MapAccum и Reduce.
  • ✔ В подключаемых файлах можно определить функции с атрибутами прогонки/специализации, компилятор их будет оптимизировать без использования -OG. Собственно, именно возможность оптимизации была основной мотивацией внедрения $INCLUDE (Реализовать включение файлов (ключевое слово $INCLUDE) #92).
    Но преимущество раздельной компиляции с древесной оптимизацией не очевидно. При разработке компилятор собирается без оптимизаций для ускорения цикла самоприменения. Дистрибутив собирается редко, при выпуске новой версии, и со всеми оптимизациями — можно и подождать.
  • ✔ Предобъявления встроенных функций реализованы как неявный $INCLUDE. Реализация довольно простая и красивая. Без механизма подключаемых файлов, вероятно, было бы немного сложнее.
  • (✔) Маленькая приятность — т.к. в каждую единицу трансляции включаются свои локальные функции Map и др, по логу профилировщика можно понять, вызов Map в каком файле требует много времени.
    Преимущество несущественное.
  • (✔) Функции Map и др. теперь определены через Mu. Поэтому теперь можно в Рефале-5λ писать <Map Func e.Items>, где Func — локальная функция. Если Map будет располагаться в другой единице трансляции (как в Рефале-05 + фреймворк), то косвенные функции придётся определять как entry.
    Преимущество несущественное.
    Для совместимости с фреймворком такие функции всё равно нужно определять как entry. А если почему-то хочется косвенную функцию разрешать по имени (например, для вызова <Map s.Func …>, где s.Func приходит извне), то можно использовать конструкцию (&Mu s.Func).
  • ❌ Включаемые файлы замедляют компиляцию программ — для каждого файла, включающего $INCLUDE "LibraryEx";, приходится заново сканировать и разбирать определения одних и тех же функций.
  • ❌ Цель, ради которой добавлялись в язык $INCLUDE, уже решается опцией -OG. Более того, решаемая проблема эта сугубо второстепенная и техническая (ускорение конкретной реализации), но решалась она внесением нового средства в язык.
  • ❌ Реализация подключаемых файлов довольно громоздкая и некрасивая, слишком длинные и ажурные образцы. Пути рефакторинга мне не очевидны.
  • ❌ В компилятор было бы неплохо добавить предупреждения о неиспользуемых функциях и неиспользуемых extern’ах (Предупреждения об избыточных функциях и внешних объявлениях #326). Но одновременная поддержка включения файлов сильно затрудняет эту диагностику: во включённых файлах могут быть и неиспользуемые функции, и неиспользуемые extern’ы, о которых предупреждать не стоит.
  • ❌ Использование $INCLUDE для оптимизации вызовов требует синтаксиса оторванных $ENTRY (см. далее).
  • ❌ Когда функции библиотеки LibraryEx располагались в префиксе, префикс можно было собрать один раз с максимальной оптимизацией, после чего разработка компилятора незначительно ускорялась. Функции типа Map вызываются повсеместно, поэтому их сборка с -OPR заметно ускоряла работу. С переходом на $INCLUDE "LibraryEx"; это перестало работать.
  • ❌ Анализ $INCLUDE также несколько усложняет SRMake, в частности, в нём приходится анализировать escape-последовательности.

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

Синтаксис оторванных $ENTRY (detached $ENTRY)

Эта конструкция была добавлена в язык в сочетании с предыдущей — ради оптимизации некоторых функций библиотеки с сохранением возможности их вызова через обычный $EXTERN. Подробнее смотри в #159.

  • ✔ В сочетании с включением файлов позволяет осуществлять оптимизацию библиотечных функций при раздельной компиляции. Но это преимущество не очевидно, см. аналогичный пункт в предыдущем разделе.
  • ✔ По аналогии с синтаксисом оторванных $ENTRY можно добавить синтаксис «пришитых» $DRIVE, $INLINE и, в перспективе, $OPT. Про это даже задача есть — Синтаксис атрибутов функций #250.
  • ❌ Очевидно, усложняется синтаксис языка. Когда пометка отделена от самой функции, появляется возможность синтаксической ошибки — функцию не определить, но пометить как entry. Т.е. синтаксис концептуально приводит к ошибкам.
  • ❌ Очевидно, усложняется компилятор. На уровне синтаксического анализа ненамного, сложности вылезают на последующих проходах. В частности, именно в этой логике недавно была обнаружена (и исправлена) проблема Проблема с глобальной оптимизацией и detached $ENTRY #302.

Единственная причина, заставляющая этот синтаксис существовать в компиляторе — возможность оптимизировать map-подобные функции с раздельной компиляцией.

$SCOPEID (#284)

Устаревшая конструкция языка. Подробнее — в задаче на удаление #284. Дублировать или конспектировать содержимое #284 я не буду.

Нативные вставки

Нативные вставки, они же вставки кода на С++, позволяют в исходном коде на Рефале-5λ описывать функции, тело которых реализовано на C++. По своему замыслу они являются аналогом ассемблерных вставок уже в самом C++ (или в расширениях некоторых компиляторов Си). В компилятор были добавлены в рамках задачи #11.

Мотивация их добавления была в возможности смешивать в одном файле код на Рефале и код на C++. В частности, так сделано для того, чтобы в одном файле Library.sref иметь некоторые функции, написанные на Рефале, и некоторые функции, написанные на C++.

Рассмотрим их преимущества и недостатки.

  • ✔ Возможность в одном файле иметь и функции, написанные на Рефале, и функции, описанные на C++. В частности, функция, написанная на C++ может быть локальной или даже безымянной:
    (e.Numerator) s.DenomFirst e.Denominator (e.QuotSign) (e.RemSing)
    = s.DenomFirst
    : {
    %%
    refalrts::Iter pfunc = arg_begin->next;
    assert(refalrts::cDataFunction == pfunc->tag);
  • ✔ Функции, написанные на C++, меньше зависят от деталей кодогенерации. Например, и в Простом Рефале, и в Рефале-05 однажды происходил переход от представления функций как пар 〈указатель на код, указатель на строку〉 к указателю на дескриптор, который сам содержит указатель на код и указатель на строку. Оба перехода были совершены после реализации нативных вставок и потребовали минимум изменений:
  • ❌ Проблему в предыдущем пункте можно решить более грамотным продумыванием FFI. Например, в Рефале-05 для объявления и определения функций используются макросы, а значит, ту же реализацию дескрипторов можно менять, не требуя стереотипных (boilerplate) правок.
  • ❌ Польза от перемешивания кода на Рефале и кода на C++ не очевидна. В актуальной реализации такое перемешивание активно используется при реализации длинной арифметики. Но длинная арифметика медленная (как раз из-за своей реализации), и её всё равно надо переписывать на C/C++.
    Но вот недостаток у перемешивания кода Рефала и C++ есть. Со смешанным файлом нельзя работать инструментами, ориентированными на C++, например, IDE с автодополнением.
  • ❌ Возможность перемешивать код уже не совсем адекватна реализации компилятора. Простой Рефал изначально компилировался в C++ — так было задумано изначально. Поэтому использовать вставки кода для него была логичной. Рефал-5λ может компилироваться как в C++, так и в RASL, причём последний используется по умолчанию. Вставки кода на C++ при компиляции в RASL бессмысленны.
  • ❌ Возможность компиляции с нативными вставками, т.е. написания на C++ локальной функции, требует такой костыльной вещи, как идентификаторы области видимости (Удалить $SCOPEID #284). Все функции считаются как бы глобальными, но частью имени являются два целых числа — два нуля для entry-функций и два как бы случайных числа для локальных.
  • ❌ Ну, и очевидно, синтаксис языка усложняется на одну конструкцию — нативные вставки.
  • ❌ Усложняются преобразования программ. Например, нельзя удалить локальную функцию, если она нигде не вызывается из кода на Рефале. Она может вызываться из нативной вставки.
  • ❌ Против нативных вставок также то, что они сначала были добавлены в Рефал-05 (Поддержка нативных вставок Mazdaywik/Refal-05#11), а затем после некоторого анализа всё-таки удалены из него (Удалить нативные вставки Mazdaywik/Refal-05#36).

Подытоживая. Нативные вставки — не самый лучший способ осуществления FFI. При этом они требуют заметного усложнения архитектуры компилятора. Так что нужно продумывать хороший FFI и нативные вставки удалять из языка.

Директива $LABEL

Она нужна только в рамках актуального FFI — с нативными вставками. При использовании другого FFI она становится избыточной.

Простой Рефал (#327)

А вообще, зачем нужна поддержка этого front-end’а? Единственный эксплуатируемый код на Простом Рефале — это его самоприменимый front-end. Потому что если бы я перевёл front-end на Рефал-5λ, то никакой пользы от Простого Рефала уже не было бы.

Помимо front-end’а на Простом Рефале написана куча автоматизированных тестов. Небольшая часть тестов проверяют front-end Простого Рефала, остальные более глубокую логику. При удалении Простого Рефала первую часть тестов логично удалить, а вторую переписать на Рефал-5λ — нет никаких причин продолжать использовать Простой Рефал.

Есть задача по унификации парсеров Рефала-5λ и Простого Рефала — #201. Поскольку их синтаксисы похожи и различаются они по сути только идентификаторами и указателями на функции, можно обойтись одним парсером со внутренним флагом режима. В парсере Рефала-5λ уже есть флаг режима, его можно расширить. Но, если эту задачу реализовать, то объём эксплуатируемого кода на Простом Рефале уполовинится — останется только лексер.

Модульный Рефал поддерживает компиляцию себя в Простой Рефал. Но это не существенно, т.к. переделать кодогенератор на Рефал-5λ нет никакой сложности.

LexGen

Это генератор лексических анализаторов в виде конечного автомата. Также к нему частично написан front-end в виде синтаксиса регулярных выражений (#50).

На данный момент он используется только во front-end’е Простого Рефала. Если последний будет удалён, то генератор станет не нужен.

При разработке лексера Рефала-5λ LexGen не использовался по двум причинам. Во-первых, уже был готовый лексер Рефала-5 из тогда ещё будущего фреймворка. Во-вторых, написать хороший эффективный лексер не сложнее, чем написать автомат и постобработку.

В своём текущем виде генератор мало полезен. Написание автомата с его помощью не на много проще написания генератора лексических анализаторов вручную. Поэтому либо его надо дорабатывать, чтобы он наоборот облегчал разработку, либо выбрасывать.

Генерируемый код тоже не слишком эффективен. Вопросы вызывает эффективность построенных функций, порой содержащих десятки предложений. Не исключено, что вызов функции Type и анализ её возвращаемого значения будет быстрее.

На доработку front-end’а уже давно есть задача #50. На оптимизацию генерируемого кода задача пока не ставилась.

Рассахаривание условий (-OC-)

Режим рассахаривания условий существует в компиляторе по историческим причинам. Когда-то был Простой Рефал, в котором условий не было. Потом независимо я создал проект по конвертации Рефала-5 с условиями и блоками в его базисное подмножество.

Затем я решил внедрить условия в компилятор. На первом этапе я решил адаптировать конвертор, чтобы во входном языке были доступны условия, но при этом не требовалось менять back-end и рантайм.

Затем были реализованы условия (@Anastasya34, #17, #161), но режим рассахаривания условий остался. Режим нативной поддержки условий был оформлен как оптимизация -OC (по умолчанию устанавливается скриптами rlc и rlmake), т.е. при использовании этого ключа рассахаривание условий отключается. Забавно, что это единственный режим оптимизации, включение которого ускоряет работу компилятора.

Рассахаривание условий был оставлено по двум причинам. Во-первых, оно позволило @Anastasya34 сделать сравнительные замеры производительности между нативной поддержкой условий и рассахариванием. Во-вторых, жалко было удалять.

Сейчас рассахаривание условий интересно тем, что в этом режиме возможны и прогонки условий (#248) и прогонки вызовов в условиях (#283). Это может быть актуально для классического Рефала-5 и некоторых стилей программирования. В частности, с рассахариванием условий и пометкой одной из функций как $DRIVE можно ускорить MSCP-A (версия от января 2020) в два раза (там есть специфическое узкое место и оно в условии).

Но концептуально этот режим уже не нужен.

Избыточные ^ (#337)

Стоит запретить знак ^, записанный после ранее не встречавшейся переменной.

Формально это не удаление синтаксического средства. Но, поскольку это сужение входного языка, то здесь оно вполне уместно.

Выводы

Перечислено несколько устаревших средств. Их нужно или удалить (те, которые удалить надо), или аргументированно оставить (те, с которыми всё неоднозначно).

@Mazdaywik
Copy link
Member Author

В заявке #337 предложена следующая стратегия удаления устаревших элементов:

Предлагается такая стратегия: версию, помеченную тегом N, всегда можно собрать версиями N−1 (техническое требование, ибо раскрутка) и N+1. Последнее требование новое, оно теперь будет определять политику подобных изменений. При этом, если некоторый синтаксис планируется объявить ошибкой со следующей версии, то в текущей версии должно выдаваться на это предупреждение.

Соответствующие предупреждения будут называться -Wdeprecated.

Mazdaywik added a commit that referenced this issue Jul 24, 2021
Ранее древенсая оптимизация в lexgen’е подавлялась при помощи объявления
функций как

    $SPEC State (e.acc) e.text;

До реализации #251 «Специализация без шаблона» эта строчка подавляла
специализацию функции-состояния в режиме -OA+. Сейчас эта строчка наоборот,
форсирует специализацию этой функции даже в режиме -OA-.

Избыточная прогонка подавлена при помощи организации автомата так, чтобы
авторазметка (#251) рассматривала некоторые «опасные» функции как базисные,
см. 0cda99e2d.

В текущей версии 3.3 реализована поддержка функции обобщения gen_e__ (#331),
которая предназначена для подавления специализации. Теперь все аккумуляторы
принудительно обобщаются при помощи gen_e__. Прогонка подавляется за счёт
того, что в актуальной версии не реализована прогонка функций с активным
аргументом (#230).

Потеря быстродействия не страшна, т.к. Простой Рефал уже deprecated (#318,
DSL, который растворяется во время компиляции (#50).

Потеря быстродей
Mazdaywik added a commit that referenced this issue Aug 9, 2021
В заявке #318 рассматривается возможность удаления рассахаривателя
условий -OC-. А ведь эта ошибка была найдена именно при компиляции
рассахаренного кода.
@Mazdaywik Mazdaywik added this to the 4.0 milestone Aug 18, 2021
Mazdaywik added a commit that referenced this issue Aug 23, 2021
В заявке #195 предлагалось заменить $LABEL на *$PRAGMA-NATIVE-IDENTS,
но было выбрано слово $IDENT. Во-первых, вещь второстепенная
и с удалением нативных вставок (см. #318) она будет удалена, во-вторых,
имеющийся код легко расширить новым ключевым словом, если оно совпадает
с точностью до регистра с узлом дерева. А директива $LABEL отображается
на узел дерева (Ident t.Pos e.Name).
@Mazdaywik
Copy link
Member Author

Классический режим как режим

Обзор

Компилятор поддерживает возможность компиляции как в режиме «расширенного» Рефала-5λ, так и в «классическом подмножестве». В «расширенном режиме» доступны все средства языка, в «классическом» — только те синтаксические конструкции, которые поддерживают refc и crefal. Использование «расширенного» синтаксиса в классическом режиме является синтаксической ошибкой.

Предложение было внесено в #144 без какой-либо мотивации.

В комментарии #195 (comment) предлагается отказаться от псевдокомментария *$EXTENDED, псевдокомментарий *$CLASSIC — сделать глобальным (единственное его упоминание в любом месте исходника включает классический режим на весь файл).

Из-за того, что в актуальной реализации используется переключение режимов псевдокомментариями, по всему исходнику парсера приходится таскать флаг текущего режима (который может меняться). При изменении семантики *$CLASSIC и удалении *$EXTENDED флаг останется, но будет неизменным (в выходных форматах функций его можно будет убрать).

Как переделать

Когда включен «классический» режим, компилятор считает некоторые синтаксические конструкции ошибками. Когда он выключен, не считает. В компиляторе уже есть другой механизм, способный в зависимости от настроек или считать, или не считать некоторые синтаксические/семантические конструкции ошибками, прерывающими компиляцию. Предлагается «классический» режим реализовать через него.

Речь идёт о предупреждениях и режиме -Werror=…. Если предупреждение выключено, программа принимается молча. Если включено — выдаются сообщения, не препятствующие компиляции. Если включен режим -Werror=…, то предупреждения считаются ошибками, прерывающими компиляцию.

При условии реализации #195 (comment) (т.е. удалении *$EXTENDED и изменении семантики *$CLASSIC) трактовку расширенных конструкций в классическом режиме можно изменить — трактовать их как предупреждения -Wclassic:

  • Расширенный режим будет соответствовать -Wno-classic. Предупреждения на расширенные конструкции не выдаются, это нормальный режим пользования Рефалом-5λ.
  • Классический режим будет соответствовать -Werror=classic. Конструкции Рефала-5λ, отсутствующие в классическом Рефале-5, будут трактоваться как синтаксические ошибки.
  • Появится и промежуточный режим, включаемый -Wclassic (без error) — выдача предупреждений без прерывания компиляции. Такой режим может быть полезен, например, при миграции с Рефала-5λ на классический Рефал-5: по одному убирать предупреждения, после каждой правки тестируя код.

Опция --classic будет неявно соответствовать -Werror=classic, опция --extended — -Wno-classic. Псевдокомментарий *$CLASSIC будет принудительно включать -Werror=classic на конкретный файл. Получится функция, в чём-то противоположная подавлению предупреждений (#345).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant