На данный момент перед развитием компилятором стоит ряд принципиальных препятств…ий, которые мешают использовать его в новых окружениях (в первую очередь .NET Core / .NET 5+).
1. Компилятор загружает в свой процесс библиотечные сборки, используемые пользовательской программой.
- Это не даёт нам возможности поддержать multi-targeting или кросс-компиляцию (компилировать сборку для .NET Core, запускаясь на .NET Framework — потому что мы не сможем загрузить зависимости).
- Это не даёт нам возможности работать с архитектуро-зависимыми сборками для другой архитектуры (например, с x64 сборками на x86 системе, или с ARM64 сборками на x64 системе).
- Это создаёт путаницу и проблемы при работе с NuGet-библиотеками, применяющими гибкое версионирование: нам приходится писать достаточно сложный код в компиляторе для работы со сборками, и даже после всех ухищрений мы не можем поддержать все сценарии (например, загрузить несколько разных версий одной и той же сборки не получится — а, например, в C# есть даже специальный языковой механизм assembly aliases для работы в случаях, когда это _очень хочется сделать_ — но в PascalABC.NET мы на данный момент даже создать такой механизм, кажется, не сможем, даже если захотим).
2. Для генерации кода используется System.Reflection.Emit.
Это очень удобный способ генерации метаданных и байткода, но он обладает существенными ограничениями.
- Поскольку Reflection.Emit занимается генерацией кода во время исполнения (компилятора), он требует наличия всех библиотек в этом же процессе компилятора. Что влечёт за собой все последствия, описанные в п. 1.
- Для сохранения сборок используется `AssemblyBuilder.Save`, который до сих пор не поддерживается в современных версиях .NET (и не будет поддерживаться даже в 8, хотя небольшой и очень медленный прогресс в этом направлении идёт).
- Некоторые типы невозможно скомпилировать из-за того, что Reflection.Emit требует последовательной их генерации. Я не помню точных примеров (мы их находили, когда писали [LENS](https://github.com/impworks/lens)), но некоторые виды взаимозависимых типов могут доставить неприятности. Что-то вроде такого, кажется, не было возможно сгенерировать:
```csharp
class A {
class NestedA : B.NestedB {}
}
class B : A {
class NestedB : A.NestedA {}
}
```
Для решения обозначенных проблем предлагаю следующий план действий.
1. Переписать кодогенерацию вместо Reflection.Emit на Mono.Cecil.
Это позволит нам разрешить все проблемы из п. 2, и технически разблокирует дорогу для запуска компилятора на .NET 5+. Возможно, он даже без дополнительных изменений запустится (так же, как на Mono).
Это весьма объёмная и неудобная работа, потому что её нужно делать «за один присест»: не получится частично перевести некоторые возможности на новый движок кодогенерации. Надо переводить сразу всё.
К счастью, API Mono.Cecil не очень сильно идеологически отличается от Reflection.Emit, так что я ожидаю, что большая часть работы тут будет чисто механической.
Зато Mono.Cecil умеет работать с библиотеками, загруженными в текущий процесс, и их можно использовать в сгенерированном коде (если правильно импортировать в метаданные, что, как правило, несложно). Поэтому эта работа не становится автоматически заблокированной из-за необходимости также заодно переписывать взаимодействие с библиотечными сборками.
Основное место выполнения этой работы — кажется, файл `NETGenerator.cs`, в котором почти 12 тысяч строк.
2. Переписать работу с библиотеками: полностью отказаться от `Assembly.Load*`, вместо этого подгружать сборки через Mono.Cecil.
Это полностью ликвидирует все проблемы, связанные с невозможностью загрузить сборки или перечислить типы в них в рамках текущего процесса: компилятор будет работать только с метаданными сборок, которые обрабатываются простым портабельным кодом.
Также это позволит очень просто выполнять кросс-компиляцию под другие версии целевого рантайма (то есть можно будет компилироваться под .NET Core, даже ещё не выполняясь на нём).
Хочется услышать мнения разработчиков по этому вопросу.
Со своей стороны могу отметить, что такая организация работы бы приблизила PascalABC.NET к схеме, по которой работают современные компиляторы C# и F# (там, конечно, используется не Cecil, но тоже написаны отдельные кодогенераторы и загрузчики библиотек, которые не полагаются на соответствующие возможности хост-рантайма). Также, она бы позволила мне продолжить работу над PascalABC.NET SDK более эффективно (потому что сейчас я постоянно спотыкаюсь об неправильно прогружающиеся зависимости, пытаюсь это как-то ремонтировать, в процессе ломается что-нибудь другое и т.д.).