Статьи

Seph — жесткий язык для компиляции

Недавно я снова начал работать над Seph. Я объявил об этом прошлым летом ( здесь ), а затем быстро стал чрезвычайно занят на работе. Достаточно занят, что у меня действительно не было сил работать над этим проектом некоторое время. К сожалению, я все еще занят, но мне все же удалось найти немного времени, чтобы начать работу над частями реализации компилятора. Это стало намного проще и веселее, так как JSR292 близок к завершению, и доступна ветка ASM 4, которая упрощает компиляцию байт-кода Java с поддержкой встроенного динамического вызова.

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

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

Напомним, что ограничения, над которыми работает Seph, заключаются в том, что он должен работать на Java 7. Он должен быть полностью скомпилирован (на самом деле, я не решил, сохраню ли я вообще интерпретатор после работы компилятора). И это должно быть быстро. Иш. Я стремлюсь как минимум к Ruby 1.8. Я не думаю, что это неразумно, учитывая размеры гибкости, которые Seph должен допустить.

Итак, давайте углубимся. Это основные болевые точки прямо сейчас — и в некоторых случаях они довольно взаимосвязаны …

Хвостовая рекурсия

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

Подход, который я сейчас использую, довольно похож на Erjangs. У меня есть объект SThread, который должны пройти все вызовы Seph — это будет действовать как контекст потока, как только я добавлю потоки легкого веса в Seph. Но это место также служит хорошим местом для хранения информации о том, куда идти дальше. Моя текущая кодировка хвоста — это просто MethodHandle, который не принимает аргументов. Таким образом, единственное, что вам нужно сделать для прокачки хвостового вызова, это повторно проверять токен и вызывать дескриптор хвостового метода. Тем не менее, делать это повсюду, возможно, не так уж и эффективно. На данный момент код не ищет MethodHandle с нуля по горячему пути, но ему нужно будет связать несколько аргументов, чтобы создать дескриптор метода tail. Я не уверен, что последствия для производительности будут прямо сейчас.

Оценка аргумента от вызываемого

Один из аспектов Seph, который работает так же, как и в Ioke, заключается в том, что вызов метода никогда не оценивает аргументы. Ответственность за оценку аргументов лежит на принимающем коде, а не на вызывающем коде. И поскольку мы не знаем, будет ли что-то делать обычную оценку или делать что-то похожее на макрос, на самом деле невозможно предварительно оценить аргументы и поместить их в стек.

Подход, который использует Ioke и Seph, состоит в том, чтобы просто отправить объект Message и позволить вызываемому объекту оценить его. Но это именно то, чего я хочу избежать с Seph — все должно быть возможно скомпилировать, и, если это возможно, работать горячо. Таким образом, отправка сообщений вокруг поражает цель.

Я нашел подход к компиляции, который на самом деле работает довольно хорошо. Это также уменьшает раздувание кода в большинстве случаев. По сути, каждый фрагмент кода, который является частью отправки сообщения, будет скомпилирован в отдельный метод. Так что если у вас есть что-то вроде foo (bar baz, qux), которое скомпилируется в основной метод активации и два метода аргумента. Этот подход, конечно, рекурсивный. Это дает мне протокол, в котором я могу использовать дескрипторы методов для методов аргументов, помещать их в стек и затем позволять вызываемому пользователю оценивать их так, как они хотят. Я могу предоставить стандартный путь оценки, который по очереди вызывает каждый из дескрипторов метода для генерации значений. Но мне также становится очень легко отправлять их в неоцененном виде. В качестве примера это почти точно, как выглядит текущая реализация встроенного метода «если».(Это не совсем так сейчас, из-за переходных деталей интерпретатора).

public final static SephObject _if(SThread thread, LexicalScope scope,
MethodHandle condition, MethodHandle then, MethodHandle _else) {
SephObject result = (SephObject)condition.invokeExact(thread, scope,
true, true);

if(result.isTrue()) {
if(null != then) {
return (SephObject)then.invokeExact(thread, scope,
true, true);
} else {
return Runtime.NIL;
}
} else {
if(null != _else) {
return (SephObject)_else.invokeExact(thread, scope,
true, true);
} else {
return Runtime.NIL;
}
}
}

 

Конечно, такой подход не идеален. Это все еще много раздувания кода, я не могу использовать стек, чтобы передать вещи в оценку аргумента, и код, который связывает дескрипторы метода аргумента, занимает большую часть сгенерированного кода в данный момент. Тем не менее, это похоже на работу и дает большую гибкость. А компиляция регулярных оценок методов позволит напрямую связать эти дескрипторы метода аргумента с сайтом динамического вызова, что может существенно повысить производительность при оценке аргументов (что, вероятно, случается довольно часто в коде реального мира… =).

Intrinsics это просто обычные сообщения

Многие из элементов синтаксиса в других языках — это просто сообщения в Seph. Такие вещи, как «ноль», «истина», «ложь», «если» и многие другие, работают точно так же, как обычные сообщения, отправляемые на то, что вы определили сами. Однако во многих случаях это совершенно не нужно — и в большинстве случаев знание реализации на сайте вызова позволяет существенно улучшить ситуацию во многих случаях. Я думаю, что будет довольно необычно переопределить любое из этих стандартных имен. Но я все еще хочу сделать это возможным. И я в порядке с программами, которые делают это, снижая производительность. Таким образом, подход, который я предложил (но еще не реализовал), заключается в следующем — я буду в особом случае составлять компиляцию каждого места, имя которого совпадает с именем встроенного объекта.Этот специальный регистр будет привязан к методу начальной загрузки, отличному от обычных методов Seph. В качестве рабочего примера давайте рассмотрим компиляцию фрагмента кода с «true». Это сгенерирует отправку сообщения, о котором позаботится sephTrueBootstrapMethod. Мы все еще должны отправить все обычные аргументы активации метода. Этот метод начальной загрузки будет создавать сайт вызова, который указывает на особый дескриптор метода. Этот дескриптор метода будет guardWithTest, созданным через SwitchPoint, специфичным для истинного значения. Первый путь этого GWT (guardWithTest) просто вернет истинное значение напрямую, без каких-либо проверок. Путь else GWT откатится к обычному откатному методу Seph, который выполняет встроенное кэширование и регулярный поиск.Волшебство происходит с SwitchPoint — места, которые создают новые привязки, будут проверять эти внутренние имена, и если одно из этих имен используется где-либо в коде клиента, SwitchPoint будет переключен на медленный путь.

Таким образом, я думаю, что для большинства программ возможен быстрый путь для многих из этих вещей. Поведение при переопределении «если» все равно должно работать, как и ожидалось, но замедлит глобальную производительность этой программы до конца выполнения.

Когда уходит лексический прицел?

Сеф имеет изменчивые лексические рамки. Но невозможно знать, какие имена будут спасаться, а какие — нет — насколько я могу видеть, я не могу использовать стек Java для представления переменных, за исключением небольшого количества очень вырожденных случаев. Я не уверен, стоит ли иметь этот путь к коду, поэтому я не особо задумывался об этом.

PIC на основе классов не подходят

Одна из стандартных оптимизаций, которую используют объектно-ориентированные языки, называется полиморфным встроенным кешем. Основная идея заключается в том, что поиск метода — это действительно медленная операция. Поэтому, если вы можете сохранить результат выполнения этого, за исключением очень дешевого теста, вы можете упростить наиболее распространенные случаи. Теперь этот дешевый тест — это обычно проверка класса. Пока вы отправляете экземпляр с тем же классом, поиск нового метода не должен происходить. Выполнение getClass, а затем равенство идентичности обычно выполняется довольно быстро (сравнение указателей на большинстве архитектур), поэтому вы можете создавать PIC, которые на самом деле не проводят много времени в защите.

Но Seph — это язык, основанный на прототипах. Таким образом, любой объект в системе может иметь разные методы или значения, связанные с именем, и нет четкого разграничения объектов с новыми именами и значениями в них. В частности, поскольку объекты Seph являются неизменяемыми, каждый новый объект, скорее всего, будет иметь новый набор значений. И способ сохранения объектов и диспетчеризация по ним становятся намного менее производительными, поскольку сайты вызовов в основном никогда не будут работать с одним и тем же объектом. Теперь есть решения для этого — но большинство из них предназначены для языков, где вы обычно используете шаблон на основе классов. V8 использует подход, называемый скрытыми классами, чтобы выяснить подобные вещи. Я подумываю о реализации чего-то подобного, но меня немного беспокоит, что шаблон использования Seph будет достаточно далеко от мира, основанного на классах, и может не сработать.

Резюме

Итак, Seph не так-то просто собрать, и у меня нет хорошего представления о том, насколько быстро это можно сделать. Я думаю, нам придется подождать и посмотреть. Но это также интересная задача — найти решение этих проблем. Я думаю, что мне, возможно, придется пойти на новое исследование, исследуя, как Self и NewtonScript делали вещи.