Реализации для asm ("nop") в windows - PullRequest
0 голосов
/ 28 февраля 2019

Является ли пустая строка кода, которая заканчивается точкой с запятой, эквивалентной инструкции 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

Я могу использовать этот void __nop ();Функция, которую msdn, кажется, рекомендует, но я не хочу перетаскивать в библиотеку, если мне не нужно.https://docs.microsoft.com/en-us/cpp/intrinsics/nop?view=vs-2017

Есть ли дешевый, переносимый способ добавить инструкцию nop, которая не будет скомпилирована?Я думал, что пустая точка с запятой либо не была, либо скомпилирована, но я по какой-то причине не могу найти информацию об этом сегодня вечером.

РЕДАКТИРОВАТЬ УТОЧНЕНИЕ Я могу использовать inline asm, чтобы сделать это для x86, но яхотел бы, чтобы это было портативно.Я могу использовать библиотеку Windows __nop (), но я не хочу импортировать библиотеку в свой проект, это приводит к нежелательным накладным расходам.

Я ищу более умный способ создания инструкции NOP, которая не будет оптимизирована (предпочтительно со стандартным синтаксисом C), который можно превратить в MACRO и использовать в проекте, с минимальными накладными расходами и работает (или его можно легко улучшить) для windows / linux / x86 / x64.

Спасибо.

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Я имею в виду, я не хочу добавлять библиотеку только для того, чтобы заставить компилятор добавить 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, читающую переменную.

0 голосов
/ 28 февраля 2019

Является ли пустая строка кода, которая заканчивается точкой с запятой, эквивалентной команде 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 с этим asm

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 с этим asm

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 к этому ассемблеру ( 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.


Если вы используете tВ функции с 1 инструкцией вы получаете npad 2 (2-байтовый NOP) перед npad 1 от MSVC x64:

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() ifdefed out,функции компилируются нормально, с тем же кодом, но без директивы npad.


Инструкция nop будет выполняться столько раз, сколько макрос NOP () выполняет в C абстрактноммашина. Заказ в отношении.окружающий доступ не к volatile памяти не гарантируется оптимизатором или относительно него.вычисления в регистрах.

Если вы хотите, чтобы он был барьером переупорядочения памяти во время компиляции, для GNU C используйте asm ("nop" ::: "memory"); `.Я полагаю, что для MSVC это должно быть отдельным.

...