В чем разница между «asm», «__asm» и «__asm__»?

Насколько я могу судить, единственная разница между __asm { ... }; и __asm__("..."); что первый использует mov eax, var а второй использует movl %0, %%eax с :"=r" (var) в конце. Какие еще существуют различия? А как насчет просто asm ?

Какой из них вы используете, зависит от вашего компилятора. Это не стандартно, как язык C.

Между MSVC inline asm и GNU C inline asm существует огромная разница. Синтаксис GCC предназначен для оптимального вывода без использования инструкций, для упаковки одной инструкции или чего-то еще. Синтаксис MSVC разработан довольно просто, но AFAICT невозможно использовать без задержки и дополнительных инструкций по обходному пути через память для ваших входов и выходов.

Если вы используете встроенный asm для повышения производительности, это делает MSVC inline asm жизнеспособным только в том случае, если вы полностью пишете весь цикл в asm, а не для обертывания коротких последовательностей встроенной функцией. Пример ниже (обертывание idiv с помощью функции) – это то, что MSVC плохо работает: ~ 8 дополнительных инструкций по хранению / загрузке.

MSVC inline asm (используется MSVC и, возможно, icc, возможно, также доступен в некоторых коммерческих компиляторах):

  • смотрит на ваш asm, чтобы выяснить, что регистрирует ваши шаги кода.
  • может передавать данные только через память. Данные, которые были mov ecx, shift_count в регистры, хранятся компилятором, например, для подготовки вашего mov ecx, shift_count . Таким образом, используя единую инструкцию asm, которую компилятор не будет генерировать для вас, вы должны совершать кругооборот по памяти в пути и на выходе.
  • более дружелюбный к новичкам, но часто невозможно избежать накладных расходов при вводе / выводе данных . Даже помимо ограничений синтаксиса оптимизатор в текущих версиях MSVC также не подходит для оптимизации вокруг встроенных блоков asm.

GNU C inline asm не является хорошим способом изучения asm . Вы должны понять asm очень хорошо, чтобы вы могли рассказать компилятору о своем коде. И вы должны понять, что компиляторы должны знать. Этот ответ также содержит ссылки на другие руководства inline-asm и вопросы и ответы. В wiki для x86 есть много хороших вещей для asm в целом, а просто ссылки на них для GNU inline asm. (Материал в этом ответе применим также к GNU inline asm на платформах, отличных от x86).

GNU C inline asm синтаксис используется gcc, clang, icc и, возможно, некоторые коммерческие компиляторы, которые реализуют GNU C:

  • Вы должны сказать компилятору, что вы clobber. Несоблюдение этого требования приведет к поломке окружающего кода неочевидными труднодоступными способами.
  • Мощный, но трудный для чтения, изучения и использования синтаксиса для того, чтобы сообщать компилятору, как подавать входные данные, и где искать выходы. например, "c" (shift_count) , заставит компилятор поставить переменную shift_count в ecx до того, как ваш inline asm будет запущен.
  • лишний clunky для больших блоков кода, потому что asm должен находиться внутри строковой константы. Так что вам, как правило, нужно

     "insn %[inputvar], %%reg\n\t" // comment "insn2 %%reg, %[outputvar]\n\t" 
  • очень неумолимо / тяжелее, но позволяет снизить накладные расходы. для упаковки отдельных инструкций . (обертывание отдельных инструкций было оригинальным умыслом дизайна, поэтому вам нужно специально рассказать компилятору о ранних clobbers, чтобы он не мог использовать один и тот же регистр для ввода и вывода, если это проблема).


Пример: целочисленное деление ( div ) полной ширины

На 32-битном процессоре разделение 64-битного целого на 32-битное целое число или выполнение полного умножения (32×32-> 64) может выиграть от встроенного asm. gcc и clang не используют idiv for (int64_t)a / (int32_t)b , возможно потому, что команда не работает, если результат не подходит в 32-битном регистре. Таким образом, в отличие от этого Q & A о получении частного и остального от одного div , это пример использования inline asm. (Если есть способ сообщить компилятору, что результат будет соответствовать, значит idiv не будет виноват.)

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


MSVC

Будьте осторожны с соглашениями об использовании register-arg при использовании inline-asm. По-видимому, поддержка inline-asm настолько плохо спроектирована / реализована, что компилятор не может сохранять / восстанавливать регистры arg вокруг встроенного asm, если эти аргументы не используются во встроенном asm . Спасибо @RossRidge за это.

 // MSVC. Be careful with _vectorcall & inline-asm: see above // we could return a struct, but that would complicate things int _vectorcall div64(int hi, int lo, int divisor, int *premainder) { int quotient, tmp; __asm { mov edx, hi; mov eax, lo; idiv divisor mov quotient, eax mov tmp, edx; // mov ecx, premainder // Or this I guess? // mov [ecx], edx } *premainder = tmp; return quotient; // or omit the return with a value in eax } 

Обновление: по-видимому, оставляя значение в eax или edx:eax а затем падает с конца не-void (без return ) , поддерживается даже при вставке . Я предполагаю, что это работает только в том случае, если после оператора asm нет кода. Это позволяет избежать сохранения / перезагрузки для выхода (по крайней мере, для quotient ), но мы ничего не можем сделать о вводе. В не-встроенной функции с аргументами стека они будут уже в памяти, но в этом случае мы пишем крошечную функцию, которая могла бы быть полезной для встроенных.


Скомпилирован с MSVC 19.00.23026 /O2 в реестре (с main() который находит каталог exe и выгружает выход asm компилятора в stdout ).

 ## My added comments use. ## ; ... define some symbolic constants for stack offsets of parameters ; 48 : int ABI div64(int hi, int lo, int divisor, int *premainder) { sub esp, 16 ; 00000010H mov DWORD PTR _lo$[esp+16], edx ## these symbolic constants match up with the names of the stack args and locals mov DWORD PTR _hi$[esp+16], ecx ## start of __asm { mov edx, DWORD PTR _hi$[esp+16] mov eax, DWORD PTR _lo$[esp+16] idiv DWORD PTR _divisor$[esp+12] mov DWORD PTR _quotient$[esp+16], eax ## store to a local temporary, not *premainder mov DWORD PTR _tmp$[esp+16], edx ## end of __asm block mov ecx, DWORD PTR _premainder$[esp+12] mov eax, DWORD PTR _tmp$[esp+16] mov DWORD PTR [ecx], eax ## I guess we should have done this inside the inline asm so this would suck slightly less mov eax, DWORD PTR _quotient$[esp+16] ## but this one is unavoidable add esp, 16 ; 00000010H ret 8 

Есть тонна дополнительных команд mov, и компилятор даже не приближается к оптимизации любого из них. Я подумал, что, возможно, это увидит и поймет mov tmp, edx внутри встроенного asm и сделайте это хранилищем для premainder . premainder , это потребует загрузки premainder из стека в регистр перед встроенным блоком asm.

Эта функция на самом деле хуже с _vectorcall чем с обычной ABI. С двумя входами в регистры он сохраняет их в памяти, поэтому встроенный asm может загружать их из именованных переменных. Если бы это было включено, даже больше параметров могли бы быть в regs, и им пришлось бы хранить их все, поэтому asm имел бы операнды памяти! Так что, в отличие от gcc, мы не получаем многого от этого.

Выполнение *premainder = tmp внутри блока asm означает больше кода, написанного в asm, но не позволяет полностью сохранить магазин / путь загрузки / хранения для остатка. Это уменьшает количество команд на 2 всего, до 11 (не включая ret ).

Я пытаюсь получить лучший код из MSVC, а не «использовать его неправильно» и создать аргумент соломы. Но AFAICT это ужасно для упаковки очень коротких последовательностей. Предположительно, есть встроенная функция для разделения 64/32 -> 32, которая позволяет компилятору генерировать хороший код для этого конкретного случая, поэтому вся предпосылка использования inline asm для этого на MSVC может быть аргументом соломы . Но это показывает вам, что intrinsics намного лучше, чем встроенный asm для MSVC.


GNU C (gcc / clang / icc)

Gcc делает даже лучше, чем результат, показанный здесь при встраивании div64, поскольку он обычно может организовать для предыдущего кода генерировать 64-битное целое число в edx: eax в первую очередь.

Я не могу получить gcc для компиляции для 32-битного векторного ABI. Clang может, но он сосет во встроенном asm с ограничениями "rm" (попробуйте его на ссылке godbolt: он отскакивает функцию arg через память вместо использования опции register в ограничении). Соглашение о назначении 64-битной MS близко к 32-битовому векторному коду с первыми двумя параметрами в edx, ecx. Разница в том, что еще 2 параметра входят в регионы перед использованием стека (и что вызываемый пользователь не выставляет аргументы из стека, что и было, что ret 8 был на выходе MSVC).

 // GNU C // change everything to int64_t to do 128b/64b -> 64b division // MSVC doesn't do x86-64 inline asm, so we'll use 32bit to be comparable int div64(int lo, int hi, int *premainder, int divisor) { int quotient, rem; asm ("idivl %[divsrc]" : "=a" (quotient), "=d" (rem) // a means eax, d means edx : "d" (hi), "a" (lo), [divsrc] "rm" (divisor) // Could have just used %0 instead of naming divsrc // note the "rm" to allow the src to be in a register or not, whatever gcc chooses. // "rmi" would also allow an immediate, but unlike adc, idiv doesn't have an immediate form : // no clobbers ); *premainder = rem; return quotient; } 

скомпилирован с gcc -m64 -O3 -mabi=ms -fverbose-asm . С -m32 вы получаете только 3 загрузки, idiv и магазин, как вы можете видеть из изменения материала в этой ссылке godbolt.

 mov eax, ecx # lo, lo idivl r9d # divisor mov DWORD PTR [r8], edx # *premainder_7(D), rem ret 

Для 32bit vectorcall gcc будет делать что-то вроде

 ## Not real compiler output, but probably similar to what you'd get mov eax, ecx # lo, lo mov ecx, [esp+12] # premainder idivl [esp+16] # divisor mov DWORD PTR [ecx], edx # *premainder_7(D), rem ret 8 

MSVC использует 13 команд (не включая ret) по сравнению с gcc 4. С вложением, как я уже сказал, он потенциально компилируется только один, в то время как MSVC все равно будет использовать, вероятно, 9. (Нет необходимости резервировать пространство стека или нагрузку я предполагаю, что он все равно должен хранить около 2 из 3 входов, а затем перезагружает их внутри asm, запускает idiv , сохраняет два выхода и перезагружает их вне asm. Таким образом, это 4 загрузки / хранения для ввода и еще 4 для вывода.)

С gcc-компилятором это не имеет большого значения. asm или __asm или __asm__ одинаковы, они просто используют, чтобы избежать цели пространства имен конфликта (есть функция, определенная пользователем, имя asm и т. д.),

asm vs __asm__ в GCC

asm не работает с -std=c99 , у вас есть две альтернативы:

  • использовать __asm__
  • use -std=gnu99

Подробнее: ошибка: «asm» uneclared (сначала использовать в этой функции)

__asm vs __asm__ в GCC

Я не мог найти, где документирован документ __asm (особенно не упоминается в https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Alternate-Keywords.html#Alternate-Keywords ), но из источника GCC 8.1 они точно такие же:

  { "__asm", RID_ASM, 0 }, { "__asm__", RID_ASM, 0 }, 

поэтому я бы просто использовал __asm__ который документирован.

  • Установка Visual Studio 2010 (любая версия) устанавливает только 2 файла в каталоге заголовков C ++
  • C ++ Метод статического члена вызывает экземпляр classа
  • Почему сравнение double и float приводит к неожиданному результату?
  • Почему происходит сбой этой программы: передача std :: string между DLL
  • ошибка LNK2038: обнаружено несоответствие для «_MSC_VER»: значение «1600» не соответствует значению «1700» в CppFile1.obj
  • vcvarsall.bat, необходимый для компиляции python из visual studio 2015 (v 14)
  • Функция OpenSV SURF не реализована
  • Установка OpenCV 2.4.3 в Visual C ++ 2010 Express
  • Неразрешенный внешний символ в объектных файлах
  • Инициализация значений и типы не POD
  • Утверждение не выполнено (size.width> 0 && size.height> 0)
  • Interesting Posts

    Могу ли я захватить управление Cmd-Left в OSX?

    Как разбить строку на подстроки заданной длины?

    Конец файла (EOF) стандартного входного streamа (stdin)

    Внутренний беспроводной адаптер не работает после перемещения ноутбука

    Неправильная попытка чтения, когда данные отсутствуют

    Изменение формата даты на «% d /% m /% Y»

    Как переименовать длинные имена файлов в короткие в Linux?

    Мои значки в папке и приложении на Windows 7 являются универсальными

    Отдых против мыла. У REST лучшая производительность?

    Как отключить поиск и догадывание доменов в адресной строке Firefox, сохраняя при этом другие функции?

    Link Spark с ноутбуком iPython

    Как заблокировать горизонтальную прокрутку scrollView в iOS

    Окончательный способ использования Picasa в домашней сети

    Как написать регулярное выражение, которое соответствует не жадным?

    В чем разница между двумя рабочими процессами? Когда использовать stream кода авторизации?

    Давайте будем гением компьютера.