Почему ARM сохраняет адрес возврата в регистре ссылок, а не в стеке?

Я искал этот ответ уже более недели, но безуспешно. До сих пор я знал, что стек сохраняет адрес возврата, когда происходит вложение функций или прерывание, но недавно я узнал, что современные процессоры используют регистр ссылок для достижения той же цели. После некоторых исследований я узнал, что стек действительно использовался для сохранения адреса возврата в старых процессорах. Однако мне непонятно, почему современные процессоры используют целый отдельный регистр (LR) для сохранения адреса возврата, когда работала старая реализация? Каковы преимущества LR по сравнению со стековой реализацией?

Заранее спасибо!!!


person Jigar Agrawal    schedule 20.05.2019    source источник
comment
Это регистр, а не память. Это намного быстрее в использовании.   -  person Sami Kuhmonen    schedule 20.05.2019
comment
скорость для одного, память действительно очень медленная. В общем, больше регистров быстрее, чем использование меньшего количества регистров и большего стека. Тот же ответ для переданных параметров, вы увидите, что регистры используются вместо стека в качестве предпочтения, если в архитектуре достаточно регистров.   -  person old_timer    schedule 20.05.2019
comment
не ограничивается рукой.   -  person old_timer    schedule 20.05.2019
comment
Это имеет смысл, регистры быстрее доступны. Однако, поскольку у нас есть 1 LR, если функция переходит на более чем 1 уровень глубокой вложенности, это вынуждает нас хранить LR в стеке, что, в свою очередь, снижает скорость процессора. Пожалуйста, поправьте меня, если я ошибаюсь!   -  person Jigar Agrawal    schedule 20.05.2019


Ответы (1)


Архитектуры RISC, как правило, имеют меньше специальных инструкций или действий, и вместо этого для управления стеком используются стандартные инструкции. Обычно это означает, что программы больше, сам ЦП проще, а компилятору приходится сложнее оптимизировать.

Рассмотреть возможность:

int bar(int a)
{
    return a * a;
}

void foo()
{
    bar(22);
}


foo();

Здесь bar() – это функция-лист, которая не выполняет дальнейших вызовов функций. Поэтому адрес возврата в LR никогда не будет перезаписан. Как следствие, нет необходимости записывать его в стек вообще. Это сохраняет, загружает и сохраняет из/в память.

foo(), с другой стороны, изменит LR, потому что он вызывает функцию, поэтому ему нужно будет сохранить адрес возврата вызывающего объекта в стеке.

Сравните это с архитектурой, в которой вызов функции автоматически помещает адрес возврата в стек — такая оптимизация невозможна.

Все версии стандарта вызова процедур ARM определяют вызываемый объект сохраняет регистры для вызова функции — регистры, которые вызывающая сторона может ожидать при вызове функции. Если функция тривиальна, она снова не приводит к доступу к памяти.

В прерываниях время часто более критично. Процессоры ARM имеют набор теневых регистров, которые доступны только в состоянии прерывания. Это означает, что можно написать тривиальные обработчики прерываний, не требующие доступа к памяти.

person marko    schedule 20.05.2019
comment
Итак, вы хотите сказать, что если вложенность функций только на один уровень ниже, это оптимизация, поскольку мы обращаемся к адресу возврата из регистра, а не из памяти? Но для более глубоких вызовов функций это будет то же самое, поскольку оно включает сохранение LR в стек. Пожалуйста, поправьте меня, если я ошибаюсь. - person Jigar Agrawal; 21.05.2019
comment
Правильно - однако листовые функции по определению малы и часто вызываются. Из-за их размера загрузка/сохранение в память будет составлять большую часть времени их выполнения. - person marko; 21.05.2019
comment
Возможно, вам захочется взглянуть на архитектуру SPARC, которая имеет гораздо больший регистровый файл и скользящее окно, которое перемещается при вызове функции, что потенциально устраняет много разливов в стеке. После определенного количества вызовов функций в стеке происходит огромный выброс. - person marko; 21.05.2019
comment
Спасибо, Марко, теперь это имеет смысл. - person Jigar Agrawal; 21.05.2019