Набор IJVM-команд
Набор IJVM-команд приведен в табл. 4.2. Каждая команда состоит из кода операции и иногда из операнда (например, смещения адреса или константы). В первом столбце приводится шестнадцатеричный код команды. Во втором столбце дается мнемоника на языке ассемблера. В третьем столбце описывается назначение команды.

Команды нужны для того, чтобы помещать слова из различных источников в стек. Источники — это набор констант (LDC_W), фрейм локальных переменных (IL0AD) и сама команда (BIPUSH). Переменную можно также вытолкнуть из стека и сохранить во фрейме локальных переменных (ISTORE). Над двумя верхними словами стека можно совершать две арифметические (IADD и ISUB) и две логические операции (IAND и I0R). При выполнении любой арифметической или логической операции два слова выталкиваются из стека, а результат помещается обратно в стек. Существует 4 команды перехода: одна для безусловного перехода (GOTO), а три другие для условных переходов (IFEQ, IFLT и IFICMPEQ). Все эти команды изменяют значение PC на размер их смещения, который следует за кодом операции в команде. Операнд смещения состоит из 16 бит. Он прибавляется к адресу кода операции. Существуют также команды для перестановки двух верхних слов стека (SWAP), дублирования верхнего слова (DUP) и удаления верхнего слова (POP).
Некоторые команды имеют сложный формат, допускающий краткую форму записи для часто используемых версий. Из всех механизмов, которые JVM применяет для этого, в IJVM мы включили два. В одном случае мы пропустили краткую форму в пользу более традиционной. В другом случае мы показываем, как префиксная команда WIDE может использоваться для изменения следующей команды.
Наконец, существуют команда вызова другой процедуры (INVOKEVIRTUAL) и команда выхода из текущей процедуры и возвращения к процедуре, из которой она была вызвана (IRETURN). Из-за сложности механизма мы немного упростили определение. Ограничение состоит в том, что, в отличие от языка Java, в нашем примере процедура может вызывать только такую процедуру, которая находится внутри нее. Хотя это ограничение противоречит сути языка Java, оно позволяет представить более простой механизм без необходимости размещать процедуру динамически. (Если вы не знакомы с объектно-ориентированным программированием, можете игнорировать это предложение. Мы просто превратили язык Java из объектно-ориентированного в обычный, такой как С или Pascal.) На всех компьютерах, кроме JVM, адрес процедуры, которую нужно вызвать, непосредственно определяется командой CALL, поэтому наш подход скорее правило, чем исключение.
Механизм вызова процедуры состоит в следующем. Сначала вызывающая программа помещает в стек указатель на вызываемый объект. На рис. 4.10, а этот указатель обозначен символами OBJREF. Затем вызывающая программа помещает в стек параметры процедуры (в данном примере — это Параметр 1, Параметр 2 и Параметр 3). После этого выполняется команда INVOKEVIRTUAL.
Команда INVOKEVIRTUAL включает в себя относительный адрес (disp), указывающий на позицию в наборе констант. В этой позиции находится начальный адрес вызываемой процедуры, которая хранится в области процедур. Первые 4 байта в области процедур содержат специальные данные. Первые 2 байта представляют собой целое 16-разрядное число, указывающее на количество параметров данной процедуры (сами параметры были ранее помещены в стек). В данном случае указатель OBJREF считается параметром — параметром 0. Это 16-разрядное целое число вместе со значением SP дает адрес OBJREF. Отметим, что регистр LV указывает на OBJREF, а не на первый реальный параметр. Выбор того, на что указывает LV, в какой-то степени произволен.
Следующие 2 байта в области процедур представляют еще одно 16-разрядное целое число, задающее размер области локальных переменных для вызываемой процедуры.
Дело в том, что для данной процедуры предоставляется новый стек, который размещается прямо над фреймом локальных переменных, для этого и нужно это число. Наконец, пятый байт в области процедур содержит код первой операции, которую нужно выполнить.

Посмотрим, что происходит перед вызовом процедуры (см. также рис. 4.10). Два байта без знака, которые следуют за кодом операции, используются для индексирования таблицы констант (первый байт — старший). Команда вычисляет базовый адрес нового фрейма локальных переменных. Для этого из указателя стека вычитается число параметров, a LV устанавливается на OBJREF. В OBJREF хранится адрес ячейки, в которой находится старое значение PC. Этот адрес вычисляется следующим образом. К размеру фрейма локальных переменных (параметры + локальные переменные) прибавляется адрес, содержащийся в регистре LV. Сразу над адресом, предназначенным для сохранения старого значения PC, находится адрес, в котором должно быть сохранено старое значение LV. Над этим адресом начинается стек для новой вызванной процедуры. SP указывает на старое значение LV, адрес которого находится сразу под первой пустой ячейкой стека. Помните, что SP всегда указывает на верхнее слово в стеке. Если стек пуст, то SP указывает на адрес, который находится непосредственно под стеком, поскольку стек заполняется снизу вверх.
И наконец, для выполнения команды INVOKEVIRTUAL нужно сделать так, чтобы регистр PC указывал на пятый байт в кодовом пространстве процедуры.
Команда IRETURN противоположна команде INVOKEVIRTUAL (рис. 4.11). Она освобождает пространство, используемое процедурой. Она также возвращает стек в предыдущее состояние, за исключением того, что, во-первых, OBJREF и все параметры удаляются из стека; во-вторых, возвращенное значение помещается в стек, туда, где раньше находился параметр OBJREF. Чтобы восстановить прежнее состояние, команда IRETURN должна вернуть прежние значения указателей PC и LV. Для этого она обращается к связующему указателю (это слово, определяемое текущим значением LV). В этом месте, где изначально находился параметр OBJREF, команда INVOKEVIRTUAL сохранила адрес, содержащий старое значение PC. Это слово, а также слово над ним извлекаются, чтобы восстановить старые значения PC и LV соответственно. Возвращенное значение, которое хранится на самой вершине стека завершающейся процедуры, копируется туда, где изначально находился параметр OBJREF, после чего SP начинает указывать на этот адрес. И тогда управление переходит к команде, которая следует сразу за INVOKEVIRTUAL.

До сих пор у нашей машины не было никаких команд ввода-вывода. Мы и не собираемся их вводить. В нашем примере, как и в виртуальной машине Java, они не нужны, и в описании JVM никогда не упоминаются процессы ввода-вывода. Считается, что машина без механизмов ввода-вывода более надежна. (Чтение и запись осуществляются в JVM путем вызова специальных процедур.)

Ваш отзыв