bmstu-iu9 / refal-5-lambda

Компилятор Рефала-5λ
https://bmstu-iu9.github.io/refal-5-lambda
Other
78 stars 35 forks source link

Инициализация и финализация модулей #164

Closed Mazdaywik closed 6 years ago

Mazdaywik commented 6 years ago

Эта задача — подзадача для #76 и блокирует задачу #163.

Цель

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

Мотивация

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

Эти значения глобальных переменных надо где-то инициализировать. Сейчас код полагается на значение по умолчанию (ноль) либо использует отложенную инициализацию с финализацией по refalrts::at_exit(). А некоторые глобальные переменные так и оставлены нереентерантными.

Предлагаемая реализация

У каждой единицы трансляции могут быть свои инициализаторы и финализаторы. Код инициализации находится в функции INIT, финализации — в функции FINAL, причём обе функции являются локальными! Смысл в том, что каждая единица трансляции имеет индивидуальные cookies, которые идентифицируют её для загрузчика. Загрузчик, видя новые для себя cookies, запоминает их в очереди на инициализацию. Затем, после успешной загрузки модуля он последовательно ищет для данных cookies локальные функции INIT и вызывает их. При выгрузке модуля аналогично вызываются функции FINAL в обратном порядке.

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

Из недостатков можно отметить, что относительный порядок инициализации различных единиц трансляции будет не определён. Точно также, как не определён относительный порядок инициализации вызовов конструкторов в разных единицах трансляции C++.

Mazdaywik commented 6 years ago

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

Для модулей, загружаемых явно или неявно, большой проблемы нет. Если модуль загружается, скажем функцией <LoadModule e.Name> (название пока гипотетическое, точное будет выбрано при решении задачи #90), то этот вызов замещается вызовами функций <INIT> соответствующих объектных файлов внутри модуля. Аналогично для выгрузки. Вызов <UnloadModule s.Handle> будет замещаться вызовами <FINAL>.

Нюанс. Функции INIT и FINAL либо должны по соглашению возвращать пустоту, либо нужно будет в логику условных LoadModule и UnloadModule добавлять игнорирование их результатов (оборачивая их чем-то вроде функции { e.X = }). Но это частности задачи #90.

Интересная проблема возникает при загрузке. Нужно загрузить в поле зрения вызовы <INIT>, затем вызов <Go>, затем вызовы <FINAL>. Если бы на Рефале писался бы интерпретатор refgo, то его можно было описать примерно так:

//FROM хз откуда
$EXTERN Arg, LoadModule, LookupFunction, FWriteLine, Exit, UnloadModule;

$ENTRY __Go {
  = <Arg 1> : e.ProgName
  = <LoadModule e.ProgName> : s.ModuleHandle /* здесь вызываются INIT */
  = <LookupFunction s.ModuleHandle 'GO'>
  : {
      #Success s.GO = s.GO;
      #Fails
        = <LookupFunction s.ModuleHandle 'Go'>
        : {
            #Success s.Go = s.Go;
            #Fails
              = <FWriteLine #stderr 'INTERNAL ERROR: entry point (Go or GO) is not found'>
                <Exit 158>;
          };
    }
  : s.EntryPoint
  = <s.EntryPoint> : e.IgnoreResult
  = <UnloadModule s.ModuleHandle>; /* здесь вызываются FINAL */
}

Для справки: пляски вокруг GO/Go точно копируют реализованную семантику:

https://github.com/bmstu-iu9/simple-refal/blob/6b1235efb5a2fa0948ed0a6e54cacbaf108c240b/src/srlib/refalrts-vm.cpp#L167-L177

Пока писал примерный код интерпретатора, обнаружился другой тонкий вопрос: а что делать с функцией Exit? Она по логике должна вызывать FINAL для всех зарегистрированных cookies. Т.е. для размещения этих вызовов FINAL в поле зрения должно быть соответствующее API в refalrts.h.

В общем, сейчас стоит задача придумать, как размещать вызовы INIT до вызова функции Go и вызовы FINAL после завершения функции Go или после выполнения функции Exit.

Текущая реализация Exit устанавливает код возврата вызовом refalrts::set_return_code и завершает выполнение выдачей refalrts::cExit. Возможно, ничего менять не придётся — интерпретатор будет замечать этот факт и правильным образом обрабатывать сам.

Замечание к #90: при завершении программы (Exit или естественным путём) нужно правильно выгрузить все загруженные модули и для них вызвать FINAL.

Другие тонкости: Exit может быть вызвана из INIT и FINAL тоже.

Mazdaywik commented 6 years ago

Проблема, описанная в предыдущем комментарии, решена.

Функции загрузки и выгрузки модуля принимают указатель на виртуальную машину и указатель на звено в поле зрения, куда надо помещать вызовы INIT и FINAL. Если указатель нулевой, то вызов размещается перед правым граничным элементом (что осмыслено для начальной инициализации главного модуля).

Виртуальная машина поддерживает функцию execute_zero_arity_function, которая принимает указатель на функцию и вызывает его без параметров. Именно она используется для вызова функций Go, INIT и FINAL.

Задачу закрою после того, как переведу на INIT и FINAL библиотеку Library.

Mazdaywik commented 6 years ago

Задача полностью выполнена.