Спустя три месяца непрерывной и местами безумной работы с момента последнего релиза, мы готовы представить технологию, которая меняет правила игры в системном программировании. Мы создали AsmX Raptor — новое поколение движка, которое впервые позволяет писать высокоуровневый код и ассемблерные инструкции в едином, неразрывном потоке выполнения.

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

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


Иллюзия контроля: почему индустрия застряла в компромиссах?

Когда вы пишете критичный системный код (ОС, криптографию, кастомные аллокаторы), вам необходим прямой доступ к регистрам CPU, специфическим инструкциям (cpuid, rdtsc, wrmsr) и точное управление стеком. Что индустрия предлагает инженеру сегодня?

  1. Чистый ассемблер (NASM/GAS): Вы получаете 100% контроль, но теряете систему типов, структуры, области видимости и удобство поддержки. Написание бизнес-логики превращается в ад ручного управления памятью.

  2. Раздельная компиляция (C/C++ + ASM объектники): Вы пишете .asm файл, компилируете его, затем линкуете с высокоуровневым кодом. Возникает проблема накладных расходов: чтобы просто вызвать ассемблерную функцию, вы обязаны строго соблюдать ABI (например, System V AMD64), тратить драгоценные такты на пролог/эпилог и сохранение регистров. Это создает «мусор» в виде лишних переходов и объектов в памяти.

  3. C/C++ с inline assembly (__asm__): Самый популярный, но и самый опасный путь.

Проблема, о которой принято молчать: Боль inline asm

Многие считают asm в GCC или Clang решением всех проблем. На практике это черный ящик, который ломает абстракции.

  • Строковый ад: Код пишется внутри строковых литералов. Вы лишаетесь автодополнения, статической проверки типов операндов и адекватной подсветки синтаксиса на уровне IDE.

  • Fragile Constraints (Хрупкие ограничения): Вы пишете asm volatile ("..." : "=r" (val) : "r" (input) : "memory");. Ошиблись в одной букве в выходных операндах (=r vs +m) — и компилятор тихо сгенерирует некорректный машинный код. Эта ошибка «всплывет» только в рантайме в виде трудноуловимого Heisenbug'а.

  • Разрушение пайплайна оптимизатора: Для AST компилятора ассемблерная вставка — это слепое пятно. Оптимизатор может переставить инструкции до или после блока, нарушив хрупкую логику. Компилятор часто перестраховывается, сохраняя лишние регистры в стек при входе в asm-блок, даже если вы их не трогаете (Overhead).

  • Привязка к бэкенду: Поведение этих вставок и синтаксис ограничений специфичны для конкретных версий компиляторов.

Путь инженера — это путь итераций и устранения лишних сущностей. Мы поняли, что ассемблер не должен быть «вставкой». Он должен быть полноправным гражданином языка.


Raptor Engine: Ассемблер как нативный токен

AsmX Raptor решает проблему на уровне фундамента. Здесь нет строковых вставок. Есть единый поток выполнения, где машинные инструкции и строгие декларации типов живут в одном синтаксическом дереве (AST).

Взгляните на этот код:

fn foo_int32_t(int32_t, int32_t) -> int32_t {
 ;; code
}

fn main {
  @mov $0, %rdi;
  @add $10, %rdi;
  @cmp $0, %rdi;

 ;; Полноценная поддержка CV-квалификаторов и типизации в том же скоупе!
  const int32_t* dotw1 = nullptr;
  const volatile int32_t* const volatile dotw12 = nullptr;
  int32_t volatile * const dotw18 = nullptr;
  
  const char* str = "string";
  const int32_t& addrof = ch0;
  bool b3 = 1 > 3;
  int32_t (*funcPtr)(int32_t, int32_t) = foo_int32_t;
  
  int16_t casted = reinterpret_cast<int16_t>(43);
  
  @syscall($1, $1, &message, $13);
  @call somefunc
  @mov $60, %rax
  @mov $0, %rdi
  @syscall
}

Здесь @mov и const int32_t* парсятся одним и тем же ядром. Компилятор понимает семантику ваших действий с регистрами и типами одновременно.

Архитектурный сдвиг: От плоского списка к Frontend-Backend Split

Переход на Raptor Engine — это фундаментальная смена парадигмы для AsmX. В старом ядре (Legacy Driver) лексер передавал токены базовому парсеру рекурсивного спуска (recursive descent), который почти мгновенно транслировал их в плоский список инструкций для железа. Это делало невозможным сложный статический анализ и многопроходные оптимизации.

В Raptor мы внедрили строгую многоуровневую архитектуру компиляции:

  1. Transformer V2 (Умный препроцессинг): Пре-нормализация токенов и разрешение сложных лексических конструкций с помощью логики lookahead (заглядывания вперед).

  2. Pratt Parser: Мы объединили рекурсивный спуск с алгоритмом сортировочной станции (Precedence Climbing) для построения строго типизированного абстрактного синтаксического дерева (AST).

  3. Semantic Analyzer: Проактивная валидация. Анализатор обходит AST, выполняет проверку типов (QualType), управляет областями видимости (Scope) и разрешает символы до того, как будет сгенерирован хоть один байт машинного кода.

  4. Compiler Driver (Raptor): Мост абстракций. Он связывает высокоуровневое AST с низкоуровневым аппаратным представлением через паттерн "Operand Bridge".

Благодаря этому конвейеру, AsmX Raptor теперь не просто транслирует мнемоники, он понимает ваш код.

Эволюция системы типов: от хардкода к полноправным абстракциям

Давайте будем честны: в старой версии AsmX система типов существовала скорее для галочки. Был жестко задан один-единственный тип str, и на этом статический анализ заканчивался. Но чтобы язык мог претендовать на системный уровень, он обязан понимать, с какими данными работает железо.

В релизе AsmX Raptor мы с нуля переписали систему типов, вдохновляясь мощью C++, но отбрасывая его исторический багаж. В движок встроена таблица базовых типов, которая «из коробки» понимает int16_t, int32_t, bool, char и указатели. Что мы имеем под капотом теперь:

Истинный nullptr, а не макрос-костыль

В C и раннем C++ NULL был просто макросом, разворачивающимся в 0, что приводило к диким ошибкам при перегрузке функций. В Raptor nullptr — это самостоятельный примитивный тип (nullptr). Анализатор гарантирует, что nullptr может инициализировать только указатели.

const int32_t* p = nullptr; ;; OK: указатель принимает nullptr
const int32_t p = nullptr;  ;; Ошибка компиляции
[ExpressionException]: [type error] cannot initialize 'const int32_t' with nullptr: not a pointer type
95 |	
96 |	 const int32_t p = nullptr;
97 |	 ^-------------------------

Полноценные CV-квалификаторы и защита LValue

Язык нативно понимает const и volatile, храня их как свойства внутри обертки QualType. Семантический анализатор ударит вас по рукам, если вы попытаетесь присвоить значение read-only lvalue (константе).

const char char_a = 'a';
char_a = 'd';
[ExpressionException]: [type error] cannot assign to a const-qualified lvalue
85 |	
86 |	 char_a = 'd';
87 |	 ^------------

Reference Collapsing и строгие касты

Мы реализовали правила схлопывания для ссылочных типов (Reference Collapsing), чтобы в будущем гладко интегрировать продвинутое управление памятью и семантику перемещения.

Reference Collapsing (Схлопывание ссылок): Реализованы строгие правила для ссылочных типов (LValue/RValue). Попытка привязать неконстантную ссылку к временному объекту или другому типу вызовет контекстную ошибку:

[ExpressionException]: [type error] cannot bind non-const lvalue reference 'const int32_t&' to a value of type 'const char*'; a temporary cannot be created for a non-const reference
95 |	
96 |	 const int32_t& ref = str;
97 |	 ^------------------------

Никакого неявного превращения указателей в целые числа. Как и в C++, мы ввели явные намерения через узлы ImplicitCastExpression. Вы используете reinterpret_cast<T>, чтобы компилятор (и другие программисты) четко видели: здесь происходит низкоуровневая магия с памятью, и система типов временно отключается для конкретного блока.

Механика продвинутых кастов (Advanced Casting)

Приведения типов в Raptor — это не просто смена тега компилятора. Это полноценные AST-узлы (ImplicitCastExpression), которые проходят семантический анализ. Мы строго соблюдаем правила Value Categories, где касты порождают PRValue (Pure R-Value), за исключением ссылочных кастов, сохраняющих статус LValue.

  • const_cast<T>: Работает только с указателями и ссылками для снятия/добавления CV-квалификаторов.

    [ExpressionException]: [type error] invalid const_cast: const_cast can only be used with pointers or references
    72 |	 int16_t casted = const_cast<int32_t>(2026);
    73 |	                  ^-------------------------
  • static_cast<T>: Выполняет стандартные конверсии с проверкой размеров через AnyType.size().

    int32_t casted = static_cast<int16_t>(43); ;; OK
    int16_t casted = static_cast<int16_t>(43); ;; OK
    [ExpressionException]: [type error] cannot initialize 'int16_t' with 'int32_t'
    72 |	 int16_t casted = static_cast<int32_t>(2026);
    73 |	                  ^-------------------------
  • reinterpret_cast<T>: Низкоуровневый спасательный люк. Позволяет сырое приведение памяти (pointer punning), отключая систему типов для конкретного блока.

Инициализация данных и секции

Системное программирование требует точного размещения данных в бинарном файле. Мы полностью переработали синтаксис работы с секциями. Теперь инициализация переменных в .rodata или .data проходит через строгую проверку тайпчекером:

@section rodata {
  integer: int32_t(1);
  message: const_cast<const char*>("Hello World!\n");
}

@section data {
  call: const_cast<char*>("call from the somefunc\n");
}

Если вы попытаетесь использовать тип, который еще не зарегистрирован в ядре компилятора, AST-парсер мгновенно остановит сборку. Мы отказались от нечитаемых логов в стиле старого GCC и сделали систему ошибок человечной. Компилятор показывает конкретную строку и указывает на проблемный токен:

[ExpressionException]: Unknown type name 'int8_t'
4 |	
5 |  some_int: int8_t(1);
6 |            ^---------

Функции и тайпчекер: контроль на входе и выходе

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

Взгляните на реализацию системного вызова:

fn syscall_write(int32_t fd, const char* buf, int32_t count) -> int32_t {
  @mov $1, %rax;
  @syscall;
}

Здесь int32_t и const char* — это не просто текст. На данном этапе архитектура AST уже позволяет полноценно декларировать функции с типизированными параметрами и возвращаемыми значениями.

Примечание: Разрешение имен (Name Resolution) для перегрузок функций (overloads) и строгая валидация аргументов непосредственно при генерации вызова — это следующие этапы. Реализация перегрузок требует сложного манглирования (name mangling) по стандарту Itanium и скоринга типов. Сейчас мы заложили под это железобетонный фундамент.

Сборка модулей ядра Linux (.ko) без привязки к версии

Возможно, самое революционное системное обновление AsmX-G3 — это синтез динамических модулей ядра.

Legacy-драйвер имел захардкоженные смещения структур ядра (lock на версию 6.17.9-arch1-1). Это делало компилятор хрупким. Raptor теперь поддерживает любую версию Linux Kernel (Version-Agnostic).

Используя команду синхронизации --sync (asmx -S linux-headers), компилятор запускает интеллектуальный Makefile:

  1. Компилирует временный probe-модуль ядра.

  2. Извлекает метаданные и отступы struct module прямо из работающей системы через dmesg.

  3. Кэширует их в .cache/asmx/linux-headers_$(uname -r).json.

При сборке Raptor автоматически синтезирует обязательную секцию .gnu.linkonce.this_module и валидирует метаданные (например, vermagic) против Module.symvers хоста. Это гарантирует, что при выполнении insmod ваше ядро не упадет в Kernel Panic из-за несовпадения структур.


Инструментарий (Dev Tooling): Заглянуть под капот

Разработка системного языка невозможна без глубокой интроспекции. Мы добавили интерактивные REPL-инструменты для проверки работы компилятора в реальном времени:

  • --debug-only-lexer-repl: Интерактивная оболочка (REPL), где вы можете вводить токены и наблюдать, как Transformer V2 их сплавляет.

  • --debug-ast-dump-repl: Мощнейший инструмент для отладки. Вводите C++-подобный синтаксис (например, сложный указатель на функцию) и мгновенно получайте визуализацию дерева AST.

Функция PrintTree теперь выводит не просто плоский лог, а иерархию с указанием Kind, Type и точных координат <line:col> каждого узла, взятых из объекта StructToken.

Legacy vs. Raptor: Итоговое сравнение

Особенность

Legacy Driver

Raptor Driver (G4 Evolution)

Парсинг

Top-down Recursive Descent

Pratt (Precedence Climbing) + Recursive Descent

Валидация

Реактивная (ошибка при эмиссии кода)

Проактивная (Semantic Analyzer Pass)

Типобезопасность

Неявная (String-based)

Строгая QualType (с CV-квалификаторами)

Kernel Support

Привязка к конкретной версии

Агностичная (Dynamic Sync) через --sync

Доступ к памяти

Базовый @mov &var, %reg

Интеллектуальный @lea с deferred RIP patching

Кодогенерация

Прямые вызовы HWM

Абстракция "Operand Bridge"

Приведение типов

Сырое (None)

Поддержка static_cast, const_cast, reinterpret_cast

Roadmap: Почему мы не даем точных сроков

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

Следующий логичный шаг — кодогенерация (бэкенд) для локальных переменных, чтобы они превращались в реальные аллокации на стеке или в регистрах. И здесь нас часто спрашивают: "Когда точный релиз?"

Мы не даем жестких roadmap-ов по одной простой причине. Разработка компилятора — это классический «эффект домино». Натыкаясь на необходимость реализовать выделение памяти под переменную, мы понимаем, что нам нужно усложнить систему областей видимости (Scope). Расширение скоупов тянет за собой изменения в таблице символов (SymbolTable.Environment), а это заставляет переосмыслить фазу линковки. Внедрение новых конструкций, вроде IfStatement, потребует модификации конвейера парсера.

Мы предпочитаем выпускать фундаментально правильные решения, а не резать архитектуру ради дедлайнов. AsmX Raptor уже сейчас дает беспрецедентный контроль над инструкциями в рамках единого синтаксиса. И это только начало.

Ссылки

  • Git diff: 66 files changed, +9 707 insertions(+), -155 deletions(-)