Практика программирования на языке ассемблера в RARS
Отвлечёмся пока от собственно архитектуры ЭВМ.
Ассемблер — это транслятор исходных текстов в машинный код (возможно, в объектный фал, требующий компоновки; но вся содержательная часть — данные, программа и другие секции — уже находятся в финальном, машинном представлении). Отличие от других компиляторов — в языке ассемблера.
Язык ассемблера для определённой архитектуры — низкоуровневый ЯП, однозначно соответствующий вычислительной модели этой архитектуры и принятым в ней конвенциям. Для сравнения, язык Си включает в себя значительную часть аппаратной вычислительной модели (размер целого, адреса, регистровые переменные и др.), но унифицирует многие конвенции (например, по вызову подпрограмм), и, конечно же, берёт на себя планирование регистров при трансляции вычислений. А язык Паскаль, даром что по уровню предлагаемых абстракций мало чем отличается от Си, полностью исключает аппаратную вычислительную модель из синтаксиса (вплоть до стандартизации вычислений с плавающей точкой независимо от возможностей и даже наличия сопроцессора).
Многофайловая сборка
В операционных системах исполняемые программы — это не только код и данные, но и метаинформация относительно правил их загрузки в память, расположении и размере стека, кучи и т. п. (см., например, формат ELF). В RARS этого нет, но задачу многофайловой сборки решать надо.
Директива .include — позволяет повторно использовать уже написанный текст
- Обычно это макросы, реже — подпрограммы
- …потому что результат — это как бы один файл со встроенными include-ами, и возможен конфликт имён
- нам уже и так тесно оттого, что в ассемблере RARS нет локальных меток.
- Несколько отдельных файлов
- Частичная изоляция пространств имён
Директива .globl имя (GLOBal Label; чтобы не путаться, можно писать и .global ☺) задаёт список глобальных имён — такие имена видны при обработке всех файлов (сама метка задаётся только в одном)
Директива .extern имя размер_в_байтах — размещает данных в «общей области» (начиная с 0x10000000) и объявляет имена глобальными
«Точка входа» (main) — выполнение начинается с адреса main, помеченного как .globl
NB: отныне и навсегда включим в настройке RARS «Initialize program counter to global 'main' if defined»
соответствует параметру rars sm
В RARS есть три варианта многофайловой сборки:
- «режим проекта», когда в единый бинарный образ транслируются все файлы из определённого каталога («Settings → Assemble all files in directory»)
- «ленивый лежим», когда в единый бинарный образ транслируются все открытые для редактирования файлы («Settings → Assemple all files currently open»)
- «режим командной строки», который используется для трансляции и или запуска программы вообще без GUI (так, напрример, это происходит в EJudge); в этом режиме в единый бинарный образ транслируются все файлы, имена которых были переданы в командной строке
Макроподстановка и макрокоманды
Макроподстановка — механизм поиска шаблона в тексте и замены его другим текстом. Полученный текст также может содержать шаблоны, так что процесс макроподстановки обычно рекурсивен.
Псевдонимы
Примитивный макрос — директива .eqv имя строка, которая добавляет имя в список распознаваемых ассемблером лексем, а в результате препроцессинга (обработки текста перед трансляцией) происходит замена этой лексемы на строку:
1 .eqv Esize 16
2 .eqv Era 12(sp)
3 .eqv Es1 8(sp)
4 .eqv EA 4(sp)
5 .eqv EB (sp)
6
7 subr: # некоторая подпрограмма
8 addi sp sp -Esize # выделение памяти на стеке
9 sw ra Era # сохранение ra
10 sw s1 Es1 # сохранение s1
11 sw zero EA # первая переменная
12 sw zero EB # вторая переменная
13 # какой-то код
14 lw s1 Es1 # восстановление s1
15 lw ra Era # восстановление ra
16 addi sp sp Esize
17 ret
Макроподстановка
(Строго говоря, «макрос» — это множественное число от «макро», но в современном русском это слово приобрело свойства единственного числа. Множественное число — «макросы», по аналогии с «конверсами», «сникерсами» и т. п.)
Механизм макроподстановки может быть и посложнее:
Первые 4 строчки — задание макроса exit, оно же макроопределение, последняя — использование этого макроса, оно же макрокоманда. (Не «вызов макроса», потому что на месте макрокоманды не будет никакой инструкции вызова, только то, что составляло тело макроса).
Добрый RARS даже распишет номера строк, в которых находилось макросово тело:
0x00400000 0x00000013 addi x0,x0,0 7 nop 0x00400004 0x00a00893 addi x17,x0,10 8 <2> li a7 10 0x00400008 0x00000073 ecall 8 <3> ecall
Здесь 7 и 8 — номера строк исходного текста, а <2> и <3> — номера строк, на которых располагалось тело макроса.
Сам макрос (в отличие от подпрограммы), конечно, ни во что не странслировался, потому что он — всего лишь задание нового правила для трансляции каждой макрокоманды.
Параметрические макросы
Самое удобное в макроподстановке — параметризация макрокоманд. Общий вид макроопределения:
Например:
Здесь макрокоманда print дважды раскрывается в три инструкции, причём первая из них (mv) в первом случае подставится в виде mv a0 t0, а во втором — в виде mv a0 t1 (строго говоря add a0 zero …, конечно):
0x00400000 0x06400293 addi x5,x0,0x00000064 8 li t0 100 0x00400004 0xfec00313 addi x6,x0,0xffffffec 9 li t1 -20 0x00400008 0x00500533 add x10,x0,x5 10 <2> mv a0 t0 0x0040000c 0x00100893 addi x17,x0,1 10 <3> li a7 1 0x00400010 0x00000073 ecall 10 <4> ecall 0x00400014 0x00600533 add x10,x0,x6 11 <2> mv a0 t1 0x00400018 0x00100893 addi x17,x0,1 11 <3> li a7 1 0x0040001c 0x00000073 ecall 11 <4> ecall
Обратите внимание на то, как отмечает RARS номера строк исходного кода и строк в теле макроса.
Макроподстановка, вообще говоря, может не иметь никакого отношения к синтаксису того текста, в котором встречаются макросы (например, универсальные макропроцессоры m4 или cpp). Однако поскольку в ассемблере RARS подстановка параметров макро приводит к появлению соответствующей подстроки в в виде отдельного слова инструкции или директивы, разрешается передавать только лексемы языка ассемблера. Впрочем, можно передать, например, имя метки:
Такая реализация проще (для препроцессора и для последующей трансляции используется один и тот же анализатор), но не такая гибкая. Подставить «--» вместо $t0 в макрокоманде из примера не удастся ещё на этапе макорподстановки (ошибка «riscv1.asm line 10 column 2: forward reference or invalid parameters for macro "print"» в строке с макрокомандой). А вот 100500 вместо $t0 пройдёт макроподстановку (потому что 100500 — это хорошее годное целое число), но полученный текст не пройдёт трансляцию с сообщением «riscv2.asm line 10->2 column 11: "100500": operand is of incorrect type». Ошибка возникнет, с точки зрения ассемблера RARS, всё в той же строке 10, но по вине строки 2 макроопределения.
Кстати, print-ы в примере слились в одну строку, потому что никто не вывел между ними ещё и разделителя. Чтобы исправить это положение, не надо модифицировать основную программу! Достаточно добавить в макроопеделение print такой вывод:
Сама программа при этом разрастётся чуть ли не в два раза:
0x00400000 0x06400293 addi x5,x0,0x00000064 11 li t0 100 0x00400004 0xfec00313 addi x6,x0,0xffffffec 12 li t1 -20 0x00400008 0x00500533 add x10,x0,x5 13 <2> mv a0 t0 0x0040000c 0x00100893 addi x17,x0,1 13 <3> li a7 1 0x00400010 0x00000073 ecall 13 <4> ecall 0x00400014 0x00b00893 addi x17,x0,11 13 <5> li a7 11 0x00400018 0x00a00513 addi x10,x0,10 13 <6> li a0 '\n' 0x0040001c 0x00000073 ecall 13 <7> ecall 0x00400020 0x00600533 add x10,x0,x6 14 <2> mv a0 t1 0x00400024 0x00100893 addi x17,x0,1 14 <3> li a7 1 0x00400028 0x00000073 ecall 14 <4> ecall 0x0040002c 0x00b00893 addi x17,x0,11 14 <5> li a7 11 0x00400030 0x00a00513 addi x10,x0,10 14 <6> li a0 '\n' 0x00400034 0x00000073 ecall 14 <7> ecall
Макровзрыв
В макроопределении могу встречаться другие макрокоманды. В силу рекурсивной природы макроподстановки, эти макрокоманды будут в свою очередь тоже раскрыты, и так до тех пор, пока в полученном тексте не останется ни одной.
Определим новый макрос printS, который выводит строку, и input, который выводит строку-подсказку (задаётся непосредственным адресом), а затем вводит число. В макрос print тоже добавим подсказку. Ассемблер RARS позволяет определять несколько макросов с одинаковым именем, но разным количеством параметров. Воспользуемся этим.
1 .macro print %reg 2 mv a0 %reg 3 li a7 1 4 ecall 5 li a0 10 6 li a7 11 7 ecall 8 .end_macro 9 10 .macro printS %addr 11 la a0 %addr 12 li a7 4 13 ecall 14 .end_macro 15 16 .macro print %msg %reg 17 printS %msg 18 print %reg 19 .end_macro 20 21 .macro input %msg %reg 22 printS %msg 23 li a7 5 24 ecall 25 mv %reg a0 26 .end_macro 27 28 .data 29 msg1: .asciz "First number: " 30 msg2: .asciz "Second number: " 31 res1: .asciz "Result 1: " 32 res2: .asciz "Result 2: " 33 .text 34 input msg1 t0 35 input msg2 t1 36 print res1 t0 37 print res2 t1
Второй макрос print (тот, что с двумя параметрами), получился совсем «короткий» — всего две макрокоманды. Но на самом деле он довольно-таки объёмистый, раскрывается в 9 инструкций ассемблера (в 10, с учётом псевдоинструкции la).. Наши четыре строчки кода программы превратились в 34 инструкции!
0x00400000 0x0fc10517 auipc x10,0x0000fc10 34 <11> la a0 msg1 0x00400004 0x00050513 addi x10,x10,0 0x00400008 0x00400893 addi x17,x0,4 34 <12> li a7 4 0x0040000c 0x00000073 ecall 34 <13> ecall 0x00400010 0x00500893 addi x17,x0,5 34 <23> li a7 5 0x00400014 0x00000073 ecall 34 <24> ecall 0x00400018 0x00a002b3 add x5,x0,x10 34 <25> mv t0 a0 0x0040001c 0x0fc10517 auipc x10,0x0000fc10 35 <11> la a0 msg2 0x00400020 0xfe450513 addi x10,x10,0xffffffe4 0x00400024 0x00400893 addi x17,x0,4 35 <12> li a7 4 0x00400028 0x00000073 ecall 35 <13> ecall 0x0040002c 0x00500893 addi x17,x0,5 35 <23> li a7 5 0x00400030 0x00000073 ecall 35 <24> ecall 0x00400034 0x00a00333 add x6,x0,x10 35 <25> mv t1 a0 0x00400038 0x0fc10517 auipc x10,0x0000fc10 36 <11> la a0 res1 0x0040003c 0xfc850513 addi x10,x10,0xffffffc8 0x00400040 0x00400893 addi x17,x0,4 36 <12> li a7 4 0x00400044 0x00000073 ecall 36 <13> ecall 0x00400048 0x00500533 add x10,x0,x5 36 <2> mv a0 t0 0x0040004c 0x00100893 addi x17,x0,1 36 <3> li a7 1 0x00400050 0x00000073 ecall 36 <4> ecall 0x00400054 0x00a00513 addi x10,x0,10 36 <5> li a0 10 0x00400058 0x00b00893 addi x17,x0,11 36 <6> li a7 11 0x0040005c 0x00000073 ecall 36 <7> ecall 0x00400060 0x0fc10517 auipc x10,0x0000fc10 37 <11> la a0 res2 0x00400064 0xfa050513 addi x10,x10,0xffffffa0 0x00400068 0x00400893 addi x17,x0,4 37 <12> li a7 4 0x0040006c 0x00000073 ecall 37 <13> ecall 0x00400070 0x00600533 add x10,x0,x6 37 <2> mv a0 t1 0x00400074 0x00100893 addi x17,x0,1 37 <3> li a7 1 0x00400078 0x00000073 ecall 37 <4> ecall 0x0040007c 0x00a00513 addi x10,x0,10 37 <5> li a0 10 0x00400080 0x00b00893 addi x17,x0,11 37 <6> li a7 11 0x00400084 0x00000073 ecall 37 <7> ecall
Если активно использовать удачно названные и спланированные макросы в своих программах
- программы становятся хорошо читаемыми
- код программы разрастается с невероятной скоростью
Вопрос: Если вы использовали в программе 10 макрокоманд, каждая из которых состояла из 10 макрокоманд, каждая из которых состояла из 10 инструкций, сколько инструкций (не считая другого полезного кода) появится в оттранслированной программе?
Хорошим тоном считается составить подпрограмму, а её вызов уже «обернуть» в макрос. В этом случае макроподстановка растиражирует только преамбулу и вызов подпрограммы, а содержательный код будет оттранслирован единожды в её составе.
- Стоит ещё раз напомнить, что макросы вправе рассчитывать на соблюдение конвенций — например, конвенции по сохранению регистров
Регистр ra — не сохраняемый. По конвенции его надо как можно быстрее положить на стек, пока не испортился, но ожидать, что он не испортился, нельзя.
Это накладывает ограничение на использование макросов-обёрток в концевых подпрограммах (используете макрос? сохраняйте ra самостоятельно!) и в прологах/эпилогах
1 .text 2 _input: # a0 — message / a0 — input value 3 li a7 4 4 ecall 5 li a7 5 6 ecall 7 ret 8 9 .macro input %msg %reg 10 la a0 %msg 11 jal _input 12 mv %reg a0 13 .end_macro 14 15 _print: # a0 — message, a1 — number 16 li a7 4 17 ecall 18 mv a0 a1 19 li a7 1 20 ecall 21 li a0 10 22 li a7 11 23 ecall 24 ret 25 26 .macro print %msg %reg 27 la a0 %msg 28 mv a1 %reg 29 jal _print 30 .end_macro 31 32 .data 33 msg1: .asciz "First number: " 34 msg2: .asciz "Second number: " 35 res1: .asciz "Result 1: " 36 res2: .asciz "Result 2: " 37 38 .text 39 .globl main 40 main: 41 input msg1 t0 42 input msg2 t1 43 print res1 t0 44 print res2 t1
Обратите внимание на директиву .globl main. Видя main в списке глобальных меток, RARS будет загружать в регистр pc не начало секции .text, а адрес main (для этого надо включить соответствующую настройку).
Новая программа слегка короче предыдущей, но резуьлтат трансляции тем больше, чем больше в ней макроподстановок
0x00400000 0x00400893 addi x17,x0,4 3 li a7 4 0x00400004 0x00000073 ecall 4 ecall 0x00400008 0x00500893 addi x17,x0,5 5 li a7 5 0x0040000c 0x00000073 ecall 6 ecall 0x00400010 0x00008067 jalr x0,x1,0 7 ret 0x00400014 0x00400893 addi x17,x0,4 16 li a7 4 0x00400018 0x00000073 ecall 17 ecall 0x0040001c 0x00b00533 add x10,x0,x11 18 mv a0 a1 0x00400020 0x00100893 addi x17,x0,1 19 li a7 1 0x00400024 0x00000073 ecall 20 ecall 0x00400028 0x00a00513 addi x10,x0,10 21 li a0 10 0x0040002c 0x00b00893 addi x17,x0,11 22 li a7 11 0x00400030 0x00000073 ecall 23 ecall 0x00400034 0x00008067 jalr x0,x1,0 24 ret 0x00400038 0x0fc10517 auipc x10,0x0000fc10 41 <10> la a0 msg1 0x0040003c 0xfc850513 addi x10,x10,0xffffffc8 0x00400040 0xfc1ff0ef jal x1,0xffffffc0 41 <11> jal _input 0x00400044 0x00a002b3 add x5,x0,x10 41 <12> mv t0 a0 0x00400048 0x0fc10517 auipc x10,0x0000fc10 42 <10> la a0 msg2 0x0040004c 0xfc750513 addi x10,x10,0xffffffc7 0x00400050 0xfb1ff0ef jal x1,0xffffffb0 42 <11> jal _input 0x00400054 0x00a00333 add x6,x0,x10 42 <12> mv t1 a0 0x00400058 0x0fc10517 auipc x10,0x0000fc10 43 <27> la a0 res1 0x0040005c 0xfc750513 addi x10,x10,0xffffffc7 0x00400060 0x005005b3 add x11,x0,x5 43 <28> mv a1 t0 0x00400064 0xfb1ff0ef jal x1,0xffffffb0 43 <29> jal _print 0x00400068 0x0fc10517 auipc x10,0x0000fc10 44 <27> la a0 res2 0x0040006c 0xfc250513 addi x10,x10,0xffffffc2 0x00400070 0x006005b3 add x11,x0,x6 44 <28> mv a1 t1 0x00400074 0xfa1ff0ef jal x1,0xffffffa0 44 <29> jal _print
При дальнейшем использовании макросов print и input программа будет прирастать на 4 инструкции, а не на 7 или 10.
Метки и макроподстановка
Мы уже знаем, что процесс макроподстановки достаточно умён, чтобы находить в макроопределении формальные параметры и подставлять вместо них фактические. Не меньше (а может быть, и больше) интеллекта ему требуется, чтобы отслеживать метки.
В самом деле, стоит появиться метке в теле макроопределения, как вторая же макрокоманда раскроется в последовательность инструкций, в которой окажется такая же метка, какая была в первой. По идее это должно привести к ошибке.
Однако ассемблер RARS во время макроподстановки переименовывает все метки, которые встретит в макроопределении — и задание меток, и обращение к ним. Правило такое: метка метка переименовывается в метку метка_M№, где № — это порядковый номер текущей операции макроподстановки.
после макроподстановки будет выглядеть примерно как
0x00400000 0x000282b3 add x5,x5,x0 8 <2> add t0 t0 zero 0x00400004 0x00030333 add x6,x6,x0 8 <3> label_M0: add t1 t1 zero 0x00400008 0x000383b3 add x7,x7,x0 8 <4> add t2 t2 zero 0x0040000c 0x000282b3 add x5,x5,x0 9 <2> add t0 t0 zero 0x00400010 0x00030333 add x6,x6,x0 9 <3> label_M1: add t1 t1 zero 0x00400014 0x000383b3 add x7,x7,x0 9 <4> add t2 t2 zero 0x00400018 0x000282b3 add x5,x5,x0 10 <2> add t0 t0 zero 0x0040001c 0x00030333 add x6,x6,x0 10 <3> label_M2: add t1 t1 zero 0x00400020 0x000383b3 add x7,x7,x0 10 <4> add t2 t2 zero
Не слишком красивый приём, с учётом того, что программист может случайно сам завести такую метку в своей программе. Однако действенный: внутри раскрытого макроса метка актуальна, а во всей программе — уникальна.
Генерация меток наводит на мысль о том, что наши макрос-функции print и input можно сделать ещё более удобными, если строку-подсказку передавать макросу прямо в качестве параметра, а превращать в .asciz уже в теле макроса:
1 .globl main 2 .text 3 _input: # a0 — message / a7 — input value 4 li a7 4 5 ecall 6 li a7 5 7 ecall 8 ret 9 10 .macro input %msg %reg 11 .data 12 msg: .ascii %msg 13 .asciz ": " 14 .text 15 la a0 msg 16 jal _input 17 mv %reg a0 18 .end_macro 19 20 _print: # a0 — message, a1 — number 21 li a7 4 22 ecall 23 mv a0 a1 24 li a7 1 25 ecall 26 li a0 10 27 li a7 11 28 ecall 29 ret 30 31 .macro print %msg %reg 32 .data 33 msg: .ascii %msg 34 .asciz ": " 35 .text 36 la a0 msg 37 mv a1 %reg 38 jal _print 39 .end_macro 40 41 .text 42 main: 43 input "First input" t0 44 input "Second input" t1 45 print "First result" t0 46 print "Second result" t1
Обратите внимание на то, как чередуются .data и .text: на самом деле никакой чересполосицы кода и данных не получится, потому что каждая директива .data просто размещает последующие данные строго после содержимого предыдущей секции .data (если не задавать явно адрес — начиная с 0x10010000); то же самое верно и для .text (начиная с 0x400000).
Кроме того, теперь в параметре задаётся только содержательная подсказка, а ": " «приклеивается» к ней уже в макросе. Полученный код столь же компактен:
0x00400000 0x00400893 addi x17,x0,4 4 li a7 4 0x00400004 0x00000073 ecall 5 ecall 0x00400008 0x00500893 addi x17,x0,5 6 li a7 5 0x0040000c 0x00000073 ecall 7 ecall 0x00400010 0x00008067 jalr x0,x1,0 8 ret 0x00400014 0x00400893 addi x17,x0,4 21 li a7 4 0x00400018 0x00000073 ecall 22 ecall 0x0040001c 0x00b00533 add x10,x0,x11 23 mv a0 a1 0x00400020 0x00100893 addi x17,x0,1 24 li a7 1 0x00400024 0x00000073 ecall 25 ecall 0x00400028 0x00a00513 addi x10,x0,10 26 li a0 10 0x0040002c 0x00b00893 addi x17,x0,11 27 li a7 11 0x00400030 0x00000073 ecall 28 ecall 0x00400034 0x00008067 jalr x0,x1,0 29 ret 0x00400038 0x0fc10517 auipc x10,0x0000fc10 43 <15> la a0 msg_M0 0x0040003c 0xfc850513 addi x10,x10,0xffffffc8 0x00400040 0xfc1ff0ef jal x1,0xffffffc0 43 <16> jal _input 0x00400044 0x00a002b3 add x5,x0,x10 43 <17> mv t0 a0 0x00400048 0x0fc10517 auipc x10,0x0000fc10 44 <15> la a0 msg_M1 0x0040004c 0xfc650513 addi x10,x10,0xffffffc6 0x00400050 0xfb1ff0ef jal x1,0xffffffb0 44 <16> jal _input 0x00400054 0x00a00333 add x6,x0,x10 44 <17> mv t1 a0 0x00400058 0x0fc10517 auipc x10,0x0000fc10 45 <36> la a0 msg_M2 0x0040005c 0xfc550513 addi x10,x10,0xffffffc5 0x00400060 0x005005b3 add x11,x0,x5 45 <37> mv a1 t0 0x00400064 0xfb1ff0ef jal x1,0xffffffb0 45 <38> jal _print 0x00400068 0x0fc10517 auipc x10,0x0000fc10 46 <36> la a0 msg_M3 0x0040006c 0xfc150513 addi x10,x10,0xffffffc1 0x00400070 0x006005b3 add x11,x0,x6 46 <37> mv a1 t1 0x00400074 0xfa1ff0ef jal x1,0xffffffa0 46 <38> jal _print
Конвенции относительно регистров
Конвенции по использованию регистров — такие же, как и для подпрограмм, за исключением того, что параметры макроса нет необходимости раскладывать по регистрам a* (в примере выше этим занимается сам макрос).
Свойства макроассемблера RARS
Замечания авторов RARS относительно их макроассемблера:
Макроопределение должно идти в тексте программы до соответствующей макрокоманды (иначе пришлось бы анализировать текст дважды)
Макроопределение локально в пределах одного файла. Это с очевидностью вытекает из самого процесса макроподстановки перед трансляцией. Если нужно, чтобы один и тот же макрос был виден из нескольких файлов, используйте .include
Вложенные макросы не поддерживаются, т. е. внутри макроопределения не может встречаться директива .macro
- Внутри макроопределения, как и в тексте программы, могут встречаться только ранее определённые макрокоманды, искать их определения далее по тексту никто не будет
Все определённые в макросе метки меняются в процессе макроподстановки, превращаясь в метка_M№
- (метка может сама быть результатом макроподстановки, тогда с ней ничего не происходит, как в примере в начале этой темы)
Несколько макросов с одинаковым именем, но разным количеством параметров, считаются различными, и их можно использовать все
- Повторное определение макроса с тем же именем и тем же количеством параметров игнорируется, макрокоманда раскрывается в первое определение
Параметром макроса (в силу ограниченной реализации) может быть только атомарная лексема языка ассемблера. Например, параметром не может быть "4(t0)", потому что это две лексемы, а не одна
- Макросредства ассемблера не входят ни в какой стандарт и остаются на усмотрение авторов ассемблера
В больших многофайловых проектах принято все макросы складывать в отдельный файл и включать их в код программы с помощью директивы .include файл_с_макросами. Подпрограммы при этом складываются в другой файл (возможно, не один), т. н. «библиотеку», и подключаются посредством многофайловой сборки.
На предыдущем примере:
Файл с программой prog.asm:
Файл с подпрограммами lib.asm:
Не забываем метки всех подпрограмм, которые понадобятся в других файлах, объявлять как .globl
Файл с макросами macros.inc (имя файла не заканчивается на .asm в знак того, что его не нужно транслировать отдельно):
1 .macro input %msg %reg 2 .data 3 msg: .ascii %msg 4 .asciz ": " 5 .text 6 la a0 msg 7 jal _input 8 mv %reg a0 9 .end_macro 10 11 .macro print %msg %reg 12 .data 13 msg: .ascii %msg 14 .asciz ": " 15 .text 16 la a0 msg 17 mv a1 %reg 18 jal _print 19 .end_macro 20 21 .macro exit 22 li a7 10 23 ecall 24 .end_macro
Чего нет в RARS
Макроассемблер RARS вполне достаточен для учебных целей, но не реализует много из того, что есть в промышленных средствах программирования на ассемблере
В RARS (и только в RARS) в некоторых случаях (например, в директиве .globl) нельзя использовать имена, по написанию совпадающие с инструкциями — например, нельзя сделать глобальной метку b:!
Библиотека макросов и подпрограмм. Чтобы написать большую программу, потребуется множество подпрограмм, реализующих стандартные приёмы работы — ввод-вывод, работа с дисками, управление внешними устройствами и т. п. Для этого в профессиональных инструментариях, типа gas или Gnu Assebmler, имеются заранее подготовленные библиотеки макросов и подпрограмм. А мы пишем их сами
«Настоящий» макропроцессор. Макропросессор в RARS опирается только на лексемы языка ассемблера и не имеет собственного языка. Это почти не мешает, но временами (как в примере с 4($t0) ) слегка неудобно.
Переменные и вычисления периода трансляции. С помощью .eqv мы можем заменить некоторую константу на мнемоническое обозначение, но не более. В действительности макроассемблер RARS вообще не интересуется, что именно кроется за введённым обозначением, и просто выполняет подстановку. В промышленных макропроцессорах возможно вычисление арифметических выражений в момент трансляции, задание констант и даже переменных. Например, ввести переменную SIZE для некоторого базового размера массивов, переменную DOUBLESIZE = SIZE * 2, а в тексте программы писать что-то вроде array: .space DOUBLESIZE + 4. Ещё раз напомним, что всё это происходит до финального этапа трансляции, превращающего текст в машинный код — поэтому транслируется уже результат таких вычислений.
Адресная арифметика. Вычисление некоторых значений, смещений и размеров на основании уже известных адресов. Хотелось бы уметь писать что-то вроде Arr+20, что означало бы «отступ в 20 элементов массива» и менялось в зависимости от типа Arr. В некоторых случаях это упрощает разработку, в некоторых — усложняет работу с памятью на низком уровне.
Условная трансляция. Наиболее полезное свойство вычислений в период трансляции — это возможность транслировать или не транслировать части текста в зависимости от результата этих вычислений. Например можно вставить исходный текст отладочные сообщения, но транслировать их только если определена некоторая переменная периода трансляции DEBUG. Как-нибудь так:
Генерация макроопределений. Если разрешить создавать макросы внутри макросов, можно развёртывать целые семейства определений в зависимости от исходного параметра внешнего макроса
Конкатенация. Иногда необходимо, чтобы результат постановки нескольких макросов интерпретировался затем как одна лексема языка (например, строка label##suffix##index превращалась бы при наличии констант suffix=_M и index=5 в label_M5). В RARS такого механизма нет
Локальные метки. Бывает очень полезно ограничить видимость меток сильнее, чем просто внутри файла. Например, если в файле задано несколько подпрограмм, в каждой из них хотелось бы иметь возможность использовать метки типа start, finish, loop или стандартные имена переменных. Это можно было бы сделать, введя особенный синтаксис временных меток или ограничить видимость меток специальной конструкцией «локальное пространство имён» и т. п.
В целом макроассемблер RARS достаточен для написания программ среднего объёма, а написание действительно крупных проектов на языке ассемблера выходит за рамки данного курса.