Я пытаюсь понять значение System V AMD64 - ABI для возврата по значению из функции.
Для следующего типа данных
struct Vec3{
double x, y, z;
};
тип Vec3
относится к классу MEMORY, и, таким образом, ABI указывает следующее: "Возвращение значений":
Если тип имеет класс MEMORY, то вызывающая сторона предоставляет пространство для возвращаемого значения и передает адрес этого хранилища в% rdi, как если бы это был первый аргумент функции. По сути, этот адрес становится "скрытым" первым аргументом. Это хранилище не должно перекрывать какие-либо данные, видимые вызываемому абоненту через другие имена, кроме этого аргумента.
На return% rax будет содержать адрес, который был передан вызывающая сторона в% rdi.
Имея это в виду, следующая (глупая) функция:
struct Vec3 create(void);
struct Vec3 use(){
return create();
}
может быть скомпилировано как:
use_v2:
jmp create
По моему мнению, оптимизация tailcall может быть выполнена, поскольку мы уверены в ABI, что create
поместит переданное значение %rdi
в регистр %rax
.
Однако ни один из компиляторов (gcc, clang, icc), по-видимому, не выполняет эту оптимизацию (здесь на Godbolt). Полученный код сборки сохраняет %rdi
в стеке только для возможности перемещения его значения в %rax
, например, gcc:
use:
pushq %r12
movq %rdi, %r12
call create
movq %r12, %rax
popq %r12
ret
Ни для этой минимальной, глупой функции, ни для более сложных из реальной жизни не выполняется оптимизация tailcall. Что заставляет меня верить, что я что-то упускаю, что запрещает это.
Само собой разумеется, что для типов класса SSE (например, только 2, а не 3 двойных) выполняется оптимизация tailcall (по крайней мере, gcc и clang, live на godbolt):
struct Vec2{
double x, y;
};
struct Vec2 create(void);
struct Vec2 use(){
return create();
}
приводит к
use:
jmp create