flang компилятор доказывает, что программа не зациклится 0.6.2 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/raskrutka.sh --check на этом дереве не доходит до сравнения файлов вовсе: собранный из семени компилятор отказывается печатать сегодняшние исходники — 29 замечаний, первое FLANG_PARSE, дальше FLANG_UNKNOWN_NAME на именах «Вызвать», «Знач», «Итог прогона», «Готовая программа» и одно FLANG_NOT_TOTAL.

flang emit: печать отменена — программа не проходит проверку, замечаний 29.

Читается это однозначно: семя отстало от исходников. В исходниках появились имена и формы, которых компилятор из семени не знает, — и пока семя не перепечатано, круг не замыкается: первый двоичный есть, второго нет.

Это не поломка сборки — команда make -C bootstrap работает, и собранный ею flang проверяет и печатает обычные программы. Разомкнут именно круг: шаг, на котором компилятор доказывает, что понимает сам себя.

Как это прогнать

make -C bootstrap -j8        # собрать компилятор из семени
sh scripts/raskrutka.sh --check   # сверить семя с тем, что печатают исходники

Вторая команда печатает семь файлов заново и сравнивает их с закоммиченными. Расхождение она называет файлом, байтом и строкой, а не словом «не совпало».

Пределы печати важны, и они не украшение: --max-steps 1400000000000 --max-depth 20000 (набраны они в scripts/raskrutka.sh, а не в этой странице — сверяйтесь с ним). Эти числа впечатываются прямо в напечатанный байт (#define FL_MAX_STEPS), то есть участвуют в совпадении. Собранный по памяти, с другими пределами, разойдётся молча — ровно так уехал один из прошлых выпусков.

Почему предел такой большой, сказано замером, а не «на всякий случай». Печать зовёт те же проверки, что check, и упирается не она, а ведомость библиотеки: flang check flang/stdlib/json.flang --proof (60 обещаний, 35 функций) не укладывался в прежний миллиард и обрывался FLANG_RECURSION_LIMIT на глубине 78 из 20 000 — то есть не зацикливался, а не досчитывал. Сколько ему надо, сняли двоичным с поднятым потолком: 1 370 430 254 витка, 11 минут 31 секунда, 823 МиБ, код 0.

Нынешние 1 400 000 000 000 выведены уже не из этого пика, а из измеренной цены самого дорогого шага печати: «Суд ядра о программе» досчитан до конца 29 августа 2026 и стоит 456 857 834 234 шага, потолок — эта цена, умноженная на три. Цена запаса названа там же: незавершающаяся программа считается до остановки 16,8 часа при 23,16 млн шагов в секунду. Весь разбор — в шапке scripts/raskrutka.sh.

Здесь стояло «нынешние четыре миллиарда» и «около тридцати четырёх минут»; оба числа отстали от scripts/raskrutka.sh на две правки потолка.

Что этот круг НЕ значит

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

Что ловит эту породу ошибок в дереве — замороженные таблицы ответов (flang/test/fixtures/, 52 записи): по ним прогоняется сегодняшний двоичный, и расхождение с записанным ответом красное. Прогон стоит в CI работой «Подделки ядра отвергнуты» (bootstrap/flang io flang/scripts/kernel-forgeries.flang --plan 'Подделки остаются недоказанными', .github/workflows/binary.yml). Это ловит регресс — «вчера отвечал так, сегодня иначе» — и ничего сверх того. Реализация языка одна, сличать её ответы с независимым прочтением тех же правил не с чем.

Семя отстаёт от исходников молча

Круг сходится — это ещё не значит, что закоммиченное семя отвечает нынешним исходникам. Правку компилятора вливают, bootstrap/ не перепечатывают, и правило лежит в flang/self/*.flang, а в собранной программе НЕ РАБОТАЕТ. Зелено при этом всё: исходник разбирается, семя собирается, круг замыкается на том, что в семени лежит.

Так за одни сутки 21–22 августа 2026 потерялись три правки ядра доказательств: оговорка о конечности, принцип индукции по строке и правило конъюнкции. Проверка, которая это ловила (flang/test/self-bootstrap.test.mjs), была удалена вместе с реализацией на JavaScript, и с того дня семя не сверял никто.

Сверяют теперь двое, и вопросы у них разные.

КомандаВопросЦена (замер 22 августа, 256 ядер)
sh scripts/raskrutka.sh --checkсовпадает ли семя с печатью побайтно19 мин 58 с, 25,1 ГиБ
sh scripts/raskrutka.sh --bystroте ли у печати входы0,52 с

Дорогая сверка печатает заново — потому её и не звали. Дешёвая не печатает вовсе: она сверяет содержимое файлов замыкания компилятора (сегодня их 38), 4 файлов рантайма, уезжающих в вывод дословно, и пределы печати, попадающие в напечатанный байт. Пересчитать можно на месте: в scripts/otpechatok-semeni по строке на файл. Отпечаток лежит в scripts/otpechatok-semeni и снимается самой печатью, а не отдельной командой, которую надо не забыть.

Дешёвая ходит на каждый пуш работой semya в .github/workflows/binary.yml; расхождение — отказ, а не предупреждение. Полную сверку байт в байт зовут перед выпуском и после слияний.

Чего дешёвая не доказывает: что файлы совпадают байт в байт. Она отвечает на более узкий вопрос — «те ли входы», — и ровно этот вопрос стоял без ответа.

Что круг не проверяет

Компилятор не проверяет собственные исходники. flang check на flang/self/bootstrap/compiler.flang отвечает «не проверено — замечаний 29» и кодом 1: те же неизвестные ему имена, из-за которых отказала печать. Судить себя теми же проверками, какими он судит чужие программы, компилятор из семени сегодня не может.

Пересборке нужно дерево, а не каталог семени. Печать читает исходники рантайма C с диска (flang/src/emit/c/), и копий их в bootstrap/ нет. Отдельно взятого каталога bootstrap/ для круга недостаточно.

Откуда берётся выпуск

C в архиве выпуска напечатан из этих же исходников тем же кругом. Поэтому установка из архива, установка через Homebrew и сборка из клона дают один и тот же двоичный файл — а не «примерно такой же».