Создание и манипуляция типами из макросов уровня выражений приводит к проблемам при попытке распараллелить типизацию тел методов.
Предлагаю обсудить следующий вариант обхода этой проблемы. В двух словах он заключается в отказе от создания и модификации типов внутри макросов.
Вместо этого будет позволено задать обработчик некоего события которое будет вызвано из главного потока типизации. В этом обработчике можно будет безопасно изменять состав типов и состав их членов.
Кроме того мы позволяем макросам второго поколения использовать поля объявленные в так называемых макро-классах (в рамках которых будут объявляться макросы второго поколения). Объекты макро-классов будут создаваться в рамках потока производящего типизацию, так что к их полям можно будет безопасно обращаться из макросов. В этих полях можно накапливать данные. В последствии эти данные и можно будет обработать в рамках обработчиках событий.
Однако у нас остается одна проблема. В генерируемом макросом коде нужно ссылаться на тип который еще не создан и на его члены.
Так как раскрытие макросов производится в процессе типизации, добавление типов на которые производится ссылка в обработчиках которые сработают позже типизации возвращенного макросом выражения бесполезно!
Для разрешения этой проблемы типизация методов будет производиться в несколько проходов — этапов. В АСТ ссылающемся на типы и их члены можно ввести дополнительный параметр говорящий типизатору, стадию в конце которой должен появиться тип или его члены на которые производится ссылка. Если типизатор встретит такую ссылку и увидит, что этап больше или равен текущему, то он не станет типизировать данную ветку АСТ (пропустит ее типизацию). На следующем проходе, когда номер текущего этапа будет больше чем значение указанное в ветке АСТ он продолжит типизацию и она завершится успешно, так как к этому моменту тип уже будет добавлен.
Разберем идею на примере:
Предположим нам нужно разработать макрос который ссылается на новое поле некоторого динамически создаваемого типа из макроса уровня выражения. Скажем это будет макрос локализации "L".
macro class Localization
{
macro L(strLit)
syntax: "L" str:StringLiteral
{
def str = strLit.ToString();
def fieldName = $"Value$(Typer.NextId())";
Typer.AfterCurrentPhase += _ => // событие вызываемое после окончания текущей фазы типизации (в главном потоке)
{
def tb = Typer.UserData["Localization"] :> TypeBuilder; // тип определен ранее в макро-атрибуте
tb.Define(<[ member: mutable $(fieldName : dyn) = $(str : string); ]>)
};
<[ Localization.$(fieldName : dyn[Typer.CurrentPhase]) ]> // сообщаем компилятору, что поле будет доступно позже (после текущего этапа)
}
}
Здравствуйте, VladD2, Вы писали:
VD>Создание и манипуляция типами из макросов уровня выражений приводит к проблемам при попытке распараллелить типизацию тел методов.
Что за проблемы?
VD>Предлагаю обсудить следующий вариант обхода этой проблемы. В двух словах он заключается в отказе от создания и модификации типов внутри макросов.
VD>Вместо этого будет позволено задать обработчик некоего события которое будет вызвано из главного потока типизации. В этом обработчике можно будет безопасно изменять состав типов и состав их членов.
Боюсь, что самая тяжелая часть макроса переползет в этот обработчик и тогда выгоды от распаралеливания не будет. Это касается даже твоего примера. Плюс появилась необходимость в дополнительном проходе типизатора (((
Может запретить макросам явно генерировать типы и их члены, а вместо этого предоставить сервис, позволяющий заявить компилятору о намерениях по созданию типов/членов? А компилятор уже сам после каждой стадии будет генерировать типы по этим заявкам, возможно тоже распаралелив эту работу.
macro class Localization
{
macro L(strLit)
syntax: "L" str:StringLiteral
{
def str = strLit.ToString();
def fieldName = $"Value$(Typer.NextId())";
def tb = Typer.UserData["Localization"] :> TypeBuilder; // тип определен ранее в макро-атрибуте
def delayedField = Typer.DefineMemberFor(tb, <[ member: mutable $(fieldName : dyn) = $(str : string); ]>); // компилятор регистрирует "заявку" на создание поля и возвращает отложенное определение
<[ $(delayedField ) ]> //отложенное определение хранит ссылку на "заявку". Это облегчит работу типизатору, позволив ему определить тип поля и момент, когда его уже можно типизовать.
}
VD>Создание и манипуляция типами из макросов уровня выражений приводит к проблемам при попытке распараллелить типизацию тел методов.
Выглядит слишком сложно. А что мешает сделать фазы типизации наподобии yield? Типа как (псевдокод):
macro class Localization
{
macro L(strLit)
syntax: "L" str:StringLiteral
{
def str = strLit.ToString();
def fieldName = $"Value$(Typer.NextId())";
def tb = ...;//здесь создаётся какой-то тип
yield tb;//в этот момет поток типизации метода понимает что дальше типизировать нельзя, ложит новый тип в очередь Typer'a и откладывает типизацию этого метода до следующей версии AST в которой тип уже будет
//когда доходим до этой итерации - тип уже есть, можно использовать
}
}
Здравствуйте, hi_octane, Вы писали:
VD>>Создание и манипуляция типами из макросов уровня выражений приводит к проблемам при попытке распараллелить типизацию тел методов.
_>Выглядит слишком сложно. А что мешает сделать фазы типизации наподобии yield? Типа как (псевдокод):
Это не на yield походит. Это на по сути механизм продолжений, реализованый в ComputationExpressions.
Т.е. заявка на создание мембера:
def field = member <[ mutable x : SomeType; ]>;
foo(field);
переписывается в
context.DefineMember(<[ member: mutable x : SomeType; ]>, fun(field : TypeMember)
{
// вызываемся на следующей версии AST
foo(field);
})