Closed Mazdaywik closed 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
точно копируют реализованную семантику:
Пока писал примерный код интерпретатора, обнаружился другой тонкий вопрос: а что делать с функцией 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
тоже.
Проблема, описанная в предыдущем комментарии, решена.
Функции загрузки и выгрузки модуля принимают указатель на виртуальную машину и указатель на звено в поле зрения, куда надо помещать вызовы INIT
и FINAL
. Если указатель нулевой, то вызов размещается перед правым граничным элементом (что осмыслено для начальной инициализации главного модуля).
Виртуальная машина поддерживает функцию execute_zero_arity_function
, которая принимает указатель на функцию и вызывает его без параметров. Именно она используется для вызова функций Go
, INIT
и FINAL
.
Задачу закрою после того, как переведу на INIT
и FINAL
библиотеку Library
.
Задача полностью выполнена.
Эта задача — подзадача для #76 и блокирует задачу #163.
Цель
Требуется, чтобы при загрузке и выгрузке модуля мог выполняться специфический пользовательский код.
Мотивация
Задача #163 требует, чтобы могло одновременно существовать несколько независимых экземпляров Рефал-машины. В частности, может существовать несколько независимых экземпляров динамически загруженного модуля. В частности, если динамически загруженный модуль содержит нативный код с глобальными переменными, в каждом из экземпляров должны быть свои значения.
Эти значения глобальных переменных надо где-то инициализировать. Сейчас код полагается на значение по умолчанию (ноль) либо использует отложенную инициализацию с финализацией по
refalrts::at_exit()
. А некоторые глобальные переменные так и оставлены нереентерантными.Предлагаемая реализация
У каждой единицы трансляции могут быть свои инициализаторы и финализаторы. Код инициализации находится в функции
INIT
, финализации — в функцииFINAL
, причём обе функции являются локальными! Смысл в том, что каждая единица трансляции имеет индивидуальные cookies, которые идентифицируют её для загрузчика. Загрузчик, видя новые для себя cookies, запоминает их в очереди на инициализацию. Затем, после успешной загрузки модуля он последовательно ищет для данных cookies локальные функцииINIT
и вызывает их. При выгрузке модуля аналогично вызываются функцииFINAL
в обратном порядке.Данный механизм довольно легко встраивается в общий подход. При этом, если модули между собой не имеют циклических зависимостей, их относительный порядок инициализации и финализации будет логичным и предсказуемым.
Из недостатков можно отметить, что относительный порядок инициализации различных единиц трансляции будет не определён. Точно также, как не определён относительный порядок инициализации вызовов конструкторов в разных единицах трансляции C++.