Учебная работа. Курсовая работа: Ассемблер для платформы Java
Содержание.2
Введение.3
Постановка задачки.4
формат файла класса.5
Структура файла класса.5
Типы частей Constant_pool6
формат структуры field_info. 7
Формат структуры method_info. 7
формат атрибута Code.8
Работа JVM.. 10
Система установок JVM.12
Синтаксис языка ассемблера для платформы Java (языка JASM).14
Тестовые примеры.18
1.18
2.19
3.20
Проектирование и реализация компилятора.25
Заключение.30
Использованная литература.31
Введение.
язык программирования Java был разработан посреди 90-х годов на базе языка Oak, предназначавшегося для программирования «прошивок» для разных электрических устройств. Но, в отличие от собственного предшественника, язык Java получил обширное распространение, до этого всего как язык, использовавшийся в программировании для сети Веб. В истинное время область внедрения Java существенно расширилась, и этот язык нередко применяется и в обыкновенном прикладном программировании. Это обосновано таковыми преимуществами как кроссплатформенность и сохранность, которые обеспечиваются тем, что начальный код на Java компилируется не конкретно в машинный код, а в, так именуемый, б-код, который интерпретируется виртуальной машинкой Java (JVM). В почти всех современных реализациях JVM байт-код перед выполнением преобразуется в машинные аннотации, что существенно увеличивает производительность, приближая ее к производительности программ, написанных на C/C++. Таковым образом, Java, в современном состоянии данной нам технологии, соединяет достоинства интерпретируемых и компилируемых языков программирования.
Спецификация, описывающая JVM, как абстрактную вычислительную машинку, предоставлена компанией Sun в открытый доступ. Это дозволяет создавать как собственные реализации JVM для разных платформ, так и другие компиляторы, генерирующие б-код Java, в том числе для языков программирования, хороших от Java. Большая часть литературы, посвященной Java, практически не уделяет внимания устройству JVM и обрисовывает только сам язык Java. Но, в ряде всевозможных случаев, познание особенностей архитектуры бывает очень полезным. В данной работе я сделал учебную программку, которая может посодействовать в исследовании архитектуры JVM – легкий ассемблер для б-кода Java.
Постановка задачки.
Требуется изучить архитектуру уровня установок платформы Java, формат файла класса Java, и написать компилятор ассемблероподобного языка, позволяющего создавать файлы классов, корректно обрабатываемые настоящей JVM. Данный компилятор должен поддерживать все команды байт-кода Java и важные способности JVM.
Формат файла класса.
Главным форматом исполняемых файлов в архитектуре Java является формат файла класса, описанный в The JavaTM
Virtual Machine Specification, изданной компанией Sun. Файл данного формата имеет имя, совпадающее с идентификатором класса (кроме вложенных классов) и расширение .class.
Структура файла класса.
Файл класса имеет последующую структуру:
(тут и дальше u1, u2, u4 – целые числа размером 8, 16 и 32 бит с порядком байтов старший б по младшему адресу). Разглядим поочередно все поля.
· magic – так называемое волшебное число, имеющее в шестнадцатеричной записи вид 0xCAFEBABE;
· minor_version, major_version – версия формата файла, по ней определяется сопоставимость данного файла с определенной версией JVM;
· constant_pool_count – количество частей в Constant_pool плюс единица;
· constant_pool – область констант – массив структур переменного размера, представляющих те либо другие постоянные значения. Воззвания в область констант выполняются по индексу (индексация начинается с единицы; индексы, последующие за позициями констант, представляющих числа типов long и double, не употребляются). Форматы констант разных видов будут рассмотрены ниже;
· access_flags – композиция битовых флагов, определяющих права доступа и некие остальные свойства класса:
Флаг
Смысл
ACC_PUBLIC
0x0001
Доступен из-за пределов пакета
ACC_FINAL
0x0010
Запрещено наследование от данного класса
ACC_SUPER
0x0020
В способах данного класса требуется употреблять принятую в Java2 трактовку команды invokespecial
ACC_INTERFACE
0x0200
интерфейс (является классом специального вида)
ACC_ABSTRACT
0x0400
Абстрактный класс
· this_class, super_class – индексы структур в области констант, ссылающихся на данный класс и его класс-предок;
· interfaces_count – число интерфейсов, реализуемых данным классом;
· interfaces – массив индексов структур в области констант, ссылающихся на интерфейсы, реализуемые данным классом;
· fields_count – количество полей в данном классе;
· fields – массив структур field_info, описывающих поля класса. формат структуры field_info будет рассмотрен ниже;
· methods_count – количество способов;
· methods – массив структур method_info, описывающих способы класса. формат структуры mettho_info будет рассмотрен ниже. Конструкторы и статические инициализаторы представляются способами со особыми именами <init> и <clinit>;
· attributes_count – количество атрибутов класса;
· attributes – массив структур-атрибутов класса (поля, способы и байт-код способов также могут иметь свои атрибуты). Любая таковая структура сначала имеет два неотклонимых поля, описывающих тип атрибута и его размер. К классу могут быть использованы последующие обычные атрибуты:
– показывает на файл начального текста, из которого был получен данный файл класса, и
– класс оставлен для сопоставимости со старенькым кодом и его внедрение не рекомендуется. Может быть создание атрибутов необычных типов, но они будут игнорироваться средой выполнения.
Типы частей
Constant_pool
Любой элемент сonstant_pool начинается с однобайтного поля, определяющего его тип. Размер и содержание остальной части структуры зависит от типа. Есть последующие типы констант (частей constant_pool):
· CONSTANT_Class – указываетнакласс. Содержит индекс константы типа CONSTANT_Utf8, хранящей дескриптор класса;
· CONSTANT _Fieldref – показывает на поле класса. Содержитиндексыконстанттипа CONSTANT_Class и CONSTANT_NameAndType;
· CONSTANT _Methodref показывает на способ класса (не интерфейса). Содержитиндексыконстанттипа CONSTANT_Class и CONSTANT_NameAndType;
· CONSTANT _InterfaceMethodref указываетнаметодинтерфейса. Содержитиндексыконстанттипа CONSTANT_Class и CONSTANT_NameAndType;
· CONSTANT_String – показывает на строчку, содержит индекс константы типа CONSTANT_Utf8;
· CONSTANT_Integer – содержит целое 32-разрядное число;
· CONSTANT_Float – содержит вещественное число одинарной точности;
· CONSTANT_Long – содержит целое 64-разрядное число;
· CONSTANT_Double – содержит вещественное число двойной точности;
· CONSTANT_NameAndType – обрисовывает сигнатуру и имя способа или тип и имя поля. Содержит индексы 2-ух констант типа CONSTANT_Utf8, хранящих соответственно имя и дескриптор типа (сигнатуры);
· CONSTANT_Utf8 – содержит строчку в формате Utf8 (знаки Unicode представляются комбинациями от 1 до 3-х б, при этом знаки с кодами, не превосходящими 127, представляются одним б).
Дескрипторы – это строчки, описывающие типы и сигнатуры способов в малогабаритном формате. Примитивные типы обозначаются одной буковкой, типы массивов – открывающими квадратными скобками в количестве, равном размерности массива, перед обозначением базисного типа. Классы описываются строчкой, содержащей имя класса с полным методом, при всем этом заместо точки роль разделителя имен пакетов и класса делает слэш. В дескрипторах сигнатур способов в круглых скобках без разделителей перечисляются дескрипторы типов характеристик; опосля закрывающей скобки находится дескриптор типа возвращаемого значения. Для устранения неоднозначностей при всем этом перед дескрипторами классов записывается буковка L, а опосля их – точка с запятой. к примеру, (ILjava/lang/Object;)I – (int, Object):int (буковкой I обозначается тип int).
формат структуры
field_info
Структура field_info имеет последующий формат:
тут:
· access_flags — композиция битовых флагов, определяющих права доступа и некие остальные характерист ики поля:
имя флага
Значение
Смысл
ACC_PUBLIC
0x0001
Поле объявлено как public
ACC_PRIVATE
0x0002
Поле объявлено как private
ACC_PROTECTED
0x0004
Поле объявлено как protected
ACC_STATIC
0x0008
Поле является статическим
ACC_FINAL
0x0010
Поле объявлено как final и не быть может изменено опосля исходной инициализации
ACC_VOLATILE
0x0040
Поле объявлено как volatile
ACC_TRANSIENT
0x0080
Поле объявлено как transient – не сохранятся при сериализации
· name_index – индекс строковой константы-имени поля в ConstantPool;
· descriptor_index – индекс строковой константы-дескриптора поля (обрисовывает тип) в ConstantPool;
· attributes_count – число атрибутов поля;
· attributes – атрибуты поля. К полям могут быть использованы обычные атрибуты Deprecated (см. выше), Synthetic (поле сотворено компилятором и не объявлено очевидно в начальном тексте) и ConstantValue (инициализирующее
формат структуры
method_info
структура method_info имеет последующий формат:
тут:
· access_flags – битовые флаги, определяющие права доступа и некие доп характеристики способа:
Flag Name
Value
Interpretation
ACC_PUBLIC
0x0001
способ объявлен как public
ACC_PRIVATE
0x0002
способ объявлен как private
ACC_PROTECTED
0x0004
способ объявлен как protected
ACC_STATIC
0x0008
способ является статическим
ACC_FINAL
0x0010
способ является финишным и не быть может замещен
ACC_SYNCHRONIZED
0x0020
способ объявлен как synchronized
ACC_NATIVE
0x0100
способ является «родным» и содержит код, конкретно выполняющийся физическим микропроцессором
ACC_ABSTRACT
0x0400
способ является абстрактным
ACC_STRICT
0x0800
Устанавливает «серьезный» режим работы с вещественными числами (лишь в Java 2).
· name_index, descriptor_index, attributes_count – аналогично field_info;
· attributes – атрибуты способа. способы могут иметь последующие обычные атрибуты:
o Deprecated, Synthetic – аналогично подходящим атрибутам полей;
o Exceptions – описание исключений, которые может генерировать способ. Необходимо отметить, что непременное описание исключений не является нужным требованием для корректного выполнения;
o Code – фактически говоря, б-код способа.
формат атрибута
Code.
Атрибут Code имеет последующую структуру:
тут:
· attribute_name_index, attribute_length – обычные для хоть какого атрибута поля, описывающие его тип и размер;
· max_stack – предельный размер стека операндов для способа;
· max_locals – предельное количество локальных переменных способа (включая формальные характеристики);
· code_length – размер б-кода способа в б;
· code – фактически говоря, б-код;
· exception_table_length – количество защищенных блоков;
· exception_table – таблица защищенных блоков (обработчиков исключений). Любая ее запись имеет последующие поля:
o start_pc – индекс начала защищенного блока в массиве б-кода,
o end_pc – индекс конца защищенного блока,
o handler_pc – индекс начала обработчика,
o catch_type – тип обрабатываемого исключения (индекс в ConstantPool) либо 0 для блока try … finally;
· attributes_count – числоатрибутов;
· attributes – атрибуты кода способа. Могут употребляться обычные атрибуты LineNumberTable и LocalVariableTable, содержащие отладочную информацию.
Работа
JVM
При запуске JVM в качестве характеристик ей передаются имя класса, с способа main которого будет начато выполнение программки, также аргументы командной строчки программки. Сначала загружается обозначенный класс. Остальные классы, применяемые в программке, загружаются при первом воззвании к ним. процесс загрузки класса состоит из нескольких шагов:
· фактически загрузка файла класса (loading). По дефлоту осуществляется при помощи класса ClassLoader из обычной библиотеки Java, но можно употреблять пользовательский загрузчик для конфигурации метода поиска файла;
· связывание (linking). Состоит из 3-х стадий:
o проверка (verification) на корректность формата файла класса и правильность б-кода (к примеру, на отсутствие переходов на середину аннотации),
o подготовка (preparation) – выделение памяти для статических полей класса и наполнение их нулевыми значениями,
o разрешение имен (resolution) ;
· инициализация (initialization) статических данных исходными значениями. Включает вызов способа <clinit>, если он находится в классе.
программка, выполняемая JVM, может иметь несколько потоков выполнения. Реализация многопоточности зависит от применяемого аппаратного обеспечения и быть может различной – различные потоки могут производиться на различных микропроцессорах либо им могут выделяться кванты времени на одном микропроцессоре. JVM имеет ряд средств для синхронизации работы потоков и защиты разделяемых ими данных. Важным из их является механизм блокировок (locks), поддерживаемый на уровне системы установок JVM. Любой объект имеет ассоциированный с ним «замок» (lock). Если один из потоков «закрыл» этот «замок», то ни один иной поток не сумеет также его «закрыть» до того времени, пока 1-ый поток его не «откроет».
JVM описывает несколько виртуальных областей памяти, которые она употребляет при собственной работе:
· регистр PC (programcounter), указывающий на текущую позицию выполнения в способе. Любой поток программки имеет собственный регистр PC;
· стек. Любой поток имеет собственный свой стек. При входе в способ на верхушке стека создается фрейм, содержащий локальные переменные способа и его стек операндов. Размер конкретно этих областей указывается полями max_locals и max_stack атрибута способа Code;
· куча – область памяти, в какой динамически располагаются объекты классов и массивы. память из-под не применяемых наиболее объектов (на которые нет ссылок) автоматом освобождается так именуемым собирателем мусора;
· область способов. В нее при загрузке классов помещаются б-код способов, разная информация о способах и полях. Область способов также содержит области констант времени выполнения, которые хранят содержимое constantpool из загруженных классов;
· стеки для native-методов.
Размещение и часть из установок JVM делают одно из последующих действий:
· считывают
· сохраняют
· делают те либо другие деяния над значениями, взятыми с верхушки стека, и записывают результирующее
· делают переход на команду с данным смещением относительно текущего значения регистра PC непременно либо зависимо от значений, прочитанных с верхушки стека.
Хоть какое чтение из стека операндов приводит к удалению из него прочитанного значения. Размер стека операндов, указываемый как max_stack, рассчитывается последующим образом: значения типов long и double занимают две ячейки стека (8 б), любые остальные значения – одну (4 б). значения типов char, boolean, byte, short сохраняются в одной четырехбайтной ячейке. Здесь можно отметить, что в подавляющем большинстве случаев JVM не делает различий меж логическими значениями и целыми числами типа int, для среды выполнения не существует отдельного булевского типа (ереси соответствует нулевое один б. Существует последующее ограничение на б-код: всякий раз, когда точка выполнения добивается хоть какой определенной позиции в способе, глубина стека обязана быть схожей, не считая того, тип верхних значений в стеке должен соответствовать требуемому типу извлекаемых очередной командой значений.
В области локальных переменных на момент начала выполнения способа в первых позициях находятся фактические характеристики способа, а в случае способа экземпляра первую (нулевую) позицию занимает ссылка this на текущий объект. Никакого различия в процессе выполнения способа меж параметрами (даже ссылкой this) и, фактически говоря, локальными переменными не делается. Так же как и в стеке, значения типа long и double в области локальных переменных занимают две четырехбайтные ячейки, значения типов размером наименее 32-х разрядов расширяются до 4 б. В корректном байт-коде должны производиться, а именно, последующие условия: во-1-х, типы значений в локальных переменных должны соответствовать требуемым для установок, которые обращаются к сиим переменным, во-2-х, не допускается чтение значения переменной до ее инициализации (присвоения значения).
Перед вызовом способа его фактические характеристики должны находиться на верхушке стека; они стают операндами соответственной команды вызова. При возврате из способа, кроме способов, возвращающих void, возвращаемое
В процессе выполнения программки в итоге появления той либо другой ошибки или выполнения команды athrow быть может сгенерировано исключение. При всем этом происходит поиск пригодного обработчика исключения (защищенного блока) в текущем способе, если он не найден, то в способе, вызвавшем текущий и т. д. Если пригодный обработчик найден, то управление передается в точку, определяемую полем handler_pc соответственной записи таблицы exception_table в атрибуте Code способа. ссылка на объект исключения при всем этом помещается на верхушку стека. объект исключения непременно должен принадлежать классу Throwable либо классу, производному от него.
Система установок
JVM.
1-ый б каждой команды JVM содержит код операции. Существует несколько обычных форматов, которые имеют большая часть установок:
· лишь код операции,
· код операции и однобайтный индекс,
· код операции и двухбайтный индекс,
· код операции и двухбайтное смещение перехода,
· код операции и четырехбайтное смещение перехода.
несколько установок употребляют остальные форматы, посреди их две команды переменного размера — tableswitch и lookupswitch. Не считая того, существует особый префикс wide, который изменяет размер неких установок, заменяя однобайтный индекс локальной переменной двухбайтным. В TheJavaVirtualMachineSpecification для каждой команды установлено свое мнемоническое обозначение.
Существует много групп подобных по выполняемому действию установок, работающих с разными типами данных, к примеру, команды iload, lload, aload, fload, dload делают функцию загрузки значений соответственных типов из локальных переменных на верхушку стека. Реализация таковых установок быть может схожей, но он различаются при проверке правильности б-кода. Приняты последующие обозначения для типов данных, с которыми работают команды:
· i — int (также byte, short, char и boolean),
· l — long,
· f — float,
· d — double,
· a — ссылка на объект либо массив.
Не считая того, есть несколько установок, работающих с типами char, byte и short.
Можно выделить несколько групп установок по предназначению:
· команды загрузки и сохранения:
o загрузка локальной переменной на стек:
,
,
,
,
,
,
,
,
,
;
o Сохранение значения с верхушки стека в локальной переменной:
,
,
,
,
,
,
,
,
,
;
o Загрузкаконстантнастек:
,
,
,
,
,
,
,
,
,
;
· арифметические и логические команды:
o сложение:
,
,
,
o вычитание:
,
,
,
o умножение:
,
,
,
o деление:
,
,
,
;
o остаток:
,
,
,
o изменение знака:
,
,
,
;
o сдвиги и побитовые операции:
,
,
,
,
,
,
,
,
,
o сопоставление:
,
,
,
,
o инкремент локальной переменной:
.
Все эти команды, кроме iinc, не имеют характеристик. Они извлекают операнды с верхушки стека и записывают итог на верхушку стека. Команда
имеет два операнда — индекс локальной переменной и величину, на которую
· команды преобразования типов:
o расширяющее:
,
,
,
,
,
;
o сужающее:
,
,
,
,
,
,
,
,
;
· команды работы с объектами и массивами:
o создание объекта:
o создание массива:
(простого типа),
(ссылочного типа),
(многомерного);
o доступ к полям:
,
(для полей экземпляра),
,
(для статических полей);
o загрузка элемента массива на стек:
(тип byte),
(тип char),
(тип short),
,
,
,
,
;
o сохранение значения с верхушки стека в элемент массива:
,
,
,
,
,
,
,
;
o получение размера массива:
o проверка типов:
(возвращает на верхушке стека логическое checkcast
(генерирует исключение в случае несоответствия типа ссылки на верхушке стека требуемому типу);
· команды манипуляций со стеком операндов:
o
— удаление верхнего элемент стека;
o
— удаление 2-ух верхних элемента стека
o
,
,
,
,
,
дублирование частей на верхушке стека;
o
— перемена местами 2-ух верхних частей стека;
· команды бесспорной передачи управления:
o
,
вызов подпрограмм и возврат из их. Употребляются при компиляции блока finally;
o
— бесспорный переход;
· команды условного перехода:
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
· команды вызова способов:
o
обыденный вызов способа экземпляра с внедрением механизма позднего связывания;
o
вызов статического способа;
o
— вызов способа интерфейса у объекта, реализующего данный интерфейс;
o
вызов способа экземпляра без использования механизма позднего связывания. Употребляется для вызова конструкторов, способов суперкласса и private-методов;
· команды возврата из способа:
o
— возврат из способа, возвращающего void;
o
,
,
,
,
возврат значения соответственного типа;
· команда генерации исключений:
· команды синхронизации (механизм блокировок):
o
— установить блокировку (войти в критичную секцию);
o
— высвободить блокировку (выйти из критичной секции).
Синтаксис языка ассемблера для платформы
Java (языка JASM).
Файл начального текста на языке ассемблера для платформы Java (языке JASM) представляет собой текстовый файл, строчки которого разбиты последовательностью знаков с кодами 13 и 10. имя файла начального текста и его расширение могут быть хоть какими, но рекомендуется, чтоб имя совпадало с именованием описанного в файле класса, а расширением было .jasm или .jsm. файл начального текста состоит из предложений, разбитых точкой с запятой. Крайнее предложение может не иметь в конце точки с запятой. Комменты отделяются знаком процента и распространяются до конца строчки. Точки с запятой и знаки процента снутри строковых констант, ограниченных двойными кавычками, не имеют собственного специального значения. Две идущие попорядку двойные кавычки снутри строковой константы интерпретируются как одна двойная кавычка в строке. Любые последовательности пробельных знаков (пробелов, табуляций, переводов строчки и т. д.) интерпретируются как один пробел, если с обеих сторон от их находятся знаки последующих видов: буковкы латинского алфавита, числа, символ подчеркивания, или, в неприятном случае, игнорируются. Исключение составляют пробельные знаки в строковых константах и комментах. Верхний и нижний регистр букв в идентификаторах, именах установок и остальных лексемах различается.
Любой файл начального текста компилируется в один файл класса. файл начального текста обязан иметь последующую структуру:
[модификаторы_доступа] interface <имя_класса>;
[extends <базовый класс>;]
[implements <интерфейс_1>, <интерфейс_2>, … , <интерфейс_n>;]
[fields;
<описания_полей>
]
[methods;
<описания_методов>
]
тут и дальше в квадратные скобки заключены необязательные элементы, в фигурные — другие варианты (разбиты вертикальной чертой), в угловые — нетерминальные знаки.
Модификаторы_доступа — это слова public, final, abstract, super, надлежащие флагам прав доступа ACC_PUBLIC, ACC_FINAL, ACC_ABSTRACT, ACC_STATIC. Эти флаги инсталлируются в единицу и тогда лишь тогда, когда в объявлении класса находится соответственное ключевое слово. Класс может иметь несколько разных модификаторов доступа, разбитых пробелом (либо хоть какой иной последовательностью пробельных знаков). Повторение схожих модификаторов в заголовке 1-го класса не допускается. Когда класс не имеет флага ACC_INTERFACE, в его объявлении употребляется слово class, по другому употребляется ключевое слово interface. Все имена классов и интерфейсов записываются с указанием полного пути (пакетов, в каких эти классы содержатся). Имена пакетов и класса отделяются точкой, к примеру, Java.lang.String. В аргументах установок, там, где это нужно, заместо полного имени текущего класса можно употреблять знак «@». Если базисный класс не указан (предложение extendsотсутствует), то по дефлоту употребляется Java.lang.Object. Интерфейсы — праотцы описываемого интерфейса записываются в секции implements.
Для идентификаторов — имен пакетов, классов, полей и способов, также меток, употребляются последующие правила: они должны состоять из букв латинского алфавита хоть какого регистра (регистр имеет Java, что может привести к неправильной компиляции, или интерпретации файлов классов JVM. Два особых имени <init> и <clinit> также рассматриваются как идентификаторы.
Простой пример описания класса, не имеющего полей и способов:
public abstract class some_package.SomeClass;
% это комментарий
extends
some_package.nested_package1.BaseClass;
implements % и это комментарий
some_package.Interface1, some_package.nested_package2.Interface2;
Описание поля имеет последующий вид:
[модификаторы_доступа] <имя_поля>:<тип_поля> [=<начальное
Тут модификаторы_доступа — последующие слова: public, protected, private, final, static, transient, volatile, надлежащие флагам доступа поля ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE, ACC_FINAL, ACC_STATIC, ACC_TRANSIENT, ACC_VOLATILE. Повторение схожих модификаторов доступа в объявлении 1-го поля и сочетания модификаторов, надлежащие нелегальным сочетаниям флагов доступа (см. The Java Virtual Machine Specification), вызываютошибкувременикомпиляции. Поля интерфейса непременно должны быть объявлены с модификаторами public, static и final. имя_поля — корректный идентификатор. Тип_поля — имя класса или имя простого типа (имена простых типов совпадают с надлежащими главными словами языка Java — byte, short, int, long, char, float, double, boolean). Изначальное Java. Символьные константы заключаются в апострофы. Не считая того, быть может указан код знака как обыденное целое число. Логические константы записываются в виде слов trueи false. Примеры описаний полей:
public final static COUNT:int = 10;
static c:char = ‘A’;
static c1:char = 13;
private volatile m_flag:boolean;
protected m_list:Java.util.ArrayList;
Описание способа в общем случае имеет вид:
[<модификаторы_доступа>] <имя_метода>(<тип_параметра_1>,<тип_параметра_2>, … ,<тип_параметра_n>):<тип_возвращаемого_значения> [throws <класс_исключения_1>, … , <класс_исключения_n>];
% для способов с модификатором abstract нижележащая часть описания
% отсутствует
maxstack <число>;
maxlocals <число>;
[<метка_1>:]
<команда_1>;
…
[<метка_n>:]
<команда_n>;
[
protected_blocks;
finally <метка> : <метка> > <метка>;
…
finally <метка> : <метка> > <метка>;
]
end;
тут модификаторы_доступа — главные слова: public, protected, private, static, final, abstract, надлежащие последующим флагам доступа способа: ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_ABSTRACT. Повторение схожих модификаторов доступа в заголовке 1-го способа и сочетания модификаторов, надлежащие нелегальным сочетаниям флагов доступа (см. The Java Virtual Machine Specification), вызываютошибкувременикомпиляции. Способы интерфейса непременно должны быть объявлены с модификаторами public и abstract. имя_метода — корректный идентификатор, или <init> либо <clinit> для конструкторов и статических инициализаторов. Типы характеристик и тип возвращаемого значения должны быть именами классов, или именами простых типов, принятыми в языке Java (byte, short, int, long, char, float, double, boolean). Не считая того, тип возвращаемого значения быть может указан как void. Опосля главного слова throws в заголовке способа могут быть перечислены через запятую имена классов исключений, генерируемых способом. Для способов, не являющихся абстрактными, опосля заголовка непременно записываются предложения maxstack и maxlocals, в каких указывается размер стека операндов и области локальных переменных способа (в четырехбайтных ячейках). Потом следует код способа в виде последовательности установок, разбитых точками с запятыми. Каждой команде может предшествовать метка, отделяемая от нее двоеточием. Метка обязана быть корректным. Любая команда может иметь не наиболее одной метки, и любая метка обязана предшествовать той либо другой команде. Но, имеется особая псевдокоманда none, для которой не генерируется какой-нибудь код (пустая команда). Ее можно употреблять, если нужно расположить наиболее одной метки у одной команды либо поместить метку в конец способа. Опосля главного слова protected_blocks могут быть перечислены защищенные блоки (обработчики исключений) способа. Описание всякого защищенного блока состоит из имени класса исключения либо главного слова finally и 3-х меток, разбитых знаками ‘:’ и ‘>’. 1-ая из их показывает на начало защищенного блока, 2-ая на его конец, 3-я — на пространство в коде способа, куда перебегает управление при появлении исключения либо при выходе из защищенного блока в случае finally.
Применяемые в коде мнемонические имена установок совпадают с принятыми в TheJavaVirtualMachineSpecification. Но, как исключение, префикс
не рассматривается как отдельная команда, заместо этого команды, его имеющие, записываются как wide_<имя_команды>. Форматы записи установок:
·
Такуюформуимеютследующиекоманды:
·
Такуюформуимеюткомандыперехода:
·
Числодолжноудовлетворятьограничениямконкретнойкоманды:
·
Тип_поля
имя простого типа, принятое в языке Java, или имя класса. Команды:
·
Тут типы характеристик и возвращаемого значения — имена простых типов, принятые в языке Java, имена классов, или (лишь для возвращаемого значения) void. Команды:
·
Таковой формат имеют последующие команды:
·
Команды:
·
команды
Здесьтип —
Константа обязана иметь соответственный тип (целые числа записываются обыденным методом, вещественные — в десятичной либо экспоненциальной форме, в формате, принятом в Java, строчки записываются в двойных кавычках, при всем этом две двойные кавычки снутри строчки интерпретируются как одна двойная кавычка в строке);
·
типы — аналогично остальным командам вызова способов;
·
·
·
тут числа
должны быть поочередными целыми числами. При всем этом числа, обозначенные сходу опосля мнемонического имени команды, должны совпадать с границами спектра чисел, для которых указаны метки перехода.
тут посреди чисел, для которых указаны метки перехода, не обязано быть схожих. Эти числа должны быть целыми, они не должны быть упорядочены по возрастанию, сортировка происходит при обработке команды компилятором.
Тестовые примеры.
Для тестирования компилятора использовались, а именно, последующие примеры:
1.
%файл Summator.jsm
public class Summator;
fields;
private m_i:int;
methods;
%Конструктор. Вносит в поле m_i целое число, находящееся в строке,
%передаваемой в качестве параметра. В случае, если строчка не содержит
%правильной записи целого числа, или это число отрицательное,
%то выводится сообщение о ошибке.
public <init>(Java.lang.String):void;
maxstack 4;
maxlocals 2;
aload_0; %this
dup;
invokespecial Java.lang.Object::<init>():void;
aload_1; %arg1
begin_try:
invokestatic Java.lang.Integer::parseInt(java.lang.String):int;
dup;
iconst_0;
if_icmpge end_try;
new Java.lang.Exception;
dup;
invokespecial java.lang.Exception::<init>():void;
athrow;
end_try:
putfield @::m_i:int;
return;
exception:
pop;
getstatic Java.lang.System::out:java.io.PrintStream;
ldc string «Invalid argument»;
invokevirtual Java.io.PrintStream::println(java.lang.String):void;
return;
protected_blocks;
Java.lang.Exception
begin_try : end_try > exception;
end;
%возвращает сумму натуральных чисел от 1 до m_i.
public getSum():int;
maxstack 3;
maxlocals 2;
iconst_0;
istore_1;
aload_0; %this
getfield @::m_i:int;
loop:
dup;
iload_1; %result
iadd;
istore_1; %result
iconst_1;
isub;
dup;
iconst_0;
if_icmpgt loop;
pop;
iload_1; %result
ireturn;
end;
%возвращает
public getI():int;
maxstack 1;
maxlocals 1;
aload_0; %this
getfield @::m_i:int;
ireturn;
end;
2.
%файл Switches.jsm
public class Switches;
fields;
methods;
%оба способа функционально эквивалентны последующей функции, написанной на Java.
% static int function(int i) {
% switch(i) {
% case 1: return 2;
% case 2: return -1;
% default: return 0;
% }
% }
public static lookup(int):int;
maxstack 1;
maxlocals 1;
iload_0;
lookupswitch
default : l_def
1 : l_1
2 : l_2;
l_def:
iconst_0;
ireturn;
l_1:
iconst_2;
ireturn;
l_2:
iconst_m1;
ireturn;
end;
public static table(int):int;
maxstack 1;
maxlocals 1;
iload_0;
tableswitch 1:2
default : l_def
1 : l_1
2 : l_2;
l_def:
iconst_0;
ireturn;
l_1:
iconst_2;
ireturn;
l_2:
iconst_m1;
ireturn;
end;
3.
Последующий пример представляет собой программку, состоящую из 5 классов.
%————————————————————-%
%файл Figure.jsm
public interface Figure;
methods;
public abstract getArea():double;
%————————————————————-%
%————————————————————-%
%файл Circle.jsm
public class Circle;
implements Figure;
fields;
private m_radius:double;
methods;
public <init>(double):void;
maxstack 4;
maxlocals 3;
aload_0;
invokespecial Java.lang.Object::<init>():void;
dload_1;
dconst_0;
dcmpg;
ifge l_endif;
new Java.lang.IllegalArgumentException;
dup;
invokespecial java.lang.IllegalArgumentException::<init>():void;
athrow;
l_endif:
aload_0;
dload_1;
putfield @::m_radius:double;
return;
end;
public getArea():double;
maxstack 4;
maxlocals 1;
aload_0;
getfield @::m_radius:double;
aload_0;
getfield @::m_radius:double;
dmul;
ldc2_w double 3.14159265;
dmul;
dreturn;
end;
%————————————————————-%
%————————————————————-%
%файл Rectangle.jsm
public class Rectangle;
implements Figure;
fields;
private m_a:double;
private m_b:double;
methods;
public <init>(double, double):void;
maxstack 4;
maxlocals 5;
aload_0;
invokespecial Java.lang.Object::<init>():void;
dload_1;
dconst_0;
dcmpl;
iflt l_error;
dload_3;
dconst_0;
dcmpl;
ifge l_endif;
l_error:
new Java.lang.IllegalArgumentException;
dup;
invokespecial java.lang.IllegalArgumentException::<init>():void;
athrow;
l_endif:
aload_0;
dload_1;
putfield @::m_a:double;
aload_0;
dload_3;
putfield @::m_b:double;
return;
end;
public getArea():double;
maxstack 4;
maxlocals 1;
aload_0;
getfield @::m_a:double;
aload_0;
getfield @::m_b:double;
dmul;
dreturn;
end;
%————————————————————-%
%————————————————————-%
%файл Square.jsm
public class Square;
extends Rectangle;
methods;
public <init>(double):void;
maxstack 5;
maxlocals 3;
aload_0;
dload_1;
dload_1;
invokespecial Rectangle::<init>(double, double):void;
return;
end;
%————————————————————-%
%————————————————————-%
%файл MainClass.jsm
public class MainClass;
methods;
public <init>():void;
maxstack 1;
maxlocals 1;
aload_0;
invokespecial Java.lang.Object::<init>():void;
return;
end;
public static main(Java.lang.String[]):void;
maxstack 8;
maxlocals 7;
iconst_3;
anewarray Figure;
astore_1;
aload_1;
iconst_0;
new Circle;
dup;
ldc2_w double 10;
invokespecial Circle::<init>(double):void;
aastore;
aload_1;
iconst_1;
new Rectangle;
dup;
dconst_1;
ldc2_w double 2;
invokespecial Rectangle::<init>(double, double):void;
aastore;
aload_1;
iconst_2;
new Square;
dup;
ldc2_w double 3;
invokespecial Square::<init>(double):void;
aastore;
dconst_0;
dstore_2;
iconst_0;
istore 4;
l_50:
iload 4;
aload_1;
arraylength;
if_icmpge l_75;
dload_2;
aload_1;
iload 4;
aaload;
invokeinterface Figure::getArea():double, 1;
dadd;
dstore_2;
iinc 4, 1;
goto l_50;
l_75:
new Java.io.BufferedReader;
dup;
new java.io.InputStreamReader;
dup;
getstatic java.lang.System::in:Java.io.InputStream;
invokespecial java.io.InputStreamReader::<init>(Java.io.InputStream):void;
invokespecial java.io.BufferedReader::<init>(Java.io.Reader):void;
astore 4;
l_50:
aload 4;
invokevirtual Java.io.BufferedReader::readLine():java.lang.String;
invokestatic Java.lang.Double::parseDouble(java.lang.String):double;
dstore 5;
getstatic Java.lang.System::out:java.io.PrintStream;
dload 5;
dload_2;
dadd;
invokevirtual Java.io.PrintStream::println(double):void;
l_114:
goto l_127;
l_117:
astore 4;
getstatic Java.lang.System::out:java.io.PrintStream;
ldc string «Error»;
invokevirtual Java.io.PrintStream::println(java.lang.String):void;
l_127:
return;
protected_blocks;
Java.io.IOException l_75 : l_114 > l_117;
end;
%————————————————————-%
Данная программка функционально эквивалентна последующему коду на Java (ассемблерный вариант сотворен на базе дизассемблированной при помощи утилиты javapJava-программы):
//————————————————————//
public interface Figure {
double getArea();
}
//————————————————————//
//————————————————————//
public class Circle implements Figure {
private double m_radius;
public Circle(double radius) {
if(radius<0)
throw new IllegalArgumentException();
m_radius = radius;
}
public double getArea() {
return m_radius*m_radius*Math.PI;
}
}
//————————————————————//
//————————————————————//
public class Rectangle implements Figure {
private double m_a;
private double m_b;
public Rectangle(double a, double b) {
if(!((a>=0)&&(b>=0)))
throw new IllegalArgumentException();
m_a = a;
m_b = b;
}
public double getArea() {
return m_a*m_b;
}
}
//————————————————————//
//————————————————————//
public class Square extends Rectangle {
public Square(double a) {
super(a, a);
}
}
//————————————————————//
//————————————————————//
import Java.io.*;
public class MainClass {
public static void main(String[] args) {
Figure[] figures = new Figure[3];
figures[0] = new Circle(10);
figures[1] = new Rectangle(1, 2);
figures[2] = new Square(3);
double sum = 0;
for(int i = 0; i<figures.length; i++)
sum += figures[i].getArea();
try{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
double d = Double.parseDouble(br.readLine());
System.out.println(d+sum);
} catch(IOException exc) {
System.out.println(«Error!»);
}
}
}
//————————————————————//
Проектирование и реализация компилятора.
Для реализации компилятора был применен язык программирования Java (JDK версии 1.5). Это дозволяет запускать данный компилятор на хоть какой платформе, для которой существует виртуальная машинка Javav 1.5.
При любом запуске компилятора обрабатывается один файл начального текста на языке ассемблера для платформы Java. Компилятор воспринимает два аргумента командной строчки: имя файла начального текста и имя создаваемого файла класса (очевидное указание расширения .class непременно). В случае, если выходной файл уже существует, он перезаписывается без предупреждения. В случае синтаксической либо другой ошибки на консоль выводится соответственное сообщение.
Можно выделить несколько главных шагов компиляции (проходов):
· Чтение начального файла. При всем этом он разбивается на предложения, разбитые точками с запятыми, также выбрасываются комменты;
· Разбор начального текста. При поочередном переборе перечня предложений выделяются синтаксические конструкции. При разборе употребляется лексический анализатор, разделяющий предложения на лексемы. На основании выделенных синтаксических конструкций генерируется внутреннее б-кода способов как массивов б;
· Генерация файла класса на основании внутреннего представления программки.
Данное деление является условным и не значит серьезной временной последовательности. 3-ий и 4-ый этапы, на самом деле дела, являются частями второго шага.
Диаграмма пакетов проекта изображена на рис. 1.
Корневой пакет проекта имеет имя jasm. Он содержит класс MainClass, способ main() которого является точкой входа в программку, и классы SyntaxErrorException и InternalCompilerErrorException, унаследованные от Java.lang.Exception и представляющие ошибки, которые могут появиться в процессе компиляции. Пакет compiler содержит классы, ответственные за разбор начального текста, при этом классы, работающие с кодом способов, содержатся во вложенном пакете code_compiler. Пакет structures содержит классы, из объектов которых состоит внутреннее промежуточное б-кода, элементы ConstantPool и атрибуты полей и способов. В пакет commands в свою очередь вложены пакет command_formats, содержащий базисные абстрактные классы для установок обычных форматов, пакеты, содержащие классы, представляющие команды всякого из обычных форматов, также пакет special для классов, представляющих команды, имеющие особенный формат.
Большая часть классов из пакета structures входят в иерархию, корнем которой является интерфейс IStructure, содержащий два способа intgetLength() и byte[] getContent(), дозволяющие получить, соответственно, размер, который займет структура при записи в выходной файл, и массив б, которыми она представляется в выходном файле. Данный интерфейс не употребляется для полиморфного вызова способов, он играет только роль структурирования программки. Главные классы пакета structures изображены на диаграмме на рис. 2.
Генерируемый класс как целое представляется объектом класса ClassFile, который содержит в собственных полях ссылки на объекты классов ConstantPool, FiledInfo и MethodInfo, описывающие область констант, поля и способы создаваемого класса. Сам класс ClassFile интерфейс IStructure не реализует. Посреди его членов необходимо подчеркнуть способ writeToFile(), создающий файл класса на основании инфы, содержащейся в объекте.
Данный компилятор может создавать атрибуты способов Code, Exceptions и атрибут поля ConstantValue, которые представляются классами, производными от AttributeStructure. Отмечу, что объект класса CodeAttribute содержит байт-код способа уже в виде массива б, а не в виде объектов классов, представляющих отдельные команды (производных от Command), списки таковых объектов употребляются только в процессе обработки кода способа.
Абстрактный класс Command добавочно к способам интерфейса IStructure содержит абстрактный способ bytegetByte(intn), который должен возвращать б с данным номером в команде. Очередной способ changeOffset имеет пустую реализацию, но переопределяется в классах-потомках, соответственных командам перехода. Он употребляется для подмены номеров меток смещениями на 3-ем шаге компиляции. Конкретными наследниками класса Command являются классы, надлежащие обычным форматам установок (абстрактные) и командам, имеющим неповторимые форматы. Большая часть установок представляются классами, наследующими классы обычных форматов. Имена классов установок имеют вид C_xxx, где xxx — мнемоническое имя команды. Пустой команде none соответствует класс NoCommand.
Класс ConstantPool содержит как общий перечень для всех типов констант, хранящий объекты класса CpInfo (базисный тип для классов, представляющих разные виды констант), так и списки для констант отдельных типов, содержащие индексы частей в первом перечне. Эти списки описываются классами, вложенными в класс ConstantPool. Таковая структура употребляется для того, чтоб при добавлении константы можно было стремительно проверить, не находится ли уже схожий элемент в ConstantPool (эта проверка делается не для всех типов констант). Для всякого типа констант в классе ConstantPool существует собственный способ прибавления, который возвращает индекс добавленного (либо отысканного имеющегося) элемента в общем перечне. Посреди наследников CpInfo имеется особый класс CpNone, который соответствует пустой структуре, вставляемой опосля констант типа Long и Double т. к. последующий за ними индекс считается неиспользуемым.
За процесс компиляции отвечает пакет compiler, который содержит последующие классы:
· Source — делает функцию выделения предложений в начальном тексте. Конструктор этого класса воспринимает в качестве параметра имя файла, содержимое которого разбивается на предложения и заносится в коллекцию типа ArrayList<String>. способ StringnextStatement() при любом вызове возвращает еще одно предложение;
· StringParser — делает функцию разделения строк на лексемы. Любой объект этого класса соответствует одной обрабатываемой строке.
· TokenRecognizer — имеет статические способы, дозволяющие найти, является ли некая строчка главным словом либо корректным идентификатором. Объекты этого класса никогда не создаются;
· DescriptorCreator — содержит статические способы для сотворения дескрипторов типов, полей и способов из строк, содержащих запись типов и сигнатур, применяемую в языке. Экземпляры класса также не создаются;
· ClassHeaderParser, FieldDeclarationParser, MethodHeaderParser — употребляются при анализе заголовка класса, описаний полей и заголовков способов. Объекты этих классов сохраняют внутри себя информацию, извлеченную из анализируемого в конструкторе класса предложения;
· code_compiler.CodeCompiler — производит анализ кода способа и генерацию б-кода (включая 3-ий и 4-ый этапы компиляции). Данный процесс будет рассмотрен подробнее ниже;
· code_compiler.CommandCompiler — анализирует команды в начальном коде способа и делает объекты классов-потомков Command;
· code_compiler.Label — представляет метку в коде способа;
· code_compiler.LabelTable — таблица меток способа. Содержит имена меток, номера соответственных им строк и смещения установок.
· SourceAnalyser — занимает центральное пространство в процессе анализа начального текста. Конструктор данного класса воспринимает в качестве параметра объект класса Source. При вызове способа analyse() происходит анализ начального кода и генерируется промежуточное представление программки в виде описанной чуть повыше структуры. В процессе анализа употребляются классы StringParser, ClassHeaderParser, FieldDeclarationParser, MethodHeaderParser, CodeCompiler и др. Данный способ возвращает объект класса ClassFile.
Класс MainClass содержит единственный способ main, являющийся точкой входа в программку. тут сначала создается объект класса Source, который передается для обработки объекту класса SourceAnalyser, потом у возвращенного способом SourceAnalyser.analyse() объекта класса ClassFile вызывается способ writeToFile, который и генерирует файл класса, являющийся результатом работы компилятора. Все перечисленные операции заключены в блок try/catch, перехватывающий любые исключения, в случае появления которых на консоль выводится соответственное сообщение и процесс компиляции заканчивается. Диаграмма, в облегченном виде показывающая этот процесс, изображена на рис. 3.
Разглядим подробнее процесс компиляции кода способа. Опосля обработки заголовка способа при помощи класса MethodHeaderParser, в случае, если способ не является абстрактным, в способе SourceAnalyser .analyse() считываются предложения maxstack и maxlocals. Потом считываются и заносятся в массивы предложения, содержащие команды и описания защищенных блоков. Эти массивы, также ссылка на объект ConstantPool, представляющий область констант класса, передаются в качестве характеристик конструктору класса CodeCompiler. У сделанного объекта CodeCompiler вызывается способ compile(), ворачивающий объект класса CodeAttribute, описывающий атрибут Code, содержащий б-код способа. При всем этом происходят последующие процессы. В конструкторе класса CodeCompiler из строк, содержащих команды, выделяются имена меток, которые сохраняются в объекте класса LabelTable. Потом обрабатывается перечень строк, описывающих защищенные блоки. В способе CodeCompiler.compile() производятся последующие операции. Поначалу при помощи объекта класса CommandCompiler для каждой команды создается объект соответственного класса. При всем этом сразу для установок, при которых имеется метка, в объекте LabelTable сохраняется информация о смещении метки относительно начала способа. Как в описаниях защищенных блоков, так и в объектах, соответственных командам перехода, на момент окончания этого шага заместо смещений перехода, содержатся порядковые номера установок, при которых размещены надлежащие метки. Подмена их на действительные смещения делается на крайнем шаге при помощи способов LabelTable.changePC() и Command.changeOffset().
Заключение.
разработка Java нацелена на внедрение 1-го языка программирования. Система типов данных и остальные индивидуальности языка Java тесновато соединены с функционированием JVM и форматом файла класса. Но, существует открытая спецификация, дозволяющие создавать как собственные реализации JVM, так и другие средства разработки. С ее внедрением мною разработан язык JASM, представляющий из себя язык ассемблера для платформы Java, который дозволяет создавать файлы классов, использующие значительную часть способностей JVM, и реализован его компилятор.
Использованная литература.
1. Грис, Д. Конструирование компиляторов для цифровых вычислительных машин. М., «Мир», 1975.
2. Эккель, Б. Философия JAVA. СПб. 3-е изд.: Питер, 2003.
3. Tim Lindholm, Frank Yellin. The Java Virtual Machine Specification Second Edition. Sun Microsystems Inc. 1999.
]]>