Этот вопрос следует за этим один, учитывая GCC
совместимый компилятор и x86-64
архитектура.
Мне интересно, есть ли разница между option 1
, option 2
а также option 3
ниже. Будет ли результат одинаковым во всех контекстах или будет другим. И если так, то какая разница?
// Option 1
asm volatile(:::"memory");
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):);
а также
// Option 2
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):);
asm volatile(:::"memory");
а также
// Option 3
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
Варианты 1 & 2 позволил бы переупорядочить сам CPUID с не связаннымиvolatile
загружает / хранит (в одном или другом направлении). Это очень вероятно не то, что ты хочешь.
Вы могли бы поставить барьер памяти на и то и другое стороны CPUID, но лучше просто сделать CPUID барьером памяти.
Как указывает Шут, вариант 1 приведет к перезагрузке level
из памяти, если он когда-либо передавал свой адрес за пределы функции, или если он уже является глобальный или static
,
(Или каким бы ни был точный критерий, который решает, может ли переменная C быть изменена для чтения или записи asm, который использует "memory"
тряпки. Я думаю, что это по сути то же самое, что и то, что оптимизатор использует, чтобы решить, может ли переменная храниться в регистре при вызове не встроенной функции непрозрачной функции, так что чисто локальные переменные, чей адрес нигде не был передан, и что не являются входными данными для оператора 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, но с встроенным asm было бы хорошей идеей всегда использовать 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):);
}
ASM выход из 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 только для комментариев. Я отключил фильтр комментариев и нашел барьер mem. Вы можете удалить -fverbose-asm, чтобы получить меньше шума. Или используйте строку без комментариев для барьера mem: он не должен собираться, если вы просто смотрите на вывод asm компилятора. (Если вы не используете clang, в который встроен ассемблер).
Кстати, оригинальная версия вашего вопроса не скомпилирована: вы пропустили пустую строку как шаблон asm. asm(:::"memory")
, Секции output, input и clobber могут быть пустыми, но строка инструкции asm не является обязательной.
Интересный факт, вы можете поместить asm комментарии в строку:
asm volatile("# memory barrier here":::"memory");
GCC заполняет любой %whatever
вещи в шаблоне строки, так как он записывает вывод asm, так что вы можете даже делать такие вещи, как "CPUID # %%0 was in %0"
и посмотрите, что gcc выбрал для ваших «фиктивных» аргументов, которые иначе не упомянуты в шаблоне asm. (Это более интересно для фиктивных операндов ввода / вывода памяти, чтобы сообщить компилятору, какую память вы читаете / записываете вместо использования "memory"
clobber, когда вы даете оператору asm указатель.)
Других решений пока нет …