Танненбаум Е. Архітектура компютера [pdf]
.pdf394 Глава 5. Уровень архитектуры команд
Pentium II имеет ряд префиксов. Один из них (REP) мы уже упомянули. Префикс — это специальный байт, который может ставиться практически перед любой командой (подобно WIDE в IJVM). Префикс REP заставляет команду, идущую за ним, повторяться до тех пор, пока регистр ЕСХ не примет значение 0, как было сказано выше. REPZ и REPNZ заставляют команду выполняться снова и снова, пока код выполнения условия Z не примет значение 1 или 0 соответственно. Префикс LOCK резервирует шину для всей команды, чтобы можно было осуществлять многопроцессорную синхронизацию. Другие префиксы используются для того, чтобы команда работала в 16-битном или 32-битном формате. При этом не только меняется длина операндов, но и полностью переопределяются способы адресации.
Команды UltraSPARC II
Все целочисленные команды пользовательского режима UltraSPARC II приведены на рис. 5.22. Здесь не даются команды с плавающей точкой, команды управления (например, команды управления кэш-памятью, команды перезагрузки системы), команды, включающие адресные пространства, отличные от пользовательских, или устаревшие команды. Набор команд удивительно мал: UltraSPARC II — это процессор типа RISC.
Структура команд LOAD и STORE очень проста. Эти команды имеют варианты для 1, 2,4 и 8 байтов. Если в 64-разрядный регистр загружается число размером меньше 64 битов, это число может быть либо расширено по знаку, либо дополнено нулями. Существуют команды для обоих вариантов.
Следующая группа команд предназначена для арифметических операций. Команды с буквами СС в названии устанавливают биты кода условия. На машинах CISC большинство команд устанавливают коды условия, но в машине типа RISC это нежелательно, поскольку ограничивает способность компилятора перемещать команды, стараясьзаполнить отсрочки. Если изначальныйпорядоккоманд А...В...С, где А устанавливает коды условия, а В проверяет их, то компилятор не может вставить С между А и В, если С устанавливает условные коды. По этой причине многие команды имеют два варианта, при этом компилятор обычно использует ту команду, которая не устанавливает коды условия, если не планируется проверить их позже. Команды умножения, деления со знаком и деления без знака тоже поддерживаются.
Кроме этого, поддерживается специальный формат 30-битных чисел с автоматическим опознаванием типа данных за счет поля тега. Он используется для таких языков, как Smalltalk и Prolog, в которых тип переменных может меняться во время выполнения программы. При наличии таких чисел компилятор может породить команду ADD, а во время выполнения программы машина определяет, нужна ли в данном случае целочисленная команда ADD или команда ADD с плавающей точкой.
Группа команд сдвига включает одну команду сдвига влево и две команды сдвига вправо. Каждая из них имеетдва варианта: 32-битный и 64-битный. Команды сдвига в основном используются для манипуляции с битами. Большинство машин CISC имеют довольно много различных команд обычного и циклического сдвига, и практически все они совершенно бесполезны.
Типы команд |
397 |
Предусмотрено два способа вызова процедур. Для команды CALL используется формат 4 (см. рис. 5.10) с 30-битным смещением. Этого значения достаточно для того, чтобы добраться до любой команды в пределах 2 Гбайт от вызывающего оператора в любом направлении. Команда CALL копирует адрес возврата в регистр R15, который после вызова превращается в регистр R31.
Второй способ вызова процедуры — команда JMPL, для которой используется формат 1а или lb, позволяющая помещать адрес возврата в любой регистр. Такая форма может быть полезной в том случае, если целевой адрес вычисляется во времявыполнения.
Команды SAVE и RESTORE манипулируют регистровым окном и указателем стека. Обе команды совершают прерывание, если следующее (предыдущее) окно недоступно.
В последней группе содержатся команды, которые не попали ни в одну из групп. Команда SETHI необходима, поскольку невозможно поместить 32-битный непосредственный операнд в регистр. Для этого команда SETHI устанавливает биты с 10 по 31, а затем следующая команда передает оставшиеся биты, используя непосредственный формат.
Команда РОРС подсчитывает число битов со значением 1 в слове. Последние три команды предназначены для чтения и записи специальных регистров.
Ряд широко распространенных команд CISC, которые отсутствуют в этом списке, можно легко получить, используя либо регистр GO, либо операнд-константу (формат lb). Некоторые из них даны в табл. 5.9. Эти команды узнаются ассемблером UltraSPARC II и часто порождаются компиляторами. Многие из них используют тот факт, что регистр GO связан с 0 и что запись в этот регистр не произведет никакого результата.
Команды компьютера picoJava II
Настало время рассмотреть уровень команд машины picoJava II. Здесь реализован полный набор команд JVM (226 команд), а также 115 дополнительных команд, предназначенных для С, C++ и операционной системы. Мы сосредоточимся главным образом на командах JVM, поскольку компилятор Java производит только эти команды. Архитектура командJVM не содержит регистров, доступных пользователю, а также не имеет некоторых других особенностей, обычных для большинства центральных процессоров. (В процессоре picoJava II есть 64 встроенных регистра для вершины стека, но пользователи их не видят.) Большинство команд JVM помещают слова в стек, оперируют словами, находящимися в стеке, и выталкивают слова из стека. Большинство команд JVM выполняются непосредственно аппаратным обеспечением picoJava II, но некоторые из них микропрограммируются, а некоторые даже передаются программе обработки для выполнения.
Таким образом, для того чтобы заставить машину работать, требуется небольшая система уровня команд, но эта система гораздо меньше по размеру, чем полный интерпретатор JVM, и вызывается она только в редких случаях. Она содержит код для интерпретации нескольких сложных команд, загрузчик класса, верификатор байт-кода, администратор потока и программу чистки памяти («сборщик мусора»).
3 9 8 |
Глава 5. Уровень архитектуры команд |
|
Таблица 5.9. Некоторые моделируемые команды UltraSPARC II |
||
Команда |
|
Как получить команду |
MOV SRC, DST |
Выполнить команду OR над SRC и GO и сохранить результат в DST |
|
CMP SRC1, SRC2 |
Вычесть SRC2 из SRC1 (команда SUBCC) и сохранить результат в GO |
|
TST SRC |
|
Выполнить команду ORCC над SRC и GO и сохранить результат в GO |
NOT DST |
|
Выполнить команду XNOR над DST и GO |
NEG DST |
|
Вычесть SRC2 из SRC1 (команда SUBCC) и сохранить результат в GO |
INC DST |
|
Прибавить 1 к DST (непосредственный операнд) — команда ADD |
DEC DST |
|
Отнять 1 от DST (непосредственный операнд) — команда SUB |
CLR DST |
|
Выполнить команду OR над GO и GO и сохранить результат в DST |
NOP |
|
SETHI GO на О |
RET |
|
JMPL%l7+8, %G0 |
JVM содержит относительно небольшой набор простых команд. Набор всех команд JVM (за исключением некоторых расширенных, коротких и быстрых вариантов команд) приведен на рис. 5.23.
Команды JVM типизированы. Одну и ту же операцию с разными типами данных выполняют разные команды. Например, команда ILOAD помещает в стек целое 32-битное число, а команда ALOAD помещает в стек 32-битный указатель. Такое строгое разделение необязательно для правильного выполнения программы, поскольку в обоих случаях 32 бита, которые находятся в определенной ячейке памяти, передаются в стек независимо от типа этого 32-битного слова. Такое жесткое разграничение типов требуется для того, чтобы можно было проверить во время выполнения программы, не нарушены ли какие-нибудь ограничения (например, не пытается ли программа превратить целое число в указатель, чтобы обратиться к памяти).
Перейдем к командамJVM. Первая команда в списке — typeLOAD IND8.
На самом деле это не одна команда, а шаблон для порождения команд. Команды JVM регулярны, поэтому вместо того чтобы приводить все команды по одной, в некоторых случаях мы будем давать правило для порождения команд. В данном случае слово type заменяет одну из четырех букв: I, L, F и D, которые соответствуют типам integer (целые числа), long, float (32-битные числа с плавающей точкой) и double (64-битные числа с плавающей точкой) соответственно. Следовательно, здесь подразумевается 4 команды (ILOAD, LLOAD, FLOAD и DLOAD), каждая из которых содержит 8-битный индекс IND8 для нахождения локальной переменной и помещает в стек значение соответствующей длины и типа. Мы рассматривали только одну из этих команд — ILOAD, но остальные действуют точно так же и отличаются только по числу слов, помещаемых в стек, и по типу значения.
Кроме этих четырех команд существует еще четыре команды загрузки typeALOAD. Эти команды помещают в стек элементы массива. Во всех четырех случаях сначала в стек должен загружаться указатель на массив и индекс элемента массива. Затем эти команды выталкивают индекс и указатель, производят вычисление, чтобы найти элемент массива, и помещают этот элемент в стек. При вычислении нужно знать размер элементов, который определяется по типу. Эти команды получают индекс массива из стека, поэтому сама команда не содержит операнда.
Типы команд |
401 |
Последние четыре команды этой группы также предназначены для работы с элементами массива, но только других типов. Они поддерживают byte (байт — 8 битов), short (16 битов), char (символ — 16 битов) и pointer (указатель — 32 бита). Таким образом, всего существует 12 команд LOAD.
Команды typeSTORE обратны командам typeLOAD. Каждая команда выталкивает элемент из стека и сохраняет его в локальной переменной. Одну из этих команд (ISTORE) мы уже рассматривали, когда изучали машину IJVM. Здесь также имеются команды для сохранения элементов массива. В вершине стека находится значение нужного типа. Под ним находится индекс, а еще ниже — указатель на массив. Все три элемента удаляются из стека этой командой.
Команды PUSH помещают значение в стек. Команду ВIPUSH мы уже рассматривали. Команда SIPUSH выполняет ту же операцию, но только с 16-битным числом. Команда LDC помещает в стек значение из набора констант. Следующая команда представляет целую группу команд всех четырех основных типов (integer, long, float и double). Каждая команда содержит только код операции, и каждый код операции помещает определенное значение в стек. Например, команда ICONST0 (код операции 0x03) помещает в стек 32-битное слово 0. То же самое действие можно произвести с помощью команды BIPUSH, но это займет два байта. Благодаря оптимизации самых распространенных команд программы JVM получаются небольшими по размеру. Поддерживаются следующие значения: Integers (целые числа) - 1 , 0, 1, 2, 3, 4, 5; longs 0 и 1; floats (числа с плавающей точкой) 0,0, 1,0 и 2,0 и doubles 0,0 и 1,0. Команда CONST_NULL помещает в стек нулевой указатель. Сочетание кодов операций и самых распространенных адресов в одной 1 -байтной команде сильно сокращает размер команды, что экономит память и время на передачу бинарных программ на языке Java по Интернету.
Арифметические операции абсолютно регулярны. Для каждого из основных четырех типов имеется 4 команды. Три логические операции и три операции сдвига применяются только для целых чисел и чисел типа long. Наличие команды AND для чисел с плавающей точкой противоречило бы строгим правилам типизирования JVM. Строка х2у в таблице представляет 4x4 команд преобразования. Значения каждого типа могут переделываться в любой другой тип (но только не в тот же самый), поэтому здесь имеется 12 команд. Самой типичной из них является команда I2F, которая превращает целое число в число с плавающей точкой.
Группа команд управления стеком содержит команды, которые дублируют верхнее значение или два значения стека и помещают их в различные части стека (не обязательно в вершину). Остальные команды выталкивают значения из стека и меняют местами два верхних значения.
Команды группы сравнения выталкивают одно или два значения из стека и проверяют их. Если из стека выталкивается два значения, то одно из них вычитается из другого, а результат проверяется. Если выталкивается одно значение, то оно и проверяется. Суффикс rel замещает реляционные операторы: LT, LE, EQ, NE, GE и GT. Команды со смещением совершают переход, если определенное условие подтверждено. Остальные команды помещают результат обратно в стек.
Следующая группа предназначена для вызова процедур и возвращения значений. При изучении машины IJVM мы рассматривали очень простые версии команд INVOKEVIRTUAL и IRETURN. Полные версии содержат гораздо больше параметров,
4 0 2 Глава 5. Уровень архитектуры команд
исуществует множество команд, которые покрывают самые различные случаи.
Вэтой книге мы не будем описывать эти команды. Подробнее см. [85].
Еще одну группу образуют 4 команды для создания одномерных и многомерных массивов и проверки их длины. В машине JVM массивы хранятся в «куче» и периодически очищаются (процесс «сборки мусора»), когда они уже больше не нужны.
Последняя группа включает в себя оставшиеся команды. Каждая из этих команд имеетспециальное назначение,связанноескакой-нибудьособенностьюязыка Java. Описание этих команд не входит в задачи этой книги.
А теперь нужно сказать пару слов о самом уровне команд picojava II. Это машина с обратным порядком байтов (хотя существует несколько команд, которые можно переделать в формат с прямым порядком байтов). Слова состоят из 32 битов, хотя существуют команды для работы с единицами по 8,16 и 64 бита. Стек в памяти располагается от верхних адресов к нижним в отличие от IJVM (спецификация JVM допускает оба варианта).
Машина picojava II была разработана для программ наJava, С и C++. Но чтобы запустить программы на С и C++, нужен компилятор, который превращает С и C++ в команды picojava И. После того как программа на С или C++ была скомпилирована на JVM, все способы оптимизации аппаратного обеспечения, которые мы описывали в главе 4, становятся применимы для С и C++.
Чтобы программы на С и C++ могли работать на машине picojava II, к уровню архитектуры команд было добавлено 115 дополнительных команд. Большинство из них составляют два или более байтов в длину и начинаются с одного из двух зарезервированных кодов JVM (OxFE и OxFF), которые показывают, что дальше следует расширенная команда. Ниже мы дадим краткий обзор особенностей picojava II, не характерных для JVM.
В машине picojava II содержится 25 32-битных регистров. Четыре из них по функциям эквивалентны регистрам PC, LV, SP и СРР машины IJVM. Регистр OPLIM помещает определенное предельное значение в SP. Ести значение SP выходит за пределы OPLIM, то происходит прерывание. Эта особенность позволяет представлять стек в виде связного списка участков стека, а не как один непрерывный блок памяти. Регистр FRAME отмечает конец фрейма локальных переменных и указывает на слово, которое содержит счетчик команд вызывающей процедуры.
Среди других регистров можно назвать слово состояния программы — это регистр, который следит, насколько заполнен 64-регистровый стековый кэш, и четыре регистра, которые используются для управления потоком. Кроме того, существует 4 регистра для ловушек и прерываний и 4 регистра для вызова процедур и возвращения значений в командах на языках С и C++. Поскольку picojava II не имеет виртуальной памяти,для ограничения определенной части памяти, к которой может иметь доступ текущая программа на С или C++, используются два специальных регистра. Расширенные команды можно разделить на 5 категорий. К первой категории относятся команды для чтения и записи верхних регистров. Ко второй категории относятся команды для работы с указателями. Они позволяют считывать из памяти и записывать в память произвольные слова. Большинство из этих команд выталкивают машинный адрес из стека, а затем помещают в стек содержи-