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

Составление парфе

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

Естественно, что любая осмысленная программа, создаваемая компилятором, будет использовать Parfait, поэтому Parfait должен быть доступен во время выполнения, т. е. проанализирован и скомпилирован. Поскольку я был занят созданием основ, это уже давно было в ToDo.

Теперь, наконец, большинство основ на месте, и я начал то, что кажется огромной задачей. На самом деле я успешно скомпилировал три файла, если быть точным, Object, DataObject и Integer.

Значимость этого на самом деле намного больше (тем более, что тестов пока нет). Парфе, как часть рубикса, можно назвать идеоматическим рубином, то есть рубином реального мира. Конечно, мне пришлось сгладить несколько ошибок, прежде чем компиляция действительно заработала, но на удивление мало. Другими словами, компилятор достаточно функционален, чтобы компилировать более крупные и многофункциональные программы на Ruby, но об этом ниже.

Улучшения дизайна

Общий дизайн уже давно такой, как на картинке ниже. Увы, реализации этой архитектуры немного не хватило. Если быть точным, когда генерировался код мамы, он сразу конвертировался в risc. Другими словами, слой существовал только концептуально или в пути.

Теперь код работает точно так, как рекламируется. Ruby входит сверху, а двоичный код — снизу. Но более того, каждый уровень — это отдельный шаг, фактически в самом верхнем объекте компилятора есть методы для создания каждого уровня ниже от ruby. Это, очевидно, очень удобно для тестирования, и iself ускорил тестирование на 30%, так как risc не обновляется до того, как он действительно понадобится.

Автоматические бинарные тесты

Говоря о тестировании, мы провели более 1600 тестов, что более чем на 200 больше, чем до переписывания дизайна. При более чем 15000 утверждений это по-прежнему составляет 95% кода, другими словами, все, кроме нескольких, терпит неудачу. А с параллельным выполнением еще быстро.

Но главным достижением пару недель назад стала интеграция бинарного тестирования в автоматизированный поток тестирования. В частности, на Трэвиса.

Здесь используется функция Qemu, о которой я раньше не знал, а именно то, что можно заставить qemu запускать двоичные файлы с другой цели на машине, просто вызывая ее с помощью qemu-arm.

Раньше я тестировал двоичные файлы через ssh, обычно на эмулированном qemu pi на моей машине. Эта настройка намного сложнее, как описано здесь, и я уклонился от автоматизации. Это означает, что они будут происходить нерегулярно и все такое. Моим единственным утешением было то, что тест будет выполняться на интерпретаторе, но, конечно, это не тестирует поколение рук и эльфов.

Фактические тесты, о которых я говорю, — это растущее число «основных» тестов, которые можно найти в каталогеtests/mains. Это настоящие программы, которые вычисляют или выводят данные. Они являются полными системными тестами в том смысле, что мы проверяем только их вывод (системный вывод и код выхода).

Поскольку мы обычно ссылаемся на файлы «a.out» (таким образом перезаписывая и избегая очистки), фактический вызов qemu для двоичного файла очень прост:

qemu-arm ./a.out

но это все еще оставляет вас для создания этого двоичного файла. Это можно сделать с помощью компилятора rubyxc и компоновки результирующего объектного файла (см. ошибку №13). Но поскольку я тоже ленивый программист, я автоматизировал эти шаги в компиляторе rubyxc, и поэтому можно просто скомпилировать/связать/выполнить исходный файл следующим образом:

./bin/rubyxc execute test/mains/source/fibo__8.rb

Это скомпилирует, свяжет и выполнит этот конкретный тест Фибоначчи. Код выхода этого теста будет 8, как закодировано в имени файла. Теперь этот тест и 20 других будут запускаться как двоичные файлы каждый раз, когда Трэвис делает свое дело.

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

./bin/rubyxc interpret test/mains/source/puts_Hello-there_11.rb

И во-вторых, кстати, я также добавил в компилятор опцию, чтобы можно было контролировать размер фабрики Parfait с помощью опции — parfait.

Разное другие новости

Микробенчмарки

На последней конференции в Гамбурге кто-то задал справедливый вопрос: так насколько это быстро? Я так давно делал тесты, что мог только бормотать. Сейчас я закончил обновлять тесты, но пройдет некоторое время, прежде чем я смогу более полно ответить на вопрос.

Итак, для начала, поскольку функциональность компилятора ограничена, я сделал очень маленькие тесты. Очень маленький означает 20 строк или меньше, циклы, строковый вывод, числа Фибоначчи, как линейные, так и рекурсивные. Я слишком поздно понял, что все, о чем будет рассказываться, это целочисленная производительность.

Сейчас, поскольку это ранние дни, я не буду вдаваться в подробности здесь. В целом скорость была не такой быстрой, как я надеялся, примерно такой же, как у мрт. Мне придется немного поработать над соглашением о вызовах и, возможно, над обработкой целых чисел. Я думаю, что могу довольно легко сбросить 30–50%, и уже одно это должно подтверждать высказывание о том, что все тесты — ложь. Как тот, где rubyx делает «hello world» быстрее, чем C. Да, C, а не мрт. Но только потому, что я отключил буферизацию, потому что и rubyx не буферизует (яблоки и апельсины…)

Так что я восприму этот раунд как вдохновение для оптимизации и измерения производительности. И вернуться к нему позже.

Неявная отдача

В рамках разбора Parfait я реализовал первую версию неявных возвратов. Низко висящие плоды и фактически наиболее распространенные варианты использования включали константы и вызовы. Поэтому, когда метод заканчивается простой переменной, константой или вызовом, будет добавлен возврат. Более сложные правила, такие как возвраты для if или while, придется подождать, но я обнаружил, что лично я все равно не склонен их использовать.

Поскольку методы класса — это, по сути, методы (метакласса), добавление к ним унифицированной обработки возврата тоже было несложным.

Улучшенная обработка блоков

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

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

Итак, теперь лямбда-выражение создано и просто добавлено в качестве аргумента для отправки. Компиляция Lambda запускается созданием константы, т.е. переходом от vool к mom, и компилятор блоков автоматически добавляется к компилятору методов.

Вул в фокусе

Я говорил руби без пуха, чтобы описать шерсть. И хотя это правда, это довольно расплывчато. Две основные вещи стали ясны в отношении vool благодаря приведенной выше работе.

Во-первых, в Vool нет сложных или рекурсивных операторов отправки. Аргументы должны быть переменными или константами. Вызовы выполняются раньше и присваиваются временной переменной. По сути, рекурсивные вызовы объединяются в список, и поэтому вызов не зависит от стека, как в ruby.

Во-вторых, Вул проводит различие между выражениями и утверждениями. Как и другие языки более низкого уровня, но не ruby. Как правило, операторы делают что-то, а выражение — это что-то. Другими словами, значение имеют только выражения, а операторы (такие как if или while) — нет.

Планы

Вызов

Призвание может работать, и я заметил две ошибки, которые я сделал. Во-первых, создание нового сообщения для каждого звонка излишне сложно. Только в особом случае, когда создается Proc, последовательность возврата (мам-инструкция) должна поддерживать сообщение в актуальном состоянии.

Во-вторых, наличие аргументов и локальных переменных в виде отдельных массивов может быть удобным и простым для кодирования. Но это добавляет дополнительную косвенность для каждого доступа _and_ store. Поскольку Mom основан на памяти, а Mom переводится как risc, это составляет множество инструкций.

Целые числа

Я по-прежнему хочу, чтобы целые числа были объектами, хотя создание явно дорого обходится. В будущем, конечно, поможет полный анализ побега, но сейчас должно быть достаточно просто выяснить, передается ли int. Если нет, можно сделать циклы, чтобы деструктивно изменить int.

вызов инструкции мамы

У меня есть идея, что я могу кодировать больше вещей выше. Чтобы сделать это более эффективным, я думаю о вызове макросов или инструкций на уровне vool. Только внутри парфе конечно. Основная идея состоит в том, чтобы сохранить код вызова/возврата и сделать так, чтобы компилятор сопоставил, например, X.return_jump с инструкцией Mom::ReturnJump. «Просто» нужно выяснить семантику передачи или то, как она интегрируется в код vool.

Лучше встроенный

Генерация текущих встроенных методов всегда немного беспокоила меня. Это правда, что некоторые вещи просто не могут быть выражены как ruby, и поэтому нужен какой-то альтернативный механизм (даже в c можно встроить ассемблер).

Основная проблема, с которой я сталкиваюсь, заключается в том, что эти методы не проверяют свои аргументы и поэтому могут вызывать дампы памяти. Так что они слишком высокого уровня, и, надеюсь, все, что нам действительно нужно, — это предыдущая идея о возможности интегрировать код Mom в vool. Поскольку мама является расширяемой, она должна позаботиться о любых возможных потребностях. И мы могли бы кодировать методы как часть Parfait, сделать их безопасными и просто использовать нижний уровень внутри них. Посмотрим!

Составление тестов Парфе

Поскольку Parfait является частью rubyx, у нас, конечно же, есть модульные тесты для него. План состоит в том, чтобы также разобрать тесты и запустить их как тест как для Prfait, так и для компилятора. Конечно, это потребует написания мини-версии мини-теста, с которой компилятор действительно может справиться (почему у меня такое ощущение, что настоящий мини-тест включает в себя слишком много магии).

Конференция GrillRB

И последнее, но не менее важное: я буду выступать во Вроцлаве примерно через неделю. План состоит в том, чтобы провести сравнение с рельсами и сосредоточиться на возможностях, а не на технических деталях. Увидимся там :-)

Первоначально опубликовано на http://ruby-x.org.