Компилирующий тулчейн является одним из самых больших и самых сложных компонентов любой системы, и, как правило, основан на опенсорсном коде, либо GCC, либо LLVM. На Linux-системе, только ядро операционной системы и браузер имеют больше строк кода. Для коммерческих систем, компилятор должен быть абсолютно надёжным, каким бы ни был исходный код, он должен генерировать надёжный, высокопроизводительный бинарный код.
Сколько стоит такой большой, сложный и важный компонент системы? Благодаря опенсорсу, не так много, как вы можете подумать. В этом посте, я приведу реальный пример, который показывает нам, что построение нового коммерческого компилирующего тулчейна возможно без огромных затрат.
Сколько нужно кода?
Анализ, проделанный с помощью (SLOCCount, автор David A Wheeler) показывает, что GCC содержит больше 5 миллионов строк. LLVM меньше, всего 1.6миллионов строк, но он более молодой, поддерживает по умолчанию только C и C++ и имеет в три раза меньше целевых архитектур. Однако полезный тулчейен имеет больше компонентов.
- Отладчик: либо GDB (800К строк), либо LLDB (600К строк)
- Линкер: GNU ld (160К строк), gold (140К строк), либо lld (60К строк)
- Ассемблер/дизассемблер: GNU gas (850К строк), либо ассемблер LLVM
- Binary utilities: GNU (90К строк) и/или LLVM (включено в исходники LLVM)
- Emulation library: libgcc (входит в исходники GCC) или CompilerRT (340К строк)
- Стандартная библиотека C: newlib (850К строк), glibc (1.2M строк), musl (82К строк) или uClibC-ng (251К строк)
Кроме этого, тулчейн нуждается в тестировании. В большинстве инструментов GNU, регрессионные тесты встроены в исходники. В LLVM, регрессионные тесты имеют отдельную кодовую базу, 500К строк. Плюс к этому, во встраиваемых системах нужен отладочный сервер для взаимодействия с отладчиком при тестировании.
Что нужно для портирования компилятора?
Нам нужно портировать тулчейн для коммерческого применения. Многие аспиранты по всему миру портируют компиляторы в ходе своих исследований, но их усилия сосредоточены в одной узкой области данного исследования. В результате компилятор портируется быстро, но он не является ни полным, ни надёжным, так как это не было целью исследовательской программы.
Эта статья посвящена созданию набора инструментальных средств, который надёжно генерирует корректные и эффективные бинарники для любого исходного кода, и предназначен для коммерческого и индустриального применения.
К счастью, большая часть огромной кодовой базы является обобщённой. При разработке всех основных компиляторов были приложены значительные усилия для хорошего отделения кода, зависящего от целевой платформы, и портирование компиляторного тулчейна на новую архитектуру является посильной задачей. Эта задача включает в себя пять стадий.
1. Прототипирование
Вначале создаётся порт, содержащий все компоненты. Создание прототипа важно для того, чтобы выявить наиболее проблемные места при полном портировании, и должно занять несколько месяцев. По окончании этой стадии мы должны уметь компилировать программы, демонстрирующие, что все компоненты работают вместе, как ожидается.
2. Реализация полной функциональности
Завершение всех функций компилятора и других инструментов. Атрибуты, встроенные/intrinsic — функции, а также эмуляция недостающей функциональности должны быть завершены. Должен работать линкер, написан BSP, и, при необходимости, добавлены кастомные опции. В конце этого процесса пользователь получает полнофункциональный тулчейн. Самое важное, полный набор регрессионных тестов должен проходить.
3. Тестирование.
Самая большая часть проекта. Тестирование должно покрывать три области:
— регрессионное тестирование, показывающее, что тулчейн не сломан и может работать для разных архитектур.
-Аттестационное тестирование (compliance testing), часто используются тесты заказчика, показывающие, что вся требуемая функциональность присутствует.
-бенчмарки, показывающие, что сгенерированный тулчейном код удовлетворяет критериям требуемой скорости выполнения кода, размера кода и энергоэффективности.
4. Развёртывание.
На этой стадии вам нужно помочь пользователям изучить новый компилятор и то, насколько он отличается от предыдущих инструментов, и обычно для этого нужны руководства в письменной форме и на видео. Будут обнаруживаться новые баги, и также будет много сообщений о багах, вызванных различиями между старым и новым компилятором. Так бывает, когда LLVM и GCC заменяют старые проприетарные компиляторы, в связи с тем, что они гораздо более развиты в плане функциональности. Если пользовательская база велика, фаза развёртывания становится очень существенной.
5. Сопровождение
LLVM и GCC очень активно развиваются, и новая функциональность добавляется постоянно, как для поддержки новых языковых стандартов во фронтенд, так и для добавления новых оптимизаций в бэкенд. Вы должны поддерживать свой компилятор в актуальном состоянии при всех этих изменениях. Плюс к этому, конечно, у вас будет новая функциональность, специфичная для целевой архитектуры, и багрепорты от пользователей.
Сколько нужно усилий в общем случае
Рассмотрим общий случай. Новая архитектура с большой пользовательской базой, должна поддерживать C и C++, и для bare metal, и для Linux. В этом случае, вероятно, архитектура поддерживает различные реализации, от маленьких процессоров, используемых для bare metal приложений или RTOS во встраиваемых системах, до больших процессоров, способных выполнять полноценное Linux-окружение.
Полный релиз такого тулчейна занимает 1-3 человеко-года. Начальная версия тулчейна (proof of concept) должна быть реализована в течение 3-х месяцев. Реализация всей функциональности должна занять 6-9 месяцев, и ещё 3 месяца, если требуется поддержка bare metal и Linux.
Тестирование занимает как минимум 6 месяцев, но с большим количеством специфических тестов может занять до 12 месяцев. Начальное развёртывание занимает 3 месяца, но с большой пользовательской базой, может занять на 9 месяцев больше.
Усилия по поддержке сильно зависят от количества репортов от пользователей и количества новых функций. Эти усилия могут отнимать от 0,5 человеко-месяца в месяц до, что более вероятно, 1 человеко-месяца в месяц.
Важно отметить, что над проектом должна работать целая команда инженеров: специалист по компиляторам, специалист по отладке, инженер по реализации библиотеки и т.д. Разработка компидяторов является одной из наиболее сложных дисциплин, и ни один инженер не может иметь опыт сразу во всех областях.
Сколько нужно усилий в простейшем случае
Не всегда нужен компилятор, который будет использоваться большим количеством пользователей. Существуют различные специфичные процессоры, особенно DSP, которые разработаны в маленьких компаниях, состоящих из одного инженера. Там, где такие процессоры доказали свою коммерческую успешность, они начинают развиваться, и вместо крошечного ядра, программируемого на ассемблере инженером-одиночкой, становятся гораздо более мощными процессорами с большой командой программистов на ассемблере. В этом случае переход на компиляцию языка С будет означать огромный прирост продуктивности и снижение стоимости разработки.
Для таких случаев, тулчейн должен поддерживать С, без С++, и минимально необходимую библиотеку С. Также архитектура может иметь уже готовый ассеблер и линковщик, который можно использовать. Это сильно уменьшает усилия, до одного человеко-года для создания полностью работающего компилятора.
Стадия proof-of-concept по-прежнему занимает 3 месяца, но затем доводится до полнофункциональной версии ещё за 3 месяца. Тестирование по-прежнему занимает больше всего усилий, и продолжается от 3 до 6 месяцев, но при маленькой пользовательской базе 3 месяца более чем достаточно.
Поддержка также необходима, но для маленькой системы с небольшой пользовательской базой будет достаточно 0,25 человеко-месяца в месяц.
Для маленьких заказчиков, может бвть важно остановиться после реализации всей функциональности. Если небольшая горстка стандартных программ компилируется, этого может быть достаточно для демонстрации эффективности компилятора, без прохождения полного набора тестов.
Исследование темы
В 2016 году в Embecosm обратилась компания, занимающаяся разработкой микроэлектроники, которая много лет использовала свой собственный DSP c 16-битным адресным пространством, разработнный специально для их узкой области. Они использовали уже третье поколение своего процессора, когда осознали, что тратят слишком большие усилия на программирование на ассемблере. Ситуация усугублялась тем, что стандартные кодеки, которые они использовали, имели референсную реализацию на С. У них был компилятор, но очень старый, и портирование кода на новое поколение DSP было невозможно.
Embecosm был заказан тулчейн на основе LLVM, способный компилировать кодеки LLVM и производить высококачественный код. Предполагалось, сто код при необходимости будет дорабатываться вручную. У заказчика был готовый ассеблер и линковщик, который комбинировал все ассемблерные файлы в один, связывал все ссылки, и генерировал бинарный файл, загружаемый в DSP. Заказчик также хотел получить опыт построения компиляторов, поэтому один из инженеров заказчика присоединился к команде Embecosm и занимался поддержкой компилятора, когда проект был закончен.
В первые три месяца мы разработали тулчейн на основе существующего ассемблера и дизассемблера. Для того, чтобы использовать newlib, мы создали псевдолинкер, который извлекает требуемые файлы из newlib в виде ассемблерных исходников, и объединяет их с тестовой программой. Т.к. процессор в кремнии был недоступен, мы проводили тестирование на Verilator-модели чипа. Для этого мы написали gdb-сервер, позволяющий GDB взаимодействовать с моделью. При отсутствии ELF, отладка на уровне исходника невозможна, но GDB способен загрузить программу и получить результат, что достаточно для тестирования.
Это позволило нам скомпилировать тестовую программу и продемонстрировать, что компилирующий тулчейн работает. Стало ясно, что есть два препятствия для достижения полной функциональности: 1) отсутствие поддержки бинарных ELF-файлов и 2) отсутствие поддержки 16-битных char.
На второй фазе мы реализовали GNU-ассемблер/дизассемблер, используя CGEN, на это ушло около 10 дней. Мы также реализовали поддержку 16-битных char в LLVM, как описано в этом посте. С этими двумя вещами завершение полнофункционального тулчейна стало более простым, и мы смогли запускать стандартные LLVM lit и регрессионные тесты GCC для тулчейна, большинство из которых прошли успешно. DSP имеет ряд специальных режимов поддержки арифметики с фиксированной точкой. Для их поддержки мы реализовали специальные встроенные и intrinsic-функции.
На этой стадии у нас получился компилятор, который корректно компилирует код заказчика. Поддержка ELF означает, что возможны такие техники, как link-time optimization (LTO) и garbage collection для секций, что приводит к успешной оптимизации кода, и это соответствует требованиям заказчика при ограниченном объёме памяти. При затратах в 120 человеко-дней, была достигнута цель компиляции С-кода для нового DSP.
Заказчик решил, что на этой стадии функциональность его устраивает, и продолжения работы не требуется. Если они решат, что компилятор будет доступен широкому потребителю, они могут продолжить работу с полным тестированием тулчейна.
Выводы
Два фактора сделали возможным построение полнофункционального компилирующего тулчейна за 120 человеко-дней.
Использование опенсорсного компилятора
Инструменты, использованные в проекте, представляю собой результат общих усилий в тысячи человеко-лет, потраченных компиляторным сообществом на протяжении последних трёх десятилетий. Наши заказчики смогли использовать это преимущество для получения современного тулчейна.
Команда экспертов
Хотя это был проект на 120 дней, над ним работала команда из пяти человек, каждый из которых имеет многолетний опыт. Ни один челове не может знать всё про эмуляцию, GDB, CGEN-ассемблеры, GNU-линкер и компилятор LLVM.
Автор: Владимир