Node ушёл с пути сборки: точку раскрутки печатает сам двоичный, и обе печати совпали байт в байт
До 20 августа 2026 перепечатать bootstrap/ — компилятор, напечатанный в C99, из которого он и собирается, — можно было ровно одним способом: node scripts/bootstrap-c.mjs. Значит Node лежал на пути СБОРКИ, а не только в проверках, и удалить реализацию на JavaScript было физически нельзя: без неё дерево не восстанавливается из своих же исходников flang/self/*.flang.
Теперь печатает сам двоичный: sh scripts/bootstrap-c.sh зовёт bootstrap/flang emit --target c. Курица и яйцо решается тем, что двоичный уже лежит в дереве собранным — make -C bootstrap даёт его из точки раскрутки, и он печатает следующую.
Главное число: две печати совпали БАЙТ В БАЙТ
Прогон 20 августа 2026, ветка vypusk/raskrutka-sama от c3bebbf0, окружение без Node (env -i PATH=/usr/bin:/bin, где node недостижим):
| файл | байт |
|---|---|
Makefile | 2 893 |
compiler_flang.c | 12 353 351 |
compiler_flang.h | 2 525 322 |
flang_cli.c | 49 020 |
flang_repl.c | 499 612 |
flang_runtime.c | 150 606 |
flang_runtime.h | 60 567 |
| итого | 15 641 371 |
Все семь совпали с печатью реализации на JavaScript — сверено cmp пофайлово, не по сумме байт. Компилятор в этой печати: 4273 функции, 392 типа после связывания.
Это снято сейчас нарочно. Пока вторая реализация жива, сравнить есть с чем; после её удаления сравнивать будет не с чем вовсе, и утверждение «двоичный печатает то же самое» станет непроверяемым задним числом.
Повторено на восьмицелевой точке: снова СЕМЬ из семи
Ствол втащил в двоичный все восемь целей печати (9bc9da69) и следом удалил реализацию на JavaScript (fe8e8a37). Точка раскрутки выросла с 15 641 371 до 23 903 454 байт. Сверка повторена на этом дереве, в окружении без Node, той командой, что зовёт scripts/bootstrap-c.sh:
| файл | байт | совпал с деревом |
|---|---|---|
Makefile | 2 893 | да |
compiler_flang.c | 19 291 878 | да |
compiler_flang.h | 3 833 997 | да |
flang_cli.c | 49 020 | да |
flang_repl.c | 514 493 | да |
flang_runtime.c | 150 606 | да |
flang_runtime.h | 60 567 | да |
| итого | 23 903 454 | 7 из 7 |
Печать стоила 11 м 44 с и 12,4 ГБ — против 5 м 37 с и 8,3 ГБ на одноцелевой. То есть цена растёт вместе с компилятором, и линейно по памяти она уже не помещается в обычный прогон CI.
ГРАБЛИ: без ключей пределов расходится flang_runtime.h, и это не беда печати
Замер той же командой БЕЗ --max-steps 40000000 --max-depth 20000 даёт шесть из семи: расходится flang_runtime.h. Причина не в печати, а в том, что пределы попадают прямо в напечатанный байт — #define FL_MAX_STEPS и #define FL_MAX_DEPTH стоят именно там. Умолчания бэкенда — 10⁶ и 10⁴; компилятор печатается с 40 000 000 и 20 000, иначе собранный из этой печати компилятор отвечает FLANG_RECURSION_LIMIT на любой настоящий вход (ровно так уехал v0.4.1).
Отсюда правило: команду печати не набирают по памяти. Она записана один раз в scripts/bootstrap-c.sh, и звать надо его, а не двоичный напрямую.
Цена: двоичный печатает в 99 раз дольше и берёт 8,3 ГБ
| реализация на JavaScript | двоичный | |
|---|---|---|
| печать точки раскрутки | 3,4 с | 5 м 37 с |
сверка --check целиком | 3,4 с | 4 м 57 с |
| память | доли гигабайта | 8,3 ГБ |
Восемь гигабайт — не мелочь: обычный прогон CI даёт 7 ГБ, то есть там это отказ по памяти, а не медлительность. Поэтому проверка «печать двоичного совпадает с деревом» (flang/test/tochka-dvoichnym.test.mjs) по умолчанию говорит «НЕ ПРОВЕРЕНО» и включается явно — FLANG_TOCHKA_DVOICHNYM=1 или заранее собранным bootstrap/flang. Причина расхода известна и записана в bootstrap-circle-is-broken-but-the-binary-cannot-check-itself: вычислитель здесь написан на самом языке, а арена не отдаёт память до конца запроса.
Сколько JavaScript лежало на пути сборки — замерено загрузчиком, а не поиском
Мерить grep-ом по строкам import неверно: часть импортов динамические. Замер снят перехватчиком загрузки модулей (module.registerHooks) на настоящем прогоне печати:
| было | стало | |
|---|---|---|
| файлов JavaScript, загруженных при печати точки раскрутки | 24 | 0 |
| строк в них | 31 597 | 0 |
| байт | 2 110 475 | 0 |
объявлений function | 583 | 0 |
Крупнейшие из 24: flang/src/types.mjs (7 963 строки), flang/src/parser.mjs (4 739), flang/src/emit/c.mjs (2 342), flang/bin/flang.mjs (2 140).
Сборка релизного архива (scripts/build-release-c.mjs) грузила те же 24, потому что печатала архив сама. Теперь она берёт готовые семь файлов из bootstrap/ и грузит из дерева ОДИН файл — scripts/bootstrap-c.mjs, и то ради имён и пределов.
Проверено на дереве, где реализации на JavaScript нет вовсе
Выгрузка git archive HEAD в пустой каталог, затем удалены все 49 файлов .mjs из flang/src и flang/bin (56 256 строк). В таком дереве, без Node в PATH там, где он не нужен:
make -C bootstrap→ двоичный 8 544 752 байта, код 0;sh scripts/bootstrap-c.sh --check→ 7 файлов, 15 641 371 байт, код 0;node scripts/build-release-c.mjs→ «релиз готов»: архив собрался на PATH без Node, собранный компилятор ответил, установка проверена;node packaging/postinstall.mjs→ двоичный собран за 34,2 с;node packaging/flang-launch.mjs --version→flang 0.5.1.
Ловушка: flang/src/emit/c/ — это C, а не JavaScript
В flang/src лежат не только 45 файлов .mjs. Там же — 20 673 строки на C (flang_runtime.[ch], flang_cli.c, flang_repl.c, flang_conc.[ch]), и печать читает их с диска дословно: они уезжают в вывод с приписанной сверху шапкой. Удали их вместе с JavaScript — и печать откажет словами «не найдены исходники рантайма C», а точка раскрутки перестанет перепечатываться совсем.
Отсюда же следует, что для восстановления компилятора нужен не каталог bootstrap/, а дерево целиком: bootstrap/ содержит только НАПЕЧАТАННЫЕ копии этих файлов, с шапкой, и печать их отвергает по делу — приписать шапку второй раз значило бы соврать о происхождении файла.
Рецепт печати переехал туда, где переживёт удаление
Пределы (--max-steps 40000000, --max-depth 20000) попадают прямо в напечатанный байт — #define FL_MAX_STEPS в bootstrap/flang_runtime.h. Единственной их записью был scripts/bootstrap-c.mjs; уйдёт JavaScript — уйдёт и рецепт, а собранный по памяти разойдётся молча. Теперь запись — в scripts/bootstrap-c.sh (FLANG_MAX_STEPS, FLANG_MAX_DEPTH), а ПРЕДЕЛЫ в .mjs читаются оттуда разбором ИМЯ='значение' у левого края. Это закрывает названное препятствие №1 из bootstrap-circle-is-broken-but-the-binary-cannot-check-itself.
Чем ограничено
Ступени не сняты. Печатает двоичный, собранный из того, что лежит в дереве СЕЙЧАС, а печатает он СЕГОДНЯШНИЕ исходники. Если правка тронула сам слой печати (flang/self/emit-c.flang), одного захода мало: первый даёт печать по старым правилам от новых исходников. Надо пересобрать и перепечатать, пока два захода подряд не дадут одно и то же. Скрипт называет число тронутых файлов сам.
Двоичный по-прежнему не проверяет то, что печатает: flang emit не гоняет ни типы, ни завершаемость, ни примеры, а flang check на самом компиляторе исчерпывает бюджет шагов. Это препятствие №3 из соседней заметки, и оно НЕ закрыто. То есть пересобрать компилятор без Node можно, а убедиться, что пересобираешь исправный, — нечем.
Всё измерено на одной машине: Linux 7.0.0, cc 15.2.0, 256 ядер, 499 ГБ памяти, версия 0.5.1. Секунды плавают с загрузкой машины; байты — нет.
Сверку двух печатей повторить уже нельзя: реализация на JavaScript удалена в тот же день. Число 15 641 371 снято нарочно в последний день, когда было с чем сверять. Дальше сверяется только «печать двоичного совпадает с тем, что лежит в bootstrap/», и это другое, более слабое утверждение: оно ловит устаревший артефакт, но не ловит расхождение двух пониманий языка — второго понимания больше нет.
Связано: bootstrap-circle-is-broken-but-the-binary-cannot-check-itself, four-pieces-of-javascript, javascript-stays-only-as-a-print-target, byte-for-byte-comparison