Ответ 1
Похоже на пропущенную ошибку оптимизации, о которой следует сообщить, если еще нет открытых дубликатов для gcc и clang.
(Нередко и в gcc, и в clang бывает одинаковая пропущенная оптимизация в подобных случаях; не предполагайте, что что-то незаконно, только потому, что компиляторы этого не делают. Единственные полезные данные - это когда компиляторы делают это. выполнить оптимизацию: либо ошибка компилятора, либо, по крайней мере, некоторые разработчики компилятора решили, что это безопасно в соответствии с их интерпретацией любых стандартов.)
Мы можем видеть, что GCC возвращает свой собственный входящий аргумент вместо того, чтобы возвращать его копию, которую create()
вернет в RAX. Это пропущенная оптимизация, блокирующая оптимизацию tailcall.
Для ABI требуется функция с возвращаемым значением типа MEMORY для возврата "скрытого" указателя в RAX 1.
GCC/clang уже понимают, что они могут исключить фактическое копирование, передавая собственное пространство возвращаемых значений вместо выделения нового пространства. Но для оптимизации хвостового вызова им нужно понять, что они могут оставить значение RAX вызываемого в RAX вместо сохранения входящего RDI в регистре с сохранением вызова.
Если бы ABI не требовал возврата скрытого указателя в RAX, я ожидаю, что у gcc/clang не было бы проблем с передачей входящего RDI как части оптимизированного хвостового вызова.
Обычно компиляторы любят сокращать цепочки зависимостей; это, вероятно, то, что здесь происходит. Компилятор не знает, что задержка от rdi
arg до rax
результата create()
, вероятно, является всего лишь одной инструкцией mov
. По иронии судьбы, это может быть пессимизацией, если вызываемый объект сохраняет/восстанавливает некоторые регистры, сохраняющие вызовы (например, r12
), представляя сохранение/перезагрузку указателя обратного адреса. (Но это, в основном, имеет значение, только если что-то и использует. Я получил некоторый лязгательный код, см. ниже.)
Сноска 1: Возвращение указателя звучит как хорошая идея, но почти всегда вызывающая сторона уже знает, куда она помещает аргумент в свой собственный кадр стека, и будет просто использовать режим адресации, такой как 8(%rsp)
, вместо того, чтобы фактически использовать RAX. По крайней мере, в сгенерированном компилятором коде возвращаемое значение RAX обычно не используется. (И при необходимости звонящий всегда может сохранить его где-нибудь самостоятельно.)
Как обсуждалось в Что мешает использовать аргумент функции в качестве скрытого указателя? Существуют серьезные препятствия для использования чего-либо, кроме пробела в кадре стека вызывающих, для получения ответа.
Наличие указателя в регистре просто сохраняет LEA в вызывающей стороне, если вызывающая сторона хочет где-то сохранить адрес, если это статический адрес или адрес стека.
Однако этот случай близок к тому, в котором он был бы полезен. Если мы передаем наше собственное пространство для поиска дочерней функции, мы можем захотеть изменить это пространство после вызова. Тогда это полезно для легкого доступа к этому пространству, например, изменить возвращаемое значение, прежде чем мы вернемся.
#define T struct Vec3
T use2(){
T tmp = create();
tmp.y = 0.0;
return tmp;
}
Эффективный рукописный ассемблер:
use2:
callq create
movq $0, 8(%rax)
retq
Фактический clang asm по крайней мере все еще использует оптимизацию возвращаемого значения, а не копирование GCC9.1. (Godbolt)
# clang -O3
use2: # @use2
pushq %rbx
movq %rdi, %rbx
callq create
movq $0, 8(%rbx)
movq %rbx, %rax
popq %rbx
retq
Это правило ABI, возможно, существует специально для этого случая, или, может быть, разработчики ABI представляли, что пространство поиска может быть вновь выделенным динамическим хранилищем (на которое вызывающий абонент должен будет сохранить указатель, если ABI его не предоставил) в RAX). Я не пробовал этот случай.