Функції та стек
Функція — це іменований блок інструкцій, який можна використовувати більше ніж один раз. Ви переходите до неї, вона виконує своє завдання й повертає керування туди, звідки ви її викликали. Це дає змогу написати фрагмент логіки один раз і використовувати його з багатьох місць замість повторення тих самих рядків у всій програмі.
Використовуйте , щоб викликати функцію: вона переходить до мітки й зберігає адресу повернення в ra. Функція завершується , який переходить назад за адресою в ra. і — це псевдоінструкції: зручні назви, які асемблер розгортає в справжні інструкції, і .
Аргументи розміщуються в a0, a1 і наступних регістрах. Значення, що повертається, розміщується в a0. Такий порядок — це угода про виклики, спільна домовленість, яка дає змогу окремим частинам коду працювати разом.
li a0, 6 # аргументcall square # виклич, і ra пам'ятає, куди повернутисяli a7, 1 # square залишив свій результат у a0ecallli a7, 10ecallsquare:mul a0, a0, a0 # a0 = a0 * a0ret # повернутися до ra
Простежте виклик. розміщує аргумент 6 у a0. переходить до мітки square і зберігає адресу повернення в ra. Усередині функції обчислює 6 * 6 = 36 і залишає результат у a0 — регістрі, який використовується для значень, що повертаються. переходить назад до інструкції одразу після виклику, де і друкують 36, після чого програма завершується.
Викликач збирається викликати A.
caller виконує , що автоматично зберігає точку повернення до caller у регістрі ra. Перш ніж A зможе виконати , виникає проблема: інструкція ось-ось перезапише ra новою адресою повернення, знищивши ту, яка потрібна A, щоб повернутися додому. Щоб це виправити, A звільняє місце в стеку () і зберігає там початковий ra (). Тепер можна безпечно виконати . B виконується й повертається звичайною інструкцією , приземляючись назад усередину A. Нарешті A відновлює початковий ra зі стеку (), очищає вказівник стеку (), і його власний використовує це відновлене значення, щоб успішно повернутися до caller. Якби A пропустив цей крок, інструкція назавжди перезаписала б ra, і фінальний у A перейшов би не туди, замість того щоб повернутися додому.Стек
Існує лише кілька регістрів, тож функціям, які викликають інші функції, потрібне тимчасове місце. Стек — це пам'ять, яку ви позичаєте через вказівник стеку sp: віднімайте, щоб звільнити місце, зберігайте те, що потрібно зберегти, відновлюйте це перед .
Стек — це структура Last-In, First-Out (LIFO): найостанніше збережене значення вилучається першим. У RISC-V стек росте вниз у пам'яті. Віднімання від sp резервує новий простір, який безпечно використовувати. Коли ви вилучаєте значення назад, ви маєте робити це в порядку, зворотному до того, у якому ви їх зберігали, інакше потік виконання програми буде порушено.
addi sp, sp, -4 # звільнити місце для одного словаsw ra, 0(sp) # зберегти адресу повернення# ... викликати інші функції тут ...lw ra, 0(sp) # відновити їїaddi sp, sp, 4 # повернути місце назадret
Кожен / перезаписує ra, тож якщо ваша функція викликає іншу функцію, внутрішній виклик знищує вашу адресу повернення, і ваш власний переходить не туди. Щоб запобігти цьому, резервує 4 байти в стеку, а зберігає ra там. Після завершення внутрішніх викликів відновлює адресу повернення, а повертає цей простір перед .
ra перед і завжди повертайте sp до значення, яке він мав на початку. Стек, який не відповідає своєму початковому стану, — звичайна причина збою в програмах на асемблері.