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

Warnings, Errors: На пути к умной диагностике

Хороший компилятор — это не тот, который просто работает, а тот, который помогает вам писать код лучше. Мы уделяем огромное внимание качеству диагностики. Сейчас компилятор уже выдает осмысленные предупреждения и ошибки, указывая на конкретное место в коде:

[zcc::driver<impl AssemblyDriver>]: Compilation of user-defined module is not supported
2 |
3 |     @include std::fs::read_to_string;
4 |     ^--------------------------------
[zcc::driver<impl AssemblyDriver>]: Compilation of user-defined module is not supported
4 |
5 |     @include ownlib("./mylibs/ownlib.asmx");
6 |     ^------------------------------------
[zcc::driver<impl AssemblyDriver>]: Compilation of user-defined fn functions is not supported
16 |
17 |    @fn pub assume {}
18 |            ^--------
[zcc::driver<impl AssemblyDriver>]: Compilation of user-defined fn functions is not supported
18 |
19 |    @fn pub consume_message_broker(str message, uint64 len) {
20 |            ^------------------------------------------------

Но этого мало. Сейчас мы тестируем две новые возможности:

  1. Счетчик предупреждений и ошибок. По завершении компиляции вы будете видеть четкую сводку, которая сразу даст понять масштаб проблем:

    i 4 Warnings, 0 Errors.

    Это стандарт для серьезных инструментов, и мы его внедряем.

  2. Флаг -W для подавления предупреждений. Иногда предупреждения могут мешать, особенно если вы уверены в своем коде. Мы тестируем флаг -W, который полностью отключит вывод всех warnings, оставляя только критические ошибки.

И здесь нам нужен ваш совет. Мы рассматриваем введение более гранулярных флагов, по аналогии с GCC/Clang. Напишите в комментариях, хотели бы вы видеть, например, флаг -Wno-unused-variable, который бы отключал предупреждения только о неиспользуемых переменных?

T-API: Улучшение интерфейса

T-API — это наш парсер аргументов командной строки. Он должен быть гибким и интуитивно понятным. Мы обнаружили недоработку в текущей версии: флаги вроде --llvm@version работают корректно, но более привычный формат --llvm-version интерпретируется как два отдельных флага: --llvm и --version. Это контринтуитивно. Сейчас в лаборатории находится патч, который научит tapi правильно обрабатывать "длинные" флаги с дефисами, чтобы вы могли использовать тот стиль, который вам удобнее.

Call: Связь с внешним миром

Это, пожалуй, самое горячее направление наших исследований. Мы работаем над полноценной интеграцией с системными библиотеками, такими как libc.

В идеале, к которому мы стремимся, код для вывода строки будет выглядеть так:

@include libc;

@section rodata {
  message: str("Hello World")
}

@fn main {
  @call libc::printf("sent %s", $message);
}

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

@include libc;

@section rodata {
  format: str("sent %s")
  message: str("Hello World")
}

@fn main {
  ;; программист сам кладет аргументы в регистры по ABI
  @call printf;
}

Почему существует эта разница и что нужно для перехода?

  1. Разбор сложных выраженийzcc_parser должен научиться понимать конструкцию libc::printf, распознавать ее как вызов функции из внешнего модуля и передавать эту информацию в hwc.

  2. Генерация PLT/GOT: Когда hwc видит вызов внешней функции (printf), он не знает ее точный адрес. Поэтому он генерирует специальный "трамплин" в секции PLT (Procedure Linkage Table). Первый вызов printf перенаправляется на динамический загрузчик системы, который находит реальный адрес функции в libc.so и записывает его в таблицу GOT (Global Offset Table). Все последующие вызовы printf уже будут идти напрямую по адресу из GOT, что практически не сказывается на производительности. hwc полностью управляет созданием этих таблиц.

  3. Сборка вызовов по ABI: Компонент assembly должен быть обучен правилам ABI (Application Binary Interface) для x86_64. Он должен знать, что первые шесть целочисленных аргументов функции передаются через регистры %rdi%rsi%rdx%rcx%r8%r9. Именно assembly должен будет генерировать mov инструкции для загрузки аргументов в правильные регистры перед инструкцией call.

Этот функционал находится в активной разработке. Ваше мнение здесь критически важно: стоит ли нам выпустить текущую "ручную" реализацию или подождать полностью автоматизированной? Напишите в комментариях, это поможет нам определить приоритеты.

Section: Работа с данными

На данный момент в ZGEN стабильно работает секция .rodata для констант. Но для полноценных программ необходима возможность работать с изменяемыми глобальными данными. Прямо сейчас мы проводим тестирование и отладку секции .data:

@section data {
  message: str("This is a data section")
}

Это позволит создавать глобальные переменные, которые можно будет изменять в ходе выполнения программы. Пока что, как и в rodata, мы поддерживаем только строковый тип (str), но с расширением системы типов появятся и другие.

И раз уж мы заговорили о типах...

Голосуйте в комментариях!

Хочу сразу отметить: типы для чисел с плавающей точкой (floatdouble) появятся не скоро. Их реализация требует значительных доработок в FPU/SSE-части компилятора и отдельного масштабного тестирования. Мы доберемся до них, но постепенно. Двигаемся итерационно.

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

Комментарии (5)