РЕДАКТИРОВАТЬ 1: Добавлен еще один пример (показывающий, что GCC, в принципе, способен делать то, что я хочу достичь) и некоторое обсуждение в конце этого вопроса.
РЕДАКТИРОВАТЬ 2: Найден атрибут функции malloc
, который должен что делать. Пожалуйста, взгляните на самый конец вопроса.
Это вопрос о том, как сообщить компилятору, что хранилища в области памяти не видны за пределами региона (и, таким образом, могут быть оптимизированы). Чтобы проиллюстрировать, что я имею в виду, давайте взглянем на следующий код
int f (int a)
{
int v[2];
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
gcc -O2
генерирует следующий код сборки (x86-64 gcc, trunk, on https://godbolt.org):
f:
leal -1(%rdi), %edx
xorl %eax, %eax
testl %edi, %edi
jle .L4
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
ret
.L4:
ret
Как видно, после оптимизации данные загружаются и сохраняются в массиве v
.
Теперь рассмотрим следующий код:
int g (int a, int *v)
{
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
Разница в том, что v
не (стек-) выделяется в функции, но предоставляется в качестве аргумента. Результат gcc -O2
в этом случае:
g:
leal -1(%rdi), %edx
movl $0, 4(%rsi)
xorl %eax, %eax
movl %edx, (%rsi)
testl %edi, %edi
jle .L4
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
movl %eax, 4(%rsi)
movl $-1, (%rsi)
ret
.L4:
ret
Очевидно, что код должен хранить окончательные значения v[0]
и v[1]
в памяти, поскольку они могут быть наблюдаемыми.
Теперь я ищу способ сообщить компилятору, что память, на которую указывает v
во втором примере, больше не доступна после возврата функции g
, чтобы компилятор мог оптимизировать прочь доступ к памяти.
Чтобы иметь еще более простой пример:
void h (int *v)
{
v[0] = 0;
}
Если память, на которую указывает v
, не доступна после возврата h
, должна быть возможность упростить функцию до одного ret
.
Я пытался добиться того, чего хочу, играя со строгими правилами наложения имен, но безуспешно.
ДОБАВЛЕНО В РЕДАКТ. 1:
GCC, кажется, имеет необходимый встроенный код, как показано в следующем примере:
include <stdlib.h>
int h (int a)
{
int *v = malloc (2 * sizeof (int));
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
Сгенерированный код не содержит загрузок и сохраняет:
h:
leal -1(%rdi), %edx
xorl %eax, %eax
testl %edi, %edi
jle .L4
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
ret
.L4:
ret
Другими словами, GCC знает, что изменение области памяти, на которую указывает v
, не наблюдается из-за какого-либо побочного эффекта malloc
. Для таких целей GCC имеет __builtin_malloc
.
Поэтому я также могу спросить: как пользовательский код (скажем, пользовательская версия malloc
) может использовать эту функцию?
ДОБАВЛЕНО В РЕДАКТОР 2:
GCC имеет следующий атрибут функции:
таНос
Это говорит компилятору, что функция подобна malloc, т. Е. Что указатель P, возвращаемый функцией, не может присвоить псевдониму любой другой указатель, действительный при возврате из функции, и, кроме того, в любом хранилище, адресуемом P, нет указателей на действительные объекты. .
Использование этого атрибута может улучшить оптимизацию. Компилятор предсказывает, что функция с атрибутом в большинстве случаев возвращает ненулевое значение. Такие функции, как malloc и calloc, имеют это свойство, поскольку они возвращают указатель на неинициализированное или обнуленное хранилище. Однако такие функции, как realloc, не имеют этого свойства, поскольку они могут возвращать указатель на хранилище, содержащее указатели.
Это кажется делать то, что я хочу, как показано в следующем примере:
__attribute__ (( malloc )) int *m (int *h);
int i (int a, int *h)
{
int *v = m (h);
v[0] = a;
v[1] = 0;
while (v[0]-- > 0)
v[1] += v[0];
return v[1];
}
Сгенерированный ассемблерный код не загружается и не хранит:
i:
pushq %rbx
movl %edi, %ebx
movq %rsi, %rdi
call m
testl %ebx, %ebx
jle .L4
leal -1(%rbx), %edx
xorl %eax, %eax
.L3:
addl %edx, %eax
subl $1, %edx
cmpl $-1, %edx
jne .L3
popq %rbx
ret
.L4:
xorl %eax, %eax
popq %rbx
ret
Однако, как только компилятор увидит определение m
, он может забыть об атрибуте. Например, это тот случай, когда дается следующее определение:
__attribute__ (( malloc )) int *m (int *h)
{
return h;
}
В этом случае функция является встроенной, и компилятор забывает об атрибуте, получая тот же код, что и функция g
.
P.S .: Сначала я думал, что ключевое слово restrict
может помочь, но это не так.