正如我在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的一些讨论。最终,我认为一切都在按预期工作。
3条答案
按热度按时间f0ofjuux1#
为什么gcc不发出指定的指令?
编译器产生的代码必须具有标准规定的 * 可观察行为 *。任何不可观察的行为都可以随意更改(和优化),因为它不会改变程序的行为(如指定的)。
你怎么能把它打得服服帖帖的?
诀窍是让编译器相信特定代码段的行为实际上是可观察的。
由于这是微基准测试中经常遇到的问题,我建议你看看(例如)Google-Benchmark如何解决这个问题。从
benchmark_api.h
我们得到:字符串
this syntax的细节很无聊,对于我们的目的,我们只需要知道:
"g"(value)
表示value
用作语句的输入"memory"
是一个编译时读/写屏障因此,我们可以将代码改为:
型
其中:
s1
和s2
在初始化和使用之间可能已被修改。不需要任何标志,它应该在任何优化级别上工作(我在https://gcc.godbolt.org/上测试了它-O3)。
wswtfjt72#
GCC在这里没有“优化”任何东西。它只是不生成无用的代码。这似乎是一个非常常见的错觉,编译器应该生成一些纯形式的代码,并且对这些代码的任何更改都是“优化”。没有这样的事情。
编译器创建一些数据结构来表示代码的含义,然后它在该数据结构上应用一些转换,并从该数据结构生成汇编程序,然后将汇编程序编译为指令。如果你在编译时没有“优化”,这意味着编译器只会尽可能少地生成代码。
在这种情况下,整个语句都是无用的,因为它没有做任何事情,并立即被丢弃(在扩展内联和内置的含义之后,它相当于写入
a/b;
,区别在于写入a/b;
将发出关于statement with no effect
的警告,而内置可能不会被相同的警告处理)。这不是优化,编译器实际上将不得不花费额外的努力来为无意义的语句创造意义,然后伪造一个临时变量来存储该语句的结果,然后将其丢弃。你要找的不是用来禁用优化的标志,而是pessimization标志。我不认为任何编译器开发人员会浪费时间实现这样的标志。除了可能是一个愚人节玩笑。
tkqqtvp13#
我不是
gcc
内部的Maven,但你的问题似乎不是通过一些优化来删除死代码,很可能编译器一开始就没有考虑 * 生成 * 这段代码。让我们把你的例子从编译器特定的intrinsic减少到一个普通的老加法:
字符串
No code for
+ 77
generated:型
当其中一个操作数有副作用时,only that operand gets evaluated。仍然,在程序集中没有加法。
但是将这个结果保存到一个(即使是未使用的)变量中会迫使编译器执行generate code for addition:
型
组装:
型
以下只是一种推测,但从我的编译器构造经验来看,不为未使用的表达式生成代码,而不是在以后消除这些代码,会更自然。
我的建议是明确您的意图,并将表达式的结果放入volatile(因此,优化器无法删除)变量中。
@Matthieu M指出,防止预计算值是不够的。因此,对于比处理信号更多的事情,您应该使用文档化的方法来执行您想要的确切指令(可能是
volatile
内联汇编)。