Архитектура

Описание архитектуры компилятора для и конкретных технических решений

На память

clang-17 не поддерживает:

Feature Proposal Available
Coroutines P0912R5 Partial Fully supported on all targets except Windows, which still has some stability and ABI issues.
Extended floating-point types and standard names P1467R9 No
Concepts P0848R3 Clang 16 (Partial)
Modules No

Начиная с C++ should adopt the same characters for C++26.

  • Add @, $, and ` to the basic character set P2558R2 Yes

Система классов реализации

RunTime - единственный класс для приложения (процесса) - интерфейс для взаимодействия с операционной системой

  • Загружат и выгружает модули и является их владельцем (shared_ptr)
  • Хранит сслыки на глобальные объекты (WeakPtr) и непосредственно глобальные объекты (встроенные типы и функции). Ключем явялется строка с внутренним именем объекта
  • Хранит список глобальных объектов (типы данных и прототипы функций, которые создаются на этапе компиляции). Используется парсером и компилятором для разбора исходного текста и создания исполняемого файла.

Module - класс для модуля

  • Хранение объектов модуля (shared_ptr) и добавляет weak_ptr в глобальный список объектов в RunTime

Context - класс для хранения временных данных при выполнении программы

  • Один класс создается сразу при создании RunTime
  • В дальнейшем создается по одному объекту для каждого нового потока (:Thread)
  • Хранение списка объектов (weak_ptr)
  • Хранение локальных объектов (ObjPtr), которые создаются на этапе выполнения

Исходный текст -> Парсинг -> AST (TermPtr) -+-> Компиляция в модуль (LLVM) -> Выполнение модуля | +-> Интерпретация (Выполнение AST)

Реализация загрузки модулей и пакетов

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

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

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

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

Загрузка пакета обработывается точто таким же образом, как и загрузка моделя.

Шаги сборки компилятора

  • Сборка nlc
  • Генерация с помощью nlc --no-runtime --no-dsl -emit-cpp модуля dsl
  • Сборка модуля dsl
  • Генерация с помощью nlc --no-runtime -emit-cpp пакета runtime
  • Сборка пакета runtime

В результате имеем готовый nlc с модулями runtime.nlm и dsl.nlm, а заодно и проверяем:

  • генерацию С++ кода без макросов
  • сборку модуля
  • загрузку модуля
  • генерацию С++ кода с макросами
  • сборку пакета
  • загрузку пакета

Выполнение тестов с модулями runtime.nlm и dsl.nlm

Опции компилятора

  • -no-dsl - не использовать автоматически загружаемый модуль dsl
  • -no-runtime - не использовать автоматически загружаемый модуль runtime
  • -emit-cpp - генерировать выходной *.cpp файл

5.5. alignas(N)

Этот спецификатор появился в C++11. Применяется к простым переменным, массивам и классам. N — это выражение, вычисляемое при компиляции, его значение должно быть степенью двойки. Переменная при этом будет размещена по адресу, кратному значению N. Например:

    alignas(64) char cacheline[64];

    cacheline:Int8[64](__alignas__=64) := _;

constexpr

Этот спецификатор появился в C++11. Применяется к простым переменным, массивам, функциям и функциям-членам. Для простой переменной или массива это означает, что ее значение вычисляется на этапе компиляции и не может быть изменено. Для функции это означает, что ее возвращаемое значение вычисляется на этапе компиляции, если значения аргументов известно на стадии компиляции. (Но такую функцию можно использовать и с обычными аргументами.) Вот пример:

    constexpr double PI = 3.1415926535897932;
    constexpr int Square(int x) { return x * x; }


    PI^:Double = 3.1415926535897932;
    Square^(x:Int64):Int64 ::=  { return x * x; }

5.7. consteval

Этот спецификатор появился в C++20. Применяется к функциям и функциям-членам. Это более строгий вариант constexpr, при вызове такой функции значения аргументов должны быть всегда известны на стадии компиляции.

?????????????????????????????????????
    Square^(x^:Int64):Int64 ::=  { return x * x; }

5.8. noexcept

Этот спецификатор появился в C++11. Применяется к функциям и функциям-членам и располагается в конце инструкции, после списка параметров. Этот спецификатор гарантирует отсутствие исключений в процессе выполнения тела функции.

    func() ::= {*

    *};

5.9. mutable

Применяются к нестатическим членам класса, такие члены можно изменять в константных функциях-членах (см. раздел 5.10.1).

В C++20 для агрегатных типов появилась еще назначенная инициализация (designated initialization):

struct Point { int X; int Y; };

Point pt = { .X = 1, .Y = 2 };

Эта инициализация позаимствована из C. Инициализируемые члены могут быть опущены и не только в конце, для них гарантируется инициализация по умолчанию. В отличие от C, порядок членов изменять нельзя, он должен быть таким же, как и при объявлении.