Lyi*_*Sky 22 c++ optimization gcc assert compiler-optimization
我有一个宏在我的代码中使用,在调试模式下:
#define contract(condition) \
if (!(condition)) \
throw exception("a contract has been violated");
Run Code Online (Sandbox Code Playgroud)
...但在发布模式下:
#define contract(condition) \
if (!(condition)) \
__builtin_unreachable();
Run Code Online (Sandbox Code Playgroud)
这样做的原因assert()是,在发布版本中,由于UB传播,编译器可以大量优化代码.
例如,使用以下代码进行测试:
int foo(int i) {
contract(i == 1);
return i;
}
// ...
foo(0);
Run Code Online (Sandbox Code Playgroud)
...在调试模式下抛出异常,但return 1;在释放模式下为无条件生成程序集:
foo(int):
mov eax, 1
ret
Run Code Online (Sandbox Code Playgroud)
条件以及依赖它的一切都已经过优化.
我的问题出现在更复杂的条件下.当编译器无法证明条件没有副作用时,它不会将其优化出来,与不使用合同相比,这是一个严重的惩罚.
有没有办法表明合同中的条件没有副作用,所以总是优化出来?
所以,不是答案,而是一些可能导致某些方面有趣的结果.
我最终得到了以下玩具代码:
#define contract(x) \
if (![&]() __attribute__((pure, noinline)) { return (x); }()) \
__builtin_unreachable();
bool noSideEffect(int i);
int foo(int i) {
contract(noSideEffect(i));
contract(i == 1);
return i;
}
Run Code Online (Sandbox Code Playgroud)
你也可以在家里跟随 ;)
noSideEffect我们知道的函数没有副作用,但编译器没有.
它是这样的:
GCC必须__attribute__((pure))将功能标记为无副作用.
预选赛noSideEffect与pure属性完全消除函数调用.太好了!
但我们无法修改声明noSideEffect.那么如何将它的调用包装在一个本身的函数中pure呢?因为我们正在尝试制作一个独立的宏,所以lambda听起来不错.
令人惊讶的是,这不起作用......除非我们添加noinline到lambda!我想pure,在考虑优化调用之前,优化器首先内联lambda,在途中丢失属性noSideEffect.以noinline某种反直觉的方式,优化器能够擦除所有内容.大!
但是,现在有两个问题:使用该noinline属性,编译器为每个lambda生成一个主体,即使它们从未被使用过.嗯 - 无论如何,链接器可能会抛弃它们.
但更重要的是......我们实际上已经失去了__builtin_unreachable()启用的优化:(
总结一下,您可以删除或放回noinline上面的代码段,最后获得以下结果之一:
同 noinline
; Unused code
foo(int)::{lambda()#2}::operator()() const:
mov rax, QWORD PTR [rdi]
cmp DWORD PTR [rax], 1
sete al
ret
foo(int)::{lambda()#1}::operator()() const:
mov rax, QWORD PTR [rdi]
mov edi, DWORD PTR [rax]
jmp noSideEffect(int)
; No function call, but the access to i is performed
foo(int):
mov eax, edi
ret
Run Code Online (Sandbox Code Playgroud)
没有 noinline
; No unused code
; Access to i has been optimized out,
; but the call to `noSideEffect` has been kept.
foo(int):
sub rsp, 8
call noSideEffect(int)
mov eax, 1
add rsp, 8
ret
Run Code Online (Sandbox Code Playgroud)
没有办法强制优化代码,就好像它是死代码一样,因为 GCC 必须始终抱怨标准。
另一方面,可以通过使用属性来检查表达式是否有任何副作用,error只要函数调用无法优化,就会显示错误。
一个宏示例,用于检查优化后的内容并执行 UB 传播:
#define _contract(condition) \
{
([&]() __attribute__ ((noinline,error ("contract could not be optimized out"))) {
if (condition) {} // using the condition in if seem to hide `unused` warnings.
}());
if (!(condition))
__builtin_unreachable();
}
Run Code Online (Sandbox Code Playgroud)
error 属性在没有优化的情况下不起作用(所以这个宏只能用于发布\优化模式编译)。请注意,链接期间会显示指示合同有副作用的错误。
有没有一种方法可以表明合同中的条件没有副作用,因此总是可以优化它?
不见得。
众所周知,您不能收集大量的断言,不能将其转化为假设(通过__builtin_unreachable),不能期望获得良好的结果(例如,John Regehr的断言是悲观的,假设是乐观的)。
一些线索:
CLANG虽然已经具有__builtin_unreachable内部功能,但正是为此目的引入了__builtin_assume。
N4425-广义动态假设(*)指出:
GCC并未明确提供一般假设工具,但可以使用控制流和
__builtin_unreachable内在函数的组合来对一般假设进行编码...
提供通用的假设现有的实现使用的一些关键字在implementationreserved标识符空间(
__assume,__builtin_assume等)。由于未评估expression参数(忽略了副作用),因此按照特殊的库函数(例如std::assume)进行指定似乎很困难。
准则支持库(GSL,由Microsoft托管,但绝非Microsoft专有)仅“具有”以下代码:
#ifdef _MSC_VER
#define GSL_ASSUME(cond) __assume(cond)
#elif defined(__clang__)
#define GSL_ASSUME(cond) __builtin_assume(cond)
#elif defined(__GNUC__)
#define GSL_ASSUME(cond) ((cond) ? static_cast<void>(0) : __builtin_unreachable())
#else
#define GSL_ASSUME(cond) static_cast<void>(!!(cond))
#endif
Run Code Online (Sandbox Code Playgroud)
并指出:
// GSL_ASSUME(cond)
//
// Tell the optimizer that the predicate cond must hold. It is unspecified
// whether or not cond is actually evaluated.
Run Code Online (Sandbox Code Playgroud)*)论文被拒绝:EWG的指南是在建议的合同设施内提供功能。