__builtin_clzll在不同优化级别上的不同gcc输出并包含在函数中

时间:2017-07-14 16:28:46

标签: c linux gcc

我对以下代码的行为/输出感到困惑,这是一个错误,或者我遗漏了一些东西。 (Ubuntu 16.04 on skylake arch)

#include <iostream>

int wrap(unsigned long long val) {
    return __builtin_clzll(val);
} 

using namespace std;
int main() {
    cout << __builtin_clzll(0) << " " << wrap(0) << endl;
    cout << __builtin_clzll(1) << " " << wrap(1) << endl;
    cout << __builtin_clzll(2) << " " << wrap(2) << endl;
}

以下是不同编译设置的不同输出。我知道如果传递零,clz可能会返回一个未定义的结果。然而,直接内联调用总是很好,但只要涉及堆栈,编译器就会搞砸。

snk@maggy:~/HCS$ g++ -O0 test.cpp -o test
snk@maggy:~/HCS$ ./test
64 4196502
63 63
62 62
snk@maggy:~/HCS$ 

-O&gt; 0级别不会改变结果,我猜gcc是内联的。这是预期的结果......

snk@maggy:~/HCS$ g++ -O1 test.cpp -o test
snk@maggy:~/HCS$ ./test
64 64
63 63
62 62

使用-mlzcnt会更好:

snk@maggy:~/HCS$ g++ -O0 -mlzcnt test.cpp -o test
snk@maggy:~/HCS$ ./test
64 0
63 0
62 1

snk@maggy:~/HCS$ g++ -O1 -mlzcnt test.cpp -o test
snk@maggy:~/HCS$ ./test
64 64
63 63
62 62

snk@maggy:~/HCS$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

谢谢, CH

3 个答案:

答案 0 :(得分:2)

这个问题中有趣的案例是-mlzcnt的行为。这在2013年报告为GCC bug 58928,但错误报告后来被撤消,因为当您为不支持-mlzcnt操作码的英特尔CPU提供LZCNT时,这是“预期”行为。 / p>

事实证明,LZCNTBSR(位搜索反向),前缀为F3;在未实现LZCNT的Intel CPU上,它被解释为无效操作码,它被解释为BSR,它返回1位的位位置(位0为低位),而不是前面的0的数量。

如上所述,使用参数0调用__builtin_clz会产生未定义的行为。你应该对未定义行为的结果没有期望;甚至不会两次都是相同的结果。

答案 1 :(得分:1)

根据GCC documentation for built-in functions(添加粗体文字)

  

内置函数: int __ builtin_clz unsigned int x

     

从最多开始,返回 x 中前导0位的数量   重要的位置。 如果 x 为0,则结果未定义

     

...

     

内置函数: int __ builtin_clzll unsigned long long

     

__builtin_clz类似,但参数类型为unsigned long long

0的结果未定义。

答案 2 :(得分:1)

让我们看看没有优化的wrap

        .globl wrap(unsigned long long)
wrap(unsigned long long):

        pushq   %rbp
        movq    %rsp, %rbp

        movq    %rdi, -8(%rbp)
        bsrq    -8(%rbp), %rax
        xorq    $63, %rax

        popq    %rbp
        ret

如果源为(0),AMD和Intel 都不会修改目标寄存器 - 尽管我相信只有AMD会记录此行为。实际上,您将获得之前设置的%rax(它不是调用者保存的寄存器 - 它用于某些返回值),与(63) xor'd。因此%rax可以设置为某些“之前”的结果。可能甚至取决于address space randomization的结果。

此功能的堆栈不是罪魁祸首。 x86-64 ELF(和OS X Mach-O)ABI中的第一个参数在%rdi中传递。缺乏优化会将其溢出到堆栈帧的内存中,但它仍在(0)处存储-8(%rbp)

TL; DR - %rax在输入时保留无关的垃圾,并且不会被(0)源操作数修改为bsrxor可能会翻转几个低位。