【问题标题】:How to guide GCC optimizations based on assertions without runtime cost?如何在没有运行时成本的情况下基于断言指导 GCC 优化?
【发布时间】:2017-10-18 15:23:26
【问题描述】:

我的代码中使用了一个在调试模式下使用的宏:

#define contract(condition) \
    if (!(condition)) \
        throw exception("a contract has been violated");

...但处于发布模式:

#define contract(condition) \
    if (!(condition)) \
        __builtin_unreachable();

这对 assert() 的影响在于,在发布版本中,由于 UB 传播,编译器可以大量优化代码。

例如,使用以下代码进行测试:

int foo(int i) {
    contract(i == 1);
    return i;
}

// ...

foo(0);

... 在调试模式下抛出异常,但在发布模式下为无条件的return 1; 生成程序集:

foo(int):
        mov     eax, 1
        ret

条件以及依赖它的所有内容都已优化。

我的问题出现在更复杂的条件下。当编译器无法证明条件没有副作用时,它不会对其进行优化,与不使用合约相比,这是一个runtme惩罚。

有没有办法表示合约中的条件没有副作用,从而总是优化出来?

【问题讨论】:

标签: c++ gcc optimization assert compiler-optimization


【解决方案1】:

所以,不是答案,而是一些有趣的结果,可能会导致某个地方。

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

#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;
}

你也可以follow along at home ;)

noSideEffect 是我们知道没有副作用的函数,但编译器没有。
事情是这样的:

  1. GCC 有__attribute__((pure)) 将函数标记为没有副作用。

  2. 使用 pure 属性限定 noSideEffect 会完全删除函数调用。不错!

  3. 但是我们不能修改noSideEffect的声明。那么如何将它的调用包装在一个本身就是pure 的函数中呢?由于我们正在尝试制作一个自包含的宏,因此 lambda 听起来不错。

  4. 令人惊讶的是,这不起作用...除非我们将noinline 添加到 lambda!我想优化器首先内联 lambda,在途中丢​​失 pure 属性,然后再考虑优化对 noSideEffect 的调用。使用noinline,优化器能够以一种有点违反直觉的方式清除所有内容。太好了!

  5. 但是,现在有两个问题:使用noinline 属性,编译器会为每个 lambda 生成一个主体,即使它们从未使用过。嗯——无论如何,链接器可能都会把它们扔掉。
    但更重要的是……我们实际上失去了__builtin_unreachable() 启用的优化:(

综上所述,你可以在上面的sn-p中删除或放回@​​987654335@,最终得到以下结果之一:

与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

没有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

【讨论】:

  • 我玩过这个玩具很多次,我认为这是 GCC 中的一个错误,它在内联时会剥离 pure 属性,无论如何感谢那里的知识。
【解决方案2】:

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

不太可能。

众所周知,您不能将大量断言转化为假设(通过__builtin_unreachable)并期望得到好的结果(例如,John Regehr 的Assertions Are Pessimistic, Assumptions Are Optimistic)。

一些线索:

  • CLANG 虽然已经拥有 __builtin_unreachable 内在函数,但正是为此目的引入了 __builtin_assume。

  • N4425 - Generalized Dynamic Assumptions(*) 注意到:

    GCC 没有明确提供一般假设工具,但可以使用控制流和 __builtin_unreachable 内在函数的组合对一般假设进行编码

    ...

    提供通用假设的现有实现在实现保留标识符空间中使用了一些关键字(__assume、__builtin_assume 等)。由于不计算表达式参数(丢弃副作用),因此根据特殊库函数(例如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
    

    并指出:

    // GSL_ASSUME(cond)
    //
    // Tell the optimizer that the predicate cond must hold. It is unspecified
    // whether or not cond is actually evaluated.
    

*) 论文rejected:EWG 的指导是在提议的合同设施内提供功能。

【讨论】:

【解决方案3】:

没有办法强制优化代码,就好像它是死代码一样,因为 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();
    }

错误属性在没有优化的情况下不起作用(所以这个宏只能用于发布\优化模式编译)。 请注意,指示合约具有副作用的错误会在链接期间显示。

A test that shows an error with unoptimizable contract.

A test that optimizes out a contract but, does UB propagation with it.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2012-08-24
    • 1970-01-01
    • 2018-04-06
    • 2019-11-15
    • 2010-11-25
    • 1970-01-01
    相关资源
    最近更新 更多