УМ-М: Регистры и косвенная адресация
Один из способов повысить эффективность работы программы — предусмотреть небольшое количество выделенных быстродействующих ячеек памяти — регистров общего назначения
- Проблема абсолютной адресации стоит не так остро: если операндами являются регистры, то их номера занимают небольшое число битов, и их можно гарантированно разместить в команде — в отличие от полных адресов ячеек памяти. Напомним, что размер адреса обычно сопоставим с размером машинного слова, нередко — равен ему.
- Скорость доступа к содержимому регистра обычно намного (на несколько порядков!) быстрее чтения из памяти.
Регистр может содержать адрес, а процессор — иметь доступ к памяти по адресу, хранящемуся в регистре (это называется косвенной адресацией). Косвенная адресация позволяет работать с массивами денных без модификации кода.
Регистровая учебная машина УМ-Р
- Адресуемая ячейка памяти — 16 разрядов
- Целое число — 32 разряда
- 16 32-разрядных регистров (идентификатор регистра — четыре разряда в коде инструкции)
- Арифметические операции имеют вид (КК — код операции, П — регистр-приёмник, И — регистр-источник, АААА — адрес ячейки-операнда, 0 — поле, не используемое в УМ-Р)
«Регистр-Регистр» (одна ячейка) : КК П И
«Регистр-Память» (две ячейки): КК П 0 АААА
16 регистров позволяют проводить довольно сложные вычисления без дополнительного обмена с ОП. В начале программы данные загружаются из памяти, в конце — результаты выгружаются в память.
Пример: максимум из трёх чисел (воспользуемся совместимой с УМ-Р поддерживаемой эмулятором архитектурой УМ-М)
TODO отладить
.cpu mm-r .input 0x20, 0x22, 0x24 .output 0x26 .code 00 1 0 0020 ; 00 ; загрузка A в R1 00 2 0 0022 ; 02 ; загрузка B в R2 00 3 0 0024 ; 04 ; загрузка C в R3 20 4 1 ; 06 ; R4:=R1 25 4 2 ; 07 ; Сравнить R4 и R2 84 0 0 000B ; 08 ; Если ⩾, обойти следующую инструкцию 20 4 2 ; 0A ; R4:=R2 25 4 3 ; 0B ; Сравнить R4 и R3 84 0 0 000F ; 0C ; Если ⩾, обойти следующую инструкцию 20 4 3 ; 0E ; R4:=R3 10 4 0 0026 ; 0F ; выгрузить R4 в D 99 0 0 ; 11 ; .enter 32 24 22
В регистрах можно эффективно хранить границы и изменения циклов.
Пример: ввести M и N, посчитать сумму квадратов от M2+(M+1)2+…+(N-1)2+N2
.cpu mm-r .input 0x1000, 0x1002 .output 0x1004 .code 00 1 0 1000 ; 00; R1:=M 00 2 0 1002 ; 02; R2:=N 22 3 3 ; 04; R3-=R3 (обнуление) 00 A 0 0013 ; 05; RA:=1 25 1 2 ; 07; сравнить R1 и R2 86 0 0 0010 ; 08; Переход если > 20 4 1 ; 0A; R4:=R1 23 4 1 ; 0B; R4*=R4 21 3 4 ; 0C; R3+=R4 21 1 A ; 0D; R1+=RA (+=1) 80 0 0 0007 ; 0E; продолжение цикла 10 3 0 1004 ; 10; выгрузить R4 в S 99 0 0 ; 12; стоп 00 0 0 0001 ; 13; 1 .enter 5 10
Косвенная адресация. Модификация адреса
Как было ранее показано, работа с массивами данных при помощи самомодификации кода имеет серьёзные недостатки, в частности, трудность отладки неработающей программы. Переменный размер команды эти трудности только усугубляет.
Нужен механизм, позволяющий хранить адреса вне инструкций (в памяти или в регистрах) и использовать эти адреса для доступа к соответствующим ячейкам оперативной памяти. Тогда проход по массиву данных циклом будет состоять в циклическом изменении не ячейки с командой, а ячейки, хранящей адрес.
Косвенная адресация — механизм доступа к оперативной памяти, при котором в инструкции указывается не сам адрес, а место хранения этого адреса (например, регистр).
В некоторых архитектурах разрешена косвенная адресация по памяти (из ячейки памяти считывается адрес другой ячейки памяти, по которому берётся значение) и даже двойная косвенная адресация (адрес адреса!).
Очевидный недостаток косвенной адресации — усложнение такта работы машины: извлечение адреса плюс чтение значения вместо одного чтения. Косвенная адресация по «медленной» памяти вдобавок сильно замедляет такт.
Машина с модификацией адреса УМ-М
Поскольку работа с массивами данных происходит в оперативной памяти, в УМ-М косвенная адресация используется в командах вида «Регистр-Память». Второй операнд — регистр команд вида «регистр-память» — называется смещением.
Исполнительный адрес (актуальный адрес, с которым работает инструкция), — сумма поля адреса и содержимого регистра смещения.
Такт работы операции вида «КК П С АААА», где КК — код операции, П — номер регистра-приёмника, С — номер регистра-смещения, AAAA — базовый адрес в УМ-Р
- Считать ячейку «КК П С»
- Проанализировать размер инструкции
- Считать ещё одну ячейку «АААА»
Вычислить адрес операнда, равный AAAA + содержимое регистра № С
- Загрузить операнд
- Вычислить операцию КК
- Результат поместить в регистр П
- Вычислить адрес следующей команды
Исключение: значение 0 в поле смещения означает прямую адресацию (без смещения). Регистр R0, таким образом, невозможно использовать для смещения.
Пример: Прибавить константу 0x123 к четырём (количество произвольное) ячейкам памяти, начиная с адреса 0x1000.
.cpu mm-m .input 0x1000, 0x1002, 0x1004, 0x1006 .output 0x1000, 0x1002, 0x1004, 0x1006 .code 22 4 4 ; 00 ; обнуление регистра (вычтем R4 из R4) 00 2 4 1000 ; 01 ; загрузим R4-й элемент массива (поначалу нулевой) в R2 01 2 0 000e ; 03 ; добавим 123 к R2 10 2 4 1000 ; 05 ; запишем обратно 01 4 0 0010 ; 07 ; добавим 2 к R4 05 4 0 0012 ; 09 ; сравним с последним индексом массива 95 0 0 0001 ; 0B ; переход обратно, если не больше 99 0 0 ; 0D ; 00000123 ; 0E ; 0x123 00000002 ; 10 ; 2 00000006 ; 12 ; 6 .enter 123 234 345 456
Здесь R4 играет роль индекса массива: инструкции 00 и 10 содержат 0x1000 в качестве базового адреса, к которому при чтении и, соответственно, записи, прибавляется содержимое R4. Размер целого в УМ-М — 4 байта, т. е. две ячейки, поэтому к R4 в цикле прибавляется константа 2.
- При вычислении адреса в УМ-М от суммы базового адреса и смещения берётся остаток по модулю 0x10000 (объём адресного пространства).
Дополнительная инструкция 11 П С АААА вычисляет исполнительный адрес (содержимое регистра С + число АААА, адрес) и записывает его в регистр П.
Замечание: нет никакой принципиальной разницы между выполнением:
команды 00 1 2 1000 при R2==8
команды 00 1 2 0008 при R2==0x1000
Однако команда 00 1 0 1008 выполняется слегка по-другому, т. к. содержимое R0 не используется, и сложения не производится.
Смещение присутствует и в командах перехода УМ-М. Это позволяет вычислять и изменять адрес перехода, не модифицируя код программы.
А зачем это может быть нужно?
В других архитектурах механизмы адресации могут быть и другие:
Во многих случаях удобно использовать непосредственную адресацию. Мы встречались с ней в стековой машине: это ситуация, когда один из операндов команды содержит не номер ячейки, а само число. Непосредственно в поле операнда можно хранить, например:
- приращение счётчика, которое надо прибавить или отнять; оно обычно невелико — это 1, или размер ячейки (в УММ — 2), или размер одного набора данных
относительный адрес перехода, при организации ветвлений и циклов, переход вперёд — положительное число, назад — отрицательное; опять-таки, это редко бывает далеко
в современных архитектурах полный адрес занимает довольно много места, поэтому в операндах типа «регистр+число» поле «число» (относительно небольшое) трактуется не как адрес, а как всё то же короткое целое, то есть как смещение. Собственно адрес лежит в регистре; правда при этом получается неразбериха: модифицируем мы регистр, то есть адрес, а непосредственно заданное смещение остаётся нетронутым
- Регистр, в котором лежит смещение, может быть неявным: при выполнении адресных операций его содержимое прибавляется ко всем адресам. Такие регистры смещения обычно не смешиваются с регистрами общего назначения
Данные и программа могут иметь разные регистры смещений
- …
Пример: «Транспонирование матрицы». 12 чисел в памяти трактуются как элементы двумерного массива 3×4:
a b c a b c d e f g h i j k l → d e f g h i j k l
Ввести это массив построчно, после чего транспонировать его и вывести построчно:
a b c a d g j d e f ⇒ b e h k → a d g j b e h k c f i l g h i c f i l j k l
Для этой цели организуем «честный» вложенный цикл — сначала по X, а внутри — по Y, будем считывать элемент массива A[X][Y] = A[X + Y*3] и записывать элемент массива B[Y][X] = B[Y + X*4]. Не забудем также про адресную арифметику: Размер целого — это две ячейки памяти.
.cpu mm-m .input 0x100, 0x102, 0x104, 0x106, 0x108, 0x10a, 0x10c, 0x10e, 0x110, 0x112, 0x114, 0x116 .output 0x300, 0x302, 0x304, 0x306, 0x308, 0x30a, 0x30c, 0x30e, 0x310, 0x312, 0x314, 0x316 .code 22 2 2 ; 00 ; R2 ← 0 (вычтем R2 из R2), это Y 22 1 1 ; 01 ; R1 ← 0 (вычтем R1 из R1), это X 20 5 2 ; 02 ; R5 ← R2 03 5 0 0206 ; 03 ; R5 *= 3 21 5 1 ; 05 ; R5 += R1 03 5 0 0202 ; 06 ; R5 *= 2 (смещение в первой матрице) 00 6 5 0100 ; 08 ; R6 ← 0x100(R5) 20 5 1 ; 10 ; R5 ← R1 03 5 0 0204 ; 11 ; R5 *= 4 21 5 2 ; 0D ; R5 += R2 03 5 0 0202 ; 0E ; R5 *= 2 (смещение во второй матрице) 10 6 5 0300 ; 10 ; R6 → 0x300(R5) 01 1 0 0200 ; 12 ; R1 += 1 05 1 0 0206 ; 14 ; R1 ? 3 83 0 0 0002 ; 16 ; R1 < 3? ⇒ 0002 01 2 0 0200 ; 18 ; R2 += 1 05 2 0 0204 ; 1A ; R2 ? 4 83 0 0 0001 ; 1C ; R2 < 4? ⇒ 0001 99 0 0 ; 1D ; .code 0x200 00000001 ; 00 ; 1 00000002 ; 02 ; 2 00000004 ; 04 ; 4 00000003 ; 06 ; 3 .enter 1 2 3 4 5 6 7 8 9 10 11 12
Программирование в машинных кодах и язык ассемблера
Цель: небессмысленно выполнить произвольную программу на ЭВМ
Задача: ввести готовую программу и данные в ОЗУ, запустить программу, получить данные
- Ввод программы
- Вручную (пульт)
- Из ПЗУ
- С устройства хранения (требуется операционная система)
- Ввод данных
Вводится вся оперативная память, включая данные и программу (возможно, не до конца, как в УМ)
- Повторно, тем же способом, что и ввод программы (надо отдельно указать адрес начала данных)
- Программа сама вводит данные после запуска (требуется операционная система)
- Вывод результатов
- Вручную (доступ пользователя к ОЗУ, как в УМ)
- Программа сама выводит на внешнее устройство (требуется операционная система)
- Адрес старта
- Зафиксировать единственный для всех программ
- Вводить с помощью специального инструмента (пульта)
Включить адрес старта в формат исполняемого файла (требуется операционная система)
Недостатки программирования в кодах
- Неудобное представление (нужен специальный редактор или транслятор из текстового представления чисел, как в УМ)
- Приходится работать со всей ОП (нет возможности заполнить только нужные ячейки в разных местах, например, код + данные)
- Трудно запомнить систему команд в виде чисел
- Адреса переменных и переходов — числа, трудно соотнести со смыслом алгоритма
Главное: адреса переменных и особенно переходов меняются при изменении кода программы
Ассемблер
Цель: упростить программирование в машинных кодах
Задача: сделать более удобным представление программы/данных и их размещение в памяти, в том числе ввести именованные обозначения вместо чисел
Решение: написать программу в более удобной форме, после чего транслировать в формат, пригодный для непосредственного исполнения
Ассемблер: транслятор, преобразующий исходный текст некоторой программы из представления, удобного для человека, в машинные коды
Название «сборщик», вероятно, происходит от способности сначала собирать метки в программе, а затем преобразовывать их в адреса (см. далее). Кроме того, исходный код большой программы обычно разбит на несколько файлов.
За исключением преобразования меток в адреса, процесс трансляции обратим. Как правило, одной ассемблерной инструкции соответствует одна машинная команда
Язык программирования, с которого происходит трансляция, называется языком ассемблера.
Свойства этого языка:
- Текстовый формат входных данных
- Мнемонические (удобные для запоминания) обозначения для команд
- Мнемонические обозначения для режимов адресации
Использование идентификаторов (т. н. меток) вместо адресов переменных и переходов
- Возможность представления чисел в удобном формате
- Явное или неявное указание адресов для размещения кода и данных
- Например, в программе присутствует секции «код» и «данные», и при трансляции каждая помещается в заранее оговоренное место памяти
- Использование сокращений и умолчаний, упрощающих внешний вид и читаемость программы
Например, регистровые команды и команды вида «регистр-память» могут иметь одинаковое обозначение, но однозначно отличаться количеством и типом операндов (и транслироваться в инструкции с различным КОП)
- Команды с пустым смещением могут быть указаны вовсе без смещения, если не возникает неоднозначности
Инструкция, выполняющая очевидные действия, но реализованная неочевидной командой, обозначаются действием, а не командой. Например, инструкция УМ-Р «zero R5» заносит ноль в регистр 5 при помощи команды вычитания регистра из самого себя — 02 5 5 (т. н. псевдоинструкция).
- Возможность задавать собственные обозначения последовательностям инструкций (т. н. макромоманды)
- …