C++ 不违反标准的近似恒定时间旋转

C++ 不违反标准的近似恒定时间旋转,c++,bitwise-operators,undefined-behavior,constant-time,C++,Bitwise Operators,Undefined Behavior,Constant Time,我花了很长时间才想出一个不违反C/C++标准的恒定时间旋转 问题是边/角情况,在这种情况下,运算在算法中调用,而这些算法无法更改。例如,以下内容来自并在(即,g++fsanize=undefined)下执行测试线束: $。/cryptest.exe v|grep运行时 杂项h:637:22:运行时错误:移位指数32对于32位类型“unsigned int”太大 杂项h:643:22:运行时错误:移位指数32对于32位类型“unsigned int”太大 杂项h:625:22:运行时错误:移位指数

我花了很长时间才想出一个不违反C/C++标准的恒定时间旋转

问题是边/角情况,在这种情况下,运算在算法中调用,而这些算法无法更改。例如,以下内容来自并在(即,
g++fsanize=undefined
)下执行测试线束:

$。/cryptest.exe v|grep运行时
杂项h:637:22:运行时错误:移位指数32对于32位类型“unsigned int”太大
杂项h:643:22:运行时错误:移位指数32对于32位类型“unsigned int”太大
杂项h:625:22:运行时错误:移位指数32对于32位类型“unsigned int”太大
杂项h:637:22:运行时错误:移位指数32对于32位类型“unsigned int”太大
杂项h:643:22:运行时错误:移位指数32对于32位类型“unsigned int”太大
杂项h:637:22:运行时错误:移位指数32对于32位类型“unsigned int”太大
代码位于
杂项h:637

template inline T rotlMod(tx,unsigned int y)
{
y%=尺寸f(T)*8;
返回T((x(sizeof(T)*8-y));
}
英特尔的ICC特别无情,它删除了整个函数调用,而没有使用
y%=sizeof(T)*8
。我们在几年前修复了这个问题,但由于缺少恒定时间的解决方案,所以保留了其他勘误表

还有一个痛点。当
y=0
时,我得到一个条件,其中
32-y=32
,它设置了未定义的行为。如果我为
If(y==0)
添加检查,则代码无法满足恒定时间要求

我已经研究了许多其他实现,从Linux内核到其他加密库。它们都包含相同的未定义行为,因此这似乎是一条死胡同

如何以最少的指令数在几乎恒定的时间内执行旋转

编辑:所谓接近恒定时间,我的意思是避免分支,以便始终执行相同的指令。我不担心CPU微码计时。虽然分支预测在x86/x64上可能很好,但在其他平台(如嵌入式)上可能表现不好



如果或提供了执行该操作的内在机制,则不需要这些技巧。我甚至满足于“执行旋转”,因为他们甚至没有这种功能。

您可以添加一个额外的模运算来防止移位32位,但我不认为这比使用if检查和分支预测器更快

template <class T> inline T rotlMod(T x, unsigned int y)
{
    y %= sizeof(T)*8;
    return T((x<<y) | (x>>((sizeof(T)*8-y) % (sizeof(T)*8))));
}
template inline T rotlMod(tx,unsigned int y)
{
y%=尺寸f(T)*8;
回报率T((x((sizeof(T)*8-y)%(sizeof(T)*8));
}

额外模的另一种选择是乘以0或1(多亏了
!!
):

template T rotlMod(tx,unsigned int y)
{
y%=尺寸f(T)*8;
返回T((x>(!!y)*(sizeof(T)*8-y));
}

将表达式写入
T((x(sizeof(T)*CHAR_BITS-y-1)>>1))
应该为小于位大小的
y
的所有值生成定义的行为,假设
T
是无填充的无符号类型。除非编译器有一个好的优化器,否则生成的代码可能不如原始表达式生成的代码好。必须忍受笨重的难读代码w然而,这将在许多编译器上产生较慢的执行速度是进步代价的一部分,因为超现代编译器

if (y) do_something();
return T((x<<y) | (x>>(sizeof(T)*8-y)));
如果(y)做某事();
返回T((x(sizeof(T)*8-y));
可以通过无条件地调用
do\u something
来提高代码的“效率”


PS:我想知道是否有任何真实世界的平台,当
y
精确地等于
x
的位大小时,需要更改右移的定义,以便
x>>y
可以产生0或x,但可以以任意方式进行选择(未指定)时尚,将要求平台生成额外的代码,或者在非人为的场景中排除真正有用的优化?

我已经链接到这个答案,以获取其他几个“轮换”问题的全部细节,包括,应该与最佳实践保持最新

我发现了一篇关于这个问题的博文,看起来它终于解决了问题(有了足够新的编译器版本)

推荐他尝试创建旋转函数的“c”版本。我用位AND替换了他的断言,发现它仍然编译为单个旋转insn

typedef uint32_t rotwidth_t;  // parameterize for comparing compiler output with various sizes

rotwidth_t rotl (rotwidth_t x, unsigned int n)
{
  const unsigned int mask = (CHAR_BIT*sizeof(x)-1);  // e.g. 31

  assert ( (n<=mask)  &&"rotate by type width or more");
  n &= mask;  // avoid undef behaviour with NDEBUG.  0 overhead for most types / compilers
  return (x<<n) | (x>>( (-n)&mask ));
}

rotwidth_t rot_const(rotwidth_t x)
{
  return rotl(x, 7);
}
内联时,编译器首先应该能够将值安排在正确的寄存器中,从而只进行一次循环

对于较旧的编译器,当rotate count是编译时常量时,您仍然可以得到理想的代码。Godbolt允许您以ARM作为目标进行测试,并且它在那里也使用了rotate。对于较旧的编译器上的变量count,您会得到一些代码膨胀,但没有分支或主要的性能问题,因此此习惯用法在一般情况下应该是安全的。

顺便说一句,我修改了John Regehr的原稿,使用CHAR_BIT*sizeof(x),gcc/clang/icc也为
uint64_t
发出最佳代码。但是,我确实注意到,当函数返回类型仍然为
uint32_t
时,将
x
更改为
uint64_t
会使gcc将其编译为移位/或。因此,如果需要低32b,请小心将结果转换为单独序列点中的32位例如,将结果赋给一个64位变量,然后强制转换/返回它。icc仍会生成一个旋转insn,但gcc和clang不会

// generates slow code: cast separately.
uint32_t r = (uint32_t)( (x<<n) | (x>>( -n&(CHAR_BIT*sizeof(x)-1) )) );
//生成慢代码:单独强制转换。
uint32_t r=(uint32_t)((x(-n&(字符位*sizeof(x)-1)));

如果有人能用MSVC测试这一点,了解那里发生了什么会很有用。

检查处理器的汇编语言。大多数处理器都有旋转和旋转
# gcc 4.9.2 rotl(unsigned int, unsigned int):
    movl    %edi, %eax
    movl    %esi, %ecx
    roll    %cl, %eax
    ret
# rot_const(unsigned int):
    movl    %edi, %eax
    roll    $7, %eax
    ret
// generates slow code: cast separately.
uint32_t r = (uint32_t)( (x<<n) | (x>>( -n&(CHAR_BIT*sizeof(x)-1) )) );