Я немного экспериментирую со сборкой x86-64. Скомпилировав эту фиктивную функцию:

long myfunc(long a, long b, long c, long d,
            long e, long f, long g, long h)
{
    long xx = a * b * c * d * e * f * g * h;
    long yy = a + b + c + d + e + f + g + h;
    long zz = utilfunc(xx, yy, xx % yy);
    return zz + 20;
}

С gcc -O0 -g я был удивлен, обнаружив следующее в начале сборки функции:

0000000000400520 <myfunc>:
  400520:       55                      push   rbp
  400521:       48 89 e5                mov    rbp,rsp
  400524:       48 83 ec 50             sub    rsp,0x50
  400528:       48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
  40052c:       48 89 75 d0             mov    QWORD PTR [rbp-0x30],rsi
  400530:       48 89 55 c8             mov    QWORD PTR [rbp-0x38],rdx
  400534:       48 89 4d c0             mov    QWORD PTR [rbp-0x40],rcx
  400538:       4c 89 45 b8             mov    QWORD PTR [rbp-0x48],r8
  40053c:       4c 89 4d b0             mov    QWORD PTR [rbp-0x50],r9
  400540:       48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  400544:       48 0f af 45 d0          imul   rax,QWORD PTR [rbp-0x30]
  400549:       48 0f af 45 c8          imul   rax,QWORD PTR [rbp-0x38]
  40054e:       48 0f af 45 c0          imul   rax,QWORD PTR [rbp-0x40]
  400553:       48 0f af 45 b8          imul   rax,QWORD PTR [rbp-0x48]
  400558:       48 0f af 45 b0          imul   rax,QWORD PTR [rbp-0x50]
  40055d:       48 0f af 45 10          imul   rax,QWORD PTR [rbp+0x10]
  400562:       48 0f af 45 18          imul   rax,QWORD PTR [rbp+0x18]

gcc очень странным образом переносит все регистры аргументов в стек, а затем берет их из памяти для дальнейших операций.

Это происходит только на -O0-O1 проблем нет), но все же почему? Мне это кажется антиоптимизацией - зачем gcc это делать?

6
Eli Bendersky 26 Авг 2011 в 12:10

2 ответа

Лучший ответ

Я ни в коем случае не эксперт по внутреннему устройству GCC, но я попробую. К сожалению, большая часть информации о распределении и разливе регистров GCC кажется устаревшей (имеется ссылка на файлы, такие как local-alloc.c, которые больше не существуют).

Я смотрю исходный код gcc-4.5-20110825.

В GNU C Compiler Internals упоминается, что исходный код функции генерируется expand_function_start в gcc/function.c. Там мы находим следующее для обработки параметров:

4462   /* Initialize rtx for parameters and local variables.
4463      In some cases this requires emitting insns.  */
4464   assign_parms (subr);

В assign_parms код, который обрабатывает, где хранятся все аргументы, следующий:

3207       if (assign_parm_setup_block_p (&data))
3208         assign_parm_setup_block (&all, parm, &data);
3209       else if (data.passed_pointer || use_register_for_decl (parm))
3210         assign_parm_setup_reg (&all, parm, &data);
3211       else
3212         assign_parm_setup_stack (&all, parm, &data);

assign_parm_setup_block_p обрабатывает агрегированные типы данных и неприменим в этом случае, и поскольку данные не передаются как указатель, GCC проверяет use_register_for_decl.

Здесь соответствующая часть:

1972   if (optimize)
1973     return true;
1974 
1975   if (!DECL_REGISTER (decl))
1976     return false;

DECL_REGISTER проверяет, была ли переменная объявлена ​​с ключевым словом register. И теперь у нас есть ответ: большинство параметров находятся в стеке, когда оптимизации не включены, а затем обрабатываются assign_parm_setup_stack. Маршрут, пройденный через исходный код до того, как он закончится утечкой значения, немного сложнее для аргументов указателя, но может быть прослежен в том же файле, если вам интересно.

Почему GCC выводит все аргументы и локальные переменные с отключенной оптимизацией? Чтобы помочь отладке. Рассмотрим эту простую функцию:

1 extern int bar(int);
2 int foo(int a) {
3         int b = bar(a | 1);
4         b += 42;
5         return b;
6 }

Скомпилировано с помощью gcc -O1 -c на моей машине:

 0: 48 83 ec 08             sub    $0x8,%rsp
 4: 83 cf 01                or     $0x1,%edi
 7: e8 00 00 00 00          callq  c <foo+0xc>
 c: 83 c0 2a                add    $0x2a,%eax
 f: 48 83 c4 08             add    $0x8,%rsp
13: c3                      retq   

Это нормально, за исключением случаев, когда вы прерываете строку 5 и пытаетесь распечатать значение a, вы получите

(gdb) print a
$1 = <value optimized out>

Поскольку аргумент перезаписывается, поскольку он не используется после вызова bar.

8
user786653 26 Авг 2011 в 09:26

Пара причин:

  1. В общем случае аргумент функции должен рассматриваться как локальная переменная, потому что он может быть сохранен или иметь адрес, взятый внутри функции. Поэтому проще всего выделить слот стека для каждого аргумента.
  2. Информацию об отладке становится намного проще выдавать с помощью местоположений стека: значение аргумента всегда находится в каком-то определенном месте, а не перемещается между регистрами и памятью.

Когда вы смотрите на код -O0 в целом, учитывайте, что главными приоритетами компилятора являются максимально возможное сокращение времени компиляции и создание высококачественной отладочной информации.

7
servn 26 Авг 2011 в 09:18