Паррот для начинающих
Это обновленная редакция статьи ``Parrot: Some Assembly Required'' (``Паррот: требуется ассемблер''), которая появилась на http://www.perl.com для 0.0.2 релиза Паррота. Она планировалась как лучший способ для начинающих узнать, что такое Паррот и как его использовать.
Во-первых, все-таки, что такое Паррот, и почему мы делаем такой ажиотаж вокруг него? Ну, если ты не живешь на необитаемом отстрове, ты должен знать, что Перл сообщество работает над дизайном и реализацией новой версии Перла.
Паррот сильно связан с Перлом 6, но это не Перл 6. Что бы узнать,
что из себя представляет Паррот, мы должны знать немного сказать о том, как работает Перл.
Когда ты запускаешь программу на Перл, она сперва компилируется
во внутреннее представление, байт-код; затем этот байт-код передается
почти отдельной подсистеме в Перл для интерпретации.
Таким образом, имеется две отдельных фазы в работе Перла - компиляция в
байт-код и интерпретация байт-кода. Перл не единственный язык,
который придерживается такого дизайна; Питон(Python),
Раби(Ruby), Тикль(Tcl) и, верьте или нет, даже Джава(Java) делают тоже самое.
Такой дизайн был очевидным для предыдущих версий Перла. Между компилятором и интерпретаром не было посредника. В результате интерпретарор получился зависимым от некоторых особенностей компилятора. Тем не менее, интерпретатор (некоторые языки называют его виртуальной машиной) может быть представлен как программный ЦПУ(CPU) - компилятор производит инструкции на ``машинном коде'' для виртуальной машины, которая его затем исполняет, подобно тому, как С компилятор производит машинный код, который запускается на настоящем ЦПУ.
В Перле 6 планируется разделить дизайн компилятора и интерпретатора. Это ответ на вопрос, почему мы пришли к отдельному проекту, который назвали ``Паррот'', и, который будет в определенной степени независимым от Перла 6.
Паррот будет виртуальной машиной для Перл 6, програмным ЦПУ, на котором будет выполняться байт-код. Мы проектиреум Паррот вперед Перл 6, потому что гораздо проще писать компилятор, когда у вас уже есть интерпретатор, под который будут компилироваться программы.
Имя ``Паррот''(в переводе на русский - попугай) было выбрано после встречи ``the 2001 April Fool's Joke''(Первоапрельская Шутка 2001), на которой сообщества Перл и Питон обсуждали сотрудничество над следующими версиями своих интерпретаторов. Имя отражает ту идею, что нам вероятно понравится, если другие языки будут использовать Паррот в качестве ВМ, в том смысле, что мы не будем против, если Паррот станет ``common language runtime''(универсальной средой исполнения) для динамических языков.
Это должно быть стрессом, но разработка все еще находится на ранней стадии.
Но пусть это не шокирует вас! Паррот, тем не менее, пригоден для использования; мы уже увидели, что появились два мини-языка, которые компилируются в Паррот байт-код (подробней об этом позже), и Леон Брокар(Leon Brocard) работает над автоматическим конвертированием Джава байт-код в Паррот.
В настоящий момент возможно написание простых программ на ассемблерном языке Паррота, воспользуйтесь ассемблером для конвертирования программы в машинный код, затем запустите его на тестовом интерпретаторе. Мы поддерживаем большое многообразие обычных и трансцендентных математических операций, начальную поддержку строк, и некоторые условные действия.
Так давайте получим себе копию Паррота, и мы сможем начать изучение программированния на паррот-ассемблере.
Периодические, пронумерованные релизы будут появляться на CPAN (текушая версия 0.0.10), но на этой стадии проекта происходят большие изменения между релизами. Что бы иметь действительно последнюю версию Паррота, мы должны получать свою копию из CVS репозитория. Здесь описано, как мы делаем это:
% cvs -d :pserver:anonymous@cvs.perl.org:/cvs/public login (Logging in to anonymous@cvs.perl.org) CVS password: [ and here we just press return ] % cvs -d :pserver:anonymous@cvs.perl.org:/cvs/public co parrot cvs server: Updating parrot U parrot/.cvsignore U parrot/Config_pm.in ....
Имеется также web интерфейс для CVS репозитория, доступный на http://cvs.perl.org/cvsweb/parrot/.
Для тех из вас, кто не умеет пользоваться CVS, есть CVS снимки(snapshots), создаваемые каждые шесть часов, и которые вы можешь найти на http://cvs.perl.org/snapshots/parrot/.
Теперь мы имеем загруженный Паррот, нам необходимо построить его. И так:
% cd parrot % perl Configure.pl Parrot Configure Copyright (C) 2001-2003 The Perl Foundation. All Rights Reserved.
Since you're running this script, you obviously have Perl 5--I'll be pulling some defaults from its configuration. ...
Затем Configure скрипт попытается автоматически определить твою
локальную конфигурацию; ты можешь применить --ask ключ, если хочешь
сконфигурировать сборку вручную. Когда Configure успешно завершится,
набери make (или имя вашей программы make). И если на вашей стороне будет
удача, Паррот успешно построит тестовый вариант интерпретатора. (Если нет,
адрес для жалоб имеется в конце этого документа).
Теперь вы можешете запустить некоторые тесты; напечатайте make test и вы
увидите вывод подобный нижеприведенному:
perl t/harness t/op/basic.....ok, 1/2 skipped: label constants unimplemented in assembler t/op/string....ok, 1/4 skipped: I'm unable to write it! All tests successful, 2 subtests skipped. Files=2, Tests=6, 2 wallclock secs ( 1.19 cusr + 0.22 csys = 1.41 CPU)
(Конечно, тестов, на самом деле, гораздо больше, но вы уловили идею. Тесты могут быть пропущены - по одной или другой причине - но ни один из них не должен провалиться!)
Если у вас возникли проблемы с Паррот, пожалуйста, отправьте сообщение с описанием вашей проблемы по адресу bugs-parrot@bugs6.perl.org. Пожалуйста, включите myconfig файл, который был создан как часть процесса сборки.
Перед тем как мы окунемся в программирование на паррот-ассемблере, давайте бросим быстрый взгляд на некоторые концепции.
Паррот-ЦПУ имеет четыре базовых типов:
INTVALFLOATVALSTRINGPMCПервые три типа не нуждаются в объяснении; последний тип, Parrot Magic Cookies, немного более труден для понимания. Но это ничего! Мы расскажем подробней о PMC в конце статьи.
Виртуальная машина существующего сейчас Перла 5 является стековой машиной - она оперирует значениями между операциями, сохраняя их в стеке. Операции загружают значения в стек, делают над ними, что необходимо, и выдают результат назад в стек. Это очень удобно для работы, но это очень медленно: чтобы сложить два числа вместе, необходимо выполнить три стековых записи и два стековых вывода. Более неприятно то, что стеку приходится увеличиваться во время выполнения, что подразумевает размещение памяти в тот момент, когда вы этого не хотите.
Поэтому уход Паррота от укоренившихся традиций виртуальных машин, и использование регистровой архитектуры, делает его более похожим на архитектуры настоящих аппаратных ЦПУ. Имеется еще одно преимущество: мы можем использовать для нашего программного ЦПУ всю имеющуюся на сегодня литературу по тому, как писать компиляторы и оптимизаторы для регистровых ЦПУ.
Паррот имеет специальные регистры для каждого типа: 32 INTVAL-регистра,
32 FLOATVAL-регистра, 32 строковых регистра и 32 PMC-регистра. В
Паррот-ассемблере они называются I0...I31, N0...N31, S0...S31,
P0...P31.
Давайте сейчас посмотрим некоторый ассемблерный код. Мы можем устанавливать
значения регистров используя оператор set:
set I1, 10
set N1, 3.1415
set S1, "Hello, Parrot"
Все Паррот операции имеют одинаковый формат: имя операции, регистр назначения и затем операнды.
Существует множество операций, которые вы можете выполнять. Файл docs/core_ops.pod документирует их, а также синтаксис асемблера. Например, мы можем напечатать содержимое регистра или константы:
print "The contents of register I1 is: "
print I1
print "\n"
или можем выполнять математические функции над регистрами:
add I1, I1, I2 # добавили содержимое I2 к содержимому I1
mul I3, I2, I4 # умножили I2 на I4 и сохранили в I3
inc I1 # увеличили I1 на единицу
dec N3, 1.5 # уменьшили N3 на 1.5
Мы можем даже выполнять некоторые простые манипуляции со строками:
set S1, "fish"
set S2, "bone"
concat S1, S2 # S1 сейчас содержит "fishbone"
set S3, "w"
substr S4, S1, 1, 7
concat S3, S4 # S3 сейчас содержит "wishbone"
length I1, S3 # I1 сейчас содержит 8
Программирование могло бы стать довольно скучным занятием, если бы
остсутствовала возможность управления потоком вычисления. Паррот знает о
ветвлении и метках перехода. Операция branch эквивалентна goto в Перл:
branch TERRY
JOHN: print "fjords\n"
branch END
MICHAEL: print " pining"
branch GRAHAM
TERRY: print "It's"
branch MICHAEL
GRAHAM: print " for the "
branch JOHN
END: end
Можно также делать простые тесты, содержит ли регистр значение истина:
set I1, 12
set I2, 5
mod I3, I1, I2
if I3, REMAIND
print "5 is an integer divisor of 12"
branch DONE
REMAIND: print "5 divides 12 with remainder "
print I3
DONE: print "\n"
end
Заметьте, что if ветка переходит в REMAIND, если I3 содержат true
(т.е. не-ноль) значение; если I3 равно нулю, выполнение переходит к следующей
инструкции. Здесь показано, как это выгладило бы в перле, для сравнения:
$i1 = 12;
$i2 = 5;
$i3 = $i1 % $i2;
if ($i3) {
print "5 divides 12 with remainder ";
print $i3;
} else {
print "5 is an integer divisor of 12";
}
print "\n";
exit;
И если говорить о сравнении, то имеется полный набор опретаров для сравнения
чисел: eq, ne, lt, gt, le и ge. Заметьте, что вы не можете
использовать эти операторы с аргументами разных типов; вам возможно потребуется
добавить суффикс _i или _n к оператору, чтобы сказать, какой тип
аргумента вы используете. Хотя ассемблер возможно буде угадывать его за вас,
к тому времени, когда вы будете читать это.
Давайте сейчас взглянем на несколько простых паррот-прогарам, что бы вы почувствовали язык.
Эта маленькая программа показывает время Unix эпохи каждую секунду: (или так)
set I3, 3000000
REDO: time I1
print I1
print "\n"
set I2 0
SPIN: inc I2
le I2, I3, SPIN
branch REDO
end
Во-первых, мы задаем целочисленному регистру 3 значение 3 миллиона - это
полностью случайное число, вследствие того, что Паррот в среднем выполняет шесть
миллионов операций в секунду на моей машине. Затем программа содержит два цикла:
внешний цикл сохраняет текущие Unix время в целочисленный регистр 1, печатает
его, печатает новую линию, и восстанавливает регистр 2 в ноль. Внутренний цикл
увеличивает значение регистра 2 до тех пор, пока оно не станет равным 3
миллионам - значению, которое мы сохранили регистре 3. Если значение больше или
равно 3 миллионам, мы идем назад к REDO внешнего цикла. По существу, мы
именно вращались в цикле, растрачивая некоторое время. Это только потому,
что Паррот не имеет в настоящие время операции ``sleep'', мы рассмотрим как
осуществить это позже.
Теперь, как запустить эту программу? Скопируй ассемблерный код в файл showtime.pasm, и внутри вашего корневого каталога Паррота запускаем:
perl assemble.pl showtime.pasm > showtime.pbc
Произойдет асемблирование и выполнение кода из showtime.pasm. Вы также можете создать асемблированный байткод из асемблера, выполнив:
parrot -o showtime.pbc showtime.pasm
(.pbc = это расширение файлов для Паррот байт-кода.)
Для выполнения этого байткода набери
parrot showtime.pb
Ряд Фибоначи определяется следующим образом. Берем два числа, 1 и 1. Затем
неоднократно складываем вместе последние два числа в ряду, чтобы получить
следующее число(1, 1, 2, 3, 5, 8, 13, и так далее). Число Фибоначи
fib(n) - это n'ное число в ряду. Здесь приведена простая программа на
ассемблерном языке Паррота, которая ищет первые 20 чисел Фибоначи:
# Some simple code to print some Fibonacci numbers # Leon Brocard <acme@astray.com>
print "The first 20 fibonacci numbers are:\n"
set I1, 0
set I2, 20
set I3, 0
set I4, 1
REDO: set I5, I4
add I4, I3, I4
set I3, I5
print I3
print "\n"
inc I1
lt I1, I2, REDO
DONE: end
Это эквивалентный код на Перле:
print "The first 20 fibonacci numbers are:\n";
my $i = 0;
my $target = 20;
my $a = 0;
my $b = 1;
until ($i == $target) {
my $num = $b;
$b += $a;
$a = $num;
print $a,"\n";
$i++;
}
Дополнительные примеры, что можно сделать на паррот-ассемблере можно найти в подкаталоге parrot/examples/assembly и в web на http://www.parrotcode.org/examples/.
Так много для программирования на ассемблере; давайте переместим взгляд на язык промежуточного уровня - Яко. Яко был написан Грегором Перди(Gregor Purdy), который по вполне понятной причине устал от программирования на ассемблере. Яко немного похож Cи, и немножко похож Перл, и может делать все, что может паррот-ассемблер, но немного проще. Давайте попробуем программу Фибоначи снова:
print("The first 20 fibonacci numbers are:\n");
var int i = 0;
var int target = 20;
var int a = 0;
var int b = 1;
var int num;
while (i != target) {
num = b;
b += a;
a = num;
print("$a\n");
i++;
}
Заметили, как похоже это на версию Перла? Я убрал $ символы, заменил
перловское until на более общие while, заменил my на var int, и уже
почти что написал всю программу.
яко-компилятор, jakoc, находиться в Парроте, в подкаталоге languages/jako.
% languages/jako/jakoc fib.jako > fib.pasm % ./parrot fib.pasm The first 20 fibonacci numbers are: 1 1 2 3 ...
Паррот, очевидно, развивается очень быстро, и мы, тем не менее, сделали длинный путь, перед тем как подготовили компилятор для этих платформ. Этот раздел для тех, кто хочет помочь нам делать Паррот дальше.
Первая вещь, которая необходима нам - это партия дополнительных операций; но их необходимо аккуратно продумать, и все новые предложения для операций следует согласовывать с Деном Шугалски(Dan Sugalski), дизайнером Паррота.
Добавлять операции, на самом деле, несложно. Давайте добавим оператор sleep,
о котором мы жаловались раньше.
Хотя мы можем сказать print "String" и print I3 для распечатки регистра,
операции Паррота неполиморфны. Это некоторый обман, выполняемый ассемблером.
Эти два действия должны выполняться двумя различными операциями: print_sc
печатает строковую константу, а print_i печатает целочисленный регистр.
Все это обрабатывается препроцессором, все что нам нужно для добавления нового
кода операции, это добавить его реализацию в файл core.ops.
Формат этого файла немного забавный; это Cи, который препроцессируется
перл-программой. Cи функции следует объявлять или как op foo (), или
inline op foo ().
Префикс inline - это подсказка для генератора кода, что это очень простая
функция кода операции, которую JIT или другой оптимизаторо может
конвертировать в inline-функцию. Для не-inline функций может потребоваться
гораздо больше работы.
Имеются специальные шаблоны, наиболее существенный из которых - goto NEXT().
Команда goto NEXT() подразумевает, что адрес следующей операции должен быть
вычислен автоматически во время генерации реального Си-кода, основываясь на
размере кода операции.
Аргументы, указываемые при помощи $1, $2 и так далее, не являются
настоящими арумениами для Си-функций и будут заменены, значениями взятями из
байт-кода.
Давайте сначала сделаем содержание sleep-операции. мы хотим взять первый
параметр, $1, и передать его sleep:
inline op sleep (i|ic) {
sleep($1);
goto NEXT();
}
Это операция будет работать и с целочисленными константами, и с целочисленными
регистрами в качестве аргументов функции. Все, чего недостает - так это набора
тестов(смотрите t/op/basic.t для примеров) и документации (добавьте свой POD
в core.ops и он будет автоматически добавлен в соответствующий файл в
docs). Таким образом мы добавим наши инструкции для паррот-ЦПУ. Ассемблер
будет автоматически определять или мы засыпаем на константное время, или на
неконстантное время и будет отправлять правильный код операции, хотя мы будем
просто говорить sleep. Сейчас мы перепишем showtime немного более
симпатичнее или лучше, вы сейчас перепишите, в качестве небольщой тренировки!
PMC похожи на SV(Scalar Value из Перл 5) и на объекты Питона, но не совсем. PMC - это объект некоторого типа, который может быть обучен выполнять различные операции. Так, когда мы говорим
inc P1
увеличивается значение в PMC регистре 1. При вызове метода increment
инвокантом является PMC и PMC решает как обрабатывать данный метод.
PMC - это то, засчет чего мы собираемся сделать Паррот независящим от конкретного языка - перл-PMC будут иметь отличное поведение от питон-PMC или тикль-PMC, возможно. Каждый отдельно взятый метод по сути является указателем, хранящимся в структуре названной vtable и каждый PMC имеет указатель vtable, которая реализует методы его ``класса''. Поэтому интерпретатор Перла будет подключать к себе библиотеку, состоящую из перл-подобных классов и PMC этого интерпретатора будут иметь перл-подобное поведение.
Доступные PMC типы описаны в doc/vtables.pod. Вы можешь создать новый PMC, используя
new P0, <typename>
и затем, используйте любую из инструкций core.ops, которая поддерживает PMC. doc/vtables.pod также поможет вам, как реализовать свои собственные PMC-классы vtable.
Имеется огромное число вещей, которые мы хотим сделать с Парротом: мы нуждаемся, например, в создании некоторых операций ввода-вывода (I/O) чтобы сделать программы по настоящему интересными; мы хотим создать ряд функций для строками, чтобы можно было работать с разными кодировками и производить преобразования между ними. Нужно больше документации. Еще больше нужно тестов. Нужно проверить переносимость Паррота на другие платформы. И наконец, целая куча кодов операций, которые мы хотим реализовать.
Над Парротом работает большое количество людей, но мы не будем против, если оно
станет еще больше. Что бы помочь, вы должны подписаться на рассылку
perl6-internals(perl6-internals@perl.org), где и происходит собственно
процесс разработки. Вы должны также синхронизироваться с CVS. Если вы хочитет
получать сообщения о всех CVS-commit, то можете подписаться на cvs-parrot
рассылку (cvs-parrot@perl.org). Commit-доступ к CVS предоставляется только
тем, кто несет ответственность за конкретную часть Паррота, или кто регулярно
предоставляет качественные патчи.
Полезна будет web-страница http://cvs.perl.org, которая напомнит вам как пользоваться CVS и позволит просматривать CVS репозиторий; кодовая страница представляет собой резюме этой информации и других ресурсов о Парроте. Еще один хороший ресурс - http://www.parrotcode.org.
Так что не медлите - возьмите себе Паррот сегодня!