Ответ 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
) пошаговые инструкции. См. в нижней части тега 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 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 ответы/сообщения об ошибках компилятора/электронные письма.)