gcc -O 0仍然优化了“未使用”的代码,这些代码会引发FP异常,是否有编译标志来改变这一点?

xam8gpfp  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(128)

正如我在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


请编辑更多详细信息。
这似乎与_mm_div_ss定义上的__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
$


(no警告或错误)

$ ./t.out
Floating point exception
$


vs below(除函数属性外相同)

$ 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
$


(no警告或错误)

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

f0ofjuux

f0ofjuux1#

为什么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"是一个编译时读/写屏障

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

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

__m128 result = _mm_div_ss(s1, s2);

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


其中:

  • 强制编译器考虑s1s2在初始化和使用之间可能已被修改。
  • 强制编译器考虑使用操作的结果

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

wswtfjt7

wswtfjt72#

GCC在这里没有“优化”任何东西。它只是不生成无用的代码。这似乎是一个非常常见的错觉,编译器应该生成一些纯形式的代码,并且对这些代码的任何更改都是“优化”。没有这样的事情。
编译器创建一些数据结构来表示代码的含义,然后它在该数据结构上应用一些转换,并从该数据结构生成汇编程序,然后将汇编程序编译为指令。如果你在编译时没有“优化”,这意味着编译器只会尽可能少地生成代码。
在这种情况下,整个语句都是无用的,因为它没有做任何事情,并立即被丢弃(在扩展内联和内置的含义之后,它相当于写入a/b;,区别在于写入a/b;将发出关于statement with no effect的警告,而内置可能不会被相同的警告处理)。这不是优化,编译器实际上将不得不花费额外的努力来为无意义的语句创造意义,然后伪造一个临时变量来存储该语句的结果,然后将其丢弃。
你要找的不是用来禁用优化的标志,而是pessimization标志。我不认为任何编译器开发人员会浪费时间实现这样的标志。除了可能是一个愚人节玩笑。

tkqqtvp1

tkqqtvp13#

我不是gcc内部的Maven,但你的问题似乎不是通过一些优化来删除死代码,很可能编译器一开始就没有考虑 * 生成 * 这段代码。
让我们把你的例子从编译器特定的intrinsic减少到一个普通的老加法:

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

相关问题