Как удалить "шум" из сборки сборки GCC/clang?

Я хочу проверить выход сборки приложения boost::variant в моем коде, чтобы увидеть, какие промежуточные вызовы оптимизированы.

Когда я компилирую следующий пример (с GCC 5.3 с помощью g++ -O3 -std=c++14 -S), кажется, что компилятор оптимизирует все и напрямую возвращает 100:

(...)
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
(...)

#include <boost/variant.hpp>

struct Foo
{
    int get() { return 100; }
};

struct Bar
{
    int get() { return 999; }
};

using Variant = boost::variant<Foo, Bar>;


int run(Variant v)
{
    return boost::apply_visitor([](auto& x){return x.get();}, v);
}
int main()
{
    Foo f;
    return run(f);
}

Однако полный вывод сборки содержит гораздо больше, чем вышеприведенный фрагмент, который для меня выглядит так, как будто он никогда не вызывается. Есть ли способ сказать GCC/clang удалить весь этот "шум" и просто вывести то, что на самом деле вызывается при запуске программы?


вывод полной сборки:

    .file   "main1.cpp"
    .section    .rodata.str1.8,"aMS",@progbits,1
    .align 8
.LC0:
    .string "/opt/boost/include/boost/variant/detail/forced_return.hpp"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC1:
    .string "false"
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDB2:
    .section    .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTB2:
    .p2align 4,,15
    .weak   _ZN5boost6detail7variant13forced_returnIvEET_v
    .type   _ZN5boost6detail7variant13forced_returnIvEET_v, @function
_ZN5boost6detail7variant13forced_returnIvEET_v:
.LFB1197:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, %ecx
    movl    $49, %edx
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    __assert_fail
    .cfi_endproc
.LFE1197:
    .size   _ZN5boost6detail7variant13forced_returnIvEET_v, .-_ZN5boost6detail7variant13forced_returnIvEET_v
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDE2:
    .section    .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTE2:
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDB3:
    .section    .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTB3:
    .p2align 4,,15
    .weak   _ZN5boost6detail7variant13forced_returnIiEET_v
    .type   _ZN5boost6detail7variant13forced_returnIiEET_v, @function
_ZN5boost6detail7variant13forced_returnIiEET_v:
.LFB9757:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, %ecx
    movl    $39, %edx
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    __assert_fail
    .cfi_endproc
.LFE9757:
    .size   _ZN5boost6detail7variant13forced_returnIiEET_v, .-_ZN5boost6detail7variant13forced_returnIiEET_v
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDE3:
    .section    .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTE3:
    .section    .text.unlikely,"ax",@progbits
.LCOLDB4:
    .text
.LHOTB4:
    .p2align 4,,15
    .globl  _Z3runN5boost7variantI3FooJ3BarEEE
    .type   _Z3runN5boost7variantI3FooJ3BarEEE, @function
_Z3runN5boost7variantI3FooJ3BarEEE:
.LFB9310:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    (%rdi), %eax
    cltd
    xorl    %edx, %eax
    cmpl    $19, %eax
    ja  .L7
    jmp *.L9(,%rax,8)
    .section    .rodata
    .align 8
    .align 4
.L9:
    .quad   .L30
    .quad   .L10
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .text
    .p2align 4,,10
    .p2align 3
.L7:
    call    _ZN5boost6detail7variant13forced_returnIiEET_v
    .p2align 4,,10
    .p2align 3
.L30:
    movl    $100, %eax
.L8:
    addq    $8, %rsp
    .cfi_remember_state
    .cfi_def_cfa_offset 8
    ret
    .p2align 4,,10
    .p2align 3
.L10:
    .cfi_restore_state
    movl    $999, %eax
    jmp .L8
    .cfi_endproc
.LFE9310:
    .size   _Z3runN5boost7variantI3FooJ3BarEEE, .-_Z3runN5boost7variantI3FooJ3BarEEE
    .section    .text.unlikely
.LCOLDE4:
    .text
.LHOTE4:
    .globl  _Z3runN5boost7variantI3FooI3BarEEE
    .set    _Z3runN5boost7variantI3FooI3BarEEE,_Z3runN5boost7variantI3FooJ3BarEEE
    .section    .text.unlikely
.LCOLDB5:
    .section    .text.startup,"ax",@progbits
.LHOTB5:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
.LFE9320:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE5:
    .section    .text.startup
.LHOTE5:
    .section    .rodata
    .align 32
    .type   _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, @object
    .size   _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, 58
_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__:
    .string "T boost::detail::variant::forced_return() [with T = void]"
    .align 32
    .type   _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, @object
    .size   _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, 57
_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__:
    .string "T boost::detail::variant::forced_return() [with T = int]"
    .ident  "GCC: (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204"
    .section    .note.GNU-stack,"",@progbits

Ответы

Ответ 1

Удаление директив .cfi, неиспользуемых меток и строк комментариев - решенная проблема: сценарии обозревателя компилятора Matt Godbolt являются открытым исходным кодом в его проекте github. Он может даже делать цветную подсветку, чтобы сопоставить исходные строки с asm-строками (используя отладочную информацию). Вы можете настроить его локально, чтобы вы могли передавать файлы, являющиеся частью вашего проекта, всеми путями #include и т.д. (Используя -I/...). И поэтому вы можете использовать его в частном исходном коде, который не хотите отправлять через Интернет.

Мэтт Годболт CppCon2017 говорит

"Что мой компилятор сделал для меня в последнее время? Откручивая крышку компилятора, показывает,как ее использовать (она довольно понятна, но имеет некоторые полезные функции, если вы читаете документы на github), а также как Прочитайте x86 asm, с кратким введением в саму x86 asm для начинающих и с обзором результатов компиляции. Далее он показывает некоторые аккуратные оптимизации компилятора (например, для деления на константу) и какие функции дают полезный вывод asm для просмотра оптимизированного вывода компилятора (аргументы функций, а не int a = 123;).


С простым gcc/clang (не g++), -fno-asynchronous-unwind-tables избегает директив .cfi. Возможно также полезно: -fno-exceptions -fno-rtti -masm=intel. Обязательно опустите -g.

Скопируйте/вставьте это для локального использования:

g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \
    -Wall -Wextra  foo.cpp   -O3 -masm=intel -S -o- | less

Но на самом деле, я бы порекомендовал просто использовать Godbolt напрямую (онлайн или настроить его локально)! Вы можете быстро переключаться между версиями gcc и clang, чтобы увидеть, что старые или новые компиляторы делают что-то глупое. (Или то, что делает ICC, или даже то, что делает MSVC.) Есть даже ARM/ARM64 gcc 6.3 и различные gcc для PowerPC, MIPS, AVR, MSP430. (Может быть интересно посмотреть, что происходит на машине, где int шире, чем регистр, или не 32-битный. Или на RISC против x86).

Для C вместо C++ используйте -xc -std=gnu11 или что-то еще; сайт обозревателя компилятора предоставляет только g++/clan g++, но не gcc/clang. (Или вы можете использовать режим C в раскрывающемся списке языков, но у него есть другой выбор компиляторов, который в основном более ограничен. И он сбрасывает исходную панель, так что это более сложное переключение между C и C++.)


Полезные опции компилятора для создания asm для потребления человеком:

  • Помните, ваш код должен компилироваться, а не ссылаться: передача указателя на внешнюю функцию, такую как void ext(int*p), является хорошим способом помешать что-либо оптимизировать. Вам нужен только прототип для него, без определения, чтобы компилятор не мог встроить его или сделать какие-либо предположения о том, что он делает.

  • Я бы рекомендовал использовать -O3 -Wall -Wextra -fverbose-asm -march=haswell) для просмотра кода. (-fverbose-asm может просто заставить источник выглядеть шумно, хотя, когда все, что вы получаете, это пронумерованные временные имена в качестве имен для операндов.) Когда вы возитесь с источником, чтобы увидеть, как он изменяет asm, вы определенно хотите включить предупреждения компилятора, Вы не хотите тратить время на то, чтобы почесать голову над асмом, если объясните, что вы сделали что-то, что заслуживает предупреждения в источнике.

  • Чтобы увидеть, как работает соглашение о вызовах, вы часто хотите посмотреть на вызывающего и вызываемого абонентов, не вставляя.

    Вы можете использовать __attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... } в определении или скомпилировать с gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions, чтобы отключить встраивание. (Но эти параметры командной строки не отключают клонирование функции для постоянного распространения.) См. С точки зрения компилятора, как обрабатывается ссылка на массив и почему недопустима передача по значению (не затуханию)? для примера.

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

  • -ffast-math получит много встроенных функций libm, некоторые в одну инструкцию (особенно с SSE4, доступным для roundsd). Некоторые будут встроены только в -fno-math-errno или в другие "более безопасные" части -ffast-math, без частей, которые позволяют компилятору округляться по-разному. Если у вас есть код FP, обязательно посмотрите на него с/без -ffast-math. Если вы не можете безопасно включить любой из -ffast-math в своей обычной сборке, возможно, вы получите представление о безопасном изменении, которое вы можете внести в исходный код, чтобы разрешить ту же оптимизацию без -ffast-math.

  • -O3 -fno-tree-vectorize оптимизирует без автоматической векторизации, поэтому вы можете получить полную оптимизацию, если не хотите сравнивать с -O2 (который не включает автовекторизацию в gcc, но включает в себя clang).
  • clang развертывает циклы по умолчанию, поэтому -funroll-loops может быть полезен в сложных функциях. Вы можете получить представление о том, "что сделал компилятор", не пробираясь через развернутые циклы. (gcc включает -funroll-loops с -fprofile-use, но не с -O3). (Это предложение для читаемого человеком кода, а не для кода, который будет работать быстрее.)
  • Определенно включите некоторый уровень оптимизации, если вы не хотите знать, что конкретно сделал -O0. Его требование "предсказуемого поведения отладки" заставляет компилятор хранить/перезагружать все между каждым оператором C, так что вы можете модифицировать переменные C с помощью отладчика и даже "переходить" к другой исходной строке в пределах одной и той же функции и выполнять выполнение так, как если бы вы сделал это в источнике C. Вывод -O0 настолько шумный при хранении/перезагрузке (и такой медленный) не только из-за отсутствия оптимизации, но и из-за вынужденной де-оптимизации для поддержки отладки.

Чтобы получить сочетание исходного кода и ассемблера, используйте gcc -Wa,-adhln -c -g foo.c | less, чтобы передать дополнительные опции as. (Подробнее об этом можно прочитать в блоге и другом блоге.). Обратите внимание, что вывод этого не является допустимым вводом ассемблера, потому что источник C находится там непосредственно, не как комментарий ассемблера. Так что не называйте это .s. .lst может иметь смысл, если вы хотите сохранить его в файл.

Цветовая подсветка Godbolt служит аналогичной цели и отлично помогает вам видеть, когда несколько несмежных asm-инструкций поступают из одной строки источника. Я вообще не использовал эту команду gcc list, так что IDK показывает, насколько хорошо она работает, и насколько это легко увидеть в этом случае.

Мне нравится высокая плотность кода на панели asm godbolt, поэтому я не думаю, что мне хотелось бы смешивать строки исходного текста. По крайней мере, не для простых функций. Может быть, с функцией, которая была слишком сложной, чтобы понять общую структуру того, что делает асм...


И помните, когда вы хотите просто посмотреть на asm, опустите main() и константы времени компиляции. Вы хотите видеть код для работы с функцией arg в регистре, а не для кода после того, как постоянное распространение превращает его в return 42 или, по крайней мере, оптимизирует некоторые вещи.

Удаление static и/или inline из функций создаст для них отдельное определение, а также определение для любых вызывающих абонентов, так что вы можете просто посмотреть на это.

Не помещайте свой код в функцию с именем main(). gcc знает, что main является особенным, и предполагает, что он будет вызываться только один раз, поэтому он помечает его как "холодный" и оптимизирует его меньше.


Еще одна вещь, которую вы можете сделать: если вы сделали main(), вы можете запустить его и использовать отладчик. stepi (si) пошаговые инструкции. См. в нижней части тега вики для получения инструкций. Но помните, что код может оптимизироваться после встраивания в main с помощью постоянных времени компиляции.

__attribute__((noinline)) может помочь с функцией, которую вы не хотите вставлять. gcc также создаст клоны функций с постоянным распространением, то есть специальную версию с одним из аргументов в качестве константы, для сайтов вызовов, которые знают, что передают константу. Имя символа будет .clone.foo.constprop_1234 или что-то в выводе asm. Вы можете использовать __attribute__((noclone)), чтобы отключить это тоже.).


Например,

Если вы хотите увидеть, как компилятор умножает два целых числа: я поместил следующий код в проводник компилятора Godbolt, чтобы получить asm (из gcc -O3 -march=haswell -fverbose-asm) для неправильного и правильного способа проверить это.

// the wrong way, which people often write when they're used to creating a runnable test-case with a main() and a printf
// or worse, people will actually look at the asm for such a main()
int constants() { int a = 10, b = 20; return a * b; }
    mov     eax, 200  #,
    ret                     # compiles the same as  return 200;  not interesting

// the right way: compiler does not know anything about the inputs
// so we get asm like what would happen when this inlines into a bigger function.
int variables(int a, int b) { return a * b; }
    mov     eax, edi  # D.2345, a
    imul    eax, esi        # D.2345, b
    ret

(Это сочетание asm и C было создано вручную путем копирования и вставки вывода asm из godbolt в нужное место. Я считаю, что это хороший способ показать, как короткая функция компилируется в SO ответы/сообщения об ошибках компилятора/электронные письма.)

Ответ 2

Вы всегда можете просмотреть сгенерированную сборку из объектного файла, вместо использования сборки сборки компиляторов. objdump приходит на ум.

Вы даже можете сказать objdump смешивать источник со сборкой, что упрощает определение того, какая строка источника соответствует инструкциям. Пример сеанса:

$ cat test.cc
int foo(int arg)
{
    return arg * 42;
}

$ g++ -g -O3 -std=c++14 -c test.cc -o test.o && objdump -dS -M intel test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z3fooi>:
int foo(int arg)
{
    return arg + 1;
   0:   8d 47 01                lea    eax,[rdi+0x1]
}
   3:   c3                      ret    

Объяснение флагов objdump:

  • -d дизассемблирует все исполняемые разделы
  • -S смешивает сборку с исходным кодом (-g, требуемую при компиляции с помощью g++)
  • -M intel исправляет синтаксис Intel по уродливому синтаксису AT & T (необязательно)

Ответ 3

Мне нравится вставлять метки, которые я могу легко вывести из вывода objdump.

int main() {
    asm volatile ("interesting_part_begin%=:":);
    do_something();
    asm volatile ("interesting_part_end%=:":);
}

У меня еще не было проблемы с этим, но asm volatile может быть очень сложным в оптимизаторе компилятора, потому что он имеет тенденцию оставлять такой код незатронутым.