В языках высокого уровня предусмотрены средства управления последовательностью выполнения программы, которые позволяют программисту задавать выполнение тех или иных операторов в зависимости от определённых условий, а именно от значений, которые принимают соответствующие переменные в ходе выполнения программы. В языках Паскаль и Фортран такое управление обеспечивается управляющими конструкциями типа IF-THEN-ELSE. Операторы, образующие эти конструкции, транслируются в инструкции машинного языка, называемые условными переходами, которые входят в набор инструкций ЭВМ VAX[1].
В языках высокого уровня предусмотрены средства для повторения выполнения отдельных частей программы. Повторяемые части программ называются циклами. В Паскале циклы создаются с помощью операторов FOR или WHILE. В Фортране для образования циклов используется оператор DO. Эти операторы могут транслироваться в те же инструкции условных переходов, которые используются и для реализации конструкций типа IF-THEN-ELSE. Однако в архитектуре ЭВМ VAX предусмотрены дополнительные инструкции, предназначенные специально для выполнения циклов. В этой главе показано, как записываются на языке ассемблера операторы условных переходов и операторы цикла.
Кроме того, в этой главе рассматривается использование шестнадцати регистров общего назначения процессора. Здесь же затрагивается вопрос организации подпрограмм для ЭВМ VAX, который детально рассматривается в гл. 9, а в данной главе будет разобран ряд простых примеров использования подпрограмм, в частности подпрограмм ввода и вывода чисел.
На рис. 5.1 представлены фрагменты программ на Фортране и Паскале, в которых из двух переменных A и B выбирается переменная с меньшим значением, после чего это значение присваивается переменной MINAB. Обратите внимание на то, что в начале обеих программ все указанные переменные объявлены как целые (INTEGER). Встретив в программе описание INTEGER, компилятор с языка Паскаль или Фортран резервирует для переменных MINAB, A, B, которые представляют собой символические адреса, 32-битовые длинные слова. В дальнейшем содержимое длинных слов MINAB, A, B будет рассматриваться как 32-битовые целые со знаком Поскольку компиляторы языков Паскаль и Фортран в операционной системе VAX/VMS по умолчанию используют для хранения целых чисел длинные слова, в наших примерах почти для всех целых чисел также будут применяться длинные слова. Кроме того, все переменные, используемые во фрагментах программ на Паскале и Фортране, будут по умолчанию рассматриваться как 32-битовые целые со знаком.
Вышеуказанные фрагменты программ на Фортране и Паскале могли бы транслироваться во фрагмент программы на языке ассемблера, который приведён на рис. 5.2. Обратите внимание, что это - фрагмент программы, а не программа целиком. Для краткости в ней опущены некоторые директивы и инструкции языка ассемблера, а также макроинструкция $EXIT_S.
Паскаль | Фортран |
---|---|
. . . |
. . . |
Рис. 5.1. Пример условного перехода
. . . MINAB: .BLKL 1 ; 32-БИТОВОЕ ЦЕЛОЕ СО ЗНАКОМ A: .BLKL 1 ; 32-БИТОВОЕ ЦЕЛОЕ СО ЗНАКОМ B: .BLKL 1 ; 32-БИТОВОЕ ЦЕЛОЕ СО ЗНАКОМ . . . MOVL A,MINAB ; MINAB := A ;НАЧАЛО БЛОКА УСЛОВИЯ CMPL A,B ; СРАВНИТЬ A И B BLEQ NEXT ; И ПЕРЕЙТИ, ЕСЛИ B>A MOVL B,MINAB ; MINAB := B ;КОНЕЦ УСЛОВИЯ NEXT: . . .
Рис. 5.2. Пример условного перехода реализованного на ассемблере
VAX-11 MACRO
После комментария "Начало блока условия" следуют инструкции: CMPL A,B и BLEQ NEXT, По инструкции CMPL производится сравнение содержимого длинных слов, а по инструкции BLEQ производится переход по условию "меньше или равно". Эти инструкции после трансляции преобразуются в последовательность инструкций машинного языка, согласно которым процессор должен сравнить содержимое длинных слов A и B, а затем перейти к инструкции, начинающейся с символического адреса NEXT в случае, если целое со знаком, содержащееся в длинном слове с адресом A, меньше или равно целому со знаком, содержащемуся в длинном слове B. Если содержимое длинного слова с адресом A больше содержимого длинного слова с адресом B, процессор выполнит инструкцию, непосредственно следующую за инструкцией BLEQ (в данном случае инструкцию MOVL B,MINAB).
Процесс выполнения условного перехода, организованного так, как показано на рис. 5.2, складывается из двух отдельных действий, для которых требуются две соответствующие инструкции. Сначала должно быть проведено сравнение значений, после чего на основе результата сравнения может произойти условный переход. В данном случае по инструкции CMPL (сравнить длинные слова) сравнивается содержимое длинных слов A и B. Затем по инструкции BLEQ (перейти, если меньше или равно) производится переход к ячейке с адресом NEXT, если условие выполняется (в данном случае содержимое слова A меньше или равно содержимому слова B). Заметьте, что в самой инструкции BLEQ не содержится информация о том, что с чем сравнивается. Предполагается, что этой инструкции всегда предшествует инструкция типа CMPL.
Обратите внимание на то, что проверяемое условие перехода в программе на ассемблере обратно условию перехода на Паскале и Фортране. Во всех трёх программах программист хочет присвоить переменной MINAB значение B тогда, и только тогда, когда A больше B. В программах на Паскале и Фортране программист проверяет то условие ("A больше B"), при выполнении которого должно быть произведено какое-либо действие ("переменной MINAB присвоить значение B"). В программах на языке ассемблера программист должен проверять обратное условие ("A меньше или равно B"), после чего делать условный переход, если это обратное условие выполняется.
В заключение отметим, что последняя строка фрагмента программы на языке ассемблера начинается с символического адреса NEXT. В языках высокого уровня, таких как Паскаль и Фортран, как правило, различают имена переменных, обозначающих ячейки памяти, содержащие данные, и метки операторов, которые указывают на ячейки памяти, содержащие инструкции. В языках ассемблера этого различия не делается. В процессе ассемблирования двоеточие, следующее за меткой NEXT в последней строке фрагмента на рис. 5.2, воспринимается как признак того, что метку NEXT следует поместить в таблицу имён как символический адрес. Таким образом, NEXT является начальным адресом инструкции, которая следует за инструкцией MOVL B,MINAB.
Инструкция BLEQ относится к группе из шести инструкций условных переходов, представленных ниже, для которых операнды интерпретируются как числа со знаком.
Мнемоника | Код операции | Описание |
---|---|---|
BNEQ |
12 |
Перейти, если первый операнд не равен (Not EQual) второму операнду |
BEQL |
13 |
Перейти, если первый операнд равен (EQuaL) второму операнду |
BGTR |
14 |
Перейти, если первый операнд больше (GReaTer) второго операнда |
BLEQ |
15 |
Перейти, если первый операнд меньше (Loss) или равен (EQual) второму операнду |
BGEQ |
18 |
Перейти, если первый операнд больше (Greater) или равен (EQual) второму операнду |
BLSS |
19 |
Перейти, если первый операнд меньше (LeSS) второго операнда |
Приведённые шесть инструкции в паре с предшествующей им инструкцией сравнения реализуют следующие шесть операции отношения языков Паскаль и Фортран:
Операция отношения в Паскале | = |
<> |
< |
>= |
<= |
> |
Операция отношения в Фортране | .EQ. |
.NE. |
.LT. |
.GE. |
.LE. |
.GT. |
Инструкция условного перехода ассемблера VAX | BEQL |
BNEQ |
BLSS |
BGEQ |
BLEQ |
BGTR |
Для каждой операции (и соответствующей ей инструкции перехода) существует обратная операция. Так, BLSS является обратной к BGEQ инструкцией, a BLEQ - обратной к BGTR инструкцией. Обратите внимание на то, что большое значение имеет порядок записи операндов в инструкции сравнения. Так, например, действие инструкций
CMPL A,B BGTR ALPHA
существенно отличается от действия инструкций
CMPL B,A BGTR ALPHA
По первой паре инструкций осуществляется переход, когда содержимое длинного слова, расположенного по адресу A, больше, чем содержимое длинного слова, расположенного по адресу B. А по второй паре инструкций осуществляется переход, когда содержимое длинного слова с адресом B больше, чем содержимое длинного слова с адресом A.
Фрагмент программы на языке ассемблера, показанный на рис. 5.2, может быть заменён следующим фрагментом:
MOVL A,MINAB ; MINAB:=A ;НАЧАЛО БЛОКА УСЛОВИЯ CMPL B,A ; СРАВНИТЬ B И A BGTR NEXT ; И ПЕРЕЙТИ,ЕСЛИ B > A MOVL B,MINAB ; MINAB:=B ;КОНЕЦ УСЛОВИЯ NEXT: . . .
Обратите внимание на то, что в этом примере в инструкции CMPL изменён порядок записи операндов (B, A вместо A, B) и при этом условие перехода также заменено на обратное (BGTR вместо BLEQ). Таким образом, приведённые на рис. 5.1 операторы Фортрана и Паскаля могут быть реализованы любым из двух представленных фрагментов программы на языке ассемблера.
Фрагменты программ на Паскале и Фортране, показанные на рис. 5.1, могут быть переписаны с использованием конструкции типа IF-THEN-ELSE, как показано на рис. 5.3. Программа, имитирующая на языке ассемблера конструкцию IF-THEN-ELSE, показана на рис. 5.4.
В этой программе инструкции CMPL и BGTR осуществляют передачу управления по адресу AGTRB (часть ELSE конструкции управления), если содержимое длинного слова с адресом A больше содержимого длинного слова с адресом B. В противном случае процессор ЭВМ VAX выполнит инструкцию, непосредственно следующую за инструкцией BGTR, в этом примере - инструкцию MOVL A,MINAB. После инструкции MOVL процессор выполнит следующую инструкцию - BRB. Этот оператор осуществляет безусловную передачу управления по символическому адресу NEXT. Если бы инструкции BRB не было, то в программе была бы ошибка: после присвоения переменной MINAB значения A далее процессор выполнил бы оператор MOVL B,MINAB, в результате чего переменной MINAB было бы присвоено значение B. Инструкция безусловного перехода BRB (сокращение от BRanch with Byte displacement - перейти по адресу, заданному смещением в формате байта) аналогична безусловным операторам перехода GO TO в Паскале и Фортране. Объяснение названию этой инструкции будет дано при описании форматов инструкций переходов.
Отметим, что фрагмент программы на языке ассемблера, представленный на рис. 5.2, проще, чем фрагмент на рис. 5.4, поскольку в нем используется только одна инструкция перехода вместо двух. Кроме того, фрагмент на рис. 5.2 занимает меньший объём памяти и требует меньше времени для выполнения. В результате оптимизирующий компилятор выполнит трансляцию конструкции IF-THEN-ELSE (рис. 5.3) в машинный код так, как показано на рис. 5.2.
Паскаль | Фортран |
---|---|
. . . |
. . . |
Рис. 5.3. Изменённый пример условного перехода
;НАЧАЛО БЛОКА УСЛОВИЯ IF A > B CMPL A,B ; СРАВНИТЬ A И B BGTR AGTRB ; ПЕРЕЙТИ, ЕСЛИ A > B ;THEN MOVL A,MINAB ; MINAB:=A BRB NEXT ;ELSE AGTRB: MOVL B,MINAB ; MINAB:=B ;КОНЕЦ УСЛОВИЯ NEXT: . . .
Рис. 5.4. Изменённый вариант программы на ассемблере VAX-11 MACRO
При программировании представляется полезным выбирать для символических адресов имена, несущие смысловую нагрузку. Например, если длинное слово содержит значение общего дохода (GROSS WAGE) некоторого лица, то такие имена, как GWAGE, GROSS_WAGE или GWAGEL[2], более удачны, чем, например, имя K. Аналогично первой инструкции, блока, вычисляющего удержания с зарплаты служащих (DEDUCTIONS), должно быть присвоено имя (метка) DEDUCT.
Однако в очень больших программах программисту трудно придумать имеющие смысл оригинальные символические имена. Большие ассемблерные программы могут содержать сотни символических имён, и программист может случайно использовать одинаковые символические имена для двух различных адресов, что приведёт к сообщению об ошибке ассемблера. Имеется множество решений этой проблемы. Одним из них является разбиение большой программы на меньшие, которые могут ассемблироваться отдельно, а затем компоноваться в единую программу в машинном коде. Этот подход рассматривается в гл. 9. Другая возможность состоит в использовании длинных символических имён, таких как WAGE_LOOP или TAX_LOOP. Третья возможность заключается в использовании специальных символических имён, которые называются локальными метками.
Локальная метка является символическим адресом, состоящим из последовательности цифр, которая заканчивается знаком доллара, например 1$, 57$ или 999$. Ниже показан фрагмент программы, в котором используются локальные метки 10$, 20$ и 30$[3]. В данном фрагменте переменной MINONE присваивается значение, равное содержимому наименьшего из трёх (A, B, C) длинного слова:
MINABC: CMPL A,B BLSS 10$ MOVL B,MINONE BRB 20$ 10$: MOVL A,MINONE 20$: CMPL C,MINONE BGTR 30$ MOVL C,MINONE 30$: MINXYZ: . . .
Символы 10$, 20$ и 30$ называются локальными метками, потому что обращение к ним возможно только в области программы между адресами MINABC и MINXYZ. Другими словами, задание обычных символических имён MINABC и MINXYZ ограничивает область видимости локальных меток 10$, 20$ и 30$. Попытка перехода по адресу 10$ из инструкции, предшествующей метке MINABC или следующей за меткой MINXYZ строки, вызовет ошибку ассемблера, обусловленную появлением неопределённого имени.
Отметим, что метке 30$ соответствует пустая строка, после которой следует строка с меткой MINXYZ. Таким образом, обе метки указывают на одну ячейку памяти, а именно на начальный адрес следующей инструкции программы. Вместо инструкции BGTR 30$ можно было бы написать инструкцию BGTR MINXYZ - после ассемблирования результат был бы одинаковым. Однако использование имени 30$ более предпочтительно, так как MINXYZ - метка, которая не имеет отношения к фрагменту программы MINABC и является началом некоторой самостоятельной части программы.
Так как локальные метки определены только в области программы между двумя метками, задаваемыми обычными именами (такими как MINABC и MINXYZ), они могут использоваться в программе многократно. Например, ниже показан фрагмент программы, в котором переменной MINONE присваивается минимальное из значений A, B и C, после чего переменной MINTWO присваивается минимальное из значений операндов X, Y и Z:
MINABC: CMPL A,B ; MINONE=MIN(A,B,C) BLSS 10$ MOVL B,MINONE BRB 20$ 10$: MOVL A,MINONE 20$: CMPL C,MINONE BGTR 30$ MOVL C,MINONE 30$: MINXYZ: CMPL X,Y ; MINTWO=MIN(X,Y,Z) BLSS 10$ MOVL Y,MINTWO BRB 20$ 10$: MOVL X,MINTWO 20$: CMPL Z,MINTWO BGTR 30$ MOVL Z,MINTWO 30$: NEXTMIN: . . .
Поскольку локальные метки 10$, 20$ и 30$, используемые в первой половине фрагмента программы, становятся неопределёнными, как только встречается метка MINXYZ, их применение во второй половине данного фрагмента не приведёт к ошибкам, вызванным повторным определением.
В дальнейшем локальные метки будут применяться во многих фрагментах программ. Однако, если в программном блоке реализована функция, к которой происходит обращение из других частей программы, блоку должно быть присвоено обычное символическое имя (например, MINAB или MINXYZ в предыдущем примере). Хороший стиль программирования исключает чрезмерное увлечение локальными метками, так как большое количество меток в листинге на несколько страниц делает программу трудно читаемой. В основном они предназначены только для задания тривиальных имён (меток) для перехода вперёд или назад на небольшое количество программных строк. Локальные метки также широко используются в макроинструкциях (см. в гл. 10).
Семейство инструкций CMP можно описать следующим образом:
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
CMPB |
91 |
} |
src1, src2 |
src1 - src2 |
CMPW |
B1 |
|||
CMPL |
D1 |
В него входят инструкции сравнения значений байтов, слов и длинных слов. Например, пара инструкций
CMPB BYTE_1,BYTE_2 BLSS 10$
выполняет переход к метке 10$, если число со знаком, содержащееся в байте BYTE_1, меньше, чем число со знаком в байте BYTE_2.
Формат этих инструкций аналогичен формату инструкций групп MOV и SUB. (Отметим, что коды операций для инструкций MOVB, MOVW и MOVL - это ^X90, ^XB0 и ^XD0 соответственно.) За кодом операции следуют два операнда. Каждому операнду предшествует байт спецификатора операнда, который определяет способ адресации операнда. Инструкции семейства CMP могут использовать любой из режимов адресации, включая литеральную, непосредственную, относительную и абсолютную адресацию.
Литеральные или непосредственные операнды могут применяться в качестве первого, и второго операнда. Например, инструкции
CMPW #21,ALPHA BLSS 20$
осуществят переход к метке 20$, если число 21 меньше содержимого слова с адресом ALPHA. По инструкции
CMPL WAGE,#1000 BLSS NOTAX
выполнится переход к метке NOTAX, если содержимое длинного слова с адресом WAGE меньше 1000.
Как указано в описании семейства инструкций CMP, инструкция сравнения на самом деле осуществляет вычитание. В отличие от семейства инструкций SUB, вычитающих первый операнд из второго, инструкции семейства CMP вычитают второй операнд из первого. (Обратите внимание, что в описании указана операция src1 - src2.) Кроме того, опять в отличие от семейства инструкций SUB, результат вычитания при выполнении инструкции CMP не сохраняется. Однако процессор сохраняет четыре бита информации или четыре признака, характеризующие полученный при вычитании результат:
Вместе эти четыре бита называются кодами условий и записываются в специальный регистр процессора, который называется длинным словом состояния процессора. Вместе биты кодов условий содержат всю информацию, необходимую для того, чтобы определить, должна ли инструкции условного перехода передать управление по требуемому адресу, т.е. осуществить переход.
Ниже показана инструкция CMPL, за которой следует инструкция условного перехода.
CMPL SCORE,#90 BGEQ A_GRADE
По инструкции CMPL процессор должен вычесть число 90 из содержимого длинного слова с адресом SCORE. По результату вычитания процессор заполняет четыре бита кодов условий (уничтожая их предыдущие значения), а затем уничтожает сам результат вычитания. Далее по инструкции BGEQ анализируется значение битов кодов условий для определения того, нужно ли передавать управление по адресу A_GRADE. Значения кодов условий, при которых каждая инструкция условного перехода передаёт управление по требуемому адресу, описаны в гл. 6.
При выполнении условных переходов необходимо, чтобы коды условий были установлены до того, как будет выполнен сам переход. В предыдущих примерах установка кодов условия выполнялась инструкцией из семейства CMP. Но имеются и другие способы установки битов кодов условия.
Одни из этих способов состоит в выполнении арифметической операции. При выполнении каждой арифметической инструкции процессор автоматически устанавливает коды условий в зависимости от результата арифметической операции. Например, по инструкции ADDL2 A,B автоматически производится проверка значения результата по адресу B. Результат почти такой же, как при выполнении пары инструкций
ADDL2 A,B CMPL B,#0
Это справедливо для всех арифметических инструкций, т.е. ещё для остальных пяти инструкций сложения, для шести инструкций вычитания и для четырёх инструкций пересылки (хотя никаких вычислений при этом не производится).
Таким образом, инструкции условного перехода могут использоваться непосредственно за инструкциями сложения, вычитания и пересылки. При выполнении инструкций пересылки значения кодов условий устанавливаются в зависимости от результатов сравнения пересылаемого значении с нулём. Следующие инструкции осуществят переход к оператору с меткой 30$, если значение числа, содержащееся по адресу B, больше или равно 0:
MOVL A,B BGEQ 30$
Приведённая пара операторов пересылает содержимое длинного слова с адресом A в длинное слово с адресом B и осуществляет переход по адресу 30$, если содержимое длинного слова с адресом B больше или равно 0. Если же содержимое длинного слова с адресом B требуется сравнить с числом, отличным от нуля, то необходимо вставить между приведёнными двумя инструкциями инструкцию CMPL.
Инструкции условного перехода анализируют значения битов кодов условий, не изменяя при этом значении этих битов. Эго даёт возможность использовать несколько инструкций условного перехода после инструкции пересылки, сложения, вычитания или сравнения. Рассмотрим, например, инструкцию SUBL3:
SUBL3 A,B,C BGTR 30$ BEQL 20$ 10$: . . . 20$: . . . 30$: . . .
По инструкции SUBL3 произойдёт вычитание содержимого длинного слова с адресом A из содержимого длинного слова с адресом B; результат вычитания при этом будет помещён в длинное слово по адресу C. Кроме того, инструкция SUBL3 установит коды условия в зависимости от содержимого длинного слова с адресом C. В результате инструкция BGTR осуществит передачу управления по адресу 30$, если содержимое длинного слова с адресом C окажется больше 0. Если оно равно 0, то процессор оставит невыполненной инструкцию BGTR и следующая инструкция - BEQL осуществит передачу управления по адресу 20$, если меньше 0, то процессор оставит невыполнимыми обе инструкции перехода и будет выполнять инструкцию, начинающуюся с адреса 10$.
Аналогично в следующем фрагменте программы осуществляется передача управления оператором с метками 40$, 50$ или 60$ в зависимости от того, меньше, равно или больше числа 50 содержимое длинного слова с адресом D:
CMPL D,#50 BGTR 60$ BEQL 50$ 40$: . . . 50$: . . . 60$: . . .
Формат семейства инструкций перехода отличается от формата остальных инструкций ЭВМ VAX в машинном коде. Во-первых, у них отсутствует байт спецификатора операнда. Все инструкции условного перехода, за редким исключением, имеют длину 16 битов или 2 байта. Первый байт содержит 8-битовый код операции, а второй - 8-битовое смещение; указывающее, куда должен осуществляться переход. Так как смещение имеет длину 8 битов, существует только 28, или 256, различных значений смещения. Как отмечалось в гл. 3, с помощью 8-битового относительного смещения можно адресовать только 128 байтов с адресами, предшествующими адресу в программном счётчике, и 127 байтов с адресами, следующими за адресом в программном счётчике. Однако это не является серьёзным ограничением, поскольку существуют инструкции, позволяющие делать переход по более "удалённым" адресам.
Как уже отмечалось, идея относительной адресации довольно проста. Предположим, например, что вы находитесь на скоростной автостраде, которая имеет 100 съездов, пронумерованных от 00 до 99. Вы остановили свою машину у 32-ого съезда, чтобы помочь советом заблудившемуся водителю. Давая совет, вы можете сказать: "Вам нужен 35-й съезд" или "Вам нужен 25-й съезд". При этом вы используете абсолютную адресацию. Однако вы могли бы сказать и так: "Поезжайте вперёд, ваш съезд - 3-й отсюда" (32+3 = 35) или "Поезжайте назад, ваш съезд - 7-й отсюда" (32-7 = 25). При этом вы используете относительную адресацию, т.е. советуете водителю проехать вперёд или назад определённое количество съездов от места, где он находится в данный момент.
Инструкция перехода действует аналогично. Инструкция перехода указывает процессору, что необходимо передать управление по адресу, смещённому на определённое число байтов вперёд или назад относительно адреса, содержащегося в программном счётчике, 8-битовое смещение интерпретируется как 8-битовое число со знаком. Таким образом, с его помощью можно представить десятичные числа от -128 до +127 (см. табл. 2.6). Это означает, что инструкции перехода могут задать переход назад в следующем диапазоне: назад на 128 байтов и вперёд на 127 байтов.
Инструкция перехода выполняется просто. Предположим, что следующая инструкция BRB (безусловный переход с относительной адресацией, заданной смещением в формате байта) записана в памяти, начиная с адреса 00000400.
Инструкция | Адрес |
---|---|
50 11 | 00000400 |
Процессор считывает код операции инструкции BRB - ^X11 из ячейки с адресом ^X0400, после чего считывает смещение ^X50 из ячейки с адресом ^X0401. К этому моменту содержимое программного счётчика достигло ^X0402. Процессор прибавляет ^X50 к ^X0402 и результат суммирования ^X0452 помещает в программный счётчик. Выборку следующей инструкции процессор будет осуществлять по адресу ^X0452.
Поскольку смещение интерпретируется как число со знаком, значения смещения от ^XFF до ^X80 задают переход назад. Например, по инструкции
Инструкция | Адрес |
---|---|
В0 11 | 00000400 |
процессор делает переход назад по адресу ^X000003В2.
Ассемблер автоматически вычисляет значения смещения, поэтому программист должен помнить следующее. Инструкция перехода задаёт переход на определённое число байтов вперёд или назад. Поскольку смещение является 8-битовым числом, значения смещения ограничены диапазоном -128...+ 127 (десятичные числа). Поскольку содержимое программного счётчика увеличивается на два перед тем, как к нему прибавляется значение смещения, переход назад возможен только на 126, а вперёд - на 129 байтов от начального адреса инструкции перехода. В программе на языке ассемблера программисту достаточно написать инструкцию перехода, например BRB, и если адрес перехода находится в пределах 126 байтов до и 129 байтов после инструкции перехода, то ассемблер вычислит смещение правильно. В противном случае ассемблер выдаёт сообщение об ошибке, указывающее на то, что адрес перехода находится дальше, чем это допускает инструкция перехода.
В ЭВМ VAX предусмотрена инструкция BRW (безусловный переход по адресу, заданному смещением в формате слова) специально для случаев, когда адрес перехода расположен слишком далеко от инструкции перехода. Инструкция BRW аналогична инструкции BRB, за исключением того, что за кодом операции следует 16-битовое смещение вместо 8-битового. Инструкция BRW может осуществлять переход на 32765 байтов назад и на 32770 байтов вперёд от адреса инструкции BRW. Например, по инструкции
Инструкция | Адрес |
---|---|
5000 31 | 00000400 |
процессор сделает переход по адресу ^X00005403. Кодом операции инструкции BRW является ^X31. К тому моменту, когда процессор считает значение 16-битового смещения - число со знаком ^X5000, содержимое программного счётчика достигнет ^X00000403. Процессор прибавит положительное значение смещения ^X5000 к адресу ^X00000403 и поместит сумму ^X00005403 в регистр PC. В результате следующий код операции процессор будет считывать из ячейки с адресом ^X00005403.
Чтобы понять, как используется инструкция BRW, рассмотрим следующий пример:
CMPL A,B BLSS ALPHA
Если адрес перехода (ALPHA) находится от инструкции BLSS слишком далеко и его невозможно задать с помощью байтового смещения, ассемблер выдаст сообщение об ошибке. Программист может переписать предыдущие операторы так:
CMPL A,B BGEQ 10$ BRW ALPHA 10$: . . .
Отметим, что в новом варианте необходимо заменить инструкцию BLSS на обратную инструкцию BGEQ.
Инструкция JMP не имеет таких ограничений, как рассмотренные инструкции. В отличие от других инструкций перехода она имеет формат, согласующийся с форматами остальных инструкций ЭВМ VAX. Инструкция JMP является инструкцией с одним операндом, в котором за кодом операции следует байт спецификатора операнда. В качестве примера рассмотрим следующую инструкцию:
Инструкция | Адрес |
---|---|
00010000 EF 17 | 00000400 |
Кодом операции инструкции JMP является ^X17. Спецификатор операнда ^XEF определяет относительную адресацию со смещением, заданным в формате длинного слова. К тому моменту, когда из памяти производится выборка смещения ^X00010000, заданного в формате длинного слова, содержимое программного счётчика увеличивается до ^X00004006. Таким образом, по этой инструкции будет осуществляться переход по адресу ^X00004006 плюс ^X00010000, т.е. по адресу ^X00014006.
При ассемблировании инструкции JMP с относительной адресацией ассемблер по умолчанию будет использовать относительную адресацию со смещением, заданным в формате длинного слова, для переходов вперёд, а для переходов назад - относительную адресацию со смещением, заданным в формате байта, слова или длинного слова (по необходимости)[4]. Использование в инструкции JMP относительной адресации со смещением в формате байта или слова не имеет большого смысла, поскольку с помощью инструкций BRB (перейти - смещение байт) или BRW (перейти - смещение слово) может быть достигнут тот же результат, но сами инструкции занимают на один байт меньше. Инструкцию JMP с относительной адресацией и со смещением, заданным в формате длинного слова, целесообразно использовать для передачи управления по адресу, который не может быть достигнут с помощью инструкции BRW (т.е. по адресу, удалённому более чем на 32000 байтов от инструкции перехода). Например, предыдущий фрагмент программы перехода по адресу ALPHA может быть переписан следующим образом:
CMPL A,B BGEQ 10$ JMP ALPHA 10$: . . .
Однако в случае соответствующего разбиения программы на модули необходимость перехода к такому удалённому адресу возникает крайне редко.
На самом деле преимущество инструкции JMP заключается в том, что она может использоваться с большим числом режимов адресации, что позволяет легко организовать сложные управляющие конструкции. Более детально режимы адресации описаны в гл. 7. Приведём описание инструкций BRB, BRW и JMP:
Мнемоника | Код операции | Описание |
---|---|---|
BRB |
11 |
Безусловная передача управления, используя смещение заданное в формате байта |
BRW |
31 |
Безусловная передача управления, используя смещение заданное в формате слова |
JMP |
17 |
Безусловная передача управления, с использованием спецификатора операнда для задания режима адресации |
При создании программ часто бывает необходимо установить содержимое некоторой ячейки памяти равным 0. Это может быть выполнено с помощью инструкций семейства MOV. Например, инструкция MOVL
MOVL #0,ALPHA
установит содержимое длинного слова, начинающегося с адреса ALPHA, равным 0. Тот же результат может быть получен с помощью инструкции SUBL2:
SUBL2 ALPHA,ALPHA
Для экономии времени, памяти и для того, чтобы программы на языке ассемблера были более читаемыми, в набор инструкций ЭВМ VAX включена группа инструкций CLR (от CLeaR - очистить). Например, инструкция
CLRL ALPHA
установит содержимое длинного слова, начинающегося с адреса ALPHA, равным 0. Эта инструкция не только проще в написании, чем инструкция MOVL #0,ALPHA, но и более эффективна, поскольку требует описания только одного операнда. Семейство инструкций CLR состоит из следующих инструкций:
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
CLRB |
94 |
} |
dst |
dst ← 0 |
CLRW |
В4 |
|||
CLRL |
D4 |
|||
CLRQ |
7С |
Эти инструкции могут использоваться для очистки байтов, слов, длинных слов и 64-битовых квадраслов.
Семейство инструкций CLR представляет пример инструкции с одним операндом, так же как MOVL - пример инструкций с двумя, а ADDL3 - с тремя операндами. Трансляция этих инструкций в машинный код имеет фиксированный формат. За кодом операции следует спецификатор единственного операнда, который начинается с байта спецификатора операнда, указывающего, как найти этот операнд. Остальные инструкции, приведённые в этом разделе, также являются инструкциями с одним операндом и имеют тот же формат.
Ещё одной распространённой операцией является прибавление 1 к содержимому ячейки памяти. Инструкция
ADDL2 #1,ALPHA
может быть заменена инструкцией INCL (от INCrement Longword - увеличение содержимого длинного слова - инкремент)
INCL ALPHA
Аналогично инструкция DECL (от DECrement Longword - уменьшение содержимого длинного слова — декремент) используется для вычитания единицы из содержимого длинного слова. Например, инструкция
DECL ALPHA
эквивалентна инструкции SUBL2 #1, ALPHA. Однако инструкция DECL короче, быстрее выполняется и делает программы на языке ассемблера более наглядными.
Приведём описание инструкций групп INC и DEC:
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
INCB |
96 |
} |
sum |
sum ← sum + 1 |
INCW |
B6 |
|||
INCL |
D6 |
|||
DECB |
97 |
} |
dif |
dif ← dif - 1 |
DECW |
B7 |
|||
DECL |
D7 |
Как следует из описания, эти инструкции применяются для увеличения или уменьшения на 1 содержимого байтов, слов и длинных слов.
Предположим, что программист хочет установить содержимое длинного слова с адресом B равным содержимому длинного слова с адресом A плюс единица, а затем сделать переход к оператору с адресом 10$ в случае, если содержимое длинного слова с адресом A равно 0.
Операторы
ADDL3 A,#1,B BEQL 10$
не дадут желаемого результата, поскольку биты кодов условия отражают состояние памяти по адресу B, а не A, и, кроме того, по инструкции BEQL произойдёт переход, если содержимое длинного слова с адресом B будет равно 0.
Требуемый результат можно получить с помощью следующих операторов:
ADDL3 A,#1,B CMPL A,#0 BEQL 10$
Отметим, что единственным назначением инструкции CMPL является установка битов кодов условий в соответствии с содержимым длинного слова с адресом A. В архитектуру ЭВМ VAX входит семейство инструкций TST специально для этой цели. С использованием инструкции этого семейства предыдущий фрагмент программы может быть переписан следующим образом:
ADDL3 A,#l,B TSTL A BEQL 10$
Инструкция TSTL (от TeST Longword - проверить содержимое длинного слова) просто выполняет выборку содержимого длинного слова с адресом A и в соответствии с его значением устанавливает биты кодов условий, после чего уничтожает полученное значение. Содержимое длинного слова с адресом A при этом не изменяется.
Ниже приведено описание семейства инструкций TST.
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
TSTB |
95 |
} |
src |
src - 0 |
TSTW |
В5 |
|||
TSTL |
D5 |
Графа "Операнды" указывает на то, что считывается исходный операнд (src), в качестве которого может быть содержимое байта, слова или длинного слова. Запись src - 0 в графе "Операция" означает, что процессор вычитает 0 из исходного операнда и в зависимости от полученного результата устанавливает коды условий. Это вычитание не изменяет его значения, оно позволяет установить коды условий. Кроме того, приведённое описание показывает, как могут быть аппаратно реализованы в процессоре инструкции TST.
Структуры циклов можно легко организовать с помощью инструкций условного перехода. Например, в следующем фрагменте программы вычисляется сумма целых чисел от 1 до 10:
SUM_TEN_TO_ONE: CLRL SUM MOVL #10,J 10$: ADDL2 J,SUM DECL J BNEQ 10$ 20$: ...
Здесь в длинном слове SUM содержится сумма целых чисел, а длинное слово с адресом J используется как счётчик цикла. Показанный цикл организован с уменьшением значения в счётчике цикла. В качестве начального значения в длинное слово J записывается число 10 с помощью инструкции MOVL #10,J, расположенной вне цикла. Внутри цикла по инструкции DEC J вычитается единица из содержимого длинного слова J на каждом шаге цикла. На первых десяти шагах цикла содержимое длинного слова J больше 0 и инструкция BNEQ 10$ передаёт управление на метку 10$. На десятом шаге цикла содержимое длинного слова становится равным 0, процессор пропускает выполнение инструкции BNEQ и переходит к инструкции с меткой 20$. В момент перехода к этому оператору в длинном слове SUM будет содержаться 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1, или 55.
Этот фрагмент программы можно упростить, применив инструкцию SOBGTR (вычесть 1 и перейти, если результат больше 0). Рассмотрим действие этой инструкции. Для нее требуется два операнда. Первый задаёт длинное слово, а второй является адресом перехода. При выполнении инструкции содержимое длинного слова автоматически уменьшается на 1. Если содержимое длинного слова все ещё больше 0, происходит переход по адресу, заданному во втором операнде. Как и в других инструкциях перехода, адрес перехода задаётся как 8-битовое смещение.
SUM_TEN_TO_ONE: CLRL SUM MOVL #10,J 10$: ADDL2 J,SUM SOBGTR J,10$
Инструкция SOBGEQ (вычесть 1 и перейти, если результат больше или равен 0) во всем идентична инструкции SOBGTR, за исключением того, что по ней делается переход и тогда, когда первый операнд равен 0.
При программировании на языке ассемблера часто используются циклы с уменьшением значений счётчиков цикла. Выход из таких циклов происходит, когда содержимое счётчика цикла становится равным 0. В языках высокого уровня принято в основном использовать циклы с увеличением значений счётчиков цикла. Например, следующие структуры циклов в программах на Фортране и Паскале задают изменение значения счётчика цикла от 5 до 25; в обеих программах предполагается, что J объявлено как целое:
Паскаль | Фортран |
---|---|
DO 100 J=5,25 100 CONTINUE |
FOR J := 5 TO 25 DO |
Аналогичные циклы могут быть организованы на языке ассемблера с помощью инструкции AOBLEQ (прибавить 1 и перейти, если результат меньше или равен чему-либо). Для этой инструкции требуются три операнда. Первым операндом является длинное слово, в котором задаётся предельное значение параметра цикла (т.е. 25). Второй операнд - это длинное слово, содержащее текущее значение индекса цикла (т.е. J). И наконец, третий операнд является смещением, заданным в формате байта. Ниже показано, как приведённые программы на Фортране и Паскале могут быть реализованы на языке ассемблера.
MOVL #5,J 10$: ТЕЛО ЦИКЛА AOBLEQ #25,J,10$
Инструкция AOBLEQ увеличивает содержимое длинного слова J на 1, после чего сравнивает его с содержимым первого длинного слова - непосредственным операндом #25. Если J меньше или равно 25, то происходит переход к метке 10$ Инструкция AOBLSS во всем идентична инструкции AOBLEQ, за исключением того, что переход осуществляется только тогда, когда параметр цикла меньше заданного предельного значения.
Как отмечалось в гл. 3, в процессоре ЭВМ VAX имеется 16 регистров общего назначения; каждый имеет 32 разряда. Эти регистры обозначаются цифрами от 0 до 15 в десятичной или от ^X0 до ^XF в шестнадцатеричной системах. Несмотря на своё название, регистры 12 - 15 (^XC - ^XF) имеют специальное назначение. Например, регистр 15 является программным счётчиком, с помощью которого процессор отслеживает текущий адрес при выполнении каждой инструкции. Однако регистры 0—11 являются регистрами общего назначения и могут использоваться для работы с данными различного формата.
В гл. 7 будет показано, как регистры процессора используются в различных режимах адресации. В этом разделе будет представлен режим адресации, называемый регистровым режимом адресации. При регистровом режиме адресации операнд находится в регистре, а не в памяти. Такое использование регистров делает программы и короче, и эффективнее.
Ниже для удобства использования регистров процессора представлена таблица постоянных имён ассемблера:
Имя | Значение | |
---|---|---|
R0 |
Регистровая адресация с использованием регистра |
0 |
R1 |
То же |
1 |
... |
— |
|
R9 |
— |
9 |
R10 |
— |
10(^XA) |
R11 |
— |
11(^XB) |
AP или R12 |
— |
12(^XC) |
FP |
— |
13(^XD) |
SP |
— |
14(^XE) |
PC |
- |
15(^XF) |
Чтобы легче было запомнить то, что регистры 12-15 используются для специальных целей, им присвоены следующие имена: AP (указатель аргументов), FP (указатель блока вызова), SP (указатель стека), PC (программный счётчик). В языке ассемблера разрешается обращаться к регистру 12 как по имени (AP), так и по номеру (R12).
После того как указанные символы определены, использование регистров процессора не представляет трудностей. Например, следующая инструкция скопирует длинное слово из регистра 3 в регистр 8.
MOVL R3,R8
Обозначение R3 указывает на то, что исходный операнд находится в регистре общего назначения 3, а обозначение R8 - что длинное слово должно быть переслано в регистр 8. Аналогично следующая инструкция прибавит содержимое длинного слова, начинающегося по адресу A, к длинному слову из регистра 2, а полученную сумму поместит в регистр 0.
ADDL3 A,R2,R0
Хотя каждый из регистров общего назначения содержит 32-битовое длинное слово, можно использовать эти регистры также и для работы с операндами в формате байта или слова.
Для иллюстрации этого предположим, что регистры 3 и 8 содержат длинные слова 11223344 и 55667788 соответственно. Инструкция MOVL R3,R8 перешлёт длинное слово из регистра 3 в регистр 8, в результате чего содержимое регистра 8 станет равным ^X11223344. Однако, если вместо инструкции MOVL использовать инструкцию MOVB, процессор перешлёт только содержимое младшего байта из регистра 3 в регистр 8. При этом 24 старших разряда регистра 8 своего значения не изменят. Таким образом инструкция MOVB R3,R8 сделает содержимое регистра 8 равным ^X55667744. Аналогично инструкции, работающие со словами, имеют дело только с двумя младшими байтами (16 разрядов) в регистре. В этом случае два старших байта вообще не участвуют в выполнении инструкции. Так, инструкция MOVW R3,R8 поместит в регистр 8 значение ^X55663344.
Допускается использовать в одной инструкции различные спецификаторы операндов. Например, ниже приведены инструкции, каждая из которых является допустимой.
MOVB A,R1 ADDW3 R1,#50,R10 SUBL2 R7,C CMPL R4,#20 SUBB3 #50,A,R11 INCW R4
Однако, например, инструкция BLEQ R5 является запрещённой. Поскольку в инструкциях перехода отсутствует спецификатор операнда, в них не может использоваться регистровая адресация. Инструкция JMP R5 также является запрещённой, хотя и имеет байт спецификатора операнда. Дело в том, что вне памяти возможно только выполнение инструкций и, следовательно, переходы к регистру запрещены.
Для демонстрации того, как использование регистров процессора может ускорить выполнение программы, рассмотрим следующий фрагмент программы, который устанавливает содержимое длинного слова A равным B + C - D - 5:
ADDL3 B,C,A SUBL2 D,A SUBL2 #5,A
Подсчитаем, сколько раз происходит выборка или занесение длинного слова в ячейку памяти с адресом A. Инструкция ADDL3 помещает результат в ячейку с адресом A. Обе инструкции SUBL2 осуществляют выборку и запись в ячейку с адресом A. Далее в последовательности инструкций используется регистр R0, что позволяет исключить все три рассмотренных обращения к ячейке с адресом A, кроме одной записи в эту ячейку:
ADDL3 B,C,R0 SUBL2 D,R0 SUBL3 #5,R0,A
Новый фрагмент программы на языке ассемблера кажется более длинным, поскольку в третьей инструкции добавился третий операнд. Однако, как будет показано далее, для регистровой адресации требуется меньше памяти, чем для относительной адресации со смещением, заданным в формате байта. Поэтому новый фрагмент программы на самом деле занимает меньше памяти, чем исходный.
Использование регистров существенно при организации циклов. Значения, к которым происходит частое обращение, должны храниться в регистрах. Ниже воспроизведён один из фрагментов программы на языке ассемблера, осуществляющий суммирование целых чисел от одного до десяти.
SUM_TEN_TO_ONE: CLRL SUM MOVL #10,J 10$: ADDL2 J,SUM SOBGTR J,10$
При использовании R0 вместо J и R1 вместо SUM этот фрагмент принимает следующий вид:
SUM_TEN_TO_ONE: CLRL R1 MOVL #10,R0 10$: ADDL2 R0,R1 SOBGTR R0,10$ MOVL R1,SUM
Несмотря на то, что пришлось добавить инструкцию MOVL R1,SUM в конце цикла,оказалось, что новый вариант программы короче и использует меньший объём памяти, чем исходный.
В предыдущих примерах показано, что регистровая адресация может использоваться в языке ассемблера. Когда регистровые операнды транслируются в машинный код, номер регистра (от ^X0 до ^XF) становится частью байта спецификатора операнда. Например, ассемблер для инструкции MOVL R8,R3 сгенерирует следующий машинный код (предполагается, что инструкция размещается в памяти по адресу ^X0400):
Операнд | Операнд | Код операции | Адрес |
---|---|---|---|
58 |
53 |
D0 |
04000 |
Отметим, что код этой инструкции на машинном языке занимает только 3 байта: ^XD0 - код операции инструкции MOVL; спецификатор первого операнда ^X53 указывает на то, что исходным операндом является 32-битовое длинное слово, находящееся в регистре 3; спецификатор второго операнда ^X58 указывает на то, что содержимое длинного слова будет помещено в регистр 8, при этом предыдущее содержимое регистра 8 уничтожается.
На примере регистровой адресации можно показать, каким образом формируются коды спецификаторов операндов для других режимов адресации. Спецификатор операнда описывает способ нахождения операнда. Содержимое спецификатора операнда обозначается двумя шестнадцатеричными цифрами. Правая шестнадцатеричная цифра (от 0 до F) определяет один из шестнадцати регистров общего назначения процессора; левая шестнадцатеричная цифра обозначает режим адресации и указывает, каким образом используется регистр общего назначения при нахождении операнда:
Режим | Регистр | |
---|---|---|
mmmm |
rrrr |
8 битов байта спецификатора операнда |
Единственным исключением из этого правила является режим литеральной адресации, при котором в байте спецификатора операнда хранится 6-битовая константа.
Регистровая адресация задаётся указанием кода ^B0101 или ^X5 в битах режима. Любой спецификатор операнда от ^X50 до ^X5E задаёт регистровую адресацию. В битах регистра указывается номер регистра, в котором хранится операнд. Например, операнды языка ассемблера R0 и R11 в машинном коде будут соответствовать спецификаторам операндов ^X50 и ^X5B.
Поскольку код режима состоит из четырёх битов, в архитектуре ЭВМ VAX всего могло быть 16 режимов адресации (от ^X0 до ^XF). Однако, как описано в гл. 7, ЭВМ VAX режимы ^X0-^X3 используют для литеральной адресации. Остальные 12 режимов (^X4-^XF) наряду с режимами литеральной адресации называются режимами адресации с использованием регистров общего назначения. Ниже описаны два таких режима адресации.
Спецификатор операнда | Название режима | Значение |
---|---|---|
00 - 3F |
Литеральный |
Байт спецификатора операнда содержит 6-битовую константу. Ассемблер выбирает этот режим адресации, если обозначение операнда начинается со знака номера (#), за которым следует небольшая по значению константа или имя, её обозначающее (#1, #20 и др.) |
50 - 5Е |
Регистровый |
Операнд находится в регистре общего назначения процессора, номеру которого соответствует вторая шестнадцатеричная цифра в значении спецификатора операнда (от ^X0 до ^XE). Ассемблер выбирает этот режим адресации всегда, когда в поле операнда указано имя регистра (R3, R10, SP и т. д.) |
Отметим, что для регистровой адресации допустимы значения спецификаторов операндов в диапазоне ^X50 - ^X5E, а не ^X50 - ^X5F. Дело в том, что при регистровой адресации никогда не должен использоваться программный счётчик (^X5F) в качестве регистра общего назначения, так как его содержимое увеличивается в процессе выполнения инструкции. Программный счётчик может применяться только в режимах ^X8 - ^XF. Поскольку программный счётчик PC является специальным регистром, режимы ^X8 - ^XF, в которых применяется регистр PC, получили название режимов адресации с использованием программного счётчика. Пять режимов адресации с использованием программного счётчика описаны ниже.
Спецификатор операнда | Название режима | Значение |
---|---|---|
8F |
Непосредственный |
Операнд содержится в байтах, следующих за байтом спецификатора операнда. Ассемблер выбирает этот режим, когда обозначение операнда начинается со знака номера (#), за которым следует число (или имя, обозначающее число), слишком большое, чтобы его можно было задать как литерал (#100, #123456 и т.д.). |
9F |
Абсолютный |
Адрес операнда содержится в четырёх байтах, следующих за байтом спецификатора операнда. (Ассемблер выбирает абсолютную адресацию, когда обозначение операнда начинается символами @#, например @#EXTNAME.) |
AF |
Относительный со смещением, заданным в формате байта |
За байтом спецификатора операнда следует смещение, заданное в формате байта |
CF |
Относительный со смещением, заданным в формате слова |
За байтом спецификатора операнда следует смещение, заданное в формате слова |
EF |
Относительный со смещением, заданным в формате длинного слова |
За байтом спецификатора операнда следует смещение, заданное в формате длинного слова |
Ассемблер выбирает один из режимов относительной адресации тогда, когда для обозначения операнда используется символическое имя или число (WAGE, 20$ и т.д.). В каждом режиме адресации с использованием программного счётчика, информация, необходимая для нахождения операнда, следует непосредственно за байтом спецификатора операнда. Программный счётчик используется в этих режимах потому, что после того, как произведена выборка байта спецификатора операнда, программный счётчик указывает на байт, который следует непосредственно за байтом спецификатора операнда. Эти режимы адресации описаны в гл. 7.
Вопрос использования подпрограмм подробно рассмотрен в гл. 9. Но есть две причины, по которым начать знакомство с подпрограммами следует именно теперь. Первая: подпрограммы играют большую роль в организации правильной структуры программ, и материал данного раздела позволит читателю написать некоторые простые подпрограммы. Вторая: в приложении Б представлены некоторые подпрограммы ввода-вывода, которые могут быть включены в программы. Использование этих подпрограмм позволит читателю приступить к созданию более сложных программ.
Существует три проблемы, связанные с подпрограммами. Во-первых, необходимо иметь средства передачи управления подпрограмме. Во-вторых, должны быть средства обратной передачи управления вызывающей программе. Для вызова подпрограммы с именем SUB в ЭВМ VAX используется инструкция JSB SUB (Jump to SuBroutine - перейти к подпрограмме). Для возврата может быть использована инструкция RSB (Return from SuBroutine - возврат из подпрограммы). И третья проблема - это передача информации между основной программой и подпрограммой; подробно она рассматривается в гл. 9. Сейчас мы ограничимся рассмотрением простого способа передачи информации подпрограммами обратно с помощью регистров общего назначения процессора. Например, основная программа может переслать данные в регистры R0, R1, ... перед тем, как вызвать подпрограмму, а подпрограмма может также поместить результаты своей работы в регистры общего назначения, после чего вернуть управление вызывающей программе. Этот способ подходит для простых задач, но, как будет показано в гл. 9, является слишком примитивным для более сложных задач.
Приложение Б содержит некоторые подпрограммы ввода-вывода, которые называются IOINIT, RNUM и PNUM. Подпрограмма IOINIT выполняет инициализацию, которая необходима для осуществления ввода или вывода. Действие подпрограммы IOINIT аналогично действию оператора OPEN в некоторых языках высокого уровня.
Для того чтобы осуществить инициализацию, требуется инструкция:
JSB IOINIT
Отметим, что эта инструкция должна выполняться только один раз в процессе работы программы.
Подпрограмма RNUM осуществляет ввод шестнадцатеричного числа с терминала пользователя и помещает его 32-битовое значение в регистр R0. Подпрограмма PNUM выводит на терминал (на печать) содержимое регистра R0 в шестнадцатеричном и десятичном виде. (При использовании режима пакетной обработки подпрограмма RNUM считывает длинное слово из входного потока данных, а подпрограмма PNUM помещает содержимое регистра R0 в файл регистрации пакетного задания.) Таким образом, чтобы ввести число и записать его значение в ячейку с адресом X, необходимо выполнить следующие инструкции:
JSB RNUM MOVL R0,X
Для того чтобы распечатать содержимое ячейки с адресом X, необходимо выполнить инструкции
MOVL X,R0 JSB PNUM
Используйте подпрограммы IOINIT, RNUM и PNUM из приложения Б как составную часть ваших программ.
Вызов подпрограммы можно сравнить с временным дорожным объездом. Например, основная программа на рис. 5.5 должна ввести два числа и поместить их сумму в память, в длинное слово с адресом SUM. (Не обращайте внимание на инструкции типа PUSHR #^M<R0,R1> в подпрограммах. Многие из них будут описаны в последующих главах.) При выполнении первой инструкции JSB RNUM процессор временно останавливает выполнение основной программы и передаёт управление подпрограмме RNUM (см. стрелку, помеченную буквой A). Когда процессор встречает инструкцию RSB в конце подпрограммы RNUM, управление автоматически передаётся инструкции, следующей за инструкцией JSB, в данном случае - MOVL R0,A (см. стрелку B). При выполнении процессором второй инструкции, JSB RNUM, управление снова передаётся подпрограмме RNUM (см. стрелку C). В конце концов инструкция RSB в конце подпрограммы RNUM передаёт управление назад - инструкции MOVL R0,B, т.е. основной программе (см. стрелку D).
Инструкция JSB, так же как инструкция JMP, осуществляет передачу управления. (Формат инструкции JSB такой же, как у инструкции JMP, за исключением кода операции - ^X16 вместо ^X17). Ещё одно отличие состоит в том, что инструкция JSB также обеспечивает механизм возврата через какое-то время к оператору, непосредственно следующему за инструкцией JSB. Для возврата управления инструкция RSB использует информацию, которая была сохранена при выполнении инструкции JSB (RSB - однобайтовая инструкция с кодом операции ^X05).
Использование подпрограмм является мощным средством для разбиения больших программ на более удобные для работы части. Однако их неправильное применение может привести к труднообнаруживаемым ошибкам. Предположим, например, что для передачи управления подпрограмме RNUM программист использовал оператор JMP RNUM вместо JSB RNUM. Подпрограмма будет выполняться правильно до тех пор, пока не дойдёт очередь до инструкции RSB в конце подпрограммы. Использование инструкции RSB предполагает, что предшествующая инструкция JSB сохранила адрес возврата. А поскольку адрес возврата не был сохранен, инструкция RSB осуществит возврат управления неизвестно куда, что приведёт к непредсказуемым результатам. Такой же результат может возникнуть, если программист забудет написать инструкцию $EXIT_S в конце основной программы (тогда управление "провалится" из основной программы в подпрограмму).
.TITLE SHOW_SUBS ;B ПРОГРАММЕ ВЫПОЛНЯЕТСЯ ВВОД ЧИСЕЛ A И B ;И ПЕЧАТАЕТСЯ ИХ СУММА SUM := A + B ;БЛОК ДАННЫХ A: .BLKL 1 B: .BLKL 1 C: .BLKL 1 ;ОСНОВНАЯ ПРОГРАММА .ENTRY START,0 ; START ЯВЛЯЕТСЯ ТОЧКОЙ ВХОДА JSB IOINIT ; ВЫЗОВ IOINIT ДЛЯ ПОДГОТОВКИ ВВОДА-ВЫВОДА JSB RNUM ; СЧИТАТЬ ЧИСЛО B R0 MOVL R0,A ; И ЗАПОМНИТЬ B A JSB RNUM ; СЧИТАТЬ ВТОРОЕ ЧИСЛО B R0 MOVL R0,B ; И ЗАПОМНИТЬ B B ADDL3 A,B,SUM ; SUM := A + B MOVL SUM,R0 ; ПЕРЕСЛАТЬ SUM В R0 JSB PNUM ; И НАПЕЧАТАТЬ $EXIT_S ; ЗАВЕРШИТЬ ПРОГРАММУ ; ; КОНСТАНТЫ ДЛЯ ПРОГРАММ RNUM,PNUM И IOINIT . . . ; ; ОБЛАСТЬ ПЕРЕМЕННЫХ ДЛЯ ПРОГРАММ RNUM,PNUM И IOINIT ; ПРОГРАММА ИНИЦИАЛИЗАЦИИ IOINIT: PUSHR #^M<R0,R1> . . . RSB ; ; ПРОГРАММА ПЕЧАТИ ЧИСЛА PNUM: PUSHR #^M<R0,R1> . . . RSB ; ; ПРОГРАММА ВВОДА ЧИСЛА RNUM: PUSHR #^M<R1> . . . RSB ;КОНЕЦ ПРОГРАММЫ,START - АДРЕС ТОЧКИ ВХОДА .END START
Рис. 5.5. Основная программа с подпрограммами
Использование подпрограмм - очень важный вопрос. Набор инструкций ЭВМ VAX содержит пять инструкций машинного языка для передачи управления подпрограмме: JSB, BSBB, BSBW, CALLG и CALLS. Инструкции BSBB (перейти к подпрограмме по адресу, заданному смещением в формате байта) и BSBW (перейти к подпрограмме по адресу, заданному смещением в формате слова) подобны инструкции JSB, за исключением того, что для них используется формат семейства инструкций переходов. Инструкция BSBB состоит из однобайтового кода операции, за которым следует байт, содержащий 8-битовый относительный адрес. Инструкция BSBW состоит из однобайтового кода операции, за которым следуют два байта, содержащие 16-битовый относительный адрес. Если управление подпрограмме передаётся с помощью инструкций JSB, BSBB или BSBW, подпрограмма должна заканчиваться инструкцией RSB. Когда подпрограмма вызывается инструкцией CALLG или CALLS, она должна заканчиваться инструкцией RET.
Подпрограммы могут быть вложенными. Например, основная программа может вызывать подпрограмму с именем SUBA, которая, в свою очередь, может вызывать подпрограмму с именем SUBB. Подпрограмма SUBB осуществит возврат управления в подпрограмму SUBA, которая, в свою очередь, передаст его основной программе.
Для упрощения вызова вложенных подпрограмм пять инструкций, осуществляющих передачу управления подпрограммам, используют регистр общего назначения с номером 14. Поскольку регистр 14 служит для указания области памяти, называемой стеком, он называется указателем стека (SP - Stack Pointer). Название "стек" объясняется тем, что области памяти подобна стопке (stack) тарелок или книг. Ещё требуется добавить одну тарелку в стопку, её обычно кладут сверху стопки. При необходимости взять одну тарелку из стопки обычно берётся верхняя. В результате, как правило, из стопки берётся тарелка, положенная последней. Инструкции вызова подпрограмм используют указатель стека (SP) для помещения адреса возврата на вершину стека. Инструкции возврата извлекают адрес возврата с вершины стека. Таким образом, стек является способом организации памяти, обеспечивающим вызов вложенных подпрограмм. Если программа MAIN вызывает подпрограмму SUBA, a SUBA вызывает подпрограмму SUBB, то на вершине стека будет находиться адрес возврата к SUBA. В результате инструкция возврата в конце подпрограммы SUBB правильно осуществит передачу управления в подпрограмму SUBA, а инструкция возврата в конце SUBA осуществит возврат управления в MAIN. В гл. 9 будут показаны другие важные области использования стека.
Инструкции CALLS, CALLG и RET обеспечивают стандартную процедуру вызова подпрограмм, написанных на различных языках. Помимо сохранения адресов возврата они поддерживают работу более сложной системы сохранения информации. Кроме указателя стека SP эти инструкции используют регистры 12 и 13. Общее описание работы этих инструкций приведено в гл. 9. Ниже даны имена регистров специального назначения.
Регистр | Имя | Название | Назначение |
---|---|---|---|
12 |
AP |
Указатель аргументов |
Передача аргументов |
13 |
FP |
Указатель блока вызова |
Сохранение содержимого указателя стека |
14 |
SP |
Указатель стека |
Сохранение адреса возврата и других данных |
15 |
PC |
Программный счётчик |
Указывает следующую выполняемую инструкцию |
Чтобы понять на примере, как использовать подпрограммы IOINIT, RNUM и PNUM и как самому писать подпрограмму, рассмотрим следующую задачу. Она состоит в том, чтобы ввести три числа и распечатать наибольшее из них. Хотя наш способ решения этой задачи может показаться надуманным и слишком сложным, он реализует общий подход, который существенно упрощает решение более сложных задач.
Как известно, у нас уже есть подпрограммы для инициализации процесса ввода-вывода, чтения и печати числа. Кроме того, мы должны будем написать подпрограмму нахождения максимума из двух чисел, которая будет называться MAX. При вызове подпрограмма MAX должна будет сравнить содержимое регистров R0 и R1 и поместить наибольшее из них в регистр R0 (рис. 5.6).
Теперь основная программа может вызвать подпрограммы IOINIT RNUM, MAX и PNUM для ввода трёх чисел и для печати наибольшего из них. На рис. 5.7 показано, как может выглядеть основная программа.
; ;ПОДПРОГРАММА МАХ ПОМЕЩАЕТ B РЕГИСТР R0 НАИБОЛЬШЕЕ ИЗ ЧИСЕЛ, ;СОДЕРЖАЩИХСЯ B РЕГИСТРАХ R0 И R1 ; MAX: CMPL R0,R1 ; ЕСЛИ ЧИСЛО B R0 БОЛЬШЕ ЧИСЛА B R1 BGEQ 10$ ; ДА,ЧИСЛО B R0 ЯВЛЯЕТСЯ НАИБОЛЬШИМ MOVL R1,R0 ; НЕТ,НАИБОЛЬШИМ ЯВЛЯЕТСЯ ЧИСЛО B R1 10$: RSB ; ВОЗВРАТ К ОСНОВНОЙ ПРОГРАММЕ
Рис. 5.6. Подпрограмма нахождения большего из двух чисел
; ;B ПРОГРАММЕ ВЫПОЛНЯЕТСЯ ВВОД ТРЁХ ШЕСТНАДЦАТЕРИЧНЫХ ЧИСЕЛ ;B ФОРМАТЕ ДЛИННОГО СЛОВА И ПЕЧАТЬ НАИБОЛЬШЕГО ИЗ ЭТИХ ЧИСЕЛ ; .ENTRY START,0 JSB IOINIT ; ИНИЦИАЛИЗАЦИЯ RNUM И PNUM JSB RNUM ; ВВЕСТИ ЧИСЛО A MOVL R0,R2 ; ПОМЕСТИТЬ ЧИСЛО A B R2 JSB RNUM ; ВВЕСТИ ЧИСЛО B MOVL R0,R1 ; ПОМЕСТИТЬ ЧИСЛО B B R1 JSB RNUM ; ВВЕСТИ ЧИСЛО C JSB MAX ; R0=MAX( B,C) MOVL R2,R1 ; ПОМЕСТИТЬ ЧИСЛО A B R1 JSB MAX ; R0=MAX( A,R0) JSB PNUM ; НАПЕЧАТАТЬ ЧИСЛО ИЗ R1 $EXIT_S ; ЗАВЕРШИТЬ ВЫПОЛНЕНИЕ
Рис. 5.7. Основная программа нахождения большего из трёх чисел
.TITLE НАИБОЛЬШЕЕ ИЗ ТРЕХ ЧИСЕЛ ; ;B ПРОГРАММЕ ВЫПОЛНЯЕТСЯ ВВОД ТРЕХ ШЕСТНАДЦАТЕРИЧНЫХ ЧИСЕЛ ;B ФОРМАТЕ ДЛИННОГО СЛОВА И ПЕЧАТЬ НАИБОЛЬШЕГО ИЗ ЭТИХ ЧИСЕЛ ; .ENTRY START,0 JSB IOINIT ; ИНИЦИАЛИЗАЦИЯ RNUM И PNUM JSB RNUM ; ВВЕСТИ ЧИСЛО A MOVL R0,R2 ; ПОМЕСТИТЬ ЧИСЛО A B R2 JSB RNUM ; ВВЕСТИ ЧИСЛО B MOVL R0,R1 ; ПОМЕСТИТЬ ЧИСЛО B B R1 JSB RNUM ; ВВЕСТИ ЧИСЛО C JSB MAX ; R0=MAX( B,C) MOVL R2,R1 ; ПОМЕСТИТЬ ЧИСЛО A B R1 JSB MAX ; R0=MAX( A,R0) JSB PNUM ; НАПЕЧАТАТЬ ЧИСЛО ИЗ R1 $EXIT_S ; ЗАВЕРШИТЬ ВЫПОЛНЕНИЕ ; ;ПОДПРОГРАММА МАХ ПОМЕЩАЕТ B РЕГИСТР R0 НАИБОЛЬШЕЕ ИЗ ЧИСЕЛ, ;СОДЕРЖАЩИХСЯ B РЕГИСТРАХ R0 И R1 ; MAX: CMPL R0,R1 ; ЕСЛИ ЧИСЛО B R0 БОЛЬШЕ ЧИСЛА B R1 BGEQ 10$ ; ДА,ЧИСЛО B R0 ЯВЛЯЕТСЯ НАИБОЛЬШИМ MOVL R1,R0 ; НЕТ,НАИБОЛЬШИМ ЯВЛЯЕТСЯ ЧИСЛО B R1 10$: RSB ; ВОЗВРАТ К ОСНОВНОЙ ПРОГРАММЕ ; ; КОНСТАНТЫ ДЛЯ ПРОГРАММ RNUM,PNUM И IOINIT . . . ; ; ОБЛАСТЬ ПЕРЕМЕННЫХ ДЛЯ ПРОГРАММ RNUM,PNUM И IOINIT ; ПРОГРАММА ИНИЦИАЛИЗАЦИИ IOINIT: PUSHR #^M<R0,R1> . . . RSB ; ; ПРОГРАММА ПЕЧАТИ ЧИСЛА PNUM: PUSHR #^M<R0,R1> . . . RSB ; ; ПРОГРАММА ВВОДА ЧИСЛА RNUM: PUSHR #^M<R1> . . . RSB ; КОНЕЦ ПРОГРАММЫ, START - АДРЕС ТОЧКИ ВХОДА .END START
Рис. 5.8. Завершённая программа нахождения большего из трёх чисел
И наконец, на рис. 5.8 показано, как объединить основную программу с подпрограммами так, чтобы получилась единая программа. Отметим, что области описания констант и переменных, используемых в подпрограммах IOINIT, RNUM и PNUM, расположены перед соответствующими подпрограммами. Это позволяет ассемблеру использовать относительную адресацию со смещением, заданным не длинным словом, а байтом или словом. Отметим, что в программе имеется только одна директива ассемблера .END, которая помещена после последней подпрограммы. Итак, мы объединили основную программу и подпрограммы в единую программу на языке ассемблера. В гл. 9 будет показано, как могут объединяться программы на языке ассемблера, ассемблированные раздельно, и даже то, как они могут включаться в раздельно скомпилированные программы, написанные на языках высокого уровня, таких как Фортран и Паскаль.
< НАЗАД | ОГЛАВЛЕНИЕ | ВПЕРЁД > |
[1] Некоторые компиляторы с языка Паскаль транслируют программы на Паскале в программы на промежуточном языке, называемом p-кодом, которые затем интерпретируются. Но в системе VAX/VMS разработан компилятор с языка Паскаль, который транслирует программы на Паскале непосредственно в программы на машинном языке ЭВМ VAX.
[2] Некоторые программисты предпочитают в имена включать буквы L, W или B для указания того, что определяет данное имя: длинное слово, слово или байт.
[3] Принято выбирать для таких символических имён числа, кратные 10. Это позволяет делать вставки с новыми символическими именами, сохраняя упорядоченность чисел. Фактические значения чисел не играют никакой роли, за исключением того, что проще отлаживать программу с упорядоченной последовательностью меток.
[4] Как отмечено в гл. 4, программист может отменять правила, принимаемые по умолчанию. Для этого перед символическим адресом операнда следует поместить префикс B^, W^ или L^, задающий относительную адресацию со смещением, заданным в формате байта, слова и длинного слова соответственно.