gcc -O0仍然优化了"未使用的"码。是否有可以更改的编译标志?

时间:2016-10-03 13:34:28

标签: c gcc

当我在this question中提出时,gcc正在移除(是的,-O0)一行代码_mm_div_ss(s1, s2);,大概是因为结果未保存。但是,此触发浮点异常并引发SIGFPE,如果删除该调用,则无法发生。

问题:是否有一个标志或多个标志传递给gcc,以便按原样编译代码?我在想fno-remove-unused之类的东西,但我没有看到类似的东西。理想情况下,这将是一个编译器标志,而不是必须更改我的源代码,但如果不支持,是否需要使用一些gcc属性/ pragma?

我尝试的事情:

$ gcc --help=optimizers | grep -i remove

没有结果。

$ gcc --help=optimizers | grep -i unused

没有结果。

明确禁用所有死代码/消除标志 - 请注意,没有关于未使用代码的警告:

$ gcc -O0 -msse2 -Wall -Wextra -pedantic -Winline \
     -fno-dce -fno-dse -fno-tree-dce \
     -fno-tree-dse -fno-tree-fre -fno-compare-elim -fno-gcse  \
     -fno-gcse-after-reload -fno-gcse-las -fno-rerun-cse-after-loop \
     -fno-tree-builtin-call-dce -fno-tree-cselim a.c
a.c: In function ‘main’:
a.c:25:5: warning: ISO C90 forbids mixed declarations and code [-Wpedantic]
     __m128 s1, s2;
     ^
$

源程序

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <xmmintrin.h>

static void sigaction_sfpe(int signal, siginfo_t *si, void *arg)
{
    printf("%d,%d,%d\n", signal, si!=NULL?1:0, arg!=NULL?1:0);
    printf("inside SIGFPE handler\nexit now.\n");
    exit(1);
}

int main()
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = sigaction_sfpe;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGFPE, &sa, NULL);

    _mm_setcsr(0x00001D80);

    __m128 s1, s2;
    s1 = _mm_set_ps(1.0, 1.0, 1.0, 1.0);
    s2 = _mm_set_ps(0.0, 0.0, 0.0, 0.0);
    _mm_div_ss(s1, s2);

    printf("done (no error).\n");

    return 0;
}

编译上述程序给出了

$ ./a.out
done (no error).

更改行

_mm_div_ss(s1, s2);

s2 = _mm_div_ss(s1, s2); // add "s2 = "

产生预期结果:

$ ./a.out
inside SIGFPE handler

使用更多详细信息进行修改。

这似乎与__always_inline__ definition上的_mm_div_ss属性相关。

$ cat t.c
int
div(int b)
{
    return 1/b;
}

int main()
{
    div(0);
    return 0;
}


$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
$  

(没有警告或错误)

$ ./t.out
Floating point exception
$

vs下面(功能属性除外)

$ cat t.c
__inline int __attribute__((__always_inline__))
div(int b)
{
    return 1/b;
}

int main()
{
    div(0);
    return 0;
}

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
$   

(没有警告或错误)

$ ./t.out
$

添加函数属性__warn_unused_result__至少会给出一条有用的信息:

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
t.c: In function ‘main’:
t.c:9:5: warning: ignoring return value of ‘div’, declared with attribute warn_unused_result [-Wunused-result]
     div(0);
     ^

编辑:

关于gcc mailing list的一些讨论。最终,我认为一切都按预期工作。

3 个答案:

答案 0 :(得分:32)

  

为什么gcc没有发出指定的指令?

编译器生成的代码必须具有Standard指定的可观察行为。任何不可观察的东西都可以随意更改(和优化),因为它不会改变程序的行为(如指定的那样)。

  

你怎么能把它击败?

诀窍是让编译器相信特定代码片段的行为实际上是可观察的。

由于这是微基准测试中经常遇到的问题,我建议你看看(例如)Google-Benchmark如何解决这个问题。从benchmark_api.h我们得到:

var mysql  = require('mysql');

//make connection here
var pool   = mysql.createPool({
    ...
});

pool.getConnection(function(err,connection){
    ...
});

module.exports.respond = function(event, cb) {     
    //use connection here
};

this syntax的详细信息很无聊,出于我们的目的,我们只需要知道:

  • template <class Tp> inline void DoNotOptimize(Tp const& value) { asm volatile("" : : "g"(value) : "memory"); } 告诉"g"(value)用作语句的输入
  • value是编译时读/写屏障

因此,我们可以将代码更改为:

"memory"

其中:

  • 强制编译器考虑asm volatile("" : : : "memory"); __m128 result = _mm_div_ss(s1, s2); asm volatile("" : : "g"(result) : ); s1可能在初始化和使用之间被修改
  • 强制编译器考虑使用操作的结果

不需要任何标志,它应该可以在任何优化级别上工作(我在https://gcc.godbolt.org/的-O3上测试过它。)

答案 1 :(得分:24)

海湾合作委员会并未在此处“优化”任何内容。它只是不会生成无用的代码。似乎有一种非常普遍的错觉,即编译器应该生成一些纯粹的代码形式,并且对它的任何更改都是“优化”。没有这样的事情。

编译器创建一些表示代码含义的数据结构,然后它对该数据结构应用一些转换,然后生成汇编程序然后编译成指令。如果编译时没有“优化”,那只意味着编译器只会尽可能少地生成代码。

在这种情况下,整个语句是无用的,因为它没有做任何事情并且立即被抛弃(在扩展内联之后以及内置函数意味着它等同于编写a/b;,不同之处在于写作a/b;将发出关于statement with no effect的警告,而内置函数可能不会被相同的警告处理)。这不是一个优化,编译器实际上不得不花费额外的努力来为无意义的语句创造意义,然后伪造一个临时变量来存储该语句的结果然后扔掉它。

您正在寻找的不是禁用优化的标志,而是禁用标志。我不认为任何编译器开发人员浪费时间实现这样的标志。除了可能是一个愚人节玩笑。

答案 2 :(得分:10)

我不是gcc内部的专家,但似乎您的问题不在于通过某些优化传递删除死代码。编译器很可能甚至不首先考虑生成这段代码。

让我们将您的示例从编译器特定的内在函数减少到一个简单的旧函数:

int foo(int num) {
    num + 77;
    return num + 15;
}

No code for + 77 generated

foo(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        add     eax, 15
        pop     rbp
        ret

当其中一个操作数有副作用时,only that operand gets evaluated。仍然没有在组装中添加。

但是将此结果保存到(甚至未使用的)变量会强制编译器generate code for addition

int foo(int num) {
  int baz = num + 77;
  return num + 15;
}

大会:

foo(int):
    push    rbp
    mov     rbp, rsp
    mov     DWORD PTR [rbp-20], edi
    mov     eax, DWORD PTR [rbp-20]
    add     eax, 77
    mov     DWORD PTR [rbp-4], eax
    mov     eax, DWORD PTR [rbp-20]
    add     eax, 15
    pop     rbp
    ret

以下只是一个推测,但根据我在编译器构造方面的经验,不为未使用的表达式生成代码更自然,而不是在以后消除此代码。

我的建议是明确你的意图,并将表达式的结果放入volatile(因此,优化器不可删除)变量。

@Matthieu M指出,防止预先计算该值是不够的。因此,除了使用信号之外,还应该使用记录的方法来执行所需的确切指令(可能是volatile内联汇编)。