Варианты 1 и 2 позволяют переупорядочивать сам CPUID с несвязанными загрузками/сохранениями, не относящимися к volatile
(в том или ином направлении). Скорее всего, это не то, что вам нужно.
Вы можете установить барьер памяти с обеих сторон CPUID, но, безусловно, лучше просто сделать CPUID барьером памяти.
Как указывает Jester, вариант 1 заставит перезагрузить level
из памяти, если его адрес когда-либо передавался вне функции или если он уже является глобальным или static
.
(Или какой-либо точный критерий, который решает, может ли переменная C быть изменена для чтения или записи с помощью ассемблера, который использует "memory"
clobber. Я думаю, что это по существу то же самое, что оптимизатор использует, чтобы решить, может ли переменная храниться в регистре через невстроенный вызов функции для непрозрачной функции, поэтому чистые локальные переменные, адрес которых никуда не передается и которые не являются входными данными для оператора asm, могут по-прежнему находиться в регистрах).
Например (Проводник компилятора Godbolt):
void foo(int level){
int eax, ebx, ecx, edx;
asm volatile("":::"memory");
asm volatile("CPUID"
: "=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx)
: "0"(level)
:
);
}
# x86-64 gcc7.3 -O3 -fverbose-asm
pushq %rbx # # rbx is call-preserved, but we clobber it.
movl %edi, %eax # level, eax
CPUID
popq %rbx #
ret
Обратите внимание на отсутствие сброса/перезагрузки функции arg.
Обычно я бы использовал синтаксис Intel, но со встроенным ассемблером рекомендуется всегда использовать AT&T, если вы не используете синтаксис ненависти AT&T или не знаете его.
Даже если он запускается в памяти (соглашение о вызовах i386 System V с аргументами стека), компилятор все равно решает, что ничто другое (включая оператор asm
с затиранием памяти) не может ссылаться на него. Но как определить разницу между задержкой загрузки? Измените функцию arg перед барьером, а затем используйте ее после:
void modify_level(int level){
level += 1; // modify level before the barrier
int eax, ebx, ecx, edx;
asm volatile("#mem barrier here":::"memory");
asm volatile("CPUID" // then read it after
: "=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx)
: "0"(level):);
}
Ассемблерный вывод из gcc -m32 -O3 -fverbose-asm
:
modify_level(int):
pushl %ebx #
#mem barrier here
movl 8(%esp), %eax # level, tmp97
addl $1, %eax #, level
CPUID
popl %ebx #
ret
Обратите внимание, что компилятор позволяет переупорядочивать level++
через барьер памяти, потому что это локальная переменная.
Godbolt фильтрует написанные от руки комментарии asm вместе со строками комментариев asm, сгенерированными компилятором. Я отключил фильтр комментариев и нашел барьер памяти. Вы можете удалить -fverbose-asm, чтобы получить меньше шума. Или используйте строку без комментариев для барьера памяти: ее не нужно собирать, если вы просто смотрите на вывод компилятора asm. (Если вы не используете clang со встроенным ассемблером).
Кстати, исходная версия вашего вопроса не скомпилировалась: вы не указали пустую строку в качестве шаблона asm. asm(:::"memory")
. Разделы output, input и clobber могут быть пустыми, но строка инструкции asm необязательна.
Забавный факт, вы можете поместить ассемблерные комментарии в строку:
asm volatile("# memory barrier here":::"memory");
gcc заполняет все %whatever
вещей в строковом шаблоне по мере записи ассемблерного вывода, так что вы даже можете делать что-то вроде "CPUID # %%0 was in %0"
и смотреть, что gcc выбрал для ваших "фиктивных" аргументов, которые иначе не упоминаются в ассемблерном шаблоне. (Более интересно, когда фиктивные операнды ввода/вывода памяти сообщают компилятору, какую память вы читаете/записываете, вместо использования "memory"
клобера, когда вы даете оператору asm указатель.)
person
Peter Cordes
schedule
31.01.2018
level
из памяти, если она там окажется. - person Jester   schedule 30.01.2018volatile
. Скорее всего, это не то, что вам нужно. - person Peter Cordes   schedule 31.01.2018