Разлив регистра аргументов gcc на x86-64

Я экспериментирую со сборкой 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 сделай это?

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,

Пара причин:

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

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

Другие вопросы по тегам