Передача параметров из программы в подпрограмму
Введение
Алгоритм команды RET
RC <– [PC] | |
Дешифрация | |
SP <– SP - LR | |
PC <– [SP] | |
SP <– SP - LR | |
RF <– [SP] |
Микрооперации 1, 2: стандартные. См [3.6.2]
Микрооперации 3, 4: Читаем слово данных из стека. Что прочитается? То? что сохранялось последним – то есть адрес возврата. Прочитанный из системного стека адрес возврата заносим в Программный счётчик (PC). Смотрите, на действия что были описаны в разделе [3.10.4] "Алгоритм команды POP" микрооперации 3, 4 этого алгоритма.
Микрооперации 5,6: Теперь - восстанавливаем регистр флагов. Смотрите, на действия что были описаны в разделе [3.10.4] "Алгоритм команды POP" микрооперации 3, 4 этого алгоритма.
3.12 Сохранение/Восстановление регистров
1) Команды CALL и RET сохраняют/восстанавливают только те 2 регистра, которые при вызове подпрограммы необходимо сохранять/восстанавливать всегда: RF и PC. Выполняют нужную но – минимально необходимую работу.
2) Однако состояние процессора определяется не только регистром RF. Программа обычно использует и другие регистры для хранения каких то данных, и они - тоже должны сохраняться/восстанавливаться, чтобы при возврате из подпрограммы - они оказались в том же состоянии, что и при вызове. Если так и есть - программист должен сам позаботиться об их сохранении/восстановлении этих регистров. Сохраняются они так же в системном стеке, но не автоматически при вызове команды CALL, а специально вставленными программистом в программу командами PUSH и POP.
Используются 2 способа программирования сохранения/восстановления регистров:
а) Сохранение./Восстановление в программе
б) Сохранение/Восстановление в подпрограмме
3.12.2 Сохранение/Восстановление в программе
Второй раз в курсе займёмся не вопросом "как процессор выполняет те или иные команды" а вопросом: "как эти команды используются". Пишем программу и подпрограмму.
В данном случае задачу сохранения/Восстановления полностью на себя берет на себя программист, пишущий программу.
Рассмотрим текст программы и подпрограммы. Отмечены только те команды что имеют отношение к вызову и возврату:
Текст программы | Текст подпрограммы |
Начало программы CLI (1) PUSH R1 … (2) PUSH Rn CALL #Adr (3) POP Rn … (4) POP R1 STI (5) Окончание программы | STI (6) Тело подпрограммы CLI (7) RET (8) |
Комментарии:
(1) – Начало подготовки перехода к подпрограмме, предстоит работа со стеком для сохранения регистров. Это – критическая секция. Этот процесс - не должен прерываться обработкой прерывания, иначе последовательность данных в стеке будет нарушена. Пока не будет закончен переход к вызываемой подпрограмме – обработку прерываний необходимо запретить. Командой CLI мы запрещаем обработку прерывания. Говорят: "открываем критическую секцию перехода к подпрограмме". Смотрите: [1.10], [3.5.6]
(2) – Сохранение регистров в системном стеке, сохраняются те n регистров, которые реально используются в программе.
(3) – Вызов подпрограммы.
(6) – После перехода в подпрограмму и конца работы со стеком нужно вновь разрешить обработку прерываний командой STI. Говорят "закрываем критическую секцию перехода к подпрограмме".
(7) – Начинается процесс возврата. Открываем критическую секцию возврата к программе.
(8) – Возврат в программу.
(4) – Вернуться то вернулись, но необходимо ещё восстановить то состояние регистров - что было на момент вызова. Восстанавливаем регистры из системного стека (порядок восстановления, в соответствии с принципом работы стековой памяти [3.10.1] – обратный порядку восстановления)
(5) – Закрываем критическую секцию возврата в программу.
3.12.3 Сохранение/Восстановление в подпрограмме
В данном случае задачу регистров берет на себя программист, пишущий подпрограмму.
Поскольку, программист пишущий подпрограмму - не обязан знать какие именно из регистров использует программа, может показаться что есть проблема – а какие же регистры ему сохранять и восстанавливать?
На самом деле всё просто - регистры для сохранения/восстановления выбираются по другому принципу: Сохраняются, а по окончании подпрограммы восстанавливаются не те регистры что использует программа, а те регистры - которые использует подпрограмма. Использует и, следовательно - меняет. Результат тот же: восстановив прежние значения - подпрограмма вернёт программе регистры в своём прежнем состоянии.
Текст программы | Текст подпрограммы |
Начало программы | PUSH R1 … (4) PUSH Rm |
CLI (1) | |
CALL #Adr (2) | |
STI (3) | STI (5) |
Окончание | Тело подпрограммы |
CLI (6) | |
POP Rm … (7) POP R1 | |
RET (8) |
(1) – открывается критическая секция перехода к подпрограмме;
(2) – переход к подпрограмме;
(4) – сохранение m регистров. 1…m – регистры что далее будет использовать программист пишущий подпрограмму;
(5) – закрывается критическая секция перехода к подпрограмме;
(6) – открывается критическая секция возврата в программу;
(7) - восстановление регистров ( порядок – обратный порядку сохранения;
(8) – возврат в программу;
(3) - закрываем критическую секцию возврата в программу.
3.12.3 Выбор способа сохранения/восстановления регистров
1) Если рассматривать ситуацию с точки зрения минимизации размера исполняемого кода – как правило предпочтительней сохранение/восстановление в подпрограмме: команды работы со стеком прописаны 1 раз в подпрограмме, а не многократно - при каждом очередном вызове подпрограммы.
2) Если рассматривать ситуацию с точки зрения скорости работы – ситуация не однозначна. Псё зависит от того как соотносятся между собой величины n и m :
Пусть:
n – число регистров используемых программой;
m – подпрограммой;
Тогда:
- если m<n – быстрее сохранение/восстановление в подпрограмме.
- если n<m - проще сохранение/восстановление в подпрограмме
3) При программировании на Assembler - выбор способа – воля программиста, вопрос договора между программистами. Причём в зависимости от случая может быть использован и один, и другой способ.
4) Трансляторы с языков высокого уровня по умолчанию используют - один из двух способов. Согласно стандарту на язык разные языки программирования по умолчанию могут использовать разные способы:.
Пример:
Стандарт Си (и стандарты других языков производных от этого языка) - предписывает сохранение/восстановление в подпрограмме;
Стандарт Паскаль (и стандарты других языков производных от этого языка) – в программе.
5) Если программисту на языке высокого уровня не нравится стандартный способ – он может изменить его опциями компилятора.
3.12.4 Сохранение/восстановление и вызов внешних подпрограмм и функций.
1) "Внешняя подпрограмма" – подпрограмма написанная совершенно отдельно от вызывающей программы, возможно на другом языке программирования, раздельно компилируемая. "Внешние подпрограммы" – вполне могут быть написаны на языке использующем другой способ сохранения/восстановления. И тут – возникает проблема:
Пусть транслятор языка А – сохраняет/восстанавливает регистры в программе
Пусть транслятор языка B – сохраняет/восстанавливает регистры в программе
Пусть из программы вызывается внешняя подпрограмма, написанная на другом языке программирования:
Если программа на языке А, а подпрограмма на В – сохранять/восстанавливать будет как программа так и подпрограмма, произойдёт двойное сохранение/восстановление регистров. Ошибки - не случится, но уйдет дополнительное время на двойное сохранение/восстановление.
Если программа на языке В, а подпрограмма на А – всё хуже: никто не сохранит не восстановит регистры, и возникает трудно обнаруживаемая.
Вывод: если программист использует язык программирования предполагающий сохранение/восстановление в подпрограмме – он должен быть особенно внимателен. Знать какой способ использует вызываемая внешняя подпрограмма и при необходимости опциями компилятора изменить умолчание для транслятора. С этой точки зрения – языки предполагающие по умолчанию сохранение/восстановление в подпрограмме более опасны с точки зрения вероятности возникновения ошибки, и требуют дополнительных знаний от программиста с таковым языком работающим. Именно поэтому разработчик "паскаль" Николас Вирт предпочел в своём языке использовать более безопасный, хоть, как правило, и дающий больший размер кода способ сохранения/восстановления.