Загрязненная память в двух вызовах встроенной сборки по сравнению с одним вызовом встроенной сборки?

Этот вопрос следует за этим один, учитывая 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");

2

Решение

Варианты 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 указатель.)

2

Другие решения

Других решений пока нет …