Реализации для 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 это должно быть отдельным.

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