- Зачем нужен кадр
А он нужен? В том смысле, что на ассемблере редко пишут настолько большие программы, чтобы это было критично. Компиляторы тоже этим особо не заморачиваются.
Возможно, в других областях, которые от меня далеки, оно и правда используется, или хотя бы из общих соображений знания окажутся полезными. Но даже так, возможно, эту часть можно сократить.
- Данные, которые подпрограмма временно хранит в памяти, называются локальными переменными.
Можно привести пример, как это выглядит на Си:
То есть в начале программы мы сохранили ra, все нужные регистры и выделили место под var1, var2. А потом, в середине кода, понадобилось выделить еще сколько-то байтов для var3, var4. Если мы обращаемся к локальным переменным через sp, смещение будет постоянно меняться. Для компилятора-то это не проблема, у него мозг железный. А вот программист существо нежное, он и ошибиться может. Поэтому выделим специальный регистр fp, относительно которого и будем адресоваться. (кстати, логично в fp записывать не sp, а sp+2k, чтобы получить полный диапазон, но это так, мелочи). Мы получили удобную адресацию. И на халяву - страховку от срыва стека.
- Стоит заметить, что конвенция с кадром удобна не всегда. Например, компилятор с любого языка программирования более высокого уровня планирует регистры автоматически, и, как следствие, всегда заранее знает, сколько места на стеке потребуется данной подпрограмме. В таком случае в использовании fp нет нужды
Кажется, вы говорили, что компилятор может использовать fp для удобства отладки. Я этого, разумеется, не проверял.
- Не полностью описана ситуация, когда подпрограмме надо передавать массивы данных. Не всегда удобно передавать все данные через стек, как в конвенции с кадром. Очевидно, в случае целого массива надо формировать блок параметров в памяти, а в регистре передавать ссылку на этот блок.
В том же Си это решается куда проще. Процедура передачи массива по значению максимально усложнена (разве что в составе структуры). То есть договоренность, что любой массив передается по указателю, а уж как обеспечить его безопасность и где разместить - задача вызывающей стороны.
- Используются, в частности, PLT и GOT
Таки не добавили в ДЗ. Кажется, в прошлом году мне в телегу какой-то халявщик предложил аналогичную задачу, я ее вам даже пересылал вместе со своим решением. Попробую пересказать по памяти.
В начале программы расположена таблица вызовов: переход на первую ячейку приводит к вызову функции func1, на вторую - func2, и т.д. до func10. Но поскольку адрес размещения кодаможет быть любым, при старте программы во всех ячейках прописан вызов func_search, который ищет номер вызываемой функции, ищет ее адрес, прописывает его в таблицу и производит вызов.
Потребуется возможность самомодификации кода. Кажется, Рарс такое умеет. Будет небольшая тонкость, как func_search узнает номер вызванной функции. Это решается, скажем, заполнением таблицы PLT командами jal t0, func_search. Разница (t0-PLT)/4 как раз будет номером. Ну и формулировку, видимо, подправить. Ну там заменить func1-func10 на что-то более осмысленное... сложение, вычитание, ввод-вывод, вариантов много.
Хотя что мешает в таблицу сразу записать позиционно-независимый j?.. Будем считать, что таблица расположена все же не в ПЗУ, а в ОЗУ. Но тогда ее надо как-то инициализировать, плюс возня с изменением расстояния между ПЗУ и ОЗУ (то самое, на которое я постоянно ругаюсь).
Георгий Курячий Главная проблема — это нельзя скормить EJudge. Как компонент финального задания довольно интересная тема, да.
- Если вы пишете программу для запуска на том же компьютере, на котором работаете, скорее всего ecall — это обращение к ядру операционной системы
Или к BIOS, или к аппаратным блокам...
Георгий Курячий Вряд ли — том же компьютере, на котором работаете, скорее всего, есть нормальная ОС.
- Возврат из системного вызова — по аналогии с возвратом из подпрограммы, на следующую после ecall инструкцию
Это если рассматривать ecall как вызов функции. Но ведь это инструкция risc-v, ничем не хуже какой-нибудь lui. После выполнения lui ведь идет переход на следующую инструкцию. Чем ecall хуже. Тем более что окружением может быть не другая часть кода, а хост-машина или аппаратный блок. То есть самого вызова тоже не происходит. Происходит просто выполнение инструкции.
- Для функций выше ещё можно вообразить, что это «классическая» реализация системных вызовов: есть ядро ОС, которое работает на том же компьютере
Кстати неплохая идея для ДЗ по теме периферии и прерываний. Реализовать ecall ввода-вывода чисел через консоль или, скажем, на семисегментник. Кажется, Рарс умеет ловить исключение ecall именно как исключение.
> strget Ввод ASCIZ-строки длиной не более 100 байтов. Ввод происходит в статический буфер, далее для строки выделяется соответствующее место на куче, и она копируется туда (\n, если есть, из строки удаляется, в конец обязательно записывается 0)
gets / fgets же
