К README · Указатель документации
Круг раскрутки: компилятор собирает сам себя
Отсюда вы узнаете, откуда берётся команда flang, если компилятор написан на самом flang, и как проверяется, что собранный компилятор понимает язык так же, как исходники. К концу страницы вы сможете пересобрать компилятор с нуля, имея только cc и make, прогнать эту проверку сами и прочитать её ответ — и узнать, чего она не проверяет.
Задача
Компилятор flang написан на flang. Чтобы его собрать, нужен компилятор flang. Круг замкнут — и разрывается он ровно одним способом: в дереве лежит закоммиченное семя, тот же компилятор, заранее напечатанный в C99.
Из семени одним make собирается двоичный компилятор — и больше для сборки ничего не нужно: ни интернета, ни другого компилятора flang, ни какого-либо установленного языка сверх cc. Дальше собранный компилятор печатает сам себя из исходников на flang, из этой печати собирается второй двоичный, и второй обязан совпасть с первым.
Совпали — значит, собранный компилятор понимает язык так же, как исходники, из которых он собран. Не совпали — значит, разошлись, и что именно разошлось, видно файлом и байтом.
Где круг стоит сегодня
Как закоммиченное семя относится к нынешним исходникам, говорят две команды.
sh scripts/bootstrap-reprint.sh --bystro идёт около двух секунд и ничего не печатает. Она называет коммит, которому отвечает семя, проверяет, что этот коммит — предок HEAD, и сверяет отпечаток семени с деревом:
семя отвечает коммиту …, и он предок HEAD:
входов в отпечатке 55, пределы совпали, сводов с тех пор …
тело семени сошлось с деревом: файлов 7, байт …
входы печати с тех пор не двигались: отставание 0 файлов
bootstrap/flang io scripts/seed/what-lags-the-seed.fscript --plan Report --timeout 300000 отвечает на вопрос тоньше: называет по файлам и по функциям, что в исходниках новее семени, то есть чего собранный из семени двоичный не знает.
Как это прогнать
make -C bootstrap -j8 # собрать компилятор из семени
sh scripts/bootstrap-reprint.sh --check # сверить семя с тем, что печатают исходники
Вторая команда печатает семь файлов заново и сравнивает их с закоммиченными. Расхождение она называет файлом, байтом и строкой, а не словом «не совпало».
Пределы печати важны, и они не украшение: --max-steps 8000000000000 --max-depth 20000 (набраны они в scripts/bootstrap-reprint.sh, а не в этой странице — сверяйтесь с ним). Эти числа впечатываются прямо в напечатанный байт (#define FL_MAX_STEPS), то есть участвуют в совпадении. Собранный по памяти, с другими пределами, разойдётся молча.
Почему предел такой большой, сказано замером, а не «на всякий случай». Печать зовёт те же проверки, что check, и самый дорогой её шаг — суд ядра о самом компиляторе. Его измеренная цена хранится в scripts/bootstrap-reprint.sh как MEASURED_COST — 2 508 501 226 319 шагов, — а потолок MAX_STEPS больше неё примерно в 3,2 раза. Цена запаса названа там же: при 23,16 млн шагов в секунду незавершающаяся программа считается до остановки около 96 часов. Весь разбор — в шапке scripts/bootstrap-reprint.sh.
Что этот круг НЕ значит
Совпадение двух двоичных говорит: компилятор понимает язык так же, как понимал его тот компилятор, который эти исходники напечатал. Оно не говорит, что язык понят правильно. Ошибка, одинаково записанная и в исходниках, и в семени, переживёт круг незамеченной: круг сверяет реализацию саму с собой.
Что ловит эту породу ошибок в дереве — замороженные таблицы ответов (flang/test/fixtures/, 55 записей — ls flang/test/fixtures | wc -l): по ним прогоняется сегодняшний двоичный, и расхождение с записанным ответом красное. Прогон стоит в CI работой «Подделки ядра отвергнуты» (bootstrap/flang io flang/scripts/kernel-forgeries.fscript --plan 'Подделки остаются недоказанными', .github/workflows/binary.yml). Это ловит регресс — «вчера отвечал так, сегодня иначе» — и ничего сверх того. Реализация языка одна, сличать её ответы с независимым прочтением тех же правил не с чем.
Семя отстаёт от исходников молча
Круг сходится — это ещё не значит, что закоммиченное семя отвечает нынешним исходникам. Правку компилятора вливают, bootstrap/ не перепечатывают, и правило лежит в flang/self/*.flang, а в собранной программе НЕ РАБОТАЕТ. Зелено при этом всё: исходник разбирается, семя собирается, круг замыкается на том, что в семени лежит.
Правка ядра доказательств теряется ровно так: в исходниках она есть, в двоичном её нет, и все проверки зелёные.
Ловят это две сверки, и вопросы у них разные.
| Команда | Вопрос | Цена |
|---|---|---|
sh scripts/bootstrap-reprint.sh --check | совпадает ли семя с печатью побайтно | полная печать: часы |
sh scripts/bootstrap-reprint.sh --bystro | те ли у печати входы | около 2 с |
Дорогая сверка печатает заново. Дешёвая не печатает вовсе: она сверяет содержимое файлов замыкания компилятора и 4 файлов рантайма, уезжающих в вывод дословно (вместе 55 входов), и пределы печати, попадающие в напечатанный байт. Пересчитать можно на месте: в scripts/seed-fingerprint по строке на файл. Отпечаток лежит в scripts/seed-fingerprint и снимается самой печатью, а не отдельной командой, которую надо не забыть.
Дешёвая ходит на каждый пуш работой semya в .github/workflows/binary.yml; расхождение — отказ, а не предупреждение. Полную сверку байт в байт зовут перед выпуском и после слияний.
Чего дешёвая не доказывает: что файлы совпадают байт в байт. Она отвечает на более узкий вопрос — «те ли входы», — и потому достаточно дешева, чтобы звать её на каждый пуш.
Что круг не проверяет
Компилятор не проверяет собственные исходники за разумное время. flang check --быстро flang/self/bootstrap/compiler.flang за потолок прогона 500 с до ответа не доходит: всё ещё «Связать исходники», больше 20 млрд шагов и 7 ГиБ. Чего двоичный из семени не знает, говорит не check, а bootstrap/flang io scripts/seed/what-lags-the-seed.fscript --plan Report --timeout 300000.
Пересборке нужно дерево, а не каталог семени. Печать читает исходники рантайма C с диска (flang/src/emit/c/), и копий их в bootstrap/ нет. Отдельно взятого каталога bootstrap/ для круга недостаточно.
Откуда берётся выпуск
C в архиве выпуска напечатан из этих же исходников тем же кругом. Поэтому установка из архива, установка через Homebrew и сборка из клона дают один и тот же двоичный файл — а не «примерно такой же».