flang компилятор доказывает, что программа не зациклится 0.7.29 GitHub English

К README · Указатель документации

Круг раскрутки: компилятор собирает сам себя

Отсюда вы узнаете, откуда берётся команда flang, если компилятор написан на самом flang, и как проверяется, что собранный компилятор понимает язык так же, как исходники. К концу страницы вы сможете пересобрать компилятор с нуля, имея только cc и make, прогнать эту проверку сами и прочитать её ответ — и узнать, чего она не проверяет.

Задача

Компилятор flang написан на flang. Чтобы его собрать, нужен компилятор flang. Круг замкнут — и разрывается он ровно одним способом: в дереве лежит закоммиченное семя, тот же компилятор, заранее напечатанный в C99.

Круг раскрутки: три шага и одно требованиесемя: C99 в bootstrap/flang₁C99 зановоflang₂makeпечатает исходники<br>flang/selfmakeобязаны совпасть<br>байт в байт
Круг раскрутки: три шага и одно требование

Из семени одним 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 и сборка из клона дают один и тот же двоичный файл — а не «примерно такой же».