Здравствуйте, fddima, Вы писали:

F>В плане — ты упомянул бэкэнд. Это в принципе наверное то, что ты имел ввиду в этой ветке
Автор: fddima
Дата: 19.01.17
, когда я говорил, что как жаль, что нет возможности делать нэйтив.


F>Я прежде всего хотел бы понять твои личные (они будут ближе всего к правде) — сколько тут работы.


F>...Т.е.:

F>а) мы генерим нативный парсер
F>б) мы генерим всю нитру нативную
F>в) ...
F>?!

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

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

Описание стадий компиляции


Итак, компиляция состоит из следующих этапов:
1. Компилятор получает список исходников и список библиотек (сборок дотнета, dll или so, в зависимости от платформы).

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

Метаинформация описывается в универсальном формате символов, предоставляемом Nitra.

Для платформ типа .Net или Java символы имеющие аналоги в эти платформах должны считываться и записываться в формате этой платформой. Это приводит к тому, что для платформ со стандартизированными метаданными нужно писать код декодирования и кодирования (записи в формате платформы) символов Nitra из/в этих форматов.

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

3. Компилятор парсит файлы и преобразует их в AST.

4. Компилятор анализирует AST и создает набор символов описывающих именованные сущности компилируемой проеграммы.

5. Компилятор анализирует AST и связывает ссылки на имена с символами и осуществляет проверки корректности.
В результате этого этапа программа описывается как граф символов и AST. При этом AST становится аннотирован информацией четко детерминирующей значение каждой его подветки. По такому представлению уже можно генерировать код.

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

7. Символы описанные в программе (вычисленные на предыдущих стадиях) сериализуются в формат платформы. Для платформ имеющих свой формат метаданных вызвается специально написанная для них функция мериализации метаданных. Она, используя некий API специфичный для платформы, записывает метаданные.

Если платформа не поддерживает метаданных, они могут быть сохранены универсальным образом в поток, а поток может быть записан в компилируемый моудуль (exe, dll, so, ...).

8. Производится генерация кода для выбранной платформы. Этот процесс опять таки можно представить как функцию. На вход ей даются корневые символы полученные на стадии 6 и предыдущих ей. Эта функция обходит все вложенные символы и AST на который они ссылаются (в основном AST кода расположенного внутри функций) и путем вызова API платформы генерирует исполняемый код. Если речь идет о совсем нэйтиве без использования промежуточных форматов вроде LLVM или кода c, то генерируется самопальный промежуточный троичный код (как это описано в учебниках по компиляторам), а уже по нему ассемблерные инструкции. На практики этого делать не надо, так как есть LLVM и тот же С.

Backend


Думаю, что из описанных шагов ясно, что к Backend-у относятся пункты: 2, 7, 8 и (частично) 6.

Таким образом бэкэнд должен уметь:
1. Считать метаданные из библиотек и преобразовать ее в граф символов Nitra.

Символы Nitra — это нечто вроде классов, но более специализированные. Они описываются в виде декларций в файлах .nitra. Вот пример такой декларации для функции в Mini C.

2. Записать граф символов (полученный в результате компиляции проекта) в формат платформы (если надо).

3. Преобразовать отсутствующие в платформе символы и AST в имеющие прямое отображение в платформе AST и символы. Например, преобразовать функциональные типы в ссылки на функции или указатели на объекты, а замыкания в классы или структуры.

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

Пункт 1, из этого списка, нужен как в компиляторе, так и в движке IDE. Остальные пункты нужны только в компиляторах. Таким образом целесообразно разбить бэкнэд на два модуля:
1. Читалка метаданны.
2. Писалка метаданных и кода.

Для поддержки IDE и прочего тулинга достаточно первого модуля (которые, существенно проще реализовать).

Backend для нэйтив


Создавать бэкэнд для нэйтива непосредственно, практически, не имеет смысла. Это большой труд, необходимость знания процессорной архитектуры и привязка к оной.

Более перспективными являются два пути:
1. Генерация кода на переносимом высокоуровневом языка, которым, естественно является С.
2. Использование LLVM как промежуточной высокопереносимой платформы.

Преимущество второго подхода:
1. Отсутствие промежуточной компиляции (из С в нэйтив). Это быстрее.
2. Возможность использования разных оптимизаций.
3. Возможность использования расшерений упрощающих генерации кода для разных фич. Например, я слышал, что в LLVM имеется поддержка замыканий. Так же в LLVM имеется какая-то поддержка GC. Примечание: Не стоит путать наличие поддержки GC и наличие самого GC. GC (вроде как) в LLVM нет. Нужно использовать внешний GC. Но даже наличие поддержки уже упрощает использование внешнего GC.

Портирование Nitra и Nemerle на другие платыормы


Собственно создание бэкэнда еще не является портированием Nitra и Nemerle на платформу поддерживаемую этим бэкэндом.

Наличие бэкэнда для платформы позволяет производить кроскомпиляцию. Так если у нас есть работающие Nitra и Nemerle на донтете, добавив бэкэнд для LLVM мы сможем скомпилировать программу на Nemerle для палтформы поддерживаемой LLVM.

Тот фатк, что сами Nitra и Nemerle являются программами на Nitra и Nemerle позволяет нам скомпилировать Nitra и Nemerle под нужнаю нам нэйтив-платформу.

При этом мы получим Nitra и Nemerle, которые могут производить компиляцию как для этой же (т.е. нэйтив) платформы, так и для всех других (если их АПИ могут работать в отсуствии самой платформы или платформа доступна на данной ОС). В общем, кроскомпиляция работает во все стороны.

Поддержка GC


Ты правильно заметил, что Nemerle, а стало быть и Nitra нуждаются в GC, и что на целевой платформе придется как-то воспроизвести поддержку GC.

Тут есть следующие соображения. Для Nitra наличие GC необходимо, но в силу природы работы Nitra этот GC может быть очень примитивный (по крайней мере на первых порах). Фактически можно свести поддержку GC к реализации пула в котором память выделяется последовательно, а освобождается залпом в одной (или двух-трех) безопасной точке. Это приведет к небольшому перерасходу памяти, но при современных ее объемах это не критично.

Фактически компиляция в интре — это эдакая последовательность действий (пайп). В конце этой последовательности (после записи сборки на диск) всю память можно освободить одним залпом.

Ну, а языки создаваемые на Nitra могут и вовсе не поддерживать GC. Так что можно создать версию того же Nemerle на подсчете ссылок, и со стековой RAII или их смеси. Ну, а потом уже можно будет докрутить полноценный GC. В этой области есть масса идей.


26.01.17 14:33: Ветка выделена из темы [Nitra] Backend
Автор: VladD2
Дата: 21.01.17
— VladD2
Автор: VladD2    Оценить