ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 24.12.2021

Просмотров: 12151

Скачиваний: 10

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

Форматы команд 363

а самый длинный — все возможные случаи. JVM содержит команду ILOAD, использу-

ющую 8-битный индекс для определения локальной переменной, которую нужно
поместить в стек. Мы также показали, как префикс WIDE позволяет использовать

тот же код операции для определения любого из первых 65 536 элементов во фрейме
локальных переменных. Для команды WIDE ILOAD требуется 4 байта: 1 — для WIDE,

1 — для ILOAD и 2 — для 16-битного индекса. Такое разделение объясняется тем, что

большинство команд ILOAD используют одну из первых 256 локальных переменных.

Префикс WIDE нужен для универсальности, применимости к любым ситуациям, и

используется он редко.

Но разработчики машины JVM пошли еще дальше. Так как параметры проце-

дуры передаются в первые несколько слов фрейма локальных переменных, коман-

да ILOAD чаще всего использует элементы фрейма локальных переменных с невы-
сокими индексами. Разработчики JVM решили, что стоит назначить отдельные

1-байтные коды операций для каждой из возможных комбинаций. Команда ILOAD_O

помещает в стек локальную переменную 0. Эта команда полностью эквивалент-
на 2-байтной команде ILOAD 0, за исключением того, что она занимает один байт

вместо двух. Точно также команды ILOAD_1, ILOADJ? и IL0AD_3 (коды операций OxlB,
OxlC и OxlD соответственно) помещают в стек локальные переменные 1, 2 и 3.

Отметим, что локальную переменную 1, например, можно загрузить одним из трех

способов: ILOAD_1, ILOAD 1 и WIDE ILOAD 1.

Многие команды имеют варианты, подобные этим. Существуют специальные

тоъшвдл, полностью эквивалентные BIPUSH, для значений 0,1, 2, 3,4, 5, а также-1.

Есть, кроме того, особые команды для записи переменных из стека в первые 4 сло-

ва пространства локальных переменных.

Отметим, что эти варианты повлекли за собой некоторые убытки. Только

256 различных команд могут определяться в одном байте. Поскольку 4 из этих
256 команд решили отвести на загрузку первых четырех локальных переменных,
число команд уменьшилось на 4. Эти команды вместе с основной командой ILOAD
в сумме составляют 5 команд. Префикс WIDE тоже использует одно из 256 воз-
можных значений (а это даже не команда, а просто префикс), но он применяется
к различным кодам операций.

Для спецификации загрузки операндов из набора констант разработчики ис-

пользовали немного другой метод, поскольку они ожидали различия в распреде-
лении адресов. Они предоставили две версии команды: LDC и LDC_W. Вторая форма

команды (она была включена в IJVM) может вызывать любое из 65 536 слов в на-
боре констант. В первой форме требуется только однобайтный индекс, но такая ко-
манда может вызывать только одно их первых 256 слов. Вызов любого из первых

256 слов можно осуществить с помощью 2-байтной команды, а вызов любого слова —
с помощью 3-байтной команды. На эти 2 варианта требуется 2 кода из 256 кодов
операций. Набор команд был бы более простым и регулярным, если бы разработ-

чики выбрали ту же технологию, которую они использовали для команды ILOAD, то

есть использовали бы префикс WIDE, а не команду LDC_W. Однако в этом случае для

вызова констант из верхних 256 слов потребовалось бы 4 байта, а не 3.

Технология объединения кодов операций и индексов в один байт, а также раз-

мещения 256 доступных байтов в соответствии с частотой их использования была
впервые предложена автором данной книги в 1978 году, но нашла применение толь-
ко спустя два десятилетия [145].


background image

3 6 4 Глава 5. Уровень архитектуры команд

Адресация

Разработка кодов операций является важной частью архитектуры команд. Однако

значительное число битов программы используется для того, чтобы определить,
откуда нужно брать операнды, а не для того, чтобы узнать, какие операции нужно

выполнить. Рассмотрим команду ADD, которая требует спецификации трех операн-

дов: двух источников и одного пункта назначения. (Термин «операнд» обычно ис-

пользуется применительно ко всем трем элементам, хотя пункт назначения — это
место, где сохраняется результат.) Так или иначе команда ADD должна сообщать,
где найти операнды и куда поместить результат. Если адреса памяти 32-битные, то
спецификация этой команды требует помимо кода операции еще три 32-битных
адреса. Адреса занимают гораздо больше бит, чем коды операции.

Два специальных метода предназначены для уменьшения размера специфика-

ции. Во-первых, если операнд должен использоваться несколько раз, его можно
переместить в регистр. В использовании регистра для переменной есть двойная

польза: скорость доступа увеличивается, а для определения операнда требуется
меньшее количество битов. Если имеется 32 регистра, любой из них можно опреде-

лить, используя всего лишь 5 битов. Если при выполнении команды ADD приме-

нять только регистровые операнды, для определения всех трех операндов понадо-

бится только 15 битов, а если бы эти операнды находились в памяти, понадобилось

бы целых 96 битов.

Однако использование регистров может вызвать другую проблему. Если опе-

ранд, находящийся в памяти, должен сначала загружаться в регистр, то потребу-
ется большее число битов для определения адреса памяти. Во-первых, для перено-
са операнда в регистр нужна команда LOAD. Для этого требуется не только код
операции, но и полный адрес памяти, а также нужно определить целевой регистр.

Поэтому если операнд используется только один раз, помещать его в регистр не

стоит.

К счастью, многочисленные измерения показали, что одни и те же операнды

используются многократно. Поэтому большинство новых архитектур содержат

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

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

неявным образом. Для этого существует несколько технологий. Один из спосо-
бов — использовать одну спецификацию для входного и выходного операндов. В то
время как обычная трехадресная команда ADD использует форму

0ESTINATI0N=S0URSEl+S0URSE2.

двухадресную команду можно сократить до формы

REGISER2-REGISTER2+S0URSE1.

Недостаток этой команды состоит в том, что содержимое REGISTER2 не сохра-

нится. Если первоначальное значение понадобится позднее, его нужно сначала

скопировать в другой регистр. Компромисс здесь заключается в том, что двухад-

ресные команды короче, но они не так часто используются. У разных разработчи-


background image

Адресация 365

ков разные предпочтения. В Pentium II, например, используются двухадресные
команды, а в UltraSPARC II — трехадресные.

Мы сократили число операндов команды

 ADD

 с трех до двух. Продолжим сокра-

щение дальше. Первые компьютеры имели только один регистр, который назы-
вался аккумулятором. Команда ADD, например, всегда прибавляла слово из памяти

к аккумулятору, поэтому нужно было определять только один операнд (операнд

памяти). Эта технология хорошо работала для простых вычислений,

 но

 когда были

нужны промежуточные результаты, аккумулятор приходилось записывать обрат-

но в память, а позднее вызывать снова. Следовательно, эта технология нам не под-

ходит.

Итак, мы перешли от трехадресной команды ADD к двухадресной, а затем к одно-

адресной. Что же остается? Ноль адресов? Да. В главе 4 мы увидели, как машина

IJVM использует стек. Команда IADD не имеет адресов. Входные и выходные опе-

ранды не показываются явным образом. Ниже мы рассмотрим стековую адреса-
цию более подробно.

Способы адресации

До сих пор мы не рассказывали о том, как интерпретируются биты адресного поля

для нахождения операнда. Один из возможных вариантов состоит в том, что они
содержат адрес операнда. Помимо огромного поля, необходимого для определе-
ния полного адреса памяти, данный метод имеет еще одно ограничение: этот адрес

должен определяться во время компиляции. Существуют и другие возможности,

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

форм, которые называются

 способами адресации.

Непосредственная адресация

Самый простой способ определения операнда — содержать в адресной части сам

операнд, а не адрес операнда или какую-либо другую информацию, описывающую,

где находится операнд. Такой операнд называется

 непосредственным операндом,

поскольку он автоматически вызывается из памяти одновременно с командой; сле-

довательно, он сразу непосредственно становится доступным. Один из вариантов

команды с непосредственным адресом для загрузки в регистр R1 константы 4 по-
казан на рис. 5.12.

MOV

R1

4

Рис. 5.12. Команда с непосредственным адресом для загрузки константы 4 в регистр 1

При непосредственной адресации не требуется дополнительного обращения

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


background image

3 6 6 Глава 5. Уровень архитектуры команд

Прямая адресация

Следующий способ определения операнда — просто дать его полный адрес. Такой

способ называется

 прямой адресацией.

 Как и непосредственная адресация, пря-

мая адресация имеет некоторые ограничения: команда всегда будет иметь доступ

только к одному и тому же адресу памяти. То есть значение может меняться, а
адрес — нет. Таким образом, прямая адресация может использоваться только для

доступа к глобальным переменным, адреса которых известны во время компиля-

ции. Многие программы содержат глобальные переменные, поэтому этот способ

широко используется. Каким образом компьютер узнает, какие адреса непосред-
ственные, а какие прямые, мы обсудим позже.

Регистровая адресация

Регистровая адресация по сути сходна с прямой адресацией, только в данном слу-

чае вместо ячейки памяти определяется регистр. Поскольку регистры очень важ-

ны (из-за быстрого доступа и коротких адресов), этот способ адресации является

самым распространенным на большинстве компьютеров. Многие компиляторы

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

ременные в регистры.

Такой способ адресации называют

 регистровой адресацией. В

 архитектурах

с загрузкой с запоминанием, например UltraSPARC II, практически все команды
используют исключительно этот способ адресации. Он не используется только

в том случае, когда операнд перемещается из памяти в регистр (команда

 LOAD)

 или

из регистра в память (команда STORE). Даже в этих командах один из операндов

является регистром — туда отправляется слово из памяти

 или

 оттуда перемещает-

ся слово в память.

Косвенная регистровая адресация

При таком способе адресации определяемый операнд берется из памяти или от-
правляется в память, но адрес не зафиксирован жестко в команде, как при прямой

адресации. Вместо этого адрес содержится в регистре. Если адрес используется
таким образом, он называется

 указателем.

 Преимущество косвенной адресации

состоит в том, что можно обращаться к памяти, не имея в команде полного адреса.

Кроме того, при разных выполнениях данной команды можно использовать раз-

ные слова памяти.

Чтобы понять, почему может быть полезно использование разных слов при каж-

дом выполнении команды, представим себе цикл, который проходит по 1024-эле-

ментному одномерному массиву целых чисел для вычисления суммы элементов
в регистре R1. Вне этого цикла какой-то другой регистр, например R2, может

указывать первый элемент массива, а еще один регистр, например R3, может ука-

зывать первый адрес после массива. Массив содержит 1024 целых числа по 4 байта
каждое. Если массив начинается с А, то первый адрес после массива будет А+4096.
Типичная программа ассемблера, выполняющая это вычисление для двухадрес-
ной машины, показана в листинге 5.1.


background image

Адресация  3 6 7

Листинг  5 . 1 . Программа на ассемблере для вычисления суммы элементов массива

MOV Rl.#0 накопление суммы в R1. изначально 0

MOV R2,#A ;R2=aflpec массива А

MOV R3,#A+4096 ;R3=aflpec первого слова после А

LOOP: ADD R1.(R2) получение операнда через регистр R2

ADD R2,#4 увеличение R2 на одно слово(4байта)

CMP R2.R3 ;проверка на завершение

BLT LOOP :если R2<R3. продолжать цикл

В этой маленькой программе мы использовали несколько способов адреса-

ции. Первые три команды используют регистровую адресацию для первого опе-
ранда (пункт назначения) и непосредственную адресацию для второго операнда
(константа, обозначенная символом #). Вторая команда помещает в R2 не содер-

жимое А, а

 адрес

 А. Именно это и сообщает ассемблеру знак #. Сходным образом

третья команда помещает в R3 первое слово после массива.

Интересно отметить, что само тело цикла не содержит каких-либо адресов

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

В пятой команде применяется регистровая и непосредственная адресация, а в ше-

стой — два раза регистровая. Команда BLT могла бы использовать адрес памяти,

однако более привлекательным является определение адреса с помощью 8-битно-
го смещения, связанного с самой командой BLT. Таким образом, полностью избегая
адресов памяти, мы получили короткий и быстрый цикл. Кстати, эта программа
предназначена для Pentium II, только мы переименовали команды и регистры и

для упрощения понимания изменили запись.

Теоретически есть еще один способ выполнения этого вычисления без исполь-

зования косвенной регистровой адресации. Этот цикл мог бы содержать команду

для прибавления А к регистру R1, например

ADD R1.A

Тогда при каждом шаге команда должна увеличиваться на 4. Таким образом,

после одного шага команда будет выглядеть следующим образом;

ADD R1.A+4

и так далее до завершения цикла.

Программа, которая сама изменяется подобным образом, называется

 самоиз-

меняющейся программой.

 Эта идея была предложена Джоном фон Нейманом и

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

очень трудными для понимания. Кроме того, их выполнение нельзя разделить меж-

ду несколькими процессорами. Они даже не могут правильно выполняться на ма-

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

Индексная адресация

Часто нужно уметь обращаться к словам памяти по известному смещению. Подоб-
ные примеры мы видели в машине IJVM, где локальные переменные определяют-
ся по смещению от регистра LV. Обращение к памяти по регистру и константе
смещения называется

 индексной адресацией.