Gcc 我可以去掉CTZ和指针加法之间的符号扩展吗?

Gcc 我可以去掉CTZ和指针加法之间的符号扩展吗?,gcc,assembly,x86,Gcc,Assembly,X86,对于此类代码: #include <stdint.h> char* ptrAdd(char* ptr, uint32_t x) { return ptr + (uint32_t)__builtin_ctz(x); } 当然,这是完全冗余的——这是对无符号整数的明显符号扩展。我能说服GCC不要这样做吗 该问题自GCC 4.9.0以来就存在,但在此之前,它是一个显式的零扩展,也是冗余的。部分解决方案是使用64位版本的ctz,以及-march参数,以便使用tzcnt代替bsf,

对于此类代码:

#include <stdint.h>

char* ptrAdd(char* ptr, uint32_t x)
{
    return ptr + (uint32_t)__builtin_ctz(x);
}
当然,这是完全冗余的——这是对无符号整数的明显符号扩展。我能说服GCC不要这样做吗


该问题自GCC 4.9.0以来就存在,但在此之前,它是一个显式的零扩展,也是冗余的。

部分解决方案是使用64位版本的
ctz
,以及
-march
参数,以便使用
tzcnt
代替
bsf
,如下所示:

char* ptrAdd(char* ptr, uint32_t x)
{
    return ptr + __builtin_ctzl(x);
}
在无符号扩展中:

ptrAdd(char*, unsigned int):
  mov eax, esi
  tzcnt rax, rax
  add rax, rdi
  ret
它有一个
mov
(执行32到64位的零扩展),它取代了32位版本中的一个零化
xor
(该版本用于解决
tzcnt
)。这些成本大致相同,但是
mov
更可能在内联后消失。64位
tzcnt
的结果与32位的结果相同,但零输入的情况除外,零输入未定义(就
gcc
内部而言,不是
tzcnt

不幸的是,如果没有允许编译器使用
tzcnt
-march
参数,它将使用
bsf
,并且在这种情况下仍然执行符号扩展

似乎
bsf
tzcnt
之间不同行为的根源在于,在使用
bsf
版本的情况下,指令行为未定义为零。因此,原则上,指令可以返回任何内容,甚至是超出我们通常期望的0到63范围的值。再加上返回值被声明为
int
,简单地忽略符号扩展可能会导致“不可能”的情况,如
(\uuuuu builtin\uclzl(x)&0xff)==0xdeadbeef

现在根据gcc文档,对
\uuuu builtin\u ctzl
的零输入有一个“未定义的结果”——但不清楚这是否与C/C++“未定义的行为”相同,在C/C++“未定义的行为”中可能发生任何事情(这将允许不可能的事情),或者仅仅意味着“一些未指定的值”


您可以在上阅读此内容,其中一个问题已经公开了大约7年。

您可以不安全地使用
asm(“:”=r”(ctz64):“0”(ctz))
告诉编译器64位输出与32位输入在同一寄存器中,没有指令()。但这是非常可怕的,如果某些优化导致不只是使用tzcnt结果寄存器,则无法保护您免受输入32位值中的高垃圾的影响。另外,我不确定
bsf
如何处理
x=0
;它甚至可能不会将EAX扩展到RAX。(我看到了符号扩展,即使在明确使用tzcnt的情况下,
-march=haswell
,tzcnt总是编写EAX)。gcc有一些遗漏的优化错误,它对输出值没有足够的了解。这一个非常奇怪;我认为内部
bsf
/
tzcnt
必须定义为返回
int
long
,但是IDK为什么首先强制转换到
uint32\u t
并不能解决它。我想知道
\u内置的假设()
是否有帮助。@PeterCordes-这看起来像是优化器中一个实际的错误代码。正如我在回答中提到的,基本问题似乎是,
bsf/bsr
的未定义行为使得您通常假设的内置返回值(即,它在0-31范围内)不一定是真的,因此编译器在扩展方面必须保守,但是这里生成的代码似乎是错误的-它可能导致从
ptr
中减去一个值,忽略对unsigned的转换(除非我在这里混淆了我的C转换规则)。可能是因为这些本质是特殊情况?@BeeOnRope:从技术上讲,这不是一个bug,因为:如果x是0,那么结果是未定义的。这不是
ffs
库函数或任何东西;内置代码似乎被定义为只使用bsf而不是tzcnt高效编译。(但不幸的是,这些天没有。)似乎
gcc7.3-march=skylake
不知道skylake修复了
tzcnt
/
lzcnt
(但不是
popcnt
)对目的地的错误依赖性。我问了一些稍微不同的问题,但答案实际上是这个问题的答案(链接到同一错误条目)。
ptrAdd(char*, unsigned int):
  mov eax, esi
  tzcnt rax, rax
  add rax, rdi
  ret