У меня чертовски время, пытаясь придумать постоянное вращение во времени, которое не нарушает стандарты C/С++.
Проблема - это край/угловые случаи, в которых операции вызывается в алгоритмах, и эти алгоритмы не могут быть изменены. Например, из Crypto ++ и выполняется тестовый жгут в GCC ubsan (т.е. g++ fsanitize=undefined
):
$ ./cryptest.exe v | grep runtime
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:625:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
И код в misc.h:637
:
template <class T> inline T rotlMod(T x, unsigned int y)
{
y %= sizeof(T)*8;
return T((x<<y) | (x>>(sizeof(T)*8-y)));
}
Intel ICC был особенно беспощаден, и он удалил весь вызов функции с помощью y %= sizeof(T)*8
. Мы исправили это несколько лет назад, но оставили другие ошибки на месте из-за отсутствия постоянного решения времени.
Там осталась одна болевая точка. Когда y = 0
, я получаю условие где 32 - y = 32
, и он устанавливает поведение undefined. Если я добавлю проверку на if(y == 0) ...
, тогда код не сможет выполнить требование времени.
Я рассмотрел ряд других реализаций: от ядра Linux до других криптографических библиотек. Все они содержат одно и то же поведение undefined, поэтому он кажется тупиковым.
Как я могу выполнить поворот почти в постоянное время с минимальным количеством инструкций?
РЕДАКТИРОВАТЬ: почти постоянным временем я имею в виду избегать ветки, так что всегда выполняются одни и те же инструкции. Я не беспокоюсь о таймингах микрокода процессора. Хотя предсказание ветвления может быть велико на x86/x64, оно может не работать также на других платформах, например вложенных.
Ни один из этих трюков не потребуется, если GCC или Clang предоставил возможность выполнять вращение в почти постоянное время. Я даже согласился на "выполнить поворот", так как у них даже этого нет.