Closed Mazdaywik closed 4 years ago
Это прекрасная задача для курсовой работы или даже ВКР. Для курсовой достаточно реализовать базовый вариант — когда компилятор отказывается от специализации, если сигнатура «свистит». Т.е. прерывание цепи специализаций без обобщения.
Задача не была выбрана в качестве курсовой.
Что надо будет сделать по коду.
e.SpecInfo
, чтобы в нём хранить ещё и историю сигнатур.SpecUnit
) до анализа вызова (SpecCall
), где её уже можно будет использовать.SpecCall
уже учитывать сигнатуры, как описано выше.Ну, по крайней мере, я так вижу.
Добрый день, @koshelevandrey!
@Kaelena разрабатывает инструмент для автоматической разметки оптимизируемых функций — её модуль будет автоматически добавлять метки $DRIVE
и $SPEC
в синтаксическое дерево (см. https://github.com/bmstu-iu9/refal-5-lambda/issues/252#issuecomment-621768213).
Метки в задаче #252 должны расставляться безопасно — пометка функций как прогоняемой или специализируемой не должна приводить к зацикливанию. Правильно добавлять метки $SPEC
чертовски сложно, если специализация может зацикливаться, но довольно легко, если специализатор умеет прерываться (предмет настоящей задачи).
Поэтому предлагаю работу разбить на два этапа:
master
, я делаю ревью и сливаю.master
в свою ветку и может вести разработку и тестирование разметки $SPEC
’ов.
Мотивация
Компилятор никак не проверяет тот факт, что специализация может привести к зацикливанию, поэтому пользователь может легко написать программу, обработка которой будет выполняться вечно (до истечения лимита итераций). В результирующей программе получится россыпь специализированных функций, каждая из которых вызывает последующую.
Например:
Здесь специализатор зависнет. С ограничением
--opt-tree-cycles=5
получится такой результат:содержимое лога
```Refal5 F { (t.X#1) t.Y#1 =Конечно, это надуманный пример. Но есть реальные ситуации, когда с таким зависанием приходится бороться. В своём недавнем письме в рассылку я описал интерпретатор стекового языка, который растворяется при оптимизации. В исходном коде этого интерпретатора есть два случая, когда реально приходится бороться с зависанием:
Во-первых, рекурсивный вызов
Loop
при обработке оператораif
-else
:Более естественно было бы написать
Во-вторых, грязный хак с
TransferBodyToDict
(о чём даже говорит комментарий в коде):вместо логичного
Содержательная оптимизация происходит в функции
Go
, где на вход программы передаётся константный исходный текст. Но есть и побочная оптимизация: оптимизируется сама функцияLoop
. И если написатьif
/else
иdefine
так, как логично, то вызовыи
приведут к зацикливанию.
В первом
e.CODE
будет на каждом цикле возрастать по цепочкеe.1
,e.1 e.2
,e.1 e.2 e.3
…, во второмe.DICT
—e.1
,(s.1 e.2) e.3
,(s.1 e.2) (s.3 e.4) e.5
…Очевидно, в обеих цепочках каждый следующий член можно получить, добавляя в предыдущий новые элементы или, иначе говоря, стирая элементы в следующем, получать предыдущий. Т.е. цепочки можно прерывать по отношению Хигмана-Крускала.
Что надо сделать
Нужно для каждой сигнатуры хранить историю сигнатур. Если специализируемый вызов возник в обычной функции, история сигнатуры состоит из одной сигнатуры. Если специализируемый вызов возник в экземпляре (instance) специализированной функции, то история состоит из истории вызывающей функции + новая сигнатура.
Истории сигнатур для примера с функцией
F
:F@1
:(t.0) ← t.Y
F@2
:(t.0) ← t.Y
,((t.0)) ← t.Y
F@3
:(t.0) ← t.Y
,((t.0)) ← t.Y
,(((t.0))) ← t.Y
F@4
:(t.0) ← t.Y
,((t.0)) ← t.Y
,(((t.0))) ← t.Y
,((((t.0)))) ← t.Y
F@5
:(t.0) ← t.Y
,((t.0)) ← t.Y
,(((t.0))) ← t.Y
,((((t.0)))) ← t.Y
,(((((t.0))))) ← t.Y
Далее, если в истории срабатывает отношение Хигмана-Крускала, т.е. если в последней сигнатуре можно стереть какие-либо элементы и получить одну из предшествующих, то цепочка специализаций прерывается.
И как её прерывать?
Для последней и предыдущей сигнатур вычисляется сигнатура их обобщения и вызов специализируется уже для него. Обобщение можно строить имеющимся алгоритмом вычисления ГСО. Дубликаты в подстановках можно не выявлять, ведь у нас не суперкомпилятор, а всего лишь оптимизатор в компиляторе.
Данное обобщение будет «перестройкой снизу» в терминах С. М. Абрамова (частная переписка). В суперкомпиляции перестройка сверху означает, что если верхняя и одна из дочерних конфигураций похожи, то вычисляется их обобщение, всё поддерево, растущее из верхней конфигурации отбрасывается и заменяется деревом, растущим из обобщения. В случае перестройки снизу дерево не отбрасывается, вместо этого нижняя конфигурация обобщается до обобщения себя и родительской.
В случае специализации при обнаружении похожих по Х.-К. сигнатур мы не отбрасываем функции, специализированные после первой сигнатуры, а обобщаем сигнатуру нижнюю.
Вывод
Обнаружение зацикливания упростит написание некоторых специализируемых функций, в частности, в примере интерпретатора стекового языка ветвление и пополнение словаря можно будет записать более естественно.
Заметим, что при пополнении словаря с известной программой отношение Хигмана-Крускала не сработает.
e.DICT
увеличивается, но при этомe.CODE
уменьшается.