clang 试图优化这个简单的递归算法是什么?

Lun*_*din 7 c x86 assembly clang

在写下这个问题的答案时:Using variable vs. using number我用 -O3 运行了 clang x86 9.0.0/trunk 以查看它是否可以对这个简单代码进行尾调用优化:

int faculty1 (const unsigned int n) {
    return n == 1 ? n : n * faculty1(n - 1);
}
Run Code Online (Sandbox Code Playgroud)

clang 不仅失败了,而且完全变成了香蕉并给了我这个:

神箭

.LCPI0_0:
        .long   0                       # 0x0
        .long   4294967295              # 0xffffffff
        .long   4294967294              # 0xfffffffe
        .long   4294967293              # 0xfffffffd
.LCPI0_1:
        .long   1                       # 0x1
        .long   1                       # 0x1
        .long   1                       # 0x1
        .long   1                       # 0x1
.LCPI0_2:
        .long   4294967292              # 0xfffffffc
        .long   4294967292              # 0xfffffffc
        .long   4294967292              # 0xfffffffc
        .long   4294967292              # 0xfffffffc
.LCPI0_3:
        .long   4294967288              # 0xfffffff8
        .long   4294967288              # 0xfffffff8
        .long   4294967288              # 0xfffffff8
        .long   4294967288              # 0xfffffff8
.LCPI0_4:
        .long   4294967284              # 0xfffffff4
        .long   4294967284              # 0xfffffff4
        .long   4294967284              # 0xfffffff4
        .long   4294967284              # 0xfffffff4
.LCPI0_5:
        .long   4294967280              # 0xfffffff0
        .long   4294967280              # 0xfffffff0
        .long   4294967280              # 0xfffffff0
        .long   4294967280              # 0xfffffff0
.LCPI0_6:
        .long   4294967276              # 0xffffffec
        .long   4294967276              # 0xffffffec
        .long   4294967276              # 0xffffffec
        .long   4294967276              # 0xffffffec
.LCPI0_7:
        .long   4294967272              # 0xffffffe8
        .long   4294967272              # 0xffffffe8
        .long   4294967272              # 0xffffffe8
        .long   4294967272              # 0xffffffe8
.LCPI0_8:
        .long   4294967268              # 0xffffffe4
        .long   4294967268              # 0xffffffe4
        .long   4294967268              # 0xffffffe4
        .long   4294967268              # 0xffffffe4
.LCPI0_9:
        .long   4294967264              # 0xffffffe0
        .long   4294967264              # 0xffffffe0
        .long   4294967264              # 0xffffffe0
        .long   4294967264              # 0xffffffe0
faculty1:                               # @faculty1
        mov     eax, 1
        cmp     edi, 1
        je      .LBB0_12
        lea     ecx, [rdi - 1]
        mov     eax, 1
        cmp     ecx, 8
        jb      .LBB0_11
        mov     r8d, ecx
        and     r8d, -8
        movd    xmm0, edi
        pshufd  xmm6, xmm0, 0           # xmm6 = xmm0[0,0,0,0]
        paddd   xmm6, xmmword ptr [rip + .LCPI0_0]
        lea     edx, [r8 - 8]
        mov     esi, edx
        shr     esi, 3
        add     esi, 1
        mov     eax, esi
        and     eax, 3
        cmp     edx, 24
        jae     .LBB0_4
        movdqa  xmm1, xmmword ptr [rip + .LCPI0_1] # xmm1 = [1,1,1,1]
        movdqa  xmm4, xmm1
        jmp     .LBB0_6
.LBB0_4:
        and     esi, -4
        neg     esi
        movdqa  xmm1, xmmword ptr [rip + .LCPI0_1] # xmm1 = [1,1,1,1]
        movdqa  xmm9, xmmword ptr [rip + .LCPI0_3] # xmm9 = [4294967288,4294967288,4294967288,4294967288]
        movdqa  xmm10, xmmword ptr [rip + .LCPI0_4] # xmm10 = [4294967284,4294967284,4294967284,4294967284]
        movdqa  xmm11, xmmword ptr [rip + .LCPI0_5] # xmm11 = [4294967280,4294967280,4294967280,4294967280]
        movdqa  xmm12, xmmword ptr [rip + .LCPI0_6] # xmm12 = [4294967276,4294967276,4294967276,4294967276]
        movdqa  xmm13, xmmword ptr [rip + .LCPI0_7] # xmm13 = [4294967272,4294967272,4294967272,4294967272]
        movdqa  xmm14, xmmword ptr [rip + .LCPI0_8] # xmm14 = [4294967268,4294967268,4294967268,4294967268]
        movdqa  xmm15, xmmword ptr [rip + .LCPI0_9] # xmm15 = [4294967264,4294967264,4294967264,4294967264]
        movdqa  xmm4, xmm1
.LBB0_5:                                # =>This Inner Loop Header: Depth=1
        movdqa  xmm0, xmm6
        paddd   xmm0, xmmword ptr [rip + .LCPI0_2]
        pshufd  xmm5, xmm1, 245         # xmm5 = xmm1[1,1,3,3]
        pshufd  xmm7, xmm6, 245         # xmm7 = xmm6[1,1,3,3]
        pmuludq xmm7, xmm5
        pmuludq xmm1, xmm6
        pshufd  xmm5, xmm4, 245         # xmm5 = xmm4[1,1,3,3]
        pshufd  xmm2, xmm0, 245         # xmm2 = xmm0[1,1,3,3]
        pmuludq xmm2, xmm5
        pmuludq xmm0, xmm4
        movdqa  xmm4, xmm6
        paddd   xmm4, xmm9
        movdqa  xmm5, xmm6
        paddd   xmm5, xmm10
        pmuludq xmm1, xmm4
        pshufd  xmm4, xmm4, 245         # xmm4 = xmm4[1,1,3,3]
        pmuludq xmm4, xmm7
        pmuludq xmm0, xmm5
        pshufd  xmm5, xmm5, 245         # xmm5 = xmm5[1,1,3,3]
        pmuludq xmm5, xmm2
        movdqa  xmm2, xmm6
        paddd   xmm2, xmm11
        movdqa  xmm7, xmm6
        paddd   xmm7, xmm12
        pshufd  xmm3, xmm2, 245         # xmm3 = xmm2[1,1,3,3]
        pmuludq xmm3, xmm4
        pmuludq xmm2, xmm1
        pshufd  xmm8, xmm7, 245         # xmm8 = xmm7[1,1,3,3]
        pmuludq xmm8, xmm5
        pmuludq xmm7, xmm0
        movdqa  xmm0, xmm6
        paddd   xmm0, xmm13
        movdqa  xmm5, xmm6
        paddd   xmm5, xmm14
        pmuludq xmm2, xmm0
        pshufd  xmm1, xmm2, 232         # xmm1 = xmm2[0,2,2,3]
        pshufd  xmm0, xmm0, 245         # xmm0 = xmm0[1,1,3,3]
        pmuludq xmm0, xmm3
        pshufd  xmm0, xmm0, 232         # xmm0 = xmm0[0,2,2,3]
        punpckldq       xmm1, xmm0      # xmm1 = xmm1[0],xmm0[0],xmm1[1],xmm0[1]
        pmuludq xmm7, xmm5
        pshufd  xmm4, xmm7, 232         # xmm4 = xmm7[0,2,2,3]
        pshufd  xmm0, xmm5, 245         # xmm0 = xmm5[1,1,3,3]
        pmuludq xmm0, xmm8
        pshufd  xmm0, xmm0, 232         # xmm0 = xmm0[0,2,2,3]
        punpckldq       xmm4, xmm0      # xmm4 = xmm4[0],xmm0[0],xmm4[1],xmm0[1]
        paddd   xmm6, xmm15
        add     esi, 4
        jne     .LBB0_5
.LBB0_6:
        movdqa  xmm5, xmm1
        movdqa  xmm0, xmm4
        test    eax, eax
        je      .LBB0_9
        neg     eax
        movdqa  xmm2, xmmword ptr [rip + .LCPI0_2] # xmm2 = [4294967292,4294967292,4294967292,4294967292]
        movdqa  xmm3, xmmword ptr [rip + .LCPI0_3] # xmm3 = [4294967288,4294967288,4294967288,4294967288]
.LBB0_8:                                # =>This Inner Loop Header: Depth=1
        movdqa  xmm0, xmm6
        paddd   xmm0, xmm2
        movdqa  xmm5, xmm6
        pmuludq xmm5, xmm1
        pshufd  xmm5, xmm5, 232         # xmm5 = xmm5[0,2,2,3]
        pshufd  xmm1, xmm1, 245         # xmm1 = xmm1[1,1,3,3]
        pshufd  xmm7, xmm6, 245         # xmm7 = xmm6[1,1,3,3]
        pmuludq xmm7, xmm1
        pshufd  xmm1, xmm7, 232         # xmm1 = xmm7[0,2,2,3]
        punpckldq       xmm5, xmm1      # xmm5 = xmm5[0],xmm1[0],xmm5[1],xmm1[1]
        pshufd  xmm1, xmm0, 245         # xmm1 = xmm0[1,1,3,3]
        pmuludq xmm0, xmm4
        pshufd  xmm0, xmm0, 232         # xmm0 = xmm0[0,2,2,3]
        pshufd  xmm4, xmm4, 245         # xmm4 = xmm4[1,1,3,3]
        pmuludq xmm4, xmm1
        pshufd  xmm1, xmm4, 232         # xmm1 = xmm4[0,2,2,3]
        punpckldq       xmm0, xmm1      # xmm0 = xmm0[0],xmm1[0],xmm0[1],xmm1[1]
        paddd   xmm6, xmm3
        movdqa  xmm1, xmm5
        movdqa  xmm4, xmm0
        inc     eax
        jne     .LBB0_8
.LBB0_9:
        pshufd  xmm1, xmm5, 245         # xmm1 = xmm5[1,1,3,3]
        pshufd  xmm2, xmm0, 245         # xmm2 = xmm0[1,1,3,3]
        pmuludq xmm2, xmm1
        pmuludq xmm0, xmm5
        pshufd  xmm1, xmm0, 78          # xmm1 = xmm0[2,3,0,1]
        pmuludq xmm1, xmm0
        pshufd  xmm0, xmm2, 162         # xmm0 = xmm2[2,0,2,2]
        pmuludq xmm0, xmm2
        pmuludq xmm0, xmm1
        movd    eax, xmm0
        cmp     ecx, r8d
        je      .LBB0_12
        sub     edi, r8d
.LBB0_11:                               # =>This Inner Loop Header: Depth=1
        imul    eax, edi
        add     edi, -1
        cmp     edi, 1
        jne     .LBB0_11
.LBB0_12:
        ret
Run Code Online (Sandbox Code Playgroud)

这里到底发生了什么!?代码是否包含一些我无法发现的 UB?据我所知,下溢/溢出不应该发生,将返回类型更改为 unsigned int 不会改变任何东西。

这是 Goolbolt 站点或 clang 的错误吗?gcc 和 icc 为同一个片段生成合理的代码。例如 gcc x86 -O3:

faculty1:
        mov     eax, 1
        cmp     edi, 1
        je      .L4
.L3:
        mov     edx, edi
        sub     edi, 1
        imul    eax, edx
        cmp     edi, 1
        jne     .L3
        ret
.L4:
        ret
Run Code Online (Sandbox Code Playgroud)

(它设法展开递归)

JL2*_*210 4

我安装了 Clang 7,它做了同样的事情,这意味着它不是编译器错误。

正如评论中所述,此递归正在转换为正在矢量化的循环。

有符号结果和无符号操作数之间的乘法将结果提升为,然后以实现定义的方式unsigned int转换回。int这意味着 Clang 不能/不会使用整数溢出作为优化方式。

本次测试程序:

#include <stdio.h>

int faculty1 (const unsigned int n) {
    return n == 1 ? n : n * faculty1(n - 1);
}

int main(void)
{
    for(int i = 0; i < 65536; i++)
    {
        printf("%d: %d\n", i, faculty1(i));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用 Clang 7 运行大约需要 3.8 秒-O2,使用 GCC 8.3.0 运行大约需要 8.6 秒-O2。所以是的,Clang 的版本更快。我认为这有点矫枉过正,但它有效并且符合标准。

  • @Lundin是的,但是从“unsigned int”到不能保存结果的“int”的转换是实现定义的。如果您没有注意到,您将返回一个“int”。 (5认同)