Как убрать "шум" из вывода сборки 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
может быть очень сложным для оптимизатора компилятора, потому что он имеет тенденцию оставлять такой код без изменений.