为什么编译器不能优化这些无法访问的指令?

时间:2014-06-29 03:10:08

标签: c gcc optimization assembly clang

鉴于此代码:

int x;

int a (int b) {
    b = a (b);
    b += x;
    return b;
}

为什么GCC会返回此输出(英特尔语法):http://goo.gl/8D32F1 - Godbolt的GCC Explorer

a:
    sub rsp, 8
    call    a
    mov edx, DWORD PTR x[rip]
    add rsp, 8
    lea eax, [rax+rdx*8]
    add eax, edx
    ret

和Clang返回此输出(AT& T语法):http://goo.gl/Zz2rKA - Godbolt的Clang Explorer

a:                                      # @a
    pushq   %rax
    callq   a
    addl    x(%rip), %eax
    popq    %rdx
    ret

部分代码显然无法访问?由于函数的第一个语句是

b = a (b);

该函数将永远以递归方式调用自身(直到堆栈溢出并出现段错误)。这意味着您永远不会超越该行,因此,其余代码无法访问。可达性优化理论上应该删除代码,对吗?


两个编译器都在x64上运行,并带有以下标志

  • -O3 - 最高优化
  • -march=native - [不必要]尽可能使用机器特定的优化
  • -x c - 假设输入语言为C

我当时认为他们应该返回更多内容(双关语):

GCC(英特尔语法):

a:
.L1:
    jmp .L1

Clang(AT& T语法):

a:
.LBB0_1:
    jmp .LBB0_1

注意:这些样本是从以前观察的记忆中手工编写的,可能不正确。


总的来说,为什么没有任何一个编译器将函数折叠成单个递归跳转,因为其余代码无法访问?


编辑:

回应杰克关于语义等值的评论:

以下代码:

int j (int x) {
    while (1) {};
    x++;
    return x;
}

GCC返回:http://goo.gl/CYSUW2

j:
.L2:
    jmp .L2

Clang回归:

j:                                      # @j
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    jmp .LBB0_1

回应亚当关于吹灭堆栈的评论:

对于此代码:

int r (int x) {
    return r (x);
}

GCC生成递归跳转:http://goo.gl/eWo2Nb

r:
.L2:
    jmp .L2

Clang早早回复:http://goo.gl/CVJKiZ

r:                                      # @r
    ret

1 个答案:

答案 0 :(得分:4)

您使用的编译器可能仅在单个功能框架内的块级别实现数据流分析,而不考虑递归。 (或许,只有有趣的递归,即尾递归。)由于递归调用不是尾调用,因此从优化的角度来看并不感兴趣。

你的函数有一个问题:编译它的方式,它会炸毁堆栈。它是这样编译的,因为调用不是尾调用;把它当作一个合法的优化并不合理。

可以将呼叫视为"伪尾呼叫"理由是调用之后的代码永远不会被调用,所以如果我们删除那个代码,那么递归调用就是函数做的最后一件事。然后我们可以将堆栈代码减少到仅仅是无限循环。但是,这实际上不能称为优化;它是用不同的bug表现来代替一个bug表现的。