Уровень 56: Вызов и возврат
Задание
Цель: запрограммировать вызов функции: jal (J-формат) прыгает на метку и пишет в rd адрес следующей команды (PC + 4); jalr прыгает по регистру — цель (rs1 + imm) с очисткой бита 0, младший бит адреса всегда 0. Соберите sum_to_n: вызов с аргументом в x5, функция суммирует 5+4+3+2+1 в x6 и возвращается через jalr x0, 0(ra). Останов .end стоит на месте возврата — в середине программы; парсер собирает и код ниже него.
Первая программа, которая умеет вызывать себя по частям: функция с аргументом, телом и возвратом. Схема по-прежнему не меняется — исполнение идёт на эталонном ядре, как на уровнях 54 и 55. Новые команды — jal (J-формат) и jalr:
jal rd, labelпрыгает на метку и одновременно пишет в rd адрес следующей команды (PC + 4) — обратный адрес.jal ra, label— вызов;ra— стандартное имя x1, регистра возврата.jalr rd, imm(rs1)прыгает по регистру: цель = rs1 + imm, причём бит 0 суммы очищается аппаратно, и только затем проверяется кратность 4.jalr x0, 0(ra)— возврат из функции.
Каноническая программа sum_to_n — функция, суммирующая числа от 1 до n (аргумент в x5, результат в x6):
addi x5, x0, 5 # 0: аргумент
jal ra, sum_to_n # 4: вызов (ra = 8)
.end # 8: останов на месте возврата — функция ниже
sum_to_n:
addi x6, x0, 0 # 12
again: beq x5, x0, back # 16
add x6, x6, x5 # 20
addi x5, x5, -1 # 24
beq x0, x0, again # 28
back: jalr x0, 0(ra) # 32
Обратите внимание на .end: здесь он стоит в середине программы. Останов — это место, куда вернётся jalr; функция пишется ниже него, и парсер спокойно собирает код после директивы. Ассемблер испускает слово jal x0, 0 на адресе 8, а метки и команды дальше получают свои адреса как обычно.
Задания уровня:
- Соберите слово
jalв «Слове из полей» (J-формат появился на 56-м) и разберите разрезку смещения: imm[20]=w31, imm[10:1]=w30:21, imm[11]=w20, imm[19:12]=w19:12 — база смещения, как у beq, это адрес самой команды. - Соберите sum_to_n во вкладке «Ассемблер». Псевдо-команды
j label(→jal x0, label) иret(→jalr x0, 0(ra)) тоже доступны с 56-го. - Перед первым «Проверить» ответьте на вопрос про ra: jal стоит по адресу 4 — посчитайте PC + 4 сами.
- Поэкспериментируйте в блоке «Исполнение» с целями jalr:
jalr x0, 1(x1)при x1 = 0x10 — переход состоится на 0x10 (бит 0 очищен);jalr x0, 2(x1)— цель 0x12, не кратна 4, ошибкаERR_INSTR_ADDRESS_MISALIGNED.
Решение для самопроверки
Сначала посчитайте ra и такты на бумаге. Решение ниже — контрольные точки.
Что должно получиться: sum_to_n возвращается корректно: ra = 8 (адрес .end), x6 = 15, x5 = 0; предсказанные 25 тактов (3 + 6 + 5 × 3 + 1) совпадают с трассой, jalr на PC 32 возвращает PC на адрес 8.
Шаг 1 — обратный адрес. jal по адресу 4 пишет в ra значение 4 + 4 = 8: после возврата исполнение продолжится с адреса 8, где стоит .end. Значит, в конце прогона ra = 8, PC = 8, статус — останов. Это же число спрашивает предикт перед первой проверкой.
Шаг 2 — посчитайте такты. Вход — 3 команды (addi, jal; останов .end такта не добавляет). Функция: страж beq исполняется 6 раз (5 раз не берётся, шестой — берётся и ведёт на back), тело — 5 итераций × (add + addi + возвратный beq), и финальный jalr. Итого 3 + 6 + 5 × 3 + 1 = 25 тактов. Результат: x6 = 1+2+3+4+5 = 15, x5 = 0.
Шаг 3 — разберите jalr. Цель = (rs1 + imm) & ~1: бит 0 очищается всегда, поэтому jalr x0, 1(x1) при x1 = 0x10 прыгает на 0x10 (сумма 0x11 стала 0x10). А вот нечётный бит 1 не исправляется: jalr x0, 2(x1) даёт цель 0x12 — ядро останавливается с ERR_INSTR_ADDRESS_MISALIGNED. Запись в x0 переход не блокирует: rd = x0 лишь отменяет сохранение обратного адреса.
Шаг 4 — сверьте с трассой. Найдите в трассе: jal на PC 4 пишет x1 = 8 и уводит PC на 12; пять возвратов beq на again; jalr на PC 32 возвращает PC на 8; запись останова .end без счётчика тактов. Дальше курса — стек: у функции появятся свои сохраняемые регистры, и ra начнёт жить в памяти.
Проверь себя
Почему jalr очищает бит 0 цели?
Чтобы нечётная сумма rs1 + imm не создала невыровненный адрес: бит 0 обнуляется аппаратно ещё до проверки кратности 4. А вот бит 1 не исправляется — jalr x0, 2(x1) при x1 = 0x10 даёт цель 0x12 и ERR_INSTR_ADDRESS_MISALIGNED.