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

Основным циклом программы сделать цикл из RASLFunction::run() #62

Closed
Mazdaywik opened this issue Sep 6, 2016 · 0 comments
Assignees
Labels

Comments

@Mazdaywik
Copy link
Member

Изначально Простой Рефал предполагался как компилируемый язык. Каждая функция Рефала компилировалась в аналогичную функцию C++. Семантика рефал-машины реализовывалась непосредственно: в цикле выбиралась очередная функция из поля зрения и вызывалась. Собственно, этот цикл был основным.

Сейчас взят курс на интерпретацию (#48). Уже реализован RASL, реализованы структуры времени выполнения (дескрипторы функций, #46), осталось реализовать двоичное представление и динамический загрузчик (собственно, #48). И теперь при выполнении интерпретируемого кода имеют место два цикла: цикл выбора следующей функции и цикл выполнения инструкций RASL.

Наблюдение: если успешно завершилась функция, написанная на RASL’е (refalrts::RASLFunction::run()), то следующая функция, скорее всего, тоже будет написана на RASL’е. Очевидно почему: в RASL обычно компилируются сразу несколько единиц трансляции (например, сейчас — за раз компилируется srefc, srmake и lexgen, за исключением библиотек srlib, компилируемых отдельно), либо, даже если только одна, то она содержит несколько функций, которые могут вызывать друг друга. Следствие: вместо завершения RASLFunction::run() можно извлекать со стека следующую функцию, и если она тоже написана на RASL’е, перезагружать переменные (указатели на интерпретируемый код и константы) и продолжать выполнение.

Слабое место здесь — проверка следующей функции, ибо нужно убедиться, что в поле node->function_info->ptr находится именно указатель на &RASLFunction::run. Но можно изменить представление функций таким образом, чтобы избегать данной проверки. Для этого в refalrts::RefalFunction поле ptr с указателем на функцию заменить на указатель на интерпретируемый код. Таким образом, все функции можно будет считать интерпретируемыми:

  • RASLFunction — уже интерпретируемые, уйдёт избыточное поле ptr, добавится новый опкод, перегружающий поля констант (см. ниже),
  • нативные функции — появится новый подтип NativeFunction, имеющий дополнительное поле ptr, RASL этой функции будет содержать единственную команду — вызвать себя как нативную,
  • RefalSwap — RASL статического ящика также будет содержать единственную команду «обменять»,
  • RefalEmptyFunction — RASL содержит единственную команду — refalrts::icFail.

Пояснения тут требует функция RASLFunction. RefalFunction не содержит полей констант, они есть только в наследнике RASLFunction. В текущей реализации RASLFunction::run() в начале извлекаются константы из дескриптора функции, затем происходит выполнение RASL’а. Если будет единый цикл для всей программы, то при переходе к следующей функции на RASL’е потребуется перезагрузка этих полей. Для этого имеет смысл ввести специальную команду, подгружающую новые значения констант из дескриптора.

В качестве оптимизации команду перезагрузки можно не добавлять функциям, которые не содержат констант, либо ввести частные команды оптимизации, которые перезагружают только часть полей (только функции, только идентификаторы). Последнее имеет смысл, поскольку, например, гигантские целые используются крайне редко, строки — тоже нечасто. Другая оптимизация: генерировать единые таблицы констант на единицу трансляции, не перезагружать константы в тех локальных функциях, которые вызываются только непосредственно. Но это частности, которые можно сделать позже.

К слову, при таком подходе обретает смысл задача #47 — для статических ящиков использовать не цельную команду, а последовательность команд RASL’а.

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

Можно заметить, что ожидаемый результат напоминает архитектуру РЕФАЛа-5. Там аналогично главный цикл программы — цикл выполнения команд RASL’а, для вызова встроенных (≈нативных) функций существует специалный опкод, функции завершаются опкодом, выполняющим переход к следующему шагу. В данном случае это не «слизывание», а результат конвергенции: схожие задачи приводят к схожим решениям.

@Mazdaywik Mazdaywik added the task label Sep 6, 2016
@Mazdaywik Mazdaywik added this to the fall 2016 milestone Sep 6, 2016
@Mazdaywik Mazdaywik self-assigned this Sep 6, 2016
Mazdaywik added a commit to bmstu-iu9/simple-refal-distrib that referenced this issue Sep 11, 2016
…ction

Последний пункт — первый шаг при выполнении задачи bmstu-iu9/refal-5-lambda#62
Mazdaywik added a commit that referenced this issue Sep 11, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
В сгенерированном коде вместо RefalFunction используется RefalNativeFunction,
что позволяет менять базовый класс RefalFunction, не влияя на сгенерированный
код.
Mazdaywik added a commit to Mazdaywik/mrefal that referenced this issue Sep 11, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Mazdaywik added a commit that referenced this issue Sep 12, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Профилировщик практически полностью переписан в виде машины состояний
(хотя многие переходы бессмысленны и бросают исключения).

Добавились новые метрики, которые будет использовать Модульный Рефал,
последний необходимо будет актуализировать.

Да, профилировщик измеряет оверхед, и результат его плачевный.
15 % программы на этот оверхед как раз и уходит. Данную проблему, возможно,
удастся решить в рамках задачи #62 (сделать цикл интерпретации основным).

Результат профилировки тестовой программы выглядит теперь так:

STEP LIMIT REACHED (100000000)

…

Total program time: 12.625 seconds (100.0 %).
(Total refal time): 8.471 seconds (67.1 %).
Linear pattern time: 4.719 seconds (37.4 %).
Runtime time overhead: 4.154 seconds (32.9 %).
Linear result time: 3.752 seconds (29.7 %).
Step count 100000000
Memory used 3 nodes, 3 * 16 = 48 bytes

Видно, что затраты времени увеличились, новый профилировщик вносит больший
оверхед на замеры времени.
Mazdaywik added a commit that referenced this issue Sep 25, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Код статического ящика описывается как последовательность из 11 команд
RASL’а, причём среди них специфических только три: icFetchSwapHead,
icFetchSwapInfoBounds и icSwapSave. Эти команды соответствуют функциям
initialize_swap_head(), swap_info_bounds() и swap_save(), причём
последние две встроены в switch интерпретатора. В refalrts.h упомянутых
функций больше нет, функция initialize_swap_head перенесена в пространство
имён refalrts::vm.
Mazdaywik added a commit to Mazdaywik/mrefal that referenced this issue Oct 9, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
…стью

Была сделана замена RefalFunction→RefalNativeFunction только в кодогенераторе,
однако в библиотечных файлах осталась старая RefalFunction. Кроме того,
согласно bmstu-iu9/refal-5-lambda#62, в обобщённом RefalFunction не должно
остаться поля указателя, а значит, вызывать из одной функции другую по полю
function_info->ptr уже невозможно (см. вызов функции ExitE_).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant