Удалить ненужные ассемблерные выражения из вывода g ++ - PullRequest
0 голосов
/ 27 сентября 2019

Я исследую проблему с локальным двоичным файлом.Я заметил, что g ++ создает много вывода ASM, который мне кажется ненужным.Пример с -O0:

Derived::Derived():
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp          <--- just need 8 bytes for the movq to -8(%rbp), why -16?
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi         <--- now we have moved rdi onto itself.
    call    Base::Base()
    leaq    16+vtable for Derived(%rip), %rdx
    movq    -8(%rbp), %rax     <--- effectively %edi, does not point into this area of the stack
    movq    %rdx, (%rax)       <--- thus this wont change -8(%rbp)
    movq    -8(%rbp), %rax     <--- so this statement is unnecessary
    movl    $4712, 12(%rax)
    nop
    leave
    ret

option -O1 -fno-inline -fno-elide-constructors -fno-omit-frame-pointer:

Derived::Derived():
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %rbx
    subq    $8, %rsp       <--- reserve some stack space and never use it.
    movq    %rdi, %rbx
    call    Base::Base()
    leaq    16+vtable for Derived(%rip), %rax
    movq    %rax, (%rbx)
    movl    $4712, 12(%rbx)
    addq    $8, %rsp       <--- release unused stack space.
    popq    %rbx
    popq    %rbp
    ret

Этот код предназначен для конструктора Derived, который вызывает базовый конструктор Base и затем переопределяетуказатель vtable в позиции 0 и устанавливает постоянное значение для члена int, который он содержит в дополнение к тому, что содержит Base.

Вопрос :

  • МожетЯ перевожу свою программу с минимальным количеством оптимизаций и избавляюсь от таких вещей?Какие параметры мне нужно установить?Или есть причина, по которой компилятор не может обнаружить эти случаи с помощью -O0 или -O1, и нет никакого способа их обойти?
  • Почему оператор subq $8, %rsp вообще генерируется?Вы не можете оптимизировать в или из заявления, которое не имеет смысла для начала.Почему компилятор генерирует его тогда?Алгоритм распределения регистров никогда не должен, даже с O0, генерировать код для чего-то, чего там нет.Так почему же это сделано?

Ответы [ 2 ]

3 голосов
/ 27 сентября 2019

есть причина, по которой компилятор не может обнаружить эти случаи с -O0 или -O1

именно потому, что вы говорите компилятору не делать этого.Это уровни оптимизации , которые необходимо отключить или выключить для правильной отладки.Вы также тратите время компиляции на время выполнения.

Вы смотрите в телескоп неверным способом, посмотрите на потрясающие оптимизации, которые компилятор сделает для вас при запуске up оптимизация.

2 голосов
/ 27 сентября 2019

Я не вижу очевидных пропущенных оптимизаций в вашем выводе -O1.За исключением, конечно, установки RBP в качестве указателя кадра, но вы использовали -fno-omit-frame-pointer, так что вы ясно знаете, почему GCC не оптимизировал это.

Функция не имеет локальных переменных

Ваша функция является нестатической функцией-членом класса, поэтому имеет один неявный аргумент: this in rdi.Какой g ++ выливается в стек из-за -O0.Аргументы функций считаются локальными переменными.

Как циклическое перемещение без эффекта улучшает процесс отладки.Пожалуйста, уточните.

Для улучшения отладки C / C ++ : форматы debug-info могут описывать только местоположение переменной C относительно RSP или RBP, а не регистр, в котором она в настоящее время находится.Таким образом, вы можете изменить любую переменную с помощью отладчика и продолжить, получая ожидаемые результаты, как если бы вы делали это на абстрактной машине C ++.Каждый оператор скомпилирован в отдельный блок asm без значений в регистрах (забавный факт: кроме register int foo: это ключевое слово влияет на код режима отладки gen).

Почему clang производит неэффективноasm с -O0 (для этой простой суммы с плавающей запятой)? относится также к G ++ и другим компиляторам.

Какие параметры мне нужно установить?

Если вы читаете / отлаживаете asm, используйте по крайней мере -Og или выше , чтобы отключить режим spill-everything-между-между-операторами отладки режима -O0.Желательно -O2 или -O3, если вы не хотите видеть еще больше пропущенных оптимизаций, чем при полной оптимизации.Но -Og или -O1 выполнит распределение регистров и создаст вменяемые циклы (с условной ветвью внизу) и различные простые оптимизации.Хотя все еще не является стандартным глазком для обнуления xor.

Как удалить "шум" из вывода сборки GCC / clang? объясняет, как писать функции, принимающие аргументыи вернуть значение, чтобы вы могли писать функции, которые не оптимизируются.

Загрузка в RAX, а затем movq %rax, %rdi - это просто побочный эффект -O0.GCC тратит так мало времени на оптимизацию внутренних представлений программной логики в GIMPLE и / или RTL (до создания ассемблера x86), что даже не замечает, что он мог изначально загрузиться в RDI.Частью -O0 является быстрая компиляция и последовательная отладка.

Почему оператор subq $8, %rsp вообще генерируется?

Поскольку ABI требует выравнивания стека 16 байтов перед call инструкцией , и эта функция выполняла четное число 8-байтовых push es.(call сам выдвигает обратный адрес).Он уйдет на -O1 без -fno-omit-frame-pointer, потому что вы не заставляете g ++ использовать push / pop RBP, а также регистр с сохранением вызовов, который ему действительно необходим.

Почему System V /AMD64 ABI требует выравнивания стека 16 байт?

Интересный факт: clang будет часто использовать фиктивный push %rcx / pop или что-то в зависимости от параметров -mtune вместо 8-байтовыхsub.

Если бы это была листовая функция, g ++ просто использовал бы красную зону ниже RSP для локальных, даже на -O0. Почему в этом прологе функции нет инструкции "sub rsp" и почему параметры функции хранятся с отрицательным смещением rbp?


В неоптимизированном коде это не редкостьдля G ++ выделить дополнительные 16 байтов, которые он никогда не использует .Даже иногда с включенной оптимизацией g ++ слишком сильно округляет размер выделения стека, стремясь к 16-байтовой границе.Это ошибка пропущенной оптимизации.например, Распределение памяти и адресация в сборке

...