2016-10-03 94 views
45

正如我在this question提出的,gcc正在删除(是的,用-O0)一行代码_mm_div_ss(s1, s2);,可能是因为结果没有保存。但是,这个应该触发一个浮点异常,并提出SIGFPE,如果删除该调用不会发生。gcc -O0仍然优化了“未使用”的代码。有没有编译标志来改变它?

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

事情我已经尝试:

$ 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 

编辑更多的细节。

这似乎与_mm_div_ssdefinition上的__always_inline__属性有关。

$ 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一些讨论。最终,我认为一切都按预期工作。

+0

尝试使用'__attribute __((used))'所涉及的变量。 –

+2

也许声明s1和s2为volatile ... – Malkocoglu

回答

22

GCC不会“优化”任何东西。它只是不会产生无用的代码。这似乎是一种非常普遍的错觉,即编译器应该生成一些纯粹的代码形式,并且对其进行的任何更改都是“优化”。哪有这回事。

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

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

你在找什么不是标志来禁用优化,但pessimization标志。我不认为任何编译器开发者浪费时间来实现这样的标志。除了可能作为一个愚人节玩笑。

+0

如何显示有关语句的警告但不起作用?因为'WALL -Wextra -pedantic'没有显示任何东西。 – BurnsBA

+2

@BurnsBA我也无法让它产生警告。这可能是gcc中的一个错误。向他们发送一个错误报告。 – Art

+7

这里的误解实际上是将一个除法的SIGFPE提升为零是一个已定义的效果,因此优化调用会除去可观察的已定义行为。 – Random832

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内联汇编)。

+1

不幸的是,仅仅将结果放在volatile中并不足以防止编译器在没有实际发出所需指令的情况下对其进行预计算,因为所有参数都是在编译时出现的。 –

+0

确实;将零点放入易失性存储器并再次读出将关闭优化器。 – Joshua

+0

@MatthieuM。我完全同意它并不总是足够的,但在这种情况下,它是([最后一个链接](https://godbolt.org/g/sE78jL))。此外,编译器*必须*更喜欢在通常情况下更快的代码来“发出所需的指令”,以便有用。这就是应该使用内联汇编的情况。 – deniss

32

为什么gcc不发出指定的指令?

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

你怎么能打败它提交?

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

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

template <class Tp> 
inline void DoNotOptimize(Tp const& value) { 
    asm volatile("" : : "g"(value) : "memory"); 
} 

this syntax细节是枯燥的,我们的目的,我们只需要知道:

  • "g"(value)告诉value作为输入的语句
  • "memory"是编译时读/写屏障

所以,我们可以改变co德到:

asm volatile("" : : : "memory"); 

__m128 result = _mm_div_ss(s1, s2); 

asm volatile("" : : "g"(result) :); 

其中:

  • 强制编译器认为s1s2可能已被他们的初始化之间修改和使用
  • 强制编译器认为该操作的结果被使用

不需要任何标志,它应该在任何优化级别工作(I t在-03处将其设置为https://gcc.godbolt.org/)。

+0

你有什么来源gcc决定观察行为或不行?如果在没有警告的情况下删除此语句是一个错误,意外或有意,我试图缩小范围。 – BurnsBA

+1

@BurnsBA:C++标准是什么是或不是可观察行为的参考。它也很难阅读和充满角落的情况......一般来说,对于单线程程序,可观察行为是影响程序输出(I/O)的任何事情。对于多线程程序来说,事情变得更复杂,因为许多交错是可能的输出。 –