Я пытаюсь понять смысл System V AMD64 - ABI соглашениео вызовах и смотрю на следующий пример:
struct Vec3{
double x, y, z;
};
struct Vec3 do_something(void);
void use(struct Vec3 * out){
*out = do_something();
}
A Vec3
-variable имеет тип MEMORY, и, следовательно, вызывающая сторона (use
) должна выделить место для возвращаемой переменной и передать его как скрытый указатель на вызываемый объект (т.е. do_something
). Вот что мы видим в получающемся ассемблере (на Godbolt, скомпилированном с -O2
):
use:
pushq %rbx
movq %rdi, %rbx ;remember out
subq $32, %rsp ;memory for returned object
movq %rsp, %rdi ;hidden pointer to %rdi
call do_something
movdqu (%rsp), %xmm0 ;copy memory to out
movq 16(%rsp), %rax
movups %xmm0, (%rbx)
movq %rax, 16(%rbx)
addq $32, %rsp ;unwind/restore
popq %rbx
ret
Я понимаю, что псевдоним указателя out
(например, как глобальная переменная) может использоваться в do_something
, и поэтому out
не может быть передан как скрытый указатель на do_something
: если это так, out
будет изменено внутри do_something
, а не когда do_something
вернется, поэтому некоторые вычисления могут стать ошибочными. Например, эта версия do_something
будет возвращать ошибочные результаты:
struct Vec3 global; //initialized somewhere
struct Vec3 do_something(void){
struct Vec3 res;
res.x = 2*global.x;
res.y = global.y+global.x;
res.z = 0;
return res;
}
если out
где псевдоним для глобальной переменной global
и использовался как скрытый указатель, переданный в %rdi
, res
также был псевдонимом global
, потому что компилятор будет использовать память, указанную для непосредственно скрытым указателем (своего рода RVO в C), без фактического создания временного объекта и копирования его при возврате, тогда res.y
будет 2*x+y
(если x,y
- старые значения global
), а не x+y
как и для любого другого скрытого указателя.
Мне было предложено, что использование restrict
должно решить проблему, т.е.
void use(struct Vec3 *restrict out){
*out = do_something();
}
потому что теперь компилятор знает, что в do_something
нет псевдонимов out
, поэтому ассемблер может быть таким простым:
use:
jmp do_something ; %rdi is now the hidden pointer
Однако это не относится ни к gcc, ни к clang - ассемблер остается неизменным (см. godbolt).
Что мешает использовать out
в качестве скрытого указателя?
Примечание: желаемое (или очень похожее) поведение будет достигнуто для слегка отличающейся сигнатуры функции:
struct Vec3 use_v2(){
return do_something();
}
что приводит к (см. крестник):
use_v2:
pushq %r12
movq %rdi, %r12
call do_something
movq %r12, %rax
popq %r12
ret