Як використовувати віртуальну машину parrot частина 1

Олексій Снастін. незалежний розробник ПЗ, консультант

Олексій Снастін - незалежний розробник ПЗ, консультант і перекладач з англійської мови технічної та навчальної літератури з інформаційних технологій. Брав участь в розробці мережевих офісних додатків типу клієнт / сервер на мові С в середовищі Linux.

Опис: У першій статті циклу описуються загальні характеристики і принципи функціонування віртуальної машини Parrot, а також наводяться приклади вихідного коду. У наступній статті будуть детально розглянуті механізм компіляції та інструментальні засоби; крім того, ми поговоримо про взаємодію Parrot з різними мовами програмування.

Використання будь-яких мов програмування передбачає наявність деякого механізму, що виконує перетворення команд і інструкцій тієї чи іншої мови в бінарний код для цільової системи, на якій буде виконуватися програма. Компілятори генерують машинні коди, відповідні заданій архітектурі. У разі інтерпретованих або скриптових мов справа йде складніше. Для них потрібна певна Виконавча, яку називають інтерпретатором або віртуальною машиною (ВМ; virtual machine).

До недавнього часу кожен інтерпретована мова "віз з собою" власну віртуальну машину. Назви Perl, Python, Ruby - це не тільки імена скриптових мов, але і позначення інтерпретаторів, віртуальних машин, необхідних для їх функціонування. Не так давно з'явилася ідея, а слідом і реалізація узагальненої віртуальної машини, що дозволяє досить ефективно виконувати програми на різних мовах програмування. Назвали цього "поліглота" Parrot. Цикл статей про цю віртуальній машині буде корисний розробникам програм на інтерпретованих мовах, а також адміністраторам unix-систем.

Отже, Parrot - це віртуальна машина. Основне її завдання полягає у виконанні спеціалізованих інструкцій, незалежних від конкретної апаратної платформи. Вихідний код на скриптових мовах спочатку перетвориться в набір спеціалізованих інструкцій, зазвичай званий байт-кодом (bytecode), а потім виконується в робочому середовищі віртуальної машини.

На даний момент Parrot може приймати інструкції для виконання в чотирьох форматах. У форматі PIR (Parrot Intermediate Representation; проміжне представлення) інструкції можуть писати програмісти або генерувати компілятори з різних мов. Цей формат дозволяє приховати деякі аспекти нижчого рівня, наприклад, спосіб передачі параметрів у функцію. На один рівень нижче PIR дозволяє опуститися формат PASM (Parrot Assembly), інструкції якого продовжують залишатися доступними для читання людиною і можуть бути згенеровані компілятором. Однак тут на розробника повністю покладається відповідальність за всі деталі реалізації: координація угод про виклики функцій і процедур, розподіл регістрів віртуальної машини і багато іншого. Загалом, асемблер він і є асемблер - потрібна висока кваліфікація програміста.

Формат PAST (Parrot Abstract Syntax Tree) дозволяє приймати в якості вхідних даних абстрактну синтаксичну деревоподібну структуру - вельми корисно для тих, хто займається розробкою компіляторів.

Всі описані вище формати в будь-якому випадку автоматично переводяться в четвертий формат PBC (Parrot Bytecode). Байт-код є скомпільований бінарний код, призначений виключно для інтерпретатора Parrot. Людина навряд чи щось зможе прочитати, і вже тим більше навряд чи зможе щось написати безпосередньо на байт-коді. Байт-код Parrot абсолютно незалежний від якої б то не було платформи. Зрозуміло, байт-код виконується набагато швидше, ніж вихідний код скрипта, який не був попередньо скомпільований.

У набір інструкцій віртуальної машини Parrot включені арифметичні і логічні оператори, конструкції порівняння і управління потоком виконання, тобто цикли, конструкція if-then і т.п. Підтримуються глобальні та локальні змінні, робота з класами і об'єктами, реалізовані механізми виклику підпрограм і методів з передачею їм параметрів, введення / виведення, багатопоточність і багато іншого.

Віртуальна машина Parrot є реєстрової, тобто подібно апаратного процесору, володіє наборами елементів, що забезпечують надшвидкий доступ до зберігаються в них даними. Такі елементи і називають регістрами. Parrot пропонує чотири типи регістрів: для цілих чисел (I), для чисел з плаваючою точкою (N), для рядків (S) і для PMC-контейнерів (P), про які піде мова трохи пізніше. Регістрів кожного типу може бути кілька, а конкретну кількість має бути визначено для кожної підпрограми під час компіляції.

Тепер розкриємо загадкову абревіатуру PMC - це PolyMorphic Container, поліморфний контейнер. PMC-контейнер може представляти будь-який складний тип або структуру даних: масив, хеш-таблицю, словник, список і т.д. Для будь-якого PMC-контейнера можна реалізувати власні спеціалізовані арифметичні, логічні та рядкові операції, тобто змоделювати необхідну поведінку об'єкта. PMC-контейнери можуть бути вбудовані в Parrot-програми або довантажуватися динамічно тільки в тих випадках, коли в цьому виникає необхідність.

У Parrot реалізований механізм збору сміття (garbage collection), тому програміст може не турбуватися з приводу явного звільнення захопленої пам'яті - про це подбає Parrot.

Для того щоб приступити до "польових випробувань" віртуальної машини Parrot, необхідно її встановити. Найкращий спосіб - скористатися штатним менеджером пакетів свого дистрибутива, і ніяких проблем при установці.

Простота освоєння нового програмного засобу зазвичай оцінюється за наступним критерієм: наскільки швидко можна написати елементарну програму виведення довільної рядки, так званий "HelloWorld-тест". Ну що ж, оцінимо цей критерій.

У файл hello.pir (ви не забули, що формат PIR займає найвищий рівень представлення коду?) Запишемо наступний код:

Інструкція виведення рядка практично нічим не відрізняється від інструкції на Perl або на Python. Це позитивний момент, маленький аргумент на користь Parrot. Але світ наш не ідеальний, і все, що в ньому існує, на жаль, недосконале. Про всяк "плюс" тут же знаходиться, щонайменше, один "мінус". Ось так і з Parrot. Після запуску нашого тесту замість очікуваного виведення ми побачимо наступне повідомлення:

Це означає, що виводиться рядок сформована некоректно. Звернувшись до документації, виявляємо, що за замовчуванням в Parrot основною є 8-бітова кодування ASCII, в той час як в переважній більшості сучасних дистрибутивів прийнятий стандарт де-факто UTF-8.

Проблема вирішується явним зазначенням необхідної кодування у вигляді префікса перед виведеної рядком. Виглядає це так:

Ось тепер наш тест виконується, як годиться. Слід зазначити, що Parrot підтримує набори символів ascii, iso-8859-1 (Latin 1), unicode (з варіантами fixed_8, ucs2, utf8 і utf16), а крім того спеціальний тип binary, який дозволяє інтерпретувати зазначений рядок як буфер з Неформатована бінарними даними.

У запалі боротьби з виниклою проблемою ми забули обговорити структуру і синтаксис самої програми. А втім, що тут обговорювати - все і так очевидно: перший рядок визначає підпрограму з ім'ям main, друга - інструкція, складова тіло підпрограми (висновок рядка-константи), третя - інструкція завершення підпрограми. Ну, ще PIR-інструкції для визначення початку і кінця підпрограми починаються з символу "точка". Ось і все опис.

Рядки, як і інші типи змінних, можна зберігати в спеціалізованих регістрах, про які ми вже говорили. Розглянемо наступний приклад:

У строковий регістр S0 записується перша частина рядка. В іншій строковий регістр S1 записується результат об'єднання (конкатенації) вмісту регістра S0 і не зазначено інакше рядки-константи. Символ "точка" позначає операцію конкатенації або об'єднання рядків. Потім вміст регістра S1 виводиться.

Важливо відзначити, що в форматі PIR не можна виконувати безпосередню запис в регістри. Замість цього використовуються посилання на регістри, що позначаються префіксом - символом долара: $ S0, $ S1. Компілятор, зустрівши посилання $ S0, асоціює її з одним з доступних строкових регістрів віртуальної машини і присвоїть цього регістру задане значення.

Можливий ще один варіант використання регістрів - своєрідні "іменовані регістри", які підвищують читабельність вихідного коду. Іменовані регістри також відображаються компілятором у внутрішні регістри віртуальної машини:

У другому рядку підпрограми main директива .local визначає, що даний іменований регістр буде використовуватися тільки усередині поточної підпрограми (або областю видимості цього іменованого регістра є поточна підпрограма). Далі вказано тип іменованого регістра string, тобто це строковий регістр (S). Також можуть бути вказані типи int для цілочисельних регістрів (I), float для регістрів чисел з плаваючою точкою (N), pmc для регістрів поліморфних контейнерів (P), а крім того можна записати тут ім'я PMC-типу.

Для того щоб продовжити вивчення синтаксису і керуючих конструкцій віртуальної машини Parrot, необхідна програма, яка робить трохи більше, ніж простий висновок рядків, але без зайвої складності. Для цієї мети цілком підходить підсумовування квадратів, наприклад, перших десяти натуральних чисел.

У цій програмі з'явилися нові елементи: арифметичні операції з цілими числами, мітка loop. конструкція перевірки умови if. Крім того, ми з'ясували, що підтримується широко поширена операція присвоювання sum + = tmp, але инкрементирование значення виконується командою inc i, а зовсім не i ++, як, наприклад, в Perl.

Справедливості заради, слід зазначити, що форма записи багатьох PIR-інструкцій є так званий "синтаксичний цукор" (syntactic sugar), тобто більш природний і зрозумілий для людини еквівалент "загадкових" ассемблерних інструкцій. Так, наприклад, фрагмент

можна записати в більш "ассемблерізованном" вигляді:

Важливе правило при використанні такого ассемблерного стилю: якщо інструкція модифікує вміст регістра, то цей регістр обов'язково повинен бути записаний першим, власне, як і в більшості "справжніх" ассемблеров.

Найкращим прикладом для демонстрації рекурсивних викликів функцій в будь-якій мові програмування є обчислення факторіала числа. Для віртуальної машини Parrot програма виглядає наступним чином:

В кінці підпрограми factorial записана директива .return (res), що дозволяє скопіювати значення, що міститься в іменованому регістрі res, в той регістр, який викликає програма зарезервувала для повертається підпрограмою значення.

Тема підпрограми main записаний у вигляді

Справа в тому, що за замовчуванням в PIR-форматі передбачається, що виконання починається з найпершої підпрограми в файлі вихідного коду. Порядок виконання можна змінити, додавши до заголовку необхідної підпрограми модифікатор: main. До речі, зовсім не обов'язково стартовою подпрограмме давати ім'я main, головне - модифікатор.

Для поліпшення продуктивності і швидкості виконання Parrot-програм можна виконувати їх попередню компіляцію в байт-код (формат PBC). Файл, в якому зберігається результат компіляції, вказується в командному рядку після прапора -o, і цей файл повинен мати розширення .pbc.

Наприклад, якщо ми вирішимо скомпілювати в байт-код нашу програму обчислення факторіала, то для цього необхідно виконати наступну команду:

Тепер можна виконувати як файл fctr_calc.pir, так і файл fctr_calc.pbc. Попередньо скомпільований файл fctr_calc.pbc буде виконуватися швидше, але навряд чи вам вдасться візуально оцінити різницю в швидкості при запуску цих програм на сучасних надшвидких комп'ютерах.

У даній статті ми розглянули загальні характеристики і принципи функціонування віртуальної машини Parrot, а також вивчили приклади вихідного коду. Це перший крок до оцінки функціональних можливостей та ефективності застосування Parrot. У наступній статті будуть описані механізм компіляції та інструментальні засоби; крім того, ми поговоримо про взаємодію Parrot з різними мовами програмування.

Олексій Снастін - незалежний розробник ПЗ, консультант і перекладач з англійської мови технічної та навчальної літератури з інформаційних технологій. Брав участь в розробці мережевих офісних додатків типу клієнт / сервер на мові С в середовищі Linux.

Схожі статті