ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 24.12.2021
Просмотров: 12151
Скачиваний: 10

Форматы команд 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].

3 6 4 Глава 5. Уровень архитектуры команд
Адресация
Разработка кодов операций является важной частью архитектуры команд. Однако
значительное число битов программы используется для того, чтобы определить,
откуда нужно брать операнды, а не для того, чтобы узнать, какие операции нужно
выполнить. Рассмотрим команду ADD, которая требует спецификации трех операн-
дов: двух источников и одного пункта назначения. (Термин «операнд» обычно ис-
пользуется применительно ко всем трем элементам, хотя пункт назначения — это
место, где сохраняется результат.) Так или иначе команда ADD должна сообщать,
где найти операнды и куда поместить результат. Если адреса памяти 32-битные, то
спецификация этой команды требует помимо кода операции еще три 32-битных
адреса. Адреса занимают гораздо больше бит, чем коды операции.
Два специальных метода предназначены для уменьшения размера специфика-
ции. Во-первых, если операнд должен использоваться несколько раз, его можно
переместить в регистр. В использовании регистра для переменной есть двойная
польза: скорость доступа увеличивается, а для определения операнда требуется
меньшее количество битов. Если имеется 32 регистра, любой из них можно опреде-
лить, используя всего лишь 5 битов. Если при выполнении команды ADD приме-
нять только регистровые операнды, для определения всех трех операндов понадо-
бится только 15 битов, а если бы эти операнды находились в памяти, понадобилось
бы целых 96 битов.
Однако использование регистров может вызвать другую проблему. Если опе-
ранд, находящийся в памяти, должен сначала загружаться в регистр, то потребу-
ется большее число битов для определения адреса памяти. Во-первых, для перено-
са операнда в регистр нужна команда LOAD. Для этого требуется не только код
операции, но и полный адрес памяти, а также нужно определить целевой регистр.
Поэтому если операнд используется только один раз, помещать его в регистр не
стоит.
К счастью, многочисленные измерения показали, что одни и те же операнды
используются многократно. Поэтому большинство новых архитектур содержат
большое количество регистров, а большинство компиляторов доходят до огром-
ных размеров, чтобы хранить локальные переменные в этих регистрах, устраняя
таким образом многочисленные обращения к памяти. Это сокращает и размер, и
время выполнения программы.
Второй метод подразумевает определение одного или нескольких операндов
неявным образом. Для этого существует несколько технологий. Один из спосо-
бов — использовать одну спецификацию для входного и выходного операндов. В то
время как обычная трехадресная команда ADD использует форму
0ESTINATI0N=S0URSEl+S0URSE2.
двухадресную команду можно сократить до формы
REGISER2-REGISTER2+S0URSE1.
Недостаток этой команды состоит в том, что содержимое REGISTER2 не сохра-
нится. Если первоначальное значение понадобится позднее, его нужно сначала
скопировать в другой регистр. Компромисс здесь заключается в том, что двухад-
ресные команды короче, но они не так часто используются. У разных разработчи-

Адресация 365
ков разные предпочтения. В Pentium II, например, используются двухадресные
команды, а в UltraSPARC II — трехадресные.
Мы сократили число операндов команды
ADD
с трех до двух. Продолжим сокра-
щение дальше. Первые компьютеры имели только один регистр, который назы-
вался аккумулятором. Команда ADD, например, всегда прибавляла слово из памяти
к аккумулятору, поэтому нужно было определять только один операнд (операнд
памяти). Эта технология хорошо работала для простых вычислений,
но
когда были
нужны промежуточные результаты, аккумулятор приходилось записывать обрат-
но в память, а позднее вызывать снова. Следовательно, эта технология нам не под-
ходит.
Итак, мы перешли от трехадресной команды ADD к двухадресной, а затем к одно-
адресной. Что же остается? Ноль адресов? Да. В главе 4 мы увидели, как машина
IJVM использует стек. Команда IADD не имеет адресов. Входные и выходные опе-
ранды не показываются явным образом. Ниже мы рассмотрим стековую адреса-
цию более подробно.
Способы адресации
До сих пор мы не рассказывали о том, как интерпретируются биты адресного поля
для нахождения операнда. Один из возможных вариантов состоит в том, что они
содержат адрес операнда. Помимо огромного поля, необходимого для определе-
ния полного адреса памяти, данный метод имеет еще одно ограничение: этот адрес
должен определяться во время компиляции. Существуют и другие возможности,
которые обеспечивают более короткие спецификации, а также могут определять
адреса динамически. В следующих разделах мы рассмотрим некоторые из этих
форм, которые называются
способами адресации.
Непосредственная адресация
Самый простой способ определения операнда — содержать в адресной части сам
операнд, а не адрес операнда или какую-либо другую информацию, описывающую,
где находится операнд. Такой операнд называется
непосредственным операндом,
поскольку он автоматически вызывается из памяти одновременно с командой; сле-
довательно, он сразу непосредственно становится доступным. Один из вариантов
команды с непосредственным адресом для загрузки в регистр R1 константы 4 по-
казан на рис. 5.12.
MOV
R1
4
Рис. 5.12. Команда с непосредственным адресом для загрузки константы 4 в регистр 1
При непосредственной адресации не требуется дополнительного обращения
к памяти для вызова операнда. Однако у такого способа адресации есть и некоторые
недостатки. Во-первых, таким способом можно работать только с константами. Во-
вторых, число значений ограничено размером поля. Тем не менее эта технология
используется во многих архитектурах для определения целочисленных констант.

3 6 6 Глава 5. Уровень архитектуры команд
Прямая адресация
Следующий способ определения операнда — просто дать его полный адрес. Такой
способ называется
прямой адресацией.
Как и непосредственная адресация, пря-
мая адресация имеет некоторые ограничения: команда всегда будет иметь доступ
только к одному и тому же адресу памяти. То есть значение может меняться, а
адрес — нет. Таким образом, прямая адресация может использоваться только для
доступа к глобальным переменным, адреса которых известны во время компиля-
ции. Многие программы содержат глобальные переменные, поэтому этот способ
широко используется. Каким образом компьютер узнает, какие адреса непосред-
ственные, а какие прямые, мы обсудим позже.
Регистровая адресация
Регистровая адресация по сути сходна с прямой адресацией, только в данном слу-
чае вместо ячейки памяти определяется регистр. Поскольку регистры очень важ-
ны (из-за быстрого доступа и коротких адресов), этот способ адресации является
самым распространенным на большинстве компьютеров. Многие компиляторы
доходят до огромных размеров, чтобы определить, к каким переменным доступ
будет осуществляться чаще всего (например, индекс цикла), и помещают эти пе-
ременные в регистры.
Такой способ адресации называют
регистровой адресацией. В
архитектурах
с загрузкой с запоминанием, например UltraSPARC II, практически все команды
используют исключительно этот способ адресации. Он не используется только
в том случае, когда операнд перемещается из памяти в регистр (команда
LOAD)
или
из регистра в память (команда STORE). Даже в этих командах один из операндов
является регистром — туда отправляется слово из памяти
или
оттуда перемещает-
ся слово в память.
Косвенная регистровая адресация
При таком способе адресации определяемый операнд берется из памяти или от-
правляется в память, но адрес не зафиксирован жестко в команде, как при прямой
адресации. Вместо этого адрес содержится в регистре. Если адрес используется
таким образом, он называется
указателем.
Преимущество косвенной адресации
состоит в том, что можно обращаться к памяти, не имея в команде полного адреса.
Кроме того, при разных выполнениях данной команды можно использовать раз-
ные слова памяти.
Чтобы понять, почему может быть полезно использование разных слов при каж-
дом выполнении команды, представим себе цикл, который проходит по 1024-эле-
ментному одномерному массиву целых чисел для вычисления суммы элементов
в регистре R1. Вне этого цикла какой-то другой регистр, например R2, может
указывать первый элемент массива, а еще один регистр, например R3, может ука-
зывать первый адрес после массива. Массив содержит 1024 целых числа по 4 байта
каждое. Если массив начинается с А, то первый адрес после массива будет А+4096.
Типичная программа ассемблера, выполняющая это вычисление для двухадрес-
ной машины, показана в листинге 5.1.

Адресация 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. Обращение к памяти по регистру и константе
смещения называется
индексной адресацией.