Содержание


Как использовать виртуальную машину Parrot

Часть 2. Работа с различными языками программирования

Comments

Серия контента:

Этот контент является частью # из серии # статей: Как использовать виртуальную машину Parrot

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Как использовать виртуальную машину Parrot

Следите за выходом новых статей этой серии.

Как уже было отмечено, главной задачей Parrot, как и любой другой виртуальной машины, является интеграция модулей и фрагментов программ, написанных на различных языках программирования, а также эффективная компоновка и выполнение получаемого в результате кода. Изучением подробностей этих процессов мы сейчас и займёмся.

1. Механизм компиляции и выполнения кода в виртуальной машине Parrot

В виртуальной машине Parrot реализована подсистема оперативной компиляции "на ходу", во время выполнения, или JIT-компиляция (JIT – Just-In-Time). Эта подсистема отвечает за преобразование байт-кода в машинные инструкции соответствующей аппаратной платформы и немедленное выполнение этих инструкций.

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

1.1. Формат байт-кода Parrot

Заголовок байт-кода Parrot состоит из 18 байтов. В байтах с 0-го по 7-й хранится "магическое число" Parrot Magic (0x13155A1). Порядок байтов соответствует внутреннему порядку байтов для данной конкретной реализации виртуальной машины. Загрузчик использует это "магическое число" для проверки корректности порядка байтов. Иными словами, все слова (не байты) в проверяемом файле байт-кода должны храниться в порядке, соответствующем текущей реализации, если только не указан явно альтернативный порядок хранения.

8-й байт (WordSize) – размер слова виртуальной машины. Он должен содержать одно из двух значений: 4 (32-битовое слово) или 8 (64-битовое слово). Загрузчик байт-кода отвечает за преобразование файла в слова заданного размера для целевой виртуальной машины "на лету".

Байт 9 (ByteOrder) определяет порядок байтов: 0 – Little Endian (прямой порядок байтов; младший байт располагается по меньшему адресу памяти); 1 – Big Endian (обратный порядок байтов; старший байт располагается по меньшему адресу памяти).

В байте 10 значение 0 определяет тип числа с плавающей точкой двойной точности (FloatType) размером 8 байтов по стандарту IEEE 754. Значение 1 соответствует 12-байтовому числу с плавающей точкой (long double), хранящемуся в формате i386 Little Endian.

Далее следует группа байтов, определяющих версию байт-кода Parrot:

  • байт 11 – Major – главный номер версии;
  • байт 12 – Minor – номер подверсии;
  • байт 13 – Patch – номер текущего релиза;
  • байт 14 – BC Major – главный номер версии данной реализации байт-кода;
  • байт 15 – BC Minor – номер подверсии данной реализации байт-кода.

В байтах 16 и 17 хранятся соответственно тип и размер UUID-идентификатора.

После заголовка записан указатель на UUID-данные.

1.2. Формат кодов операций Parrot

Коды операций виртуальной машины Parrot записываются в следующем обобщённом формате:

код цель[индекс_цели], источник1[индекс_ист1], источник2[индекс_ист2]

Обратите внимание на то, что квадратные скобки здесь не являются обозначением необязательных аргументов, а представляют "самих себя". Тем не менее индексы можно не указывать, если в них нет необходимости. В том случае, когда для индексированной цели или для индексированного источника индекс не указан, будет автоматически подставлен нулевой индекс (или ключ).

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

Операции проверки условия имеют собственный формат:

код логическое_выражение[логический_ключ], цель_для_истина

Здесь "цель_для_истина" является целочисленным смещением относительно текущего значения счётчика команд.

Коды операций содержат только буквы нижнего регистра ASCII, цифровые символы и символ подчёркивания.

Директивы ассемблера начинаются с точки.

Метки содержат любые буквы ASCII, цифровые символы и символ подчёркивания и обязательно должны завершаться символом двоеточия.

Пространства имён обозначаются директивой .namespace с параметром, обозначающим имя объявляемого пространства имён.

Константы можно определять директивой .macro_const, принимающей два параметра: имя константы и её значение.

Директива .pcc_sub позволяет объявить имя подпрограммы с одним из следующих модификаторов: :main обозначает подпрограмму, с которой начинается выполнение; :immediate или :postcomp означает, что данная подпрограмма должна выполняться сразу после компиляции; :load – подпрограмма должна быть выполнена при загрузке соответствующего ей сегмента байт-кода; :init – подпрограмма должна быть выполнена при запуске файла байт-кода.

1.3. Операции компиляции времени выполнения

Следующие операции позволяют компилировать исходный код и создавать байт-код непосредственно во время выполнения.

compile Px, Py, Sz

Здесь буквами P обозначены PMC-регистры (напомню: PMC – PolyMorphic Container – полиморфный контейнер), а буквой S – строковый регистр.

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

compreg Px, Sy

Такая операция определяет компилятор x для исходного кода заданного типа y.

compreg Sx, Py

А эта операция регистрирует подпрограмму y как функцию синтаксического анализатора (parser)/компилятора с именем, заданным в регистре Sx. Эта функция будет вызываться каждый раз, когда в операции compile будет указано её имя.

2. Инструменты компиляции

Форматы инструкций PASM и PIC позволяют в полной мере задействовать мощь виртуальной машины Parrot. Тем не менее они не предназначены для написания и сопровождения крупномасштабных программ. Разумеется, для этих целей более предпочтительными являются высокоуровневые языки Perl, Python, Ruby, PHP, Tcl и т.п., а главной целью Parrot является поддержка этих и других языков. Задача поддержки и интеграции программ, написанных на различных языках программирования, решается с помощью инструментальных средств компиляции PCT (Parrot Compiler Tools).

PCT – это набор классов и утилит, существенно упрощающих создание компиляторов языков высокого уровня для виртуальной машины Parrot. Сами инструментальные средства PCT написаны в формате PIR, компилируют исходные коды на языках высокого уровня в формат PIR для выполнения в среде виртуальной машины, но при этом позволяют разработчику компилятора проделать почти всю работу на специализированном диалекте Perl 6.

Интерфейс для сопряжения Parrot с языками высокого уровня включает следующие важные функциональные возможности: эффективная подсистема обработки исключений, компиляция в независимый от платформы байт-код, JIT-компиляция в машинный код, механизм сборки мусора, поддержка объектов и классов, а также надёжная реализация модели многопоточности (параллельности). Кроме того, для разработчиков предназначена тщательно проработанная система интерфейсов с внешними Parrot-библиотеками, написанными на C, C++ и на других компилируемых языках.

Конечно, можно написать компилятор для интеграции языка высокого уровня с Parrot исключительно в формате PIR или заняться разработкой такого компилятора на языке C с применением общеизвестных инструментов lex и yacc, но это будет далеко не самый простой, не самый удобный и, скорее всего, не самый эффективный способ по сравнению с использованием комплекта средств PCT.

Инструментальный набор PCT состоит из трёх основных средств:

  • NQP – Not Quite Perl – подмножество языка Perl 6, не требующее для функционирования библиотеки времени выполнения и предназначенное специально для разработки компиляторов.
  • PGE – Perl Grammar Engine – реализация грамматических инструментальных средств и механизма обработки регулярных выражений Perl 6.
  • HLLCompiler – High Level Language Compiler – обеспечивает управление и инкапсуляцию процесса компиляции. После создания объекта HLLCompiler разработчик получает возможность использовать соответствующий компилятор в интерактивном режиме из командной строки, в пакетном режиме из файлов исходного кода или непосредственно во время выполнения.

2.1. Что нужно для создания компилятора

При разработке компилятора PCT-средствами требуются три основных файла: главный файл с точкой входа, который обычно создаётся в формате PIR, файл грамматических спецификаций для механизма PGE и файл грамматических операций, написанный на диалекте NQP. Кроме этих обязательных файлов для большинства языков потребуются ещё и дополнительные файлы, содержащие библиотеки времени выполнения и прочие функциональные свойства.

PIR-программа главного файла содержит функцию с модификатором :main (точка входа), в которой создаётся и активизируется объект "компилятор" как экземпляр подкласса, производного от родительского класса PCT::HLLCompiler. Здесь же загружаются все требуемые библиотеки поддержки и инициализируются данные, специфические для реализуемого языка/компилятора.

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

Вся грамматика языка высокого уровня располагается в .pg-файле, в котором создаётся экземпляр подкласса, полученного наследованием от класса PCT::Grammar, в котором обеспечивается реализация всех необходимых грамматических правил (записанных с использованием PGE) для осуществления синтаксического разбора конструкций исходного языка.

Файл операций содержит методы объекта типа PCT::Grammar::Actions, которые получают данные из файла грамматических правил и на их основе формируют абстрактную синтаксическую древовидную структуру PAST (Parrot Abstract Syntax Tree).

Схема работы PCT-средств относительно проста. Созданный компилятор передаёт исходный код на соответствующем языке высокого уровня механизму PGE, который выполняет синтаксический разбор этого кода и возвращает специальный объект Match, представляющий обработанную версию кода. Затем компилятор передаёт полученный объект Match в методы объекта операций (в файле операций), которые в свою очередь осуществляют построение синтаксического дерева PAST. Наконец, компилятор преобразует PAST-дерево в PIR-код, который можно сохранить в файл, перекомпилировать в байт-код или сразу передать на выполнение.

3. Примеры взаимодействия Parrot с различными языками программирования

Очевидно, что самым лучшим методом взаимодействия модулей на различных языках программирования высокого уровня с виртуальной машиной Parrot является компиляция всех внешних модулей в байт-код с помощью соответствующих компиляторов. Такие компиляторы уже существуют для многих скриптовых языков: Perl, Python, Ruby, Tcl и т.д. С полным перечнем языков высокого уровня, поддерживаемых программной средой Parrot, можно ознакомиться на специальной странице сайта разработчиков Parrot.

И всё же достаточно часто возникают ситуации, в которых требуются нестандартные решения, например, появляется необходимость вызова функций на языке C из Parrot-кода. В подобных случаях может помочь внутренний интерфейс вызова подпрограмм NCI (Native Calling Interface).

Используя NCI-интерфейс, вы можете вызывать функции, написанные на языке C, непосредственно из Parrot-скрипта. Исходный код для такого взаимодействия подразделяется на две части: функция, написанная на языке C, которая должна быть вызвана, и PIR-код, в котором выполняется вызов C-функции.

/* Функция в файле clib4parrot.c */

/* объявление прототипа функции */
void my_c_func( void );

/* определение функции */
void my_c_func( void )
{
  printf( "Function called from Parrot\n" );
}

Файл, содержащий нашу функцию, должен быть обязательно скомпилирован как динамическая библиотека.

gcc -o clib4parrot.so --shared -fPIC clib4parrot.c

После того как динамическая библиотека с требуемой функцией подготовлена к использованию, можно написать следующий PIR-код:

.sub main :main
  .local pmc clib, cfunc

  # перед использованием необходимо загрузить динамическую библиотеку
  clib = loadlib "clib4parrot"  # расширение не указывается
  
  # создание ссылки на нужную функцию из загруженной библиотеки
  cfunc = dlfunc clib, "my_c_func", "v"  # v – сигнатура void

  # теперь можно вызвать функцию
  cfunc()
.end

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

4. Заключение. Оценка перспектив практического применения Parrot

Parrot вызывает немалый интерес у разработчиков благодаря своей ориентации на скриптовые языки с динамической типизацией (Perl, Python, Ruby, Tcl и т.п.) и обеспечению возможности интеграции программ, модулей и библиотек на этих языках. Эта возможность основана на гибкой и легко управляемой программной среде виртуальной машины, реализованной на многих платформах.

Постоянный рост интереса к скриптовым языкам с динамическими типами данных даёт шанс Parrot стать универсальным стандартом "де-факто" как для настольных приложений, так и для сетевых Web-ориентированных разработок.

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


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=485849
ArticleTitle=Как использовать виртуальную машину Parrot: Часть 2. Работа с различными языками программирования
publish-date=04272010