>> И что, это уже не костыль?
> Нет.Ну-ну. Особенно приведенный вами вами код.
> __asm__ ("" : "=r" (var) : "" (var))
соберите с ним ядро и наслаждайтесь скоростью (намек: шланг хоть ругается на "invalid constraint", а гцц "кушает" и не обляпывается, генерируя
cmp rdx, 16
jne .L5
ret
Но ладно, не будем придираться. Просто "некостыльность" обычно несколько иначе выглядит.
> строгим упорядочиванием составляющих эту функцию команд.
Гм, намекну:
Берем оригинальный код и делаем так:
unsigned long neq = 0;
unsigned long tmp1, tmp2;
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
if (sizeof(unsigned long) == 8) {
tmp1 = *(unsigned long *)(a) ^ *(unsigned long *)(b);
tmp2 = *(unsigned long *)(a + 8) ^ *(unsigned long *)(b + 8);
neq |= tmp1;
OPTIMIZER_HIDE_VAR(neq);
neq |= tmp2;
OPTIMIZER_HIDE_VAR(neq);
Для gcc49 -O2 -DCONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS на выходе у нас будет:
cmp rdx, 16
jne .L5
mov rdx, QWORD PTR [rdi+8]
xor rdx, QWORD PTR [rsi+8]
mov rax, QWORD PTR [rdi]
xor rax, QWORD PTR [rsi]
or rax, rdx
ret
Вот это вам выдаст шланг37 -O2
# BB#1: # %sw.bb
mov rax, qword ptr [rsi]
mov rcx, qword ptr [rsi + 8]
xor rax, qword ptr [rdi]
xor rcx, qword ptr [rdi + 8]
#APP
#NO_APP
or rax, rcx
#APP
#NO_APP
pop rbp
ret
ВНЕЗАПНО – и там и там – совершенно неотличимо от оригинала!1
Делаем такой же фокус для ветки else:
movzx edx, BYTE PTR [rdi+1]
movzx eax, BYTE PTR [rdi]
xor dl, BYTE PTR [rsi+1]
xor al, BYTE PTR [rsi]
movzx edx, dl
movzx eax, al
or rax, rdx
Опять же, как и ожидалось, сгенерированный код одинаков в обоих случаях, но и не соответствует порядку "составляющих эту функцию команд".
Да и чего ему соответствовать, когда костыляние OPTIMIZER_HIDE_VAR(var) не дает убрать лишние OR (и заодно "закрепляет" их очередность, да, хотя от этого мало что меняется – вариации в тайминге будут полюбому наамного больше зависить от кэшмисов).
> Указание — единственный эффект этой конструкции.
> Эта вставка вводит фиктивную зависимость по данным между обращениями к neq.
Нет.
neq = neq | expr = (a ^ b);
OPTIMIZER_HIDE_VAR(neq);
neq = neq | expr2 = (a2 ^ b2);
ничто не мешает компилятору вынести expr1,2 и т.д отдельно
Вот это вводит фиктивную зависимость по данным:
tmp = a ^ b;
OPTIMIZER_HIDE_VAR(neq);
OPTIMIZER_HIDE_VAR(tmp);
neq |= tmp;
Только она там нужна, как рыбе зонтик, ибо еще раз повторюсь – разброс в тайминге из-за кэша будет куда выше, чем из-за очередности выполнения (X)OR r, mem .
А вот преждевременное "or foo,bar; jnz quit" после сравнения первого байта – будет, как бы, очень заметно.
> правильно поняли часть её назначения: гарантировать, что сгенерированный компилятором
> код будет в точности, без изъятий соответствовать коду, написанному на C.
Там, наверху, можно посмотреть на "соответствование".
> Это вторая часть решения задачи обеспечения постоянного времени выполнения функции.
На сферическо-вакуумных суперскалярах – вполне. А так, см. тайминги для xor/or r/m и "сколько стоит кэшмисс".
> Однако ваше опасение справедливо лишь для случаев, когда значения аргументов a и
> b, а также константные смещения относительно них известны компилятору во время
> компиляции.
Компилятору достаточно того, что любое значение neq !=0 возвращает в итоге единицу.
Это, и зависимость neq от (a xor b) позволяет вообще выкинуть neq и сразу проверять результат XORа.
> Совершенно верно, это фиктивная зависимость по данным. Но предотвращает она не dead
> code elimination (тогда здесь было бы достаточно спецификатора volatile при объявлении
> neq), а переупорядочивание выражений друг относительно друга.
Хоть в ядро бы глянули, что там по этому поводу писали и чего опасались:
https://github.com/torvalds/linux/commit/6bf37e5aa90f18baf5a...
> crypto_memneq is declared noinline, placed in its own source file,
> and compiled with optimizations that might increase code size disabled
> ("Os") because a smart compiler (or LTO) might notice that the return
> value is always compared against zero/nonzero, and might then
> reintroduce the same early-return optimization that we are trying to
> avoid.
https://github.com/torvalds/linux/commit/fe8c8a126806fea4465...
> Instead of disabling compiler optimizations, use a dummy inline assembly
> (based on RELOC_HIDE) to block the problematic kinds of optimization,
> The dummy inline assembly is added after every OR, and has the
> accumulator variable as its input and output. The compiler is forced to
> assume that the dummy inline assembly could both depend on the
> accumulator variable and change the accumulator variable, so it is
> forced to compute the value correctly before the inline assembly, and
> cannot assume anything about its value after the inline assembly.
Хотя, о чем это я – этож опеннет.
> gcc или clang никогда не научатся разбирать содержимое ассемблерных вставок by design.
> Это контракт. Вопрос не имеет смысла.
В вашей вселенной – возможно и контракт. В моей об этом речи нет:
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Assembl...
Да и ваш "пример" с
> __asm__ ("" : "=r" (var) : "" (var))
отлично показал "надежность" этого "контракта".