Под капотом: как устроен klc
Чтобы писать на kola, эта страница не нужна. Но вопросы «а есть ли тут интерпретатор?», «какие бэкенды и таргеты?», «можно ли звать сишные библиотеки?» задают все — вот ответы одним местом.
Два исполнителя одной программы
У klc два способа исполнить вашу программу, и это осознанная архитектура, а не переходный период:
klc run / klc test— интерпретатор. Обходит дерево программы и исполняет его напрямую. Медленный, зато служит эталоном семантики: именно он определяет, «как язык должен себя вести», и дополнительно проверяет в рантайме то, что статика уже доказала (например, теневой учёт линейности ресурсов).klc cbuild / crun / ctest— нативная сборка. Компилятор генерирует C-код плюс маленький рантайм-заголовокkola.h, системный C-компилятор собирает объектник.crunзапускаетmain,ctestгоняет те же test-блоки уже в машинном коде.
Оба исполнителя постоянно сверяются друг с другом на всём корпусе тестов: один и тот же тест обязан дать одинаковый результат в интерпретаторе и в нативе. Такое дифференциальное тестирование — главный способ, которым проект ловит баги самого компилятора.
Насколько быстрый натив: на горячем цикле (FNV-хеш по байтам) нативный kola идёт вровень с Rust — 1.10 против 1.10 GB/s в свежем замере — и в тысячи раз быстрее интерпретатора. При этом проверки переполнения и границ никуда не деваются: это trap, а не UB. Измеренный факт: никогда не срабатывающие проверки почти бесплатны — за безопасность здесь платить практически нечем.
Честности ради: паритет — не на всех формах кода. Там, где Rust умеет сам превратить проход по слайсу в SIMD-инструкции (автовекторизация), C-бэкенд пока отстаёт — до ~2.8x на потоковом суммировании диапазона. Причина понятна: сгенерированному C не хватает подсказки restrict, и C-компилятор векторизует осторожнее; задача заведена. Весь замер — пять нагрузок через все бэкенды против эталона на Rust — лежит в docs/perf-notes.md и перезапускается одной командой (cargo run --release --example perf_corpus -p klc-native).
Компилятор доказывает, а не надеется
Внутри klc есть типизированное промежуточное представление (KIR) и небольшой встроенный прувер линейной арифметики. Каждая проверка границ (xs[i]) решается один раз: если из req-контрактов и условий циклов следует, что индекс валиден, — проверка помечается «доказана» и в нативном коде исчезает; если не следует — остаётся дешёвой рантайм-проверкой. Решение записано в IR как факт, и исполнители обязаны ему следовать; ложное «доказано» — это внутренняя ошибка компилятора с громкой остановкой, а не тихая порча памяти.
Второй нативный бэкенд: Cranelift
Кроме C-бэкенда есть klc-clif — генерация машинного кода через Cranelift, без участия C-компилятора вообще. Это единственная внешняя зависимость проекта, и она изолирована в отдельный crate: ядро klc по-прежнему собирается без единой зависимости. Зачем он нужен: воспроизводимая сборка без системного cc и кросс-компиляция — с macOS собирается корректный ELF-объектник под aarch64-linux одним флагом --triple.
Третий бэкенд: свой машинный код (klc-native)
Есть и третий путь к машинному коду — самый радикальный. klc-native пишет машинные инструкции и объектные файлы сам, байт за байтом: без C-компилятора, без Cranelift, без единой внешней зависимости. Зачем, если два бэкенда уже есть? Три причины — и ни одна не про скорость:
Криптография в константном времени. У любого оптимизирующего компилятора — clang, gcc, Cranelift — есть право «улучшить» безветвенный криптокод в ветвление по секрету, и флага «не делай так» не существует; такие поломки случались в настоящих криптобиблиотеках. Единственная честная гарантия — генератор, который ничего не оптимизирует: какие инструкции написали, такие и исполняются. Это опора для
ct fn— константновременного подмножества kola.Автономия. Объектник рождается вообще без внешнего тулчейна — важно для bare-metal и для будущего самодостаточного компилятора: kola-компилятор, написанный на kola, не сможет позвать ни Rust-библиотеку, ни системный
cc. Rust-версия этого бэкенда — эталон, с которым будущий kola-порт будет сверяться байт-в-байт.Ещё один независимый исполнитель той же программы. Дифференциальное тестирование — главный способ ловли багов компилятора, и каждый новый исполнитель реально их находит: гейт LoongArch-бэкенда, например, вскрыл расхождение семантики сдвигов на граничных значениях, общее для всех машинных бэкендов.
Сейчас klc-native покрывает пять архитектур и два объектных формата (Mach-O и ELF). Каждая инструкция каждого энкодера сверена байт-в-байт с ассемблером LLVM, а каждый бэкенд проверяется исполнением: значения и тексты трапов обязаны совпасть с интерпретатором-эталоном.
Архитектура | Где встречается | Как проверяется |
|---|---|---|
aarch64 | Apple Silicon, серверные ARM | исполнение прямо в тестах (JIT) + линковка объектников |
x86-64 | ПК и серверы | исполнение под Rosetta |
RISC-V rv64 | открытая архитектура, новое встраиваемое железо | qemu в docker-контейнере |
MIPS64 | роутеры, легаси-железо | qemu в docker-контейнере |
LoongArch64 | процессоры Loongson | кросс-сборка + qemu в docker-контейнере |
Трапы во всех этих объектниках зовут тот же символ k_panic, что и C-бэкенд, — объектники разных бэкендов взаимозаменяемы при линковке. Плата за простоту: klc-native пока не распределяет регистры — каждое промежуточное значение ходит через память, поэтому он в 3–14 раз медленнее Rust (замерено тем же перф-корпусом). Это осознанный порядок работ: сначала корректность, потом распределитель регистров — приёмочный тест для него уже написан. В нишах этого бэкенда — крипто и bare-metal — скорость не решает.
Таргеты: от macOS до голого железа — и до браузера
Hosted — обычные macOS/Linux:
cbuildдаёт объектник, который линкуется как любой C-код.Bare-metal —
klc cbuild --freestanding: объектники вообще без libc. Куча — статический bump-аллокатор, печать и часы — внешние хуки, которые линкуются только если реально используются. Чистой программе нужен ровно один внешний символ — обработчик паникиk_panic. Слинкованная с-nostdlibи собственным_start, программа работает на голом железе — и это условие существования языка: домашний стек kola — гипервизор и системные сервисы.Браузер — сам интерпретатор собирается в WebAssembly (
crates/klc-wasm): в репозитории лежитweb/playground— страница с редактором, примерами практикума и кнопками «Проверить / Запустить / Тесты». Код исполняется локально в вашем браузере, std встроена в бандл, сервер не нужен.
Си и libc: можно, но громко
Вызывать C-функции можно: extern "C"-объявления существуют. Но с тремя жёсткими условиями. Во-первых, объявление несёт полную kola-сигнатуру — эффекты и контракты, ничего неявного. Во-вторых, сам вызов возможен только внутри блока trusted "почему" { ... }, где строка-обоснование обязательна, а req-контракты обязаны доказываться статически. В-третьих, каждый trusted-блок — строка в аудите: klc audit их считает, и пакет с [trusted] allowed = false в манифесте с таким кодом просто не соберётся.
Почему так строго: за FFI-границей все гарантии kola заканчиваются. Эффекты, контракты, линейность — всё это компилятор проверить внутри чужого .so не может, он верит вашей сигнатуре на слово. Поэтому граница с C сделана дорогой и заметной: её видно в коде (trusted), в манифесте и в аудите. Сам же рантайм kola в libc не нуждается — см. bare-metal выше.
Редакторы и инструменты — честный статус
LSP-сервера и плагинов для IDE пока нет. Рабочий цикл — любой редактор плюс klc check: диагностики структурные, цитируют раздел карты и предлагают исправление — проект сознательно вкладывается в них как в основной интерфейс компилятора. Побаловаться без установки можно в браузерной песочнице web/playground (см. выше). Ещё есть klc query outline|symbol|callers|callees|effects — карта кода в JSON из фактов компилятора (сигнатуры, эффекты, граф вызовов); это готовая основа для будущей интеграции с редакторами и агентами.