如何在没有运行时成本的情况下基于断言来指导GCC优化?

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)

条件以及依赖它的一切都已经过优化.

我的问题出现在更复杂的条件下.当编译器无法证明条件没有副作用时,它不会将其优化出来,与不使用合同相比,这是一个严重的惩罚.

有没有办法表明合同中的条件没有副作用,所以总是优化出来?

Que*_*tin 7

所以,不是答案,而是一些可能导致某些方面有趣的结果.

我最终得到了以下玩具代码:

#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我们知道的函数没有副作用,但编译器没有.
它是这样的:

  1. GCC必须__attribute__((pure))将功能标记为无副作用.

  2. 预选赛noSideEffectpure属性完全消除函数调用.太好了!

  3. 但我们无法修改声明noSideEffect.那么如何将它的调用包装在一个本身的函数中pure呢?因为我们正在尝试制作一个独立的宏,所以lambda听起来不错.

  4. 令人惊讶的是,这不起作用......除非我们添加noinline到lambda!我想pure,在考虑优化调用之前,优化器首先内联lambda,在途中丢失属性noSideEffect.以noinline某种反直觉的方式,优化器能够擦除所有内容.大!

  5. 但是,现在有两个问题:使用该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)


Lyi*_*Sky 7

没有办法强制优化代码,就好像它是死代码一样,因为 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 属性在没有优化的情况下不起作用(所以这个宏只能用于发布\优化模式编译)。请注意,链接期间会显示指示合同有副作用的错误。

显示不可优化合约错误的测试。

一个优化合约的测试,但使用它进行 UB 传播。


man*_*lio 6

有没有一种方法可以表明合同中的条件没有副作用,因此总是可以优化它?

不见得。

众所周知,您不能收集大量的断言,不能将其转化为假设(通过__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的指南是在建议的合同设施内提供功能。

  • @jack_1729 `cond` 是类函数宏的输入参数(查看 https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html)。`GSL_ASSUME` 需要一个布尔表达式/条件(`cond`)。 (2认同)