Как убрать "шум" из вывода сборки 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

3 ответа

Решение

Зачистка .cfi Директивы, неиспользуемые метки и строки комментариев - это решенная проблема: скрипты, стоящие за проводником компилятора Мэтта Годболта, являются открытым исходным кодом для его проекта 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++ / clang++, а не gcc / clang.


Полезные опции компилятора для создания 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 выходные данные настолько шумны при хранении / перезагрузке (и так медленно) не только из-за отсутствия оптимизации, но и из-за вынужденной де-оптимизации для поддержки отладки.

Чтобы получить сочетание исходного кода и asm, используйте 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 Функции from создадут для них отдельное определение, а также определение для любых вызывающих, так что вы можете просто посмотреть на это.

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


Еще одна вещь, которую вы можете сделать: если вы сделали main() Вы можете запустить его и использовать отладчик. stepi (si) пошаговые инструкции. Смотрите в нижней части вики-тега x86 инструкции. Но помните, что код может оптимизироваться после встраивания в 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 doesn't 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-ответах / отчетах об ошибках компилятора / электронных письмах.)

Вы всегда можете посмотреть на сгенерированную сборку из объектного файла, вместо того, чтобы использовать вывод сборки компилятора. 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 синтаксис (необязательно)

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

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

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

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