Реализации для asm("nop") в windows
Является ли пустая строка кода, заканчивающаяся точкой с запятой, эквивалентной команде asm("nop")?
volatile int x = 5;
if(x == 5){
printf("x has not been changed yet\n");
}
else{
;//Is this the same as asm("nop") or __asm nop in windows?
//alternatively could use __asm nop or __nop();
}
Я посмотрел на этот ответ, и я не хочу использовать специфическую для x86 реализацию использования встроенной сборки. Является ли `__asm nop` эквивалентом Windows`asm volatile("nop");`от компилятора GCC
Я могу использовать эту пустоту __nop(); Функция, которую msdn, кажется, рекомендует, но я не хочу перетаскивать в библиотеку, если мне не нужно. https://docs.microsoft.com/en-us/cpp/intrinsics/nop?view=vs-2017
Есть ли дешевый, портативный способ добавить инструкцию nop, которая не будет скомпилирована? Я думал, что пустая точка с запятой либо не была, либо скомпилирована, но по какой-то причине я не могу найти информацию об этом сегодня вечером.
РЕДАКТИРОВАНИЕ РАЗЪЯСНЕНИЯ Я могу использовать встроенный ассемблер, чтобы сделать это для x86, но я хотел бы, чтобы он был переносимым. Я могу использовать библиотеку Windows __nop (), но я не хочу импортировать библиотеку в мой проект, это нежелательно.
Я ищу более умный способ создания инструкции NOP, которая не будет оптимизирована (предпочтительно со стандартным синтаксисом C), которая может быть превращена в MACRO и использована в проекте, с минимальными издержками и работой (или может быть легко улучшена до работа) на windows/linux/x86/x64.
Благодарю.
2 ответа
Я имею в виду, что я не хочу добавлять библиотеку только для того, чтобы заставить компилятор добавить NOP.
... способом, который не зависит от настроек компилятора (таких как настройки оптимизации) и способом, который работает со всеми версиями Visual C++ (и, возможно, даже с другими компиляторами):
Нет шансов: компилятор свободен в том, как он генерирует код, если код ассемблера имеет поведение, описанное кодом C.
И потому что NOP
Инструкция не меняет поведение программы, компилятор может добавить ее или оставить вне.
Даже если вы нашли способ заставить компилятор сгенерировать NOP
: Одно обновление компилятора или обновление Windows, изменяющее некоторый файл, и компилятор может не генерировать NOP
инструкция больше.
Я могу использовать встроенный asm, чтобы сделать это для x86, но я хотел бы, чтобы он был переносимым.
Как я писал выше, любой способ заставить компилятор написать NOP
будет работать только на определенной версии компилятора для определенного процессора.
Использование встроенной сборки или __nop()
Вы можете охватить все компиляторы определенного производителя (например: все компиляторы GNU C или все варианты Visual C++ и т. д.).
Другой вопрос будет: вам явно нужен "официальный" NOP
инструкция или вы можете жить с любой инструкцией, которая ничего не делает?
Если бы вы могли жить с любой инструкцией, не делая (почти) ничего, читая глобальный или статический volatile
переменная может быть заменой NOP
:
static volatile char dummy;
...
else
{
(void)dummy;
}
Это должно заставить компилятор добавить MOV
инструкция чтения переменной dummy
,
Фон:
Если вы написали драйвер устройства, вы можете связать переменную dummy
в каком-то месте, где чтение переменной имеет "побочные эффекты". Пример: чтение переменной, расположенной в видеопамяти VGA, может повлиять на содержимое экрана!
С использованием volatile
Ключевое слово: вы не только сообщаете компилятору, что значение переменной может измениться в любое время, но также и то, что чтение переменной может иметь такие последствия.
Это означает, что компилятор должен предположить, что не чтение переменной приводит к неправильной работе программы. Он не может оптимизировать (фактически ненужный) MOV
Инструкция чтения переменной.
Является ли пустая строка кода, заканчивающаяся точкой с запятой, эквивалентной команде asm("nop")?
Нет, конечно нет. Вы могли бы тривиально попробовать это сами. (На вашей собственной машине или в проводнике компилятора Godbolt, https://godbolt.org/)
Вы не хотели бы, чтобы невинные макросы CPP вводили NOP, если FOO(x);
расширен до всего ;
потому что соответствующее определение для FOO()
в этом случае была пустая строка.
__nop()
не является библиотечной функцией Это свойство, которое делает именно то, что вы хотите. например
#ifdef USE_NOP
#ifdef _MSC_VER
#include <intrin.h>
#define NOP() __nop() // _emit 0x90
#else
// assume __GNUC__ inline asm
#define NOP() asm("nop") // implicitly volatile
#endif
#else
#define NOP() // no NOPs
#endif
int idx(int *arr, int b) {
NOP();
return arr[b];
}
компилируется с Clang7.0 -O3 для x86-64 Linux в этот ассемблер
idx(int*, int):
nop
movsxd rax, esi # sign extend b
mov eax, dword ptr [rdi + 4*rax]
ret
компилируется с 32-битным x86 MSVC 19.16 -O2 -Gv к этому ассемблеру
int idx(int *,int) PROC ; idx, COMDAT
npad 1 ; pad with a 1 byte NOP
mov eax, DWORD PTR [ecx+edx*4] ; __vectorcall arg regs
ret 0
и компилирует с x64 MSVC 19.16 -O2 -Gv к этому asm ( Godbolt для всех них):
int idx(int *,int) PROC ; idx, COMDAT
movsxd rax, edx
npad 1 ; pad with a 1 byte NOP
mov eax, DWORD PTR [rcx+rax*4] ; x64 __vectorcall arg regs
ret 0
Интересно, что знак-расширение b
до 64-х бит делается до NOP. Очевидно, x64 MSVC требует (по умолчанию), чтобы функции начинались как минимум с 2-байтовой или более длинной инструкции (после пролога 1-байтового) push
инструкции, может быть?), поэтому они поддерживают горячее исправление с jmp rel8
,
Если вы используете это в функции с 1 инструкцией, вы получите npad 2
(2 байта NOP) перед npad 1
от x64 MSVC:
int bar(int a, int b) {
__nop();
return a+b;
}
;; x64 MSVC 19.16
int bar(int,int) PROC ; bar, COMDAT
npad 2
npad 1
lea eax, DWORD PTR [rcx+rdx]
ret 0
Я не уверен, насколько настойчиво MSVC будет переупорядочивать NOP по отношению к чистым инструкциям регистра, но a^=b;
после __nop()
на самом деле приведет к xor ecx, edx
перед инструкцией NOP.
Но в отношении В этом случае MSVC решает не менять порядок доступа к памяти, чтобы заполнить этот 2-байтовый слот.
int sink;
int foo(int a, int b) {
__nop();
sink = 1;
//a^=b;
return a+b;
}
;; MSVC 19.16 -O2
int foo(int,int) PROC ; foo, COMDAT
npad 2
npad 1
lea eax, DWORD PTR [rcx+rdx]
mov DWORD PTR int sink, 1 ; sink
ret 0
Сначала он делает LEA, но не двигает его раньше __nop()
; кажется очевидной пропущенной оптимизацией, но опять же, если вы вставляете __nop()
инструкции, то оптимизация явно не является приоритетом.
Если вы скомпилировали в .obj
или же .exe
и разобрали, вы бы увидели равнину 0x90 nop
, Но Godbolt не поддерживает это для MSVC, к сожалению, только для компиляторов Linux, поэтому все, что я могу легко сделать, это скопировать вывод текста asm.
И, как и следовало ожидать, с __nop()
Если функция определена, функции компилируются нормально, с тем же кодом, но без npad
директивы.
nop
Инструкция будет выполняться столько раз, сколько макрос NOP() выполняет в абстрактной машине C. Заказ по окружающие volatile
доступ к памяти не гарантируется оптимизатором или WRT. расчеты в регистрах.
Если вы хотите, чтобы это был барьер переупорядочения памяти во время компиляции, для GNU C используйте asm("nop"::: "memory");`. Я полагаю, что для MSVC это должно быть отдельным.