Соглашение о вызовах определяет только то, какие регистры сохраняются при вызове, а не о вызове, и где можно найти аргументы стека.
Функция на 100% зависит от того, как она работает, чтобы убедиться, что ее адрес возврата равен доступны где-то, когда он готов вернуться. Самый простой и эффективный способ справиться с этим - просто оставить его в LR все время в функции листьев. (Функция, которая не вызывает других: это лист в графе / дереве вызовов).
На практике компиляторы обычно просто оставляют его в LR в конечных функциях, даже если оптимизация отключена. Например, G CC устанавливает указатель кадра с отключенной оптимизацией, но по-прежнему не сохраняет / перезагружает LR, когда он знает, что ему не нужно так много чистых регистров, что он хотел бы использовать LR.
В противном случае в неконечных функциях обычные компиляторы обычно сохраняют его в стеке, но если они хотят, они могут, например, сохранить R4 в стек и mov r4, lr
, затем восстановить LR и перезагрузить R4, когда они ' готов к возврату.
Функция, не являющаяся арендатором / не поддерживающая потоки, теоретически могла бы сохранить свой адрес возврата в хранилище c, если бы захотела.
Источник и GCC8.2 -O2 -mapcs-frame
вывод от Godbolt , заставляющий его генерировать кадр стека APCS (стандарт вызова процедуры ARM), даже когда он не нужен. (Похоже, что он имеет эффект, аналогичный -fno-omit-frame-pointer
, который включен по умолчанию при оптимизации.)
void function_1(void){
//some code here
}
function_1:
bx lr @ with or without -mapcs-frame
void unknown_func(void); // not visible to the compiler; can't inline
void function_2(void){
function_1(); // inlined, or IPA optimized as pure and not needing to be called.
unknown_func(); // tailcall
unknown_func();
}
function_2: @@ Without -macps-frame
push {r4, lr} @ save LR like you expected
bl unknown_func
pop {r4, lr} @ around a call
b unknown_func @ but then tailcall for the 2nd call.
или с APCS:
mov ip, sp
push {fp, ip, lr, pc}
sub fp, ip, #4
bl unknown_func
sub sp, fp, #12
ldm sp, {fp, sp, lr}
b unknown_func
int func3(void){
unknown_func();
return 1; // prevent tailcall
}
func3: @@ Without -macps-frame
push {r4, lr}
bl unknown_func
mov r0, #1
pop {r4, pc}
Или с APCS:
func3:
mov ip, sp
push {fp, ip, lr, pc}
sub fp, ip, #4
bl unknown_func
mov r0, #1
ldmfd sp, {fp, sp, pc}
Поскольку взаимодействие большого пальца не требуется (с опциями компиляции по умолчанию), G CC вставит сохраненный LR в P C вместо того, чтобы просто вернуться в LR для bx lr
.
При нажатии R4 вместе с LR стек выравнивается на 8, что по умолчанию является IIR C.