Loment
RU

Система · в работе

Loment VM

Второй способ запустить программу: артефакт можно передать исполнителю, который принимает его по требованию и решает, принимать ли его, читая то, что артефакт уже несёт. Это не более быстрый путь и не способ писать программы легче. Работающий интерпретатор уже есть в репозитории — пакет его не поставляет, и настройка не меняет того, что излучает компилятор.

Что есть и чего нет

Репозиторий теперь несёт рабочий слой интерпретатора, от T0 до T3, читающий ту же промежуточную форму, что и линкер. T0 исполняет эту форму напрямую; T1 записывает профиль обратной связи, не меняя ни байта вывода; T2 декодирует инструкции перед циклом исполнения и работает 6.1× от T0 на корпусе при побайтово том же выводе; компактная сериализация — 28.5% текстовой формы для того же юнита; T3 добавляет специализацию под охраной и путь назад.

Чего нет — так это всего перечисленного в пакете. Интерпретатор — это семейство tools/lvm.py в репозитории, написанное на Python. Его нет среди бинарников, которые поставляет релиз, поэтому установленный инструментарий не может запустить программу этим способом. Настройка тоже не меняет артефакт: сегодня choose vm и no_vm дают побайтово одинаковый вывод.

Долг, который проект уже носил раньше. Настройка runtime какое-то время была объявлена в бинарнике, ничего не делая, и позже была доведена до того, что заявляла. Это та же форма снова: движок есть, поставки нет. Страница говорит, какая половина какая, а не оставляет читателю выяснять это самому.

Чем оно не является

Три прочтения неверны, и каждое — то, с которым читатель приходит. Они идут впереди проекта, потому что, если сказать их неверно, это превратится в нечто, что пытается делать всё и ничего не делает хорошо.

Не быстрый путь

Наоборот. Нативный путь Loment уже обменивает скорость на независимость, и он не быстр изначально: против C -O2 на тесте массивов из docs/212 он идёт в 10.7× позади — это записанное измерение, а не постоянное обещание. VM — это слой поверх этого. Ось этой линии — не производительность, а аварийный выход — это вообще другая линия.

Не «написал один раз — запускай где угодно»

Эта задача давно отдана времени компиляции: один IR, три выхода — Linux ELF, Windows PE или автономный объектный файл. Формат байткода не ближе к переносимости, чем это, и заявление проваливает собственный тест: если бы переносимость была целью, критерием было бы «добавление цели не требует нового бэкенда», а целей две.

Не способ писать программы быстрее

Это другой набор вещей, и это не касается ни одной из них: шести прочих поверхностных синтаксисов, форматтера, языкового сервера, определений синтаксиса времени компиляции, руководства, написанного для кодирующих агентов. LVM приходит после того, как программа написана.

Проект в трёх частях

Байткод — не новое изобретение

Это сериализация IR, который компилятор уже испускает. Поставляемый бэкенд — стековая машина: один слот стека на значение SSA, аргументы в стеке, агрегаты через скрытый указатель результата, — так что полная модель выполнения для этого подмножества IR уже существует и уже задействована. Не хватает шага, который её исполняет. LVM был бы четвёртым читателем того же IR: ни нового фронтенда, ни нового IR, ни новой системы типов.

Пятое измерение

choose уже имел четыре: какой слой программа может использовать, как она возвращает память, может ли артефакт нести runtime и может ли он выходить за свои пределы. vm — пятое, и оно отвечает для какой модели выполнения эта программа? Если не написано, это no_vm — то, чем всё было до появления измерения. С 0.1.4.6 его принимает и выпускная сборка; сегодня это покупает объявление, а не другой артефакт.

Значение говорит ли, а не какой. Это правило, которому уже следует измерение runtime: то, что внутри, будет меняться годами, и имя, кодирующее сегодняшнюю ступень, пришлось бы переименовывать, чтобы добавить завтрашнюю.

Ворота и почему они открылись

До 0.1.4.6 выпускная сборка отвергала choose vm во время компиляции; принимал его только канал разработки. Правило было грубым — пока не существует пригодного движка, любой вызов его в выпуске незаконен — чтобы установленный инструментарий никогда не принимал обещание, которое не может сдержать. Обещание, которое проваливается, хуже отказа.

0.1.4.6 открывает его, потому что посылка отказа перестала быть верной: первый пригодный движок появился. Теперь настройка — объявление в обоих каналах, и исправление записано, а не удалено — проверка, которая требовала отказа, теперь требует принятия, так что «эти ворота существовали, и вот когда они открылись» остаётся видимым.

Воротам нужно было понятие, которого у репозитория не было: этот компилятор — выпускная сборка или сборка разработки? Проект сводит его к одной константе, одно значение на сторону, переключаемое при упаковке. Одна строка — намеренно: упакованное руководство по языку копируется в дистрибутив без изменений, чтобы «копия в пакете есть копия в репозитории» оставалась проверяемой, и одна строка — наименьший способ согнуть это правило. Цена названа вместе с ним: поставляемое семя компилятора больше не совпадает с репозиторным побайтово, поэтому критерий падает с идентично до воспроизводимо равно, и разница — ровно эта одна константа.

Лестница

Читайте VM как ступени, а не как одну вещь, которую нужно построить. Каждая ступень должна быть достижимой и опровержимой — «написать JIT» не ступень, потому что ничто не может это опровергнуть. Список частей взят у двух систем, сделавших это в масштабе; реализация будет собственной, и останется ли она такой — открытое решение.

T0 — байткод и прямой интерпретатор

Основание. Построено в репозитории. Критерий: программа, запущенная так, даёт побайтово тот же вывод и тот же код возврата, что и нативный артефакт.

T1 — обратная связь

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

T2 — базовый компилятор

Убирает стоимость интерпретации, не оптимизируя. Построено в репозитории — 6.1× от T0 на корпусе. Критерий: измеримо быстрее T0, семантически идентично.

T3 — оптимизирующий компилятор с путём назад

От догадки, оказавшейся неверной, нужно уметь отказаться. Построено в репозитории — для единственной оптимизации, несущей догадку. Это единственный жёсткий критерий на этой ступени: после отказа от догадки результат программы совпадает с нативным артефактом.

T4 — поколенческое и параллельное возвращение памяти

Единственная ступень, не являющаяся продолжением остальных. Дизайн возврата памяти раньше говорил, что здесь это вообще невозможно: бэкенд держал указатели только в локальных переменных и слотах параметров, а это исключает обычный барьер записи. Эта причина больше не действует — появился способ превратить указатель в целое, и поколенческая половина легла на сторону возврата памяти вместе с карточной таблицей и барьером записи. Впереди — сделать это внутри VM, а это другая задача, чем сделать это заранее.

В репозитории есть T0–T3; в пакете нет ни одного. Построить интерпретатор и поставить его в дистрибутиве — два разных шага, и сделан только первый. Ступени выше — форма плана, а не расписание.

Для чего это

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

Но «крупный» уже обнажил одну проблему, которая структурна, а не дефект, и это единственное, что может её вылечить. Испускаемые символы плоские: сотня модулей означает, что имена функций приходится делать глобально уникальными вручную, а два модуля, каждый из которых определяет одно и то же короткое имя, отвергаются. Это следствие «одной статической линковки» как модели — классическая стена масштабируемости крупного проекта, выдвинутая прямо в лицо пользователю самой структурой языка. Репозиторий уже гоняет корпус чрезмерно крупного проекта как критерий, так что это измерено, а не угадано.

Принятие модуля во время работы

Добавить модуль к нативному артефакту означает передолинковать всю программу. Здесь это означает загрузить единицу.

Замена одного без передолинковки

Измените модуль, перезагрузите его, а остальную программу оставьте где она есть.

Запуск кода, который вы не писали

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

И это то единственное предложение, за которое эта страница отвечает, и оно опровергаемо: добавление модуля-расширения не означает передолинковку всей программы — и до его загрузки можно сказать, может ли он дотянуться до того, до чего не должен.

На что оно не претендует

Запись о проектировании хранит этот список, и ему место на странице, а не в ней.

Что оно новое

Ничто в нём — не новый кирпич. Свидетельства выполнения, несомые в артефакте и судимые при загрузке, — из 1990-х; отказ загружать то, что нельзя доказать, — так работают песочницы исполнения; один вход с несколькими моделями выполнения — то, что делает самая крупная мобильная среда выполнения. Иначе расположен может быть шов, а не части.

Что никто не делал этих двух вещей

Формулировка повсюду — не найдено поиском, что не то же самое, что не сделано. Два поиска, которые убили бы заявление, записаны как открытые критерии, с честной отметкой, что их не прогоняли исчерпывающе.

Что оно полезно

Сегодня коробка пуста. И третья роль выше опирается на число, которое никто не измерял: какая доля того чрезмерно крупного корпуса — действительно код, который следовало загружать поздно. Если число окажется близко к нулю, роль — просто красивая история.